]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch 'i18n-work' into i18n-0.9.x
authorBrion Vibber <brion@pobox.com>
Mon, 19 Oct 2009 16:34:37 +0000 (09:34 -0700)
committerBrion Vibber <brion@pobox.com>
Mon, 19 Oct 2009 16:34:37 +0000 (09:34 -0700)
232 files changed:
EVENTS.txt
README
actions/accesstoken.php
actions/all.php
actions/allrss.php
actions/api.php [deleted file]
actions/apiaccountratelimitstatus.php [new file with mode: 0644]
actions/apiaccountverifycredentials.php [new file with mode: 0644]
actions/apiblockcreate.php [new file with mode: 0644]
actions/apiblockdestroy.php [new file with mode: 0644]
actions/apidirectmessage.php [new file with mode: 0644]
actions/apidirectmessagenew.php [new file with mode: 0644]
actions/apifavoritecreate.php [new file with mode: 0644]
actions/apifavoritedestroy.php [new file with mode: 0644]
actions/apifriendshipscreate.php [new file with mode: 0644]
actions/apifriendshipsdestroy.php [new file with mode: 0644]
actions/apifriendshipsexists.php [new file with mode: 0644]
actions/apifriendshipsshow.php [new file with mode: 0644]
actions/apigroupcreate.php [new file with mode: 0644]
actions/apigroupismember.php [new file with mode: 0644]
actions/apigroupjoin.php [new file with mode: 0644]
actions/apigroupleave.php [new file with mode: 0644]
actions/apigrouplist.php [new file with mode: 0644]
actions/apigrouplistall.php [new file with mode: 0644]
actions/apigroupmembership.php [new file with mode: 0644]
actions/apigroupshow.php [new file with mode: 0644]
actions/apihelptest.php [new file with mode: 0644]
actions/apistatusesdestroy.php [new file with mode: 0644]
actions/apistatusesshow.php [new file with mode: 0644]
actions/apistatusesupdate.php [new file with mode: 0644]
actions/apistatusnetconfig.php [new file with mode: 0644]
actions/apistatusnetversion.php [new file with mode: 0644]
actions/apisubscriptions.php [new file with mode: 0644]
actions/apitimelinefavorites.php [new file with mode: 0644]
actions/apitimelinefriends.php [new file with mode: 0644]
actions/apitimelinegroup.php [new file with mode: 0644]
actions/apitimelinementions.php [new file with mode: 0644]
actions/apitimelinepublic.php [new file with mode: 0644]
actions/apitimelinetag.php [new file with mode: 0644]
actions/apitimelineuser.php [new file with mode: 0644]
actions/apiuserfollowers.php [new file with mode: 0644]
actions/apiuserfriends.php [new file with mode: 0644]
actions/apiusershow.php [new file with mode: 0644]
actions/confirmaddress.php
actions/deletenotice.php
actions/doc.php
actions/editgroup.php
actions/favorited.php
actions/favoritesrss.php
actions/finishaddopenid.php [deleted file]
actions/finishopenidlogin.php [deleted file]
actions/finishremotesubscribe.php
actions/foafgroup.php [new file with mode: 0644]
actions/groupbyid.php
actions/groupdesignsettings.php
actions/grouplogo.php
actions/grouprss.php
actions/groupsearch.php
actions/invite.php
actions/joingroup.php
actions/leavegroup.php
actions/login.php
actions/logout.php
actions/newgroup.php
actions/newmessage.php
actions/newnotice.php
actions/noticesearch.php
actions/openidlogin.php [deleted file]
actions/openidsettings.php [deleted file]
actions/othersettings.php
actions/postnotice.php
actions/profilesettings.php
actions/public.php
actions/publicrss.php
actions/publictagcloud.php
actions/publicxrds.php [deleted file]
actions/register.php
actions/remotesubscribe.php
actions/replies.php
actions/repliesrss.php
actions/requesttoken.php
actions/showfavorites.php
actions/showgroup.php
actions/shownotice.php
actions/showstream.php
actions/subscribers.php
actions/twitapiaccount.php [deleted file]
actions/twitapiblocks.php [deleted file]
actions/twitapidirect_messages.php [deleted file]
actions/twitapifavorites.php [deleted file]
actions/twitapifriendships.php [deleted file]
actions/twitapigroups.php [deleted file]
actions/twitapihelp.php [deleted file]
actions/twitapinotifications.php [deleted file]
actions/twitapisearchatom.php
actions/twitapisearchjson.php
actions/twitapistatuses.php [deleted file]
actions/twitapistatusnet.php [deleted file]
actions/twitapitags.php [deleted file]
actions/twitapitrends.php
actions/twitapiusers.php [deleted file]
actions/updateprofile.php
actions/userauthorization.php
actions/userrss.php
actions/xrds.php
classes/Config.php [new file with mode: 0644]
classes/Deleted_notice.php [new file with mode: 0644]
classes/File.php
classes/File_oembed.php
classes/File_redirection.php
classes/Message.php
classes/Notice.php
classes/Profile.php
classes/User.php
classes/User_group.php
classes/User_openid.php [deleted file]
classes/User_role.php [new file with mode: 0644]
classes/statusnet.ini
config.php.sample
db/08to09.sql [new file with mode: 0644]
db/08to09_pg.sql [new file with mode: 0644]
db/statusnet.sql
db/statusnet_pg.sql
doc-src/help
doc-src/openid [deleted file]
extlib/libomb/base_url_xrds_mapper.php [new file with mode: 0755]
extlib/libomb/constants.php [new file with mode: 0644]
extlib/libomb/datastore.php [new file with mode: 0755]
extlib/libomb/helper.php [new file with mode: 0644]
extlib/libomb/invalidparameterexception.php [new file with mode: 0755]
extlib/libomb/invalidyadisexception.php [new file with mode: 0755]
extlib/libomb/notice.php [new file with mode: 0755]
extlib/libomb/omb_yadis_xrds.php [new file with mode: 0755]
extlib/libomb/plain_xrds_writer.php [new file with mode: 0755]
extlib/libomb/profile.php [new file with mode: 0755]
extlib/libomb/remoteserviceexception.php [new file with mode: 0755]
extlib/libomb/service_consumer.php [new file with mode: 0755]
extlib/libomb/service_provider.php [new file with mode: 0755]
extlib/libomb/unsupportedserviceexception.php [new file with mode: 0755]
extlib/libomb/xrds_mapper.php [new file with mode: 0755]
extlib/libomb/xrds_writer.php [new file with mode: 0755]
index.php
install.php
js/util.js
lib/Shorturl_api.php
lib/accountsettingsaction.php
lib/action.php
lib/api.php [new file with mode: 0644]
lib/apiauth.php [new file with mode: 0644]
lib/apibareauth.php [new file with mode: 0644]
lib/command.php
lib/commandinterpreter.php
lib/common.php
lib/curlclient.php [new file with mode: 0644]
lib/default.php [new file with mode: 0644]
lib/deleteaction.php [deleted file]
lib/facebookaction.php
lib/facebookutil.php
lib/groupeditform.php
lib/htmloutputter.php
lib/httpclient.php [new file with mode: 0644]
lib/logingroupnav.php
lib/messageform.php
lib/noticeform.php
lib/noticelist.php
lib/oauthstore.php
lib/omb.php
lib/openid.php [deleted file]
lib/plugin.php
lib/right.php [new file with mode: 0644]
lib/router.php
lib/rssaction.php
lib/schema.php [new file with mode: 0644]
lib/settingsaction.php
lib/twitter.php
lib/twitterapi.php [deleted file]
lib/twitterbasicauthclient.php
lib/twitteroauthclient.php
lib/unqueuemanager.php
lib/util.php
plugins/FBConnect/FBConnectPlugin.php
plugins/LilUrl/LilUrlPlugin.php [new file with mode: 0644]
plugins/OpenID/OpenIDPlugin.php [new file with mode: 0644]
plugins/OpenID/User_openid.php [new file with mode: 0644]
plugins/OpenID/doc-src/openid [new file with mode: 0644]
plugins/OpenID/finishaddopenid.php [new file with mode: 0644]
plugins/OpenID/finishopenidlogin.php [new file with mode: 0644]
plugins/OpenID/openid.php [new file with mode: 0644]
plugins/OpenID/openidlogin.php [new file with mode: 0644]
plugins/OpenID/openidsettings.php [new file with mode: 0644]
plugins/OpenID/publicxrds.php [new file with mode: 0644]
plugins/Orbited/OrbitedPlugin.php [new file with mode: 0644]
plugins/Orbited/orbitedextra.js [new file with mode: 0644]
plugins/Orbited/orbitedupdater.js [new file with mode: 0644]
plugins/PtitUrl/PtitUrlPlugin.php [new file with mode: 0644]
plugins/PubSubHubBub/PubSubHubBubPlugin.php [new file with mode: 0644]
plugins/PubSubHubBub/publisher.php [new file with mode: 0644]
plugins/Realtime/RealtimePlugin.php
plugins/Realtime/jquery.getUrlParam.js [deleted file]
plugins/Realtime/realtimeupdate.js
plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php [new file with mode: 0644]
plugins/SimpleUrl/SimpleUrlPlugin.php [new file with mode: 0644]
plugins/TightUrl/TightUrlPlugin.php [new file with mode: 0644]
scripts/checkschema.php [new file with mode: 0644]
scripts/createsim.php
scripts/getvaliddaemons.php
scripts/maildaemon.php
scripts/ombqueuehandler.php
scripts/pluginqueuehandler.php [new file with mode: 0755]
scripts/showtable.php [new file with mode: 0644]
scripts/startdaemons.sh
scripts/xmppdaemon.php
tests/HashTagDetectionTests.php
tests/URLDetectionTest.php
tests/UserRightsTest.php [new file with mode: 0644]
theme/base/css/display.css
theme/base/images/icons/icons-01.png [new file with mode: 0644]
theme/default/css/display.css
theme/identica/css/display.css
theme/identica/css/ie.css
theme/iphone/bg-body.gif [deleted file]
theme/iphone/bg-header.gif [deleted file]
theme/iphone/default-avatar-mini.png [deleted file]
theme/iphone/default-avatar-profile.png [deleted file]
theme/iphone/default-avatar-stream.png [deleted file]
theme/iphone/display.css [deleted file]
theme/iphone/display.css.dist [deleted file]
theme/iphone/ie6.css.dist [deleted file]
theme/iphone/ie7.css.dist [deleted file]
theme/iphone/login-bg.gif [deleted file]
theme/iphone/logo.png [deleted file]
theme/readme.txt

index 68cb28603b644ea092a58a18fe91877c4c4d26af..9de2f8bc6bbfffaa271a442be37e8c36ba811ad3 100644 (file)
@@ -32,10 +32,10 @@ StartShowLaconicaStyles: backwards compatibility; deprecated
 EndShowLaconicaStyles: backwards compatibility; deprecated
 - $action: the current action
 
-StartShowUAStyles: Showing custom UA Style links
+StartShowUAStyles: Showing custom User-Agent style links
 - $action: the current action
 
-EndShowUAStyles: End showing custom UA Style links; good place to add user-agent (e.g., filter, -webkit, -moz) specific styles
+EndShowUAStyles: End showing custom User-Agent links; good place to add user-agent (e.g., filter, -webkit, -moz) specific styles
 - $action: the current action
 
 StartShowScripts: Showing JavaScript links
@@ -87,6 +87,18 @@ StartShowContentBlock: Showing before the content container
 EndShowContentBlock: Showing after the content container
 - $action: the current action
 
+StartShowAside: Showing before the Aside container
+- $action: the current action
+
+EndShowAside: Showing after the Aside container
+- $action: the current action
+
+StartShowNoticeFormData: Showing before the notice form data
+- $action: the current action
+
+EndShowNoticeFormData: Showing after the notice form data
+- $action: the current action
+
 StartNoticeSave: before inserting a notice (good place for content filters)
 - $notice: notice being saved (no ID or URI)
 
@@ -134,3 +146,247 @@ StartAddressData: Allows the site owner to provide additional information about
 
 EndAddressData: At the end of <address>
 - $action: the current action
+
+StartLoginGroupNav: Before showing the login and register navigation menu
+- $action: the current action
+
+EndLoginGroupNav: After showing the login and register navigation menu
+- $action: the current action
+
+StartAccountSettingsNav: Before showing the account settings menu
+- $action: the current action
+
+EndAccountSettingsNav: After showing the account settings menu
+- $action: the current action
+
+Autoload: When trying to autoload a class
+- $cls: the class being sought. A plugin might require_once the file for the class.
+
+SensitiveAction: determines if an action is 'sensitive' and should use SSL
+- $action: name of the action, like 'login'
+- $sensitive: flag for whether this is a sensitive action
+
+LoginAction: determines if an action is a 'login' action (OK for public view in private mode)
+- $action: name of the action, like 'register'
+- $login: flag for whether this is a login action
+
+StartShowHead: called before showing the <head> element and children
+- $action: action object being show
+
+EndShowHead: called after showing the <head> element (and </head>)
+- $action: action object being shown
+
+StartShowBody: called before showing the <body> element and children
+- $action: action object being shown
+
+EndShowBody: called after showing the <body> element (and </body>)
+- $action: action object being shown
+
+StartPersonalGroupNav: beginning of personal group nav menu
+- $action: action object being shown
+
+EndPersonalGroupNav: end of personal group nav menu (good place to add a menu item)
+- $action: action object being shown
+
+StartEndHTML: just before the </html> tag
+- $action: action object being shown
+
+EndEndHTML: just after the </html> tag
+- $action: action object being shown
+
+StartShowDesign: just before showing a site, user, or group design
+- $action: action object being shown
+
+EndShowDesign: just after showing a site, user, or group design
+- $action: action object being shown
+
+StartShowExportData: just before showing the <div> with export data (feeds)
+- $action: action object being shown
+
+EndShowExportData: just after showing the <div> with export data (feeds)
+- $action: action object being shown
+
+StartShowNoticeItem: just before showing the notice item
+- $action: action object being shown
+
+EndShowNoticeItem: just after showing the notice item
+- $action: action object being shown
+
+StartShowPageNotice: just before showing the page notice (instructions or error)
+- $action: action object being shown
+
+EndShowPageNotice: just after showing the page notice (instructions or error)
+- $action: action object being shown
+
+StartShowPageTitle: just before showing the main h1 title of a page (only for registration)
+- $action: action object being shown
+
+StartProfileFormData: just before showing text entry fields on profile settings page
+- $action: action object being shown
+
+EndProfileFormData: just after showing text entry fields on profile settings page
+- $action: action object being shown
+
+StartProfileSaveForm: before starting to save a profile settings form
+- $action: action object being shown
+
+EndProfileSaveForm: after saving a profile settings form (after commit, no profile or user object!)
+- $action: action object being shown
+
+StartRegistrationFormData: just before showing text entry fields on registration page
+- $action: action object being shown
+
+EndRegistrationFormData: just after showing text entry fields on registration page
+- $action: action object being shown
+
+StartRegistrationTry: before validating and saving a new user
+- $action: action object being shown
+
+EndRegistrationTry: after saving a new user (note: no profile or user object!)
+- $action: action object being shown
+
+StartNewQueueManager: before trying to start a new queue manager; good for plugins implementing new queue manager classes
+- $qm: empty queue manager to set
+
+RedirectToLogin: event when we force a redirect to login (like when going to a settings page on a remembered login)
+- $action: action object being shown
+- $user: current user
+
+StartLoadDoc: before loading a help doc (hook this to show your own documentation)
+- $title: title of the document
+- $output: HTML output to show
+
+EndLoadDoc: after loading a help doc (hook this to modify other documentation)
+- $title: title of the document
+- $output: HTML output to show
+
+StartApiRss: after the rss <channel> element is started
+- $action: action object being shown
+
+StartApiAtom: after the <feed> element is started
+- $action: action object being shown
+
+StartEnqueueNotice: about to add a notice to the queues (good place to add a new transport)
+- $notice: the notice being added
+- &$transports: modifiable list of transports (as strings) to queue for
+
+EndEnqueueNotice: after adding a notice to the queues
+- $notice: the notice being added
+- $transports: modifiable list of transports to use
+
+UnqueueHandleNotice: Handle a notice when no queue manager is available
+- $notice: the notice to handle
+- $queue: the "queue" that is being executed
+
+GetValidDaemons: Just before determining which daemons to run
+- &$daemons: modifiable list of daemon scripts to run, filenames relative to scripts/
+
+HandleQueuedNotice: Handle a queued notice at queue time (or immediately if no queue)
+- &$notice: notice to handle
+
+StartShowHeadElements: Right after the <head> tag
+- $action: the current action
+
+EndShowHeadElements: Right before the </head> tag; put <script>s here if you need them in <head>
+- $action: the current action
+
+CheckSchema: chance to check the schema
+
+StartProfilePageProfileSection: Starting to show the section of the
+                              profile page with the actual profile data;
+                              hook to prevent showing the profile (e.g.)
+- $action: the current action
+- &$profile: the profile being shown
+
+StartProfilePageProfileElements: inside the section, before the first
+                               element; prepend elements here
+- $action: the current action
+- &$profile: the profile being shown
+
+EndProfilePageProfileElements: inside the section, after the last element;
+                             append elements here
+- $action: the current action
+- &$profile: the profile being shown
+
+EndProfilePageProfileSection: After showing the section of the profile
+                            page with the profile elements
+- $action: the current action
+- &$profile: the profile being shown
+
+StartProfilePageActionsSection: Starting to show the section of the
+                                    profile page with action links; hook
+                                    to hide them (for example)
+- $action: the current action
+- &$profile: the profile being shown
+
+StartProfilePageActionsElements: inside the list, before the first
+                                     element; prepend elements here
+- $action: the current action
+- &$profile: the profile being shown
+
+EndProfilePageActionsElements: inside the list, after the last element;
+                                   append elements here
+- $action: the current action
+- &$profile: the profile being shown
+
+EndProfilePageActionsSection: After showing the section of the profile
+                                  page with the entity actions
+- $action: the current action
+- &$profile: the profile being shown
+
+StartProfilePageAvatar: before showing the avatar on the profile page
+- $action: the current action
+- &$profile: the profile being shown
+
+EndProfilePageAvatar: after showing the avatar on the profile page
+- $action: the current action
+- &$profile: the profile being shown
+
+StartProfilePageNickname: before showing the nickname on the profile page
+- $action: the current action
+- &$profile: the profile being shown
+
+EndProfilePageNickname: after showing the nickname on the profile page
+- $action: the current action
+- &$profile: the profile being shown
+
+StartProfilePageFullName: before showing the fullname on the profile page
+- $action: the current action
+- &$profile: the profile being shown
+
+EndProfilePageFullName: after showing the fullname on the profile page
+- $action: the current action
+- &$profile: the profile being shown
+
+StartProfilePageLocation: before showing the location on the profile page
+- $action: the current action
+- &$profile: the profile being shown
+
+EndProfilePageLocation: after showing the location on the profile page
+- $action: the current action
+- &$profile: the profile being shown
+
+StartProfilePageHomepage: before showing the homepage link on the profile page
+- $action: the current action
+- &$profile: the profile being shown
+
+EndProfilePageHomepage: after showing the homepage on the profile page
+- $action: the current action
+- &$profile: the profile being shown
+
+StartProfilePageBio: before showing the bio on the profile page
+- $action: the current action
+- &$profile: the profile being shown
+
+EndProfilePageBio: after showing the bio on the profile page
+- $action: the current action
+- &$profile: the profile being shown
+
+StartProfilePageProfileTags: before showing the tags on the profile page
+- $action: the current action
+- &$profile: the profile being shown
+
+EndProfilePageProfileTags: after showing the tags on the profile page
+- $action: the current action
+- &$profile: the profile being shown
+
diff --git a/README b/README
index 75621998111fa1c3f38fe8e30c03d0cdffdc02f2..037027d43132a0eb9a3c02f43b02572afa1a881e 100644 (file)
--- a/README
+++ b/README
@@ -839,40 +839,8 @@ to update it.
 Notice inboxes
 --------------
 
-Before version 0.6.2, the page showing all notices from people the
-user is subscribed to ("so-and-so with friends") was calculated at run
-time. Starting with 0.6.2, we have a new data structure for holding a
-user's "notice inbox". (Note: distinct from the "message inbox", which
-is the "inbox" tab in the UI. The notice inbox appears under the
-"Personal" tab.)
-
-Notices are added to the inbox when they're created. This speeds up
-the query considerably, and also allows us the opportunity, in the
-future, to add different kind of notices to an inbox -- like @-replies
-or subscriptions to search terms or hashtags.
-
-Notice inboxes are enabled by default for new installations. If you
-are upgrading an existing site, this means that your users will see
-empty "Personal" pages. The following steps will help you fix the
-problem.
-
-0. $config['inboxes']['enabled'] can be set to one of three values. If
-   you set it to 'false', the site will work as before. Support for this
-   will probably be dropped in future versions.
-1. Setting the flag to 'transitional' means that you're in transition.
-   In this mode, the code will run the "new query" or the "old query"
-   based on whether the user's inbox has been updated.
-2. After setting the flag to "transitional", you can run the
-   fixup_inboxes.php script to create the inboxes. You may want to set
-   the memory limit high. You can re-run it without ill effect.
-3. When fixup_inboxes is finished, you can set the enabled flag to
-   'true'.
-
-NOTE: As of version 0.8.1 notice inboxes are automatically trimmed back
-      to ~1000 notices every once in a while.
-
-NOTE: we will drop support for non-inboxed sites in the 0.9.x version
-of StatusNet. It's time to switch now!
+Notice inboxes are now required. If you don't have inboxes enabled,
+StatusNet will no longer run.
 
 UTF-8 Database
 --------------
@@ -968,8 +936,6 @@ closed: If set to 'true', will disallow registration on your site.
        the service, *then* set this variable to 'true'.
 inviteonly: If set to 'true', will only allow registration if the user
            was invited by an existing user.
-openidonly: If set to 'true', will only allow registrations and logins
-           through OpenID.
 private: If set to 'true', anonymous users will be redirected to the
          'login' page. Also, API methods that normally require no
          authentication will require it. Note that this does not turn
@@ -997,6 +963,9 @@ shorturllength: Length of URL at which URLs in a message exceeding 140
 dupelimit: minimum time allowed for one person to say the same thing
            twice. Default 60s. Anything lower is considered a user
            or UI error.
+textlimit: default max size for texts in the site. Defaults to 140.
+           0 means no limit. Can be fine-tuned for notices, messages,
+           profile bios and group descriptions.
 
 db
 --
@@ -1036,6 +1005,14 @@ utf8: whether to talk to the database in UTF-8 mode. This is the default
       with new installations, but older sites may want to turn it off
       until they get their databases fixed up. See "UTF-8 database"
       above for details.
+schemacheck: when to let plugins check the database schema to add
+             tables or update them. Values can be 'runtime' (default)
+             or 'script'. 'runtime' can be costly (plugins check the
+             schema on every hit, adding potentially several db
+             queries, some quite long), but not everyone knows how to
+             run a script. If you can, set this to 'script' and run
+             scripts/checkschema.php whenever you install or upgrade a
+             plugin.
 
 syslog
 ------
@@ -1197,14 +1174,6 @@ For configuring invites.
 
 enabled: Whether to allow users to send invites. Default true.
 
-openid
-------
-
-For configuring OpenID.
-
-enabled: Whether to allow users to register and login using OpenID. Default
-        true.
-
 tag
 ---
 
@@ -1307,9 +1276,8 @@ inboxes
 
 For notice inboxes.
 
-enabled: A three-valued flag for whether to use notice inboxes (see
-        upgrading info above for notes about this change). Can be
-        'false', 'true', or '"transitional"'.
+enabled: No longer used. If you set this to something other than true,
+        StatusNet will no longer run.
 
 throttle
 --------
@@ -1331,6 +1299,8 @@ banned: an array of usernames and/or profile IDs of 'banned' profiles.
         The site will reject any notices by these users -- they will
         not be accepted at all. (Compare with blacklisted users above,
         whose posts just won't show up in the public stream.)
+biolimit: max character length of bio; 0 means no limit; null means to use
+          the site text limit default.
 
 newuser
 -------
@@ -1427,6 +1397,9 @@ Options for group functionality.
 
 maxaliases: maximum number of aliases a group can have. Default 3. Set
             to 0 or less to prevent aliases in a group.
+desclimit: maximum number of characters to allow in group descriptions.
+           null (default) means to use the site-wide text limits. 0
+           means no limit.
 
 oohembed
 --------
@@ -1505,6 +1478,24 @@ linkcolor: Hex color of all links.
 backgroundimage: Image to use for the background.
 disposition: Flags for whether or not to tile the background image.
 
+notice
+------
+
+Configuration options specific to notices.
+
+contentlimit: max length of the plain-text content of a notice.
+              Default is null, meaning to use the site-wide text limit.
+              0 means no limit.
+
+message
+-------
+
+Configuration options specific to messages.
+
+contentlimit: max length of the plain-text content of a message.
+              Default is null, meaning to use the site-wide text limit.
+              0 means no limit.
+
 Plugins
 =======
 
index c99aaeded32ab74b544d1a532ec9630c0a784b3d..76bd40473a954729f43fd2f53bbc852292b35446 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * Access token class.
+ * Access token class
  *
  * PHP version 5
  *
@@ -32,10 +32,11 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
     exit(1);
 }
 
+require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
 require_once INSTALLDIR.'/lib/omb.php';
 
 /**
- * Access token class.
+ * Access token class
  *
  * @category Action
  * @package  StatusNet
@@ -47,28 +48,23 @@ require_once INSTALLDIR.'/lib/omb.php';
 class AccesstokenAction extends Action
 {
     /**
-     * Class handler.
+     * Class handler
      *
      * @param array $args query arguments
      *
-     * @return boolean false if user doesn't exist
-     */
+     * @return nothing
+     *
+     **/
     function handle($args)
     {
         parent::handle($args);
         try {
-            common_debug('getting request from env variables', __FILE__);
-            common_remove_magic_from_request();
-            $req = OAuthRequest::from_request('POST', common_local_url('accesstoken'));
-            common_debug('getting a server', __FILE__);
-            $server = omb_oauth_server();
-            common_debug('fetching the access token', __FILE__);
-            $token = $server->fetch_access_token($req);
-            common_debug('got this token: "'.print_r($token, true).'"', __FILE__);
-            common_debug('printing the access token', __FILE__);
-            print $token;
-        } catch (OAuthException $e) {
+            $srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
+                                            omb_oauth_server());
+            $srv->writeAccessToken();
+        } catch (Exception $e) {
             $this->serverError($e->getMessage());
         }
     }
 }
+?>
index bfde3a7e4ad051a392841abb910a449ab717dc2a..f1786462e161e9d55cbadbb806a3362ee09269cb 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-/*
+/**
  * StatusNet - the distributed open-source microblogging tool
  * Copyright (C) 2008, 2009, StatusNet, Inc.
  *
  *
  * 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 Actions
+ * @package  Actions
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @author   Adrian Lang <mail@adrianlang.de>
+ * @author   Meitar Moscovitz <meitarm@gmail.com>
+ * @author   Sarven Capadisli <csarven@status.net>
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @license  GNU Affero General Public License http://www.gnu.org/licenses/
+ * @link     http://status.net
  */
 
-if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
 
 require_once INSTALLDIR.'/lib/personalgroupnav.php';
 require_once INSTALLDIR.'/lib/noticelist.php';
@@ -43,8 +59,8 @@ class AllAction extends ProfileAction
             $this->notice = $this->user->noticesWithFriends(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
         }
 
-        if($this->page > 1 && $this->notice->N == 0){
-            $this->serverError(_('No such page'),$code=404);
+        if ($this->page > 1 && $this->notice->N == 0) {
+            $this->serverError(_('No such page'), $code = 404);
         }
 
         return true;
@@ -73,20 +89,33 @@ class AllAction extends ProfileAction
 
     function getFeeds()
     {
-        return array(new Feed(Feed::RSS1,
-                              common_local_url('allrss', array('nickname' =>
-                                                               $this->user->nickname)),
-                              sprintf(_('Feed for friends of %s (RSS 1.0)'), $this->user->nickname)),
-                     new Feed(Feed::RSS2,
-                              common_local_url('api', array('apiaction' => 'statuses',
-                                                            'method' => 'friends_timeline',
-                                                            'argument' => $this->user->nickname.'.rss')),
-                              sprintf(_('Feed for friends of %s (RSS 2.0)'), $this->user->nickname)),
-                     new Feed(Feed::ATOM,
-                              common_local_url('api', array('apiaction' => 'statuses',
-                                                            'method' => 'friends_timeline',
-                                                            'argument' => $this->user->nickname.'.atom')),
-                              sprintf(_('Feed for friends of %s (Atom)'), $this->user->nickname)));
+        return array(
+            new Feed(Feed::RSS1,
+                common_local_url(
+                    'allrss', array(
+                        'nickname' =>
+                        $this->user->nickname)
+                ),
+                sprintf(_('Feed for friends of %s (RSS 1.0)'), $this->user->nickname)),
+            new Feed(Feed::RSS2,
+                common_local_url(
+                    'api', array(
+                        'apiaction' => 'statuses',
+                        'method' => 'friends_timeline',
+                        'argument' => $this->user->nickname.'.rss'
+                    )
+                ),
+                sprintf(_('Feed for friends of %s (RSS 2.0)'), $this->user->nickname)),
+            new Feed(Feed::ATOM,
+                common_local_url(
+                    'api', array(
+                        'apiaction' => 'statuses',
+                        'method' => 'friends_timeline',
+                        'argument' => $this->user->nickname.'.atom'
+                    )
+                ),
+                sprintf(_('Feed for friends of %s (Atom)'), $this->user->nickname))
+        );
     }
 
     function showLocalNav()
@@ -106,11 +135,8 @@ class AllAction extends ProfileAction
             } else {
                 $message .= sprintf(_('You can try to [nudge %s](../%s) from his profile or [post something to his or her attention](%%%%action.newnotice%%%%?status_textarea=%s).'), $this->user->nickname, $this->user->nickname, '@' . $this->user->nickname);
             }
-        }
-        else {
-            $message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and then nudge %s or post a notice to his or her attention.'),
-                                (!common_config('site','openidonly')) ? 'register' : 'openidlogin',
-                                $this->user->nickname);
+        } else {
+            $message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
         }
 
         $this->elementStart('div', 'guide');
@@ -128,17 +154,19 @@ class AllAction extends ProfileAction
             $this->showEmptyListMessage();
         }
 
-        $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE,
-                          $this->page, 'all', array('nickname' => $this->user->nickname));
+        $this->pagination(
+            $this->page > 1, $cnt > NOTICES_PER_PAGE,
+            $this->page, 'all', array('nickname' => $this->user->nickname)
+        );
     }
 
     function showPageTitle()
     {
         $user =& common_current_user();
         if ($user && ($user->id == $this->user->id)) {
-            $this->element('h1', NULL, _("You and friends"));
+            $this->element('h1', null, _("You and friends"));
         } else {
-            $this->element('h1', NULL, sprintf(_('%s and friends'), $this->user->nickname));
+            $this->element('h1', null, sprintf(_('%s and friends'), $this->user->nickname));
         }
     }
 
index 57efb73f0e85275df28109accaabd2aae2cc110a..28b1be27d82a254d82bebca9943138576b9e3927 100644 (file)
@@ -68,6 +68,7 @@ class AllrssAction extends Rss10Action
             $this->clientError(_('No such user.'));
             return false;
         } else {
+            $this->notices = $this->getNotices($this->limit);
             return true;
         }
     }
diff --git a/actions/api.php b/actions/api.php
deleted file mode 100644 (file)
index 3705d03..0000000
+++ /dev/null
@@ -1,293 +0,0 @@
-<?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); }
-
-class ApiAction extends Action
-{
-
-    var $user;
-    var $content_type;
-    var $api_arg;
-    var $api_method;
-    var $api_action;
-    var $auth_user;
-    var $auth_pw;
-
-    function handle($args)
-    {
-        parent::handle($args);
-
-        $this->api_action = $this->arg('apiaction');
-        $method = $this->arg('method');
-        $argument = $this->arg('argument');
-       $this->basic_auth_process_header();
-
-        if (isset($argument)) {
-            $cmdext = explode('.', $argument);
-            $this->api_arg =  $cmdext[0];
-            $this->api_method = $method;
-            $this->content_type = strtolower($cmdext[1]);
-        } else {
-
-            # Requested format / content-type will be an extension on the method
-            $cmdext = explode('.', $method);
-            $this->api_method = $cmdext[0];
-            $this->content_type = strtolower($cmdext[1]);
-        }
-
-        if ($this->requires_auth()) {
-            if (!isset($this->auth_user)) {
-
-                # This header makes basic auth go
-                header('WWW-Authenticate: Basic realm="StatusNet API"');
-
-                # If the user hits cancel -- bam!
-                $this->show_basic_auth_error();
-            } else {
-                $nickname = $this->auth_user;
-                $password = $this->auth_pw;
-                $user = common_check_user($nickname, $password);
-
-                if ($user) {
-                    $this->user = $user;
-                    $this->process_command();
-                } else {
-                    # basic authentication failed
-                    list($proxy, $ip) = common_client_ip();
-
-                    common_log(LOG_WARNING, "Failed API auth attempt, nickname = $nickname, proxy = $proxy, ip = $ip.");
-                    $this->show_basic_auth_error();
-                }
-            }
-        } else {
-
-            // Caller might give us a username even if not required
-            if (isset($this->auth_user)) {
-                $user = User::staticGet('nickname', $this->auth_user);
-                if ($user) {
-                    $this->user = $user;
-                }
-                # Twitter doesn't throw an error if the user isn't found
-            }
-
-            $this->process_command();
-        }
-    }
-
-    function process_command()
-    {
-        $action = "twitapi$this->api_action";
-        $actionfile = INSTALLDIR."/actions/$action.php";
-
-        if (file_exists($actionfile)) {
-            require_once($actionfile);
-            $action_class = ucfirst($action)."Action";
-            $action_obj = new $action_class();
-
-            if (!$action_obj->prepare($this->args)) {
-                return;
-            }
-
-            if (method_exists($action_obj, $this->api_method)) {
-                $apidata = array(    'content-type' => $this->content_type,
-                                    'api_method' => $this->api_method,
-                                    'api_arg' => $this->api_arg,
-                                    'user' => $this->user);
-
-                call_user_func(array($action_obj, $this->api_method), $_REQUEST, $apidata);
-            } else {
-                $this->clientError("API method not found!", $code=404);
-            }
-        } else {
-            $this->clientError("API method not found!", $code=404);
-        }
-    }
-
-    // Whitelist of API methods that don't need authentication
-    function requires_auth()
-    {
-        static $noauth = array( 'statuses/public_timeline',
-                                'statuses/show',
-                                'users/show',
-                                'help/test',
-                                'help/downtime_schedule',
-                                'statusnet/version',
-                                'statusnet/config',
-                                'statusnet/wadl',
-                                'tags/timeline',
-                                'oembed/oembed',
-                                'groups/show',
-                                'groups/timeline',
-                                'groups/list_all',
-                                'groups/membership',
-                                'groups/is_member',
-                                'groups/timeline');
-
-        static $bareauth = array('statuses/user_timeline',
-                                 'statuses/friends_timeline',
-                                'statuses/home_timeline',
-                                 'statuses/friends',
-                                 'statuses/replies',
-                                 'statuses/mentions',
-                                 'statuses/followers',
-                                 'favorites/favorites',
-                                 'friendships/show',
-                                 'groups/list_groups');
-
-        $fullname = "$this->api_action/$this->api_method";
-
-        // If the site is "private", all API methods except statusnet/config
-        // need authentication
-
-        if (common_config('site', 'private')) {
-            return $fullname != 'statusnet/config' || false;
-        }
-
-        // bareauth: only needs auth if without an argument or query param specifying user
-
-        if (in_array($fullname, $bareauth)) {
-
-            // Special case: friendships/show only needs auth if source_id or
-            // source_screen_name is not specified as a param
-
-            if ($fullname == 'friendships/show') {
-
-                $source_id          = $this->arg('source_id');
-                $source_screen_name = $this->arg('source_screen_name');
-
-                if (empty($source_id) && empty($source_screen_name)) {
-                    return true;
-                }
-
-                return false;
-            }
-
-            // if all of these are empty, auth is required
-
-            $id          = $this->arg('id');
-            $user_id     = $this->arg('user_id');
-            $screen_name = $this->arg('screen_name');
-
-            if (empty($this->api_arg) &&
-                empty($id)            &&
-                empty($user_id)       &&
-                empty($screen_name)) {
-                return true;
-            } else {
-                return false;
-            }
-
-        } else if (in_array($fullname, $noauth)) {
-
-            // noauth: never needs auth
-
-            return false;
-        } else {
-
-            // everybody else needs auth
-
-            return true;
-        }
-    }
-
-    function basic_auth_process_header()
-    {
-       if(isset($_SERVER['AUTHORIZATION']) || isset($_SERVER['HTTP_AUTHORIZATION']))
-       {
-               $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION'])?$_SERVER['HTTP_AUTHORIZATION']:$_SERVER['AUTHORIZATION'];
-       }
-
-       if(isset($_SERVER['PHP_AUTH_USER']))
-       {
-               $this->auth_user = $_SERVER['PHP_AUTH_USER'];
-               $this->auth_pw = $_SERVER['PHP_AUTH_PW'];
-       }
-       elseif ( isset($authorization_header) && strstr(substr($authorization_header, 0,5),'Basic')  )
-       {
-               // decode the HTTP_AUTHORIZATION header on php-cgi server self
-               // on fcgid server the header name is AUTHORIZATION
-
-               $auth_hash = base64_decode( substr($authorization_header, 6) );
-               list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash);
-
-               // set all to NULL on a empty basic auth request
-               if($this->auth_user == "") {
-                       $this->auth_user = NULL;
-                       $this->auth_pw = NULL;
-               }
-       }
-       else
-       {
-               $this->auth_user = NULL;
-               $this->auth_pw = NULL;
-       }
-    }
-
-    function show_basic_auth_error()
-    {
-        header('HTTP/1.1 401 Unauthorized');
-        $msg = 'Could not authenticate you.';
-
-        if ($this->content_type == 'xml') {
-            header('Content-Type: application/xml; charset=utf-8');
-            $this->startXML();
-            $this->elementStart('hash');
-            $this->element('error', null, $msg);
-            $this->element('request', null, $_SERVER['REQUEST_URI']);
-            $this->elementEnd('hash');
-            $this->endXML();
-        } else if ($this->content_type == 'json')  {
-            header('Content-Type: application/json; charset=utf-8');
-            $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
-            print(json_encode($error_array));
-        } else {
-            header('Content-type: text/plain');
-            print "$msg\n";
-        }
-    }
-
-    function isReadOnly($args)
-    {
-        $apiaction = $args['apiaction'];
-        $method = $args['method'];
-
-        list($cmdtext, $fmt) = explode('.', $method);
-
-        static $write_methods = array(
-            'account' => array('update_location', 'update_delivery_device', 'end_session'),
-            'blocks' => array('create', 'destroy'),
-            'direct_messages' => array('create', 'destroy'),
-            'favorites' => array('create', 'destroy'),
-            'friendships' => array('create', 'destroy'),
-            'help' => array(),
-            'notifications' => array('follow', 'leave'),
-            'statuses' => array('update', 'destroy'),
-            'users' => array()
-        );
-
-        if (array_key_exists($apiaction, $write_methods)) {
-            if (!in_array($cmdtext, $write_methods[$apiaction])) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-}
diff --git a/actions/apiaccountratelimitstatus.php b/actions/apiaccountratelimitstatus.php
new file mode 100644 (file)
index 0000000..96179f1
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Dummy action that emulates Twitter's rate limit status API resource
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * We don't have a rate limit, but some clients check this method.
+ * It always returns the same thing: 150 hits left.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiAccountRateLimitStatusAction extends ApiBareAuthAction
+{
+
+    /**
+     * Handle the request
+     *
+     * Return some Twitter-ish data about API limits
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        $reset   = new DateTime();
+        $reset->modify('+1 hour');
+
+        $this->initDocument($this->format);
+
+         if ($this->format == 'xml') {
+             $this->elementStart('hash');
+             $this->element('remaining-hits', array('type' => 'integer'), 150);
+             $this->element('hourly-limit', array('type' => 'integer'), 150);
+             $this->element(
+                 'reset-time', array('type' => 'datetime'),
+                 common_date_iso8601($reset->format('r'))
+             );
+             $this->element(
+                 'reset_time_in_seconds',
+                 array('type' => 'integer'),
+                 strtotime('+1 hour')
+             );
+             $this->elementEnd('hash');
+         } elseif ($this->format == 'json') {
+             $out = array(
+                 'reset_time_in_seconds' => strtotime('+1 hour'),
+                 'remaining_hits' => 150,
+                 'hourly_limit' => 150,
+                 'reset_time' => common_date_rfc2822(
+                     $reset->format('r')
+                  )
+             );
+             print json_encode($out);
+         }
+
+         $this->endDocument($this->format);
+    }
+
+}
+
diff --git a/actions/apiaccountverifycredentials.php b/actions/apiaccountverifycredentials.php
new file mode 100644 (file)
index 0000000..08b201d
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Test if supplied user credentials are valid.
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Check a user's credentials. Returns an HTTP 200 OK response code and a
+ * representation of the requesting user if authentication was successful;
+ * returns a 401 status code and an error message if not.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiAccountVerifyCredentialsAction extends ApiAuthAction
+{
+
+    /**
+     * Handle the request
+     *
+     * Check whether the credentials are valid and output the result
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        switch ($this->format) {
+        case 'xml':
+        case 'json':
+            $args['id'] = $this->auth_user->id;
+            $action_obj = new ApiUserShowAction();
+            if ($action_obj->prepare($args)) {
+                $action_obj->handle($args);
+            }
+            break;
+        default:
+            header('Content-Type: text/html; charset=utf-8');
+            print 'Authorized';
+        }
+
+    }
+
+}
diff --git a/actions/apiblockcreate.php b/actions/apiblockcreate.php
new file mode 100644 (file)
index 0000000..1cab2df
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Block a user via the API
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Blocks the user specified in the ID parameter as the authenticating user.
+ * Destroys a friendship to the blocked user if it exists. Returns the
+ * blocked user in the requested format when successful.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@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 ApiBlockCreateAction extends ApiAuthAction
+{
+    var $other   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->auth_user;
+        $this->other  = $this->getTargetUser($this->arg('id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Save the new message
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->user) || empty($this->other)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        if ($this->user->hasBlocked($this->other)
+            || $this->user->block($this->other)
+        ) {
+            $this->initDocument($this->format);
+            $this->showProfile($this->other, $this->format);
+            $this->endDocument($this->format);
+        } else {
+            $this->serverError(_('Block user failed.'), 500, $this->format);
+        }
+
+    }
+
+}
+
diff --git a/actions/apiblockdestroy.php b/actions/apiblockdestroy.php
new file mode 100644 (file)
index 0000000..16dbf94
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Un-block a user via the API
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Un-blocks the user specified in the ID parameter for the authenticating user.
+ * Returns the un-blocked user in the requested format when successful.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@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 ApiBlockDestroyAction extends ApiAuthAction
+{
+    var $other   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->auth_user;
+        $this->other  = $this->getTargetUser($this->arg('id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Save the new message
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->user) || empty($this->other)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        if (!$this->user->hasBlocked($this->other)
+            || $this->user->unblock($this->other)
+        ) {
+            $this->initDocument($this->format);
+            $this->showProfile($this->other, $this->format);
+            $this->endDocument($this->format);
+        } else {
+            $this->serverError(_('Unblock user failed.'));
+        }
+
+    }
+
+}
+
diff --git a/actions/apidirectmessage.php b/actions/apidirectmessage.php
new file mode 100644 (file)
index 0000000..a21fe86
--- /dev/null
@@ -0,0 +1,375 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a the direct messages from or to a user
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Show a list of direct messages from or to the authenticating user
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Adrian Lang <mail@adrianlang.de>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiDirectMessageAction extends ApiAuthAction
+{
+    var $messages     = null;
+    var $title        = null;
+    var $subtitle     = null;
+    var $link         = null;
+    var $selfuri_base = null;
+    var $id           = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->auth_user;
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        $server   = common_root_url();
+        $taguribase = common_config('integration', 'taguri');
+
+        if ($this->arg('sent')) {
+
+            // Action was called by /api/direct_messages/sent.format
+
+            $this->title = sprintf(
+                _("Direct messages from %s"),
+                $this->user->nickname
+            );
+            $this->subtitle = sprintf(
+                _("All the direct messages sent from %s"),
+                $this->user->nickname
+            );
+            $this->link = $server . $this->user->nickname . '/outbox';
+            $this->selfuri_base = common_root_url() . 'api/direct_messages/sent';
+            $this->id = "tag:$taguribase:SentDirectMessages:" . $this->user->id;
+        } else {
+            $this->title = sprintf(
+                _("Direct messages to %s"),
+                $this->user->nickname
+            );
+            $this->subtitle = sprintf(
+                _("All the direct messages sent to %s"),
+                $this->user->nickname
+            );
+            $this->link = $server . $this->user->nickname . '/inbox';
+            $this->selfuri_base = common_root_url() . 'api/direct_messages';
+            $this->id = "tag:$taguribase:DirectMessages:" . $this->user->id;
+        }
+
+        $this->messages = $this->getMessages();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Show the messages
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showMessages();
+    }
+
+    /**
+     * Show the messages
+     *
+     * @return void
+     */
+
+    function showMessages()
+    {
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlDirectMessages();
+            break;
+        case 'rss':
+            $this->showRssDirectMessages();
+            break;
+        case 'atom':
+            $this->showAtomDirectMessages();
+            break;
+        case 'json':
+            $this->showJsonDirectMessages();
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+            break;
+        }
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getMessages()
+    {
+        $message  = new Message();
+
+        if ($this->arg('sent')) {
+            $message->from_profile = $this->user->id;
+        } else {
+            $message->to_profile = $this->user->id;
+        }
+
+        if (!empty($this->max_id)) {
+            $message->whereAdd('id <= ' . $this->max_id);
+        }
+
+        if (!empty($this->since_id)) {
+            $message->whereAdd('id > ' . $this->since_id);
+        }
+
+        if (!empty($since)) {
+            $d = date('Y-m-d H:i:s', $this->since);
+            $message->whereAdd("created > '$d'");
+        }
+
+        $message->orderBy('created DESC, id DESC');
+        $message->limit((($this->page - 1) * $this->count), $this->count);
+        $message->find();
+
+        $messages = array();
+
+        while ($message->fetch()) {
+            $messages[] = clone($message);
+        }
+
+        return $messages;
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this notice last modified?
+     *
+     * @return string datestamp of the latest notice in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->messages)) {
+            return strtotime($this->messages[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * Shows a list of direct messages as Twitter-style XML array
+     *
+     * @return void
+     */
+
+    function showXmlDirectMessages()
+    {
+        $this->initDocument('xml');
+        $this->elementStart('direct-messages', array('type' => 'array'));
+
+        foreach ($this->messages as $m) {
+            $dm_array = $this->directMessageArray($m);
+            $this->showXmlDirectMessage($dm_array);
+        }
+
+        $this->elementEnd('direct-messages');
+        $this->endDocument('xml');
+    }
+
+    /**
+     * Shows a list of direct messages as a JSON encoded array
+     *
+     * @return void
+     */
+
+    function showJsonDirectMessages()
+    {
+        $this->initDocument('json');
+
+        $dmsgs = array();
+
+        foreach ($this->messages as $m) {
+            $dm_array = $this->directMessageArray($m);
+            array_push($dmsgs, $dm_array);
+        }
+
+        $this->showJsonObjects($dmsgs);
+        $this->endDocument('json');
+    }
+
+    /**
+     * Shows a list of direct messages as RSS items
+     *
+     * @return void
+     */
+
+    function showRssDirectMessages()
+    {
+        $this->initDocument('rss');
+
+        $this->element('title', null, $this->title);
+
+        $this->element('link', null, $this->link);
+        $this->element('description', null, $this->subtitle);
+        $this->element('language', null, 'en-us');
+
+        $this->element(
+            'atom:link',
+            array(
+                'type' => 'application/rss+xml',
+                'href' => $this->selfuri_base . '.rss',
+                'rel' => self
+                ),
+            null
+        );
+        $this->element('ttl', null, '40');
+
+        foreach ($this->messages as $m) {
+            $entry = $this->rssDirectMessageArray($m);
+            $this->showTwitterRssItem($entry);
+        }
+
+        $this->endTwitterRss();
+    }
+
+    /**
+     * Shows a list of direct messages as Atom entries
+     *
+     * @return void
+     */
+
+    function showAtomDirectMessages()
+    {
+        $this->initDocument('atom');
+
+        $this->element('title', null, $this->title);
+        $this->element('id', null, $this->id);
+
+        $selfuri = common_root_url() . 'api/direct_messages.atom';
+
+        $this->element(
+            'link', array(
+            'href' => $this->link,
+            'rel' => 'alternate',
+            'type' => 'text/html'),
+            null
+        );
+        $this->element(
+            'link', array(
+            'href' => $this->selfuri_base . '.atom', 'rel' => 'self',
+            'type' => 'application/atom+xml'),
+            null
+        );
+        $this->element('updated', null, common_date_iso8601('now'));
+        $this->element('subtitle', null, $this->subtitle);
+
+        foreach ($this->messages as $m) {
+            $entry = $this->rssDirectMessageArray($m);
+            $this->showTwitterAtomEntry($entry);
+        }
+
+        $this->endDocument('atom');
+    }
+
+    /**
+     * An entity tag for this notice
+     *
+     * Returns an Etag based on the action name, language, and
+     * timestamps of the notice
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->messages)) {
+
+            $last = count($this->messages) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      strtotime($this->messages[0]->created),
+                      strtotime($this->messages[$last]->created)
+                )
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apidirectmessagenew.php b/actions/apidirectmessagenew.php
new file mode 100644 (file)
index 0000000..ca1ee70
--- /dev/null
@@ -0,0 +1,188 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Send a direct message via the API
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Creates a new direct message from the authenticating user to
+ * the user specified by id.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Adrian Lang <mail@adrianlang.de>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiDirectMessageNewAction extends ApiAuthAction
+{
+    var $source  = null;
+    var $other   = null;
+    var $content = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->auth_user;
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        $this->source = $this->trimmed('source'); // Not supported by Twitter.
+
+        $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api');
+        if (empty($thtis->source) || in_array($this->source, $reserved_sources)) {
+            $source = 'api';
+        }
+
+        $this->content = $this->trimmed('text');
+
+        $this->user  = $this->auth_user;
+
+        $user_param  = $this->trimmed('user');
+        $user_id     = $this->arg('user_id');
+        $screen_name = $this->trimmed('screen_name');
+
+        if (isset($user_param) || isset($user_id) || isset($screen_name)) {
+            $this->other = $this->getTargetUser($user_param);
+        }
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Save the new message
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->content)) {
+            $this->clientError(
+                _('No message text!'),
+                406,
+                $this->format
+            );
+        } else {
+            $content_shortened = common_shorten_links($this->content);
+            if (Message::contentTooLong($content_shortened)) {
+                $this->clientError(
+                    sprintf(
+                        _('That\'s too long. Max message size is %d chars.'),
+                        Message::maxContent()
+                    ),
+                    406,
+                    $this->format
+                );
+                return;
+            }
+        }
+
+        if (empty($this->other)) {
+            $this->clientError(_('Recipient user not found.'), 403, $this->format);
+            return;
+        } else if (!$this->user->mutuallySubscribed($this->other)) {
+            $this->clientError(
+                _('Can\'t send direct messages to users who aren\'t your friend.'),
+                403,
+                $this->format
+            );
+            return;
+        } else if ($this->user->id == $this->other->id) {
+
+            // Note: sending msgs to yourself is allowed by Twitter
+
+            $errmsg = 'Don\'t send a message to yourself; ' .
+                   'just say it to yourself quietly instead.';
+
+            $this->clientError(_($errmsg), 403, $this->format);
+            return;
+        }
+
+        $message = Message::saveNew(
+            $this->user->id,
+            $this->other->id,
+            html_entity_decode($this->content, ENT_NOQUOTES, 'UTF-8'),
+            $this->source
+        );
+
+        if (is_string($message)) {
+            $this->serverError($message);
+            return;
+        }
+
+        mail_notify_message($message, $this->user, $this->other);
+
+        if ($this->format == 'xml') {
+            $this->showSingleXmlDirectMessage($message);
+        } elseif ($this->format == 'json') {
+            $this->showSingleJsondirectMessage($message);
+        }
+    }
+
+}
+
diff --git a/actions/apifavoritecreate.php b/actions/apifavoritecreate.php
new file mode 100644 (file)
index 0000000..4367397
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Add a notice to a user's list of favorite notices via the API
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Favorites the status specified in the ID parameter as the authenticating user.
+ * Returns the favorite status when successful.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@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 ApiFavoriteCreateAction extends ApiAuthAction
+{
+    var $notice = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->auth_user;
+        $this->notice = Notice::staticGet($this->arg('id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->notice)) {
+            $this->clientError(
+                _('No status found with that ID.'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        // Note: Twitter lets you fave things repeatedly via API.
+
+        if ($this->user->hasFave($this->notice)) {
+            $this->clientError(
+                _('This status is already a favorite!'),
+                403,
+                $this->format
+            );
+            return;
+        }
+
+        $fave = Fave::addNew($this->user, $this->notice);
+
+        if (empty($fave)) {
+            $this->clientError(
+                _('Could not create favorite.'),
+                403,
+                $this->format
+            );
+            return;
+        }
+
+        $this->notify($fave, $this->notice, $this->user);
+        $this->user->blowFavesCache();
+
+        if ($this->format == 'xml') {
+            $this->showSingleXmlStatus($this->notice);
+        } elseif ($this->format == 'json') {
+            $this->show_single_json_status($this->notice);
+        }
+    }
+
+    /**
+     * Notify the author of the favorite that the user likes their notice
+     *
+     * @param Favorite $fave   the favorite in question
+     * @param Notice   $notice the notice that's been faved
+     * @param User     $user   the user doing the favoriting
+     *
+     * @return void
+     */
+    function notify($fave, $notice, $user)
+    {
+        $other = User::staticGet('id', $notice->profile_id);
+        if ($other && $other->id != $user->id) {
+            if ($other->email && $other->emailnotifyfav) {
+                mail_notify_fave($other, $user, $notice);
+            }
+            // XXX: notify by IM
+            // XXX: notify by SMS
+        }
+    }
+
+}
diff --git a/actions/apifavoritedestroy.php b/actions/apifavoritedestroy.php
new file mode 100644 (file)
index 0000000..f131d1c
--- /dev/null
@@ -0,0 +1,150 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Remote a notice from a user's list of favorite notices via the API
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Un-favorites the status specified in the ID parameter as the authenticating user.
+ * Returns the un-favorited status in the requested format when successful.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@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 ApiFavoriteDestroyAction extends ApiAuthAction
+{
+
+    var $notice = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->auth_user;
+        $this->notice = Notice::staticGet($this->arg('id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->notice)) {
+            $this->clientError(
+                _('No status found with that ID.'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        $fave            = new Fave();
+        $fave->user_id   = $this->user->id;
+        $fave->notice_id = $this->notice->id;
+
+        if (!$fave->find(true)) {
+            $this->clientError(
+                _('That status is not a favorite!'),
+                403,
+                $this->favorite
+            );
+            return;
+        }
+
+        $result = $fave->delete();
+
+        if (!$result) {
+            common_log_db_error($fave, 'DELETE', __FILE__);
+            $this->clientError(
+                _('Could not delete favorite.'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        $this->user->blowFavesCache();
+
+        if ($this->format == 'xml') {
+            $this->showSingleXmlStatus($this->notice);
+        } elseif ($this->format == 'json') {
+            $this->show_single_json_status($this->notice);
+        }
+    }
+
+}
diff --git a/actions/apifriendshipscreate.php b/actions/apifriendshipscreate.php
new file mode 100644 (file)
index 0000000..a824e73
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Subscribe to a user via the API
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Allows the authenticating users to follow (subscribe) the user specified in
+ * the ID parameter.  Returns the befriended user in the requested format when
+ * successful.  Returns a string describing the failure condition when unsuccessful.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@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 ApiFriendshipsCreateAction extends ApiAuthAction
+{
+    var $other  = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->auth_user;
+        $this->other  = $this->getTargetUser($id);
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->other)) {
+            $this->clientError(
+                _('Could not follow user: User not found.'),
+                403,
+                $this->format
+            );
+            return;
+        }
+
+        if ($this->user->isSubscribed($this->other)) {
+            $errmsg = sprintf(
+                _('Could not follow user: %s is already on your list.'),
+                $this->other->nickname
+            );
+            $this->clientError($errmsg, 403, $this->format);
+            return;
+        }
+
+        $result = subs_subscribe_to($this->user, $this->other);
+
+        if (is_string($result)) {
+            $this->clientError($result, 403, $this->format);
+            return;
+        }
+
+        $this->initDocument($this->format);
+        $this->showProfile($this->other, $this->format);
+        $this->endDocument($this->format);
+    }
+
+}
diff --git a/actions/apifriendshipsdestroy.php b/actions/apifriendshipsdestroy.php
new file mode 100644 (file)
index 0000000..3d9b7e0
--- /dev/null
@@ -0,0 +1,139 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Unsubscribe to a user via API
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Allows the authenticating users to unfollow (unsubscribe) the user specified in
+ * the ID parameter.  Returns the unfollowed user in the requested format when
+ * successful.  Returns a string describing the failure condition when unsuccessful.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@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 ApiFriendshipsDestroyAction extends ApiAuthAction
+{
+    var $other  = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->auth_user;
+        $this->other  = $this->getTargetUser($id);
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->other)) {
+            $this->clientError(
+                _('Could not unfollow user: User not found.'),
+                403,
+                $this->format
+            );
+            return;
+        }
+
+        // Don't allow unsubscribing from yourself!
+
+        if ($this->user->id == $this->other->id) {
+            $this->clientError(
+                _("You cannot unfollow yourself!"),
+                403,
+                $this->format
+            );
+            return;
+        }
+
+        $result = subs_unsubscribe_user($this->user, $this->other->nickname);
+
+        if (is_string($result)) {
+            $this->clientError($result, 403, $this->format);
+            return;
+        }
+
+        $this->initDocument($this->format);
+        $this->showProfile($this->other, $this->format);
+        $this->endDocument($this->format);
+    }
+
+}
diff --git a/actions/apifriendshipsexists.php b/actions/apifriendshipsexists.php
new file mode 100644 (file)
index 0000000..ae50c51
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show whether there is a friendship between two users
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Tests for the existence of friendship between two users. Will return true if
+ * user_a follows user_b, otherwise will return false.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@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 ApiFriendshipsExistsAction extends ApiAction
+{
+    var $user_a = null;
+    var $user_b = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $user_a_id = $this->trimmed('user_a');
+        $user_b_id = $this->trimmed('user_b');
+
+        common_debug("user_a = " . $user_a_id);
+        common_debug("user_b = " . $user_b_id);
+
+
+        $this->user_a = $this->getTargetUser($user_a_id);
+
+        if (empty($this->user_a)) {
+            common_debug('gargargra');
+        }
+
+        $this->user_b = $this->getTargetUser($user_b_id);
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (empty($this->user_a) || empty($this->user_b)) {
+            $this->clientError(
+                _('Two user ids or screen_names must be supplied.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        $result = $this->user_a->isSubscribed($this->user_b);
+
+        switch ($this->format) {
+        case 'xml':
+            $this->initDocument('xml');
+            $this->element('friends', null, $result);
+            $this->endDocument('xml');
+            break;
+        case 'json':
+            $this->initDocument('json');
+            print json_encode($result);
+            $this->endDocument('json');
+            break;
+        default:
+            break;
+        }
+    }
+
+}
diff --git a/actions/apifriendshipsshow.php b/actions/apifriendshipsshow.php
new file mode 100644 (file)
index 0000000..8fc4367
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show information about the relationship between two users
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Outputs detailed information about the relationship between two users
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@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 ApiFriendshipsShowAction extends ApiBareAuthAction
+{
+    var $source = null;
+    var $target = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $source_id          = (int)$this->trimmed('source_id');
+        $source_screen_name = $this->trimmed('source_screen_name');
+        $target_id          = (int)$this->trimmed('target_id');
+        $target_screen_name = $this->trimmed('target_screen_name');
+
+        if (!empty($source_id)) {
+            $this->source = User::staticGet($source_id);
+        } elseif (!empty($source_screen_name)) {
+            $this->source = User::staticGet('nickname', $source_screen_name);
+        } else {
+            $this->source = $this->auth_user;
+        }
+
+        if (!empty($target_id)) {
+            $this->target = User::staticGet($target_id);
+        } elseif (!empty($target_screen_name)) {
+            $this->target = User::staticGet('nickname', $target_screen_name);
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Determines whether this API resource requires auth.  Overloaded to look
+     * return true in case source_id and source_screen_name are both empty
+     *
+     * @return boolean true or false
+     */
+
+    function requiresAuth()
+    {
+        if (common_config('site', 'private')) {
+            return true;
+        }
+
+        $source_id          = $this->trimmed('source_id');
+        $source_screen_name = $this->trimmed('source_screen_name');
+
+        if (empty($source_id) && empty($source_screen_name)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(_('API method not found!'), 404);
+            return;
+        }
+
+        if (empty($this->source)) {
+            $this->clientError(
+                _('Could not determine source user.'),
+                404
+             );
+            return;
+        }
+
+        if (empty($this->target)) {
+            $this->clientError(
+                _('Could not find target user.'),
+                404
+            );
+            return;
+        }
+
+        $result = $this->twitterRelationshipArray($this->source, $this->target);
+
+        switch ($this->format) {
+        case 'xml':
+            $this->initDocument('xml');
+            $this->showTwitterXmlRelationship($result[relationship]);
+            $this->endDocument('xml');
+            break;
+        case 'json':
+            $this->initDocument('json');
+            print json_encode($result);
+            $this->endDocument('json');
+            break;
+        default:
+            break;
+        }
+
+    }
+
+}
diff --git a/actions/apigroupcreate.php b/actions/apigroupcreate.php
new file mode 100644 (file)
index 0000000..f66e830
--- /dev/null
@@ -0,0 +1,372 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Create a group via the API
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Make a new group. Sets the authenticated user as the administrator of the group.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @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 ApiGroupCreateAction extends ApiAuthAction
+{
+    var $group       = null;
+    var $nickname    = null;
+    var $fullname    = null;
+    var $homepage    = null;
+    var $description = null;
+    var $location    = null;
+    var $aliasstring = null;
+    var $aliases     = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user  = $this->auth_user;
+
+        $this->nickname    = $this->arg('nickname');
+        $this->fullname    = $this->arg('full_name');
+        $this->homepage    = $this->arg('homepage');
+        $this->description = $this->arg('description');
+        $this->location    = $this->arg('location');
+        $this->aliasstring = $this->arg('aliases');
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Save the new group
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+             $this->clientError(
+                 _('This method requires a POST.'),
+                 400,
+                 $this->format
+             );
+             return;
+        }
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        if ($this->validateParams() == false) {
+            return;
+        }
+
+        $group = new User_group();
+
+        $group->query('BEGIN');
+
+        $group->nickname    = $this->nickname;
+        $group->fullname    = $this->fullname;
+        $group->homepage    = $this->homepage;
+        $group->description = $this->description;
+        $group->location    = $this->location;
+        $group->created     = common_sql_now();
+
+        $result = $group->insert();
+
+        if (!$result) {
+            common_log_db_error($group, 'INSERT', __FILE__);
+            $this->serverError(
+                _('Could not create group.'),
+                500,
+                $this->format
+            );
+            return;
+        }
+
+        $result = $group->setAliases($this->aliases);
+
+        if (!$result) {
+            $this->serverError(
+                _('Could not create aliases.'),
+                500,
+                $this->format
+            );
+            return;
+        }
+
+        $member = new Group_member();
+
+        $member->group_id   = $group->id;
+        $member->profile_id = $this->user->id;
+        $member->is_admin   = 1;
+        $member->created    = $group->created;
+
+        $result = $member->insert();
+
+        if (!$result) {
+            common_log_db_error($member, 'INSERT', __FILE__);
+            $this->serverError(
+                _('Could not set group membership.'),
+                500,
+                $this->format
+            );
+            return;
+        }
+
+        $group->query('COMMIT');
+
+        switch($this->format) {
+        case 'xml':
+            $this->showSingleXmlGroup($group);
+            break;
+        case 'json':
+            $this->showSingleJsonGroup($group);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+
+    }
+
+    /**
+     * Validate params for the new group
+     *
+     * @return void
+     */
+
+    function validateParams()
+    {
+        $valid = Validate::string(
+            $this->nickname, array(
+                'min_length' => 1,
+                'max_length' => 64,
+                'format' => NICKNAME_FMT
+            )
+        );
+
+        if (!$valid) {
+            $this->clientError(
+                _(
+                    'Nickname must have only lowercase letters ' .
+                    'and numbers and no spaces.'
+                ),
+                403,
+                $this->format
+            );
+            return false;
+        } elseif ($this->groupNicknameExists($this->nickname)) {
+            $this->clientError(
+                _('Nickname already in use. Try another one.'),
+                403,
+                $this->format
+            );
+            return false;
+        } else if (!User_group::allowedNickname($this->nickname)) {
+            $this->clientError(
+                _('Not a valid nickname.'),
+                403,
+                $this->format
+            );
+            return false;
+
+        } elseif (
+            !is_null($this->homepage)
+            && strlen($this->homepage) > 0
+            && !Validate::uri(
+                $this->homepage, array(
+                    'allowed_schemes' =>
+                    array('http', 'https')
+                )
+            )) {
+            $this->clientError(
+                _('Homepage is not a valid URL.'),
+                403,
+                $this->format
+            );
+            return false;
+        } elseif (
+            !is_null($this->fullname)
+            && mb_strlen($this->fullname) > 255) {
+                $this->clientError(
+                    _('Full name is too long (max 255 chars).'),
+                    403,
+                    $this->format
+                );
+            return false;
+        } elseif (User_group::descriptionTooLong($this->description)) {
+            $this->clientError(
+                sprintf(
+                    _('Description is too long (max %d chars).'),
+                    User_group::maxDescription()
+                ),
+                403,
+                $this->format
+            );
+            return false;
+        } elseif (
+            !is_null($this->location)
+            && mb_strlen($this->location) > 255) {
+                $this->clientError(
+                    _('Location is too long (max 255 chars).'),
+                    403,
+                    $this->format
+                );
+            return false;
+        }
+
+        if (!empty($this->aliasstring)) {
+            $this->aliases = array_map(
+                'common_canonical_nickname',
+                array_unique(preg_split('/[\s,]+/', $this->aliasstring))
+            );
+        } else {
+            $this->aliases = array();
+        }
+
+        if (count($this->aliases) > common_config('group', 'maxaliases')) {
+            $this->clientError(
+                sprintf(
+                    _('Too many aliases! Maximum %d.'),
+                    common_config('group', 'maxaliases')
+                ),
+                403,
+                $this->format
+            );
+            return false;
+        }
+
+        foreach ($this->aliases as $alias) {
+
+            $valid = Validate::string(
+                $alias, array(
+                    'min_length' => 1,
+                    'max_length' => 64,
+                    'format' => NICKNAME_FMT
+                )
+            );
+
+            if (!$valid) {
+                $this->clientError(
+                    sprintf(_('Invalid alias: "%s"'), $alias),
+                    403,
+                    $this->format
+                );
+                return false;
+            }
+            if ($this->groupNicknameExists($alias)) {
+                $this->clientError(
+                    sprintf(
+                        _('Alias "%s" already in use. Try another one.'),
+                        $alias
+                    ),
+                    403,
+                    $this->format
+                );
+                return false;
+            }
+
+            // XXX assumes alphanum nicknames
+
+            if (strcmp($alias, $this->nickname) == 0) {
+                $this->clientError(
+                    _('Alias can\'t be the same as nickname.'),
+                    403,
+                    $this->format
+                );
+                return false;
+            }
+        }
+
+        // Evarything looks OK
+
+        return true;
+    }
+
+    /**
+     * Check to see whether a nickname is already in use by a group
+     *
+     * @param String $nickname The nickname in question
+     *
+     * @return boolean true or false
+     */
+
+    function groupNicknameExists($nickname)
+    {
+        $group = User_group::staticGet('nickname', $nickname);
+
+        if (!empty($group)) {
+            return true;
+        }
+
+        $alias = Group_alias::staticGet('alias', $nickname);
+
+        if (!empty($alias)) {
+            return true;
+        }
+
+        return false;
+    }
+
+}
diff --git a/actions/apigroupismember.php b/actions/apigroupismember.php
new file mode 100644 (file)
index 0000000..a8a40a6
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Check to see whether a user a member of a group
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Returns whether a user is a member of a specified group.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @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 ApiGroupIsMemberAction extends ApiBareAuthAction
+{
+    var $group   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->getTargetUser(null);
+        $this->group  = $this->getTargetGroup(null);
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Save the new message
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        if (empty($this->group)) {
+            $this->clientError('Group not found!', 404, $this->format);
+            return false;
+        }
+
+        $is_member = $this->user->isMember($this->group);
+
+        switch($this->format) {
+        case 'xml':
+            $this->initDocument('xml');
+            $this->element('is_member', null, $is_member);
+            $this->endDocument('xml');
+            break;
+        case 'json':
+            $this->initDocument('json');
+            $this->showJsonObjects(array('is_member' => $is_member));
+            $this->endDocument('json');
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                400,
+                $this->format
+            );
+            break;
+        }
+    }
+
+}
diff --git a/actions/apigroupjoin.php b/actions/apigroupjoin.php
new file mode 100644 (file)
index 0000000..071cd92
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Join a group via the API
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Joins the authenticated user to the group speicified by ID
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @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 ApiGroupJoinAction extends ApiAuthAction
+{
+    var $group   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user  = $this->auth_user;
+        $this->group = $this->getTargetGroup($this->arg('id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Save the new message
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        if (empty($this->group)) {
+            $this->clientError('Group not found!', 404, $this->format);
+            return false;
+        }
+
+        if ($this->user->isMember($this->group)) {
+            $this->clientError(
+                _('You are already a member of that group.'),
+                403,
+                $this->format
+            );
+            return;
+        }
+
+        if (Group_block::isBlocked($this->group, $this->user->getProfile())) {
+            $this->clientError(
+                _('You have been blocked from that group by the admin.'),
+                403,
+                $this->format
+            );
+            return;
+        }
+
+        $member = new Group_member();
+
+        $member->group_id   = $this->group->id;
+        $member->profile_id = $this->user->id;
+        $member->created    = common_sql_now();
+
+        $result = $member->insert();
+
+        if (!$result) {
+            common_log_db_error($member, 'INSERT', __FILE__);
+            $this->serverError(
+                sprintf(
+                    _('Could not join user %s to group %s.'),
+                    $this->user->nickname,
+                    $this->group->nickname
+                )
+            );
+            return;
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->show_single_xml_group($this->group);
+            break;
+        case 'json':
+            $this->showSingleJsonGroup($this->group);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+}
diff --git a/actions/apigroupleave.php b/actions/apigroupleave.php
new file mode 100644 (file)
index 0000000..0d4bb9e
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Leave a group via the API
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Removes the authenticated user from the group specified by ID
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @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 ApiGroupLeaveAction extends ApiAuthAction
+{
+    var $group   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user  = $this->auth_user;
+        $this->group = $this->getTargetGroup($this->arg('id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Save the new message
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400,
+                $this->format
+            );
+            return;
+        }
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        if (empty($this->group)) {
+            $this->clientError('Group not found!', 404, $this->format);
+            return false;
+        }
+
+        $member = new Group_member();
+
+        $member->group_id   = $this->group->id;
+        $member->profile_id = $this->auth->id;
+
+        if (!$member->find(true)) {
+            $this->serverError(_('You are not a member of this group.'));
+            return;
+        }
+
+        $result = $member->delete();
+
+        if (!$result) {
+            common_log_db_error($member, 'INSERT', __FILE__);
+            $this->serverError(
+                sprintf(
+                    _('Could not remove user %s to group %s.'),
+                    $this->user->nickname,
+                    $this->$group->nickname
+                )
+            );
+            return;
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->show_single_xml_group($this->group);
+            break;
+        case 'json':
+            $this->showSingleJsonGroup($this->group);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+}
diff --git a/actions/apigrouplist.php b/actions/apigrouplist.php
new file mode 100644 (file)
index 0000000..c529c1e
--- /dev/null
@@ -0,0 +1,223 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Check to see whether a user a member of a group
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Returns whether a user is a member of a specified group.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @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 ApiGroupListAction extends ApiBareAuthAction
+{
+    var $groups   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->getTargetUser($id);
+        $this->groups = $this->getGroups();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Show the user's groups
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s's groups"), $this->user->nickname);
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:Groups";
+        $link       = common_local_url(
+            'usergroups',
+            array('nickname' => $this->user->nickname)
+        );
+        $subtitle   = sprintf(
+            _("Groups %s is a member of on %s."),
+            $this->user->nickname,
+            $sitename
+        );
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlGroups($this->groups);
+            break;
+        case 'rss':
+            $this->showRssGroups($this->groups, $title, $link, $subtitle);
+            break;
+        case 'atom':
+            $selfuri = common_root_url() . 'api/statusnet/groups/list/' .
+                $this->user->id . '.atom';
+            $this->showAtomGroups(
+                $this->groups,
+                $title,
+                $id,
+                $link,
+                $subtitle,
+                $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonGroups($this->groups);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+
+    }
+
+    /**
+     * Get groups
+     *
+     * @return array groups
+     */
+
+    function getGroups()
+    {
+        $groups = array();
+
+        $group = $this->user->getGroups(
+            ($this->page - 1) * $this->count,
+            $this->count,
+            $this->since_id,
+            $this->max_id,
+            $this->since
+        );
+
+        while ($group->fetch()) {
+            $groups[] = clone($group);
+        }
+
+        return $groups;
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this feed last modified?
+     *
+     * @return string datestamp of the latest group the user has joined
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->groups) && (count($this->groups) > 0)) {
+            return strtotime($this->groups[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this list of groups
+     *
+     * Returns an Etag based on the action name, language, user ID and
+     * timestamps of the first and last group the user has joined
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->groups) && (count($this->groups) > 0)) {
+
+            $last = count($this->groups) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->user->id,
+                      strtotime($this->groups[0]->created),
+                      strtotime($this->groups[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apigrouplistall.php b/actions/apigrouplistall.php
new file mode 100644 (file)
index 0000000..89469f3
--- /dev/null
@@ -0,0 +1,208 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show the newest groups
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Returns of the lastest 20 groups for the site
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @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 ApiGroupListAllAction extends ApiAction
+{
+    var $groups   = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = $this->getTargetUser($id);
+        $this->groups = $this->getGroups();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Show the user's groups
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s groups"), $sitename);
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:Groups";
+        $link       = common_local_url('groups');
+        $subtitle   = sprintf(_("groups on %s"), $sitename);
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlGroups($this->groups);
+            break;
+        case 'rss':
+            $this->showRssGroups($this->groups, $title, $link, $subtitle);
+            break;
+        case 'atom':
+            $selfuri = common_root_url() .
+                'api/statusnet/groups/list_all.atom';
+            $this->showAtomGroups(
+                $this->groups,
+                $title,
+                $id,
+                $link,
+                $subtitle,
+                $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonGroups($this->groups);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+
+    }
+
+    /**
+     * Get groups
+     *
+     * @return array groups
+     */
+
+    function getGroups()
+    {
+        $groups = array();
+
+        // XXX: Use the $page, $count, $max_id, $since_id, and $since parameters
+
+        $group = new User_group();
+        $group->orderBy('created DESC');
+        $group->find();
+
+        while ($group->fetch()) {
+            $groups[] = clone($group);
+        }
+
+        return $groups;
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this feed last modified?
+     *
+     * @return string datestamp of the site's latest group
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->groups) && (count($this->groups) > 0)) {
+            return strtotime($this->groups[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this list of groups
+     *
+     * Returns an Etag based on the action name, language, and
+     * timestamps of the first and last group the user has joined
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->groups) && (count($this->groups) > 0)) {
+
+            $last = count($this->groups) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      strtotime($this->groups[0]->created),
+                      strtotime($this->groups[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apigroupmembership.php b/actions/apigroupmembership.php
new file mode 100644 (file)
index 0000000..b31e47b
--- /dev/null
@@ -0,0 +1,192 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * List a group's members
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * List 20 newest members of the group specified by name or ID.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @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 ApiGroupMembershipAction extends ApiAction
+{
+    var $group    = null;
+    var $profiles = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->group    = $this->getTargetGroup($this->arg('id'));
+        $this->profiles = $this->getProfiles();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Show the members of the group
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        // XXX: RSS and Atom
+
+        switch($this->format) {
+        case 'xml':
+            $this->showTwitterXmlUsers($this->profiles);
+            break;
+        case 'json':
+            $this->showJsonUsers($this->profiles);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+    /**
+     * Fetch the members of a group
+     *
+     * @return array $profiles list of profiles
+     */
+
+    function getProfiles()
+    {
+        $profiles = array();
+
+        $profile = $this->group->getMembers(
+            ($this->page - 1) * $this->count,
+            $this->count,
+            $this->since_id,
+            $this->max_id,
+            $this->since
+        );
+
+        while ($profile->fetch()) {
+            $profiles[] = clone($profile);
+        }
+
+        return $profiles;
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this list of profiles last modified?
+     *
+     * @return string datestamp of the lastest profile in the group
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->profiles) && (count($this->profiles) > 0)) {
+            return strtotime($this->profiles[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this list of groups
+     *
+     * Returns an Etag based on the action name, language
+     * the group id, and timestamps of the first and last
+     * user who has joined the group
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->profiles) && (count($this->profiles) > 0)) {
+
+            $last = count($this->profiles) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->group->id,
+                      strtotime($this->profiles[0]->created),
+                      strtotime($this->profiles[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apigroupshow.php b/actions/apigroupshow.php
new file mode 100644 (file)
index 0000000..2bdb22b
--- /dev/null
@@ -0,0 +1,152 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show information about a group
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Outputs detailed information about the group specified by ID
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @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 ApiGroupShowAction extends ApiAction
+{
+    var $group = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->group = $this->getTargetGroup($this->arg('id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (empty($this->group)) {
+            $this->clientError(
+                'Group not found!',
+                404,
+                $this->format
+            );
+            return;
+        }
+
+        switch($this->format) {
+        case 'xml':
+            $this->show_single_xml_group($this->group);
+            break;
+        case 'json':
+            $this->showSingleJsonGroup($this->group);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), 404, $this->format);
+            break;
+        }
+
+    }
+
+    /**
+     * When was this group last modified?
+     *
+     * @return string datestamp of the latest notice in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->group)) {
+            return strtotime($this->group->modified);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this group
+     *
+     * Returns an Etag based on the action name, language, and
+     * timestamps of the notice
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->group)) {
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->group->id,
+                      strtotime($this->group->modified))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apihelptest.php b/actions/apihelptest.php
new file mode 100644 (file)
index 0000000..e4ef55f
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Test that you can connect to the API
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Returns the string "ok" in the requested format with a 200 OK HTTP status code.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@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 ApiHelpTestAction extends ApiAction
+{
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($this->format == 'xml') {
+            $this->initDocument('xml');
+            $this->element('ok', null, 'true');
+            $this->endDocument('xml');
+        } elseif ($this->format == 'json') {
+            $this->initDocument('json');
+            print '"ok"';
+            $this->endDocument('json');
+        } else {
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+        }
+    }
+
+}
+
diff --git a/actions/apistatusesdestroy.php b/actions/apistatusesdestroy.php
new file mode 100644 (file)
index 0000000..8dc8793
--- /dev/null
@@ -0,0 +1,154 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Destroy a notice through the API
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Tom Blankenship <mac65@mac65.com>
+ * @author    Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Deletes one of the authenticating user's statuses (notices).
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   Tom Blankenship <mac65@mac65.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiStatusesDestroyAction extends ApiAuthAction
+{
+    var $status                = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->auth_user;
+        $this->notice_id = (int)$this->trimmed('id');
+
+        if (empty($notice_id)) {
+            $this->notice_id = (int)$this->arg('id');
+        }
+
+        $this->notice = Notice::staticGet((int)$this->notice_id);
+
+        return true;
+     }
+
+    /**
+     * Handle the request
+     *
+     * Delete the notice and all related replies
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+             $this->clientError(_('API method not found!'), $code = 404);
+             return;
+        }
+
+         if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
+             $this->clientError(_('This method requires a POST or DELETE.'),
+                 400, $this->format);
+             return;
+         }
+
+         if (empty($this->notice)) {
+             $this->clientError(_('No status found with that ID.'),
+                 404, $this->format);
+             return;
+         }
+
+         if ($this->user->id == $this->notice->profile_id) {
+             $replies = new Reply;
+             $replies->get('notice_id', $this->notice_id);
+             $replies->delete();
+             $this->notice->delete();
+
+             if ($this->format == 'xml') {
+                 $this->showSingleXmlStatus($this->notice);
+             } elseif ($this->format == 'json') {
+                 $this->show_single_json_status($this->notice);
+             }
+         } else {
+             $this->clientError(_('You may not delete another user\'s status.'),
+                 403, $this->format);
+         }
+
+        $this->showNotice();
+    }
+
+    /**
+     * Show the deleted notice
+     *
+     * @return void
+     */
+
+    function showNotice()
+    {
+        if (!empty($this->notice)) {
+            if ($this->format == 'xml') {
+                $this->showSingleXmlStatus($this->notice);
+            } elseif ($this->format == 'json') {
+                $this->show_single_json_status($this->notice);
+            }
+        }
+    }
+
+}
diff --git a/actions/apistatusesshow.php b/actions/apistatusesshow.php
new file mode 100644 (file)
index 0000000..3be22ca
--- /dev/null
@@ -0,0 +1,206 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a notice (as a Twitter-style status)
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Tom Blankenship <mac65@mac65.com>
+ * @author    Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Returns the notice specified by id as a Twitter-style status and inline user
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   Tom Blankenship <mac65@mac65.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiStatusesShowAction extends ApiAction
+{
+
+    var $notice_id = null;
+    var $notice    = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        // 'id' is an undocumented parameter in Twitter's API. Several
+        // clients make use of it, so we support it too.
+
+        // show.json?id=12345 takes precedence over /show/12345.json
+
+        $this->notice_id = (int)$this->trimmed('id');
+
+        if (empty($notice_id)) {
+            $this->notice_id = (int)$this->arg('id');
+        }
+
+        $this->notice = Notice::staticGet((int)$this->notice_id);
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the notice
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(_('API method not found!'), $code = 404);
+            return;
+        }
+
+        $this->showNotice();
+    }
+
+    /**
+     * Show the notice
+     *
+     * @return void
+     */
+
+    function showNotice()
+    {
+        if (!empty($this->notice)) {
+            if ($this->format == 'xml') {
+                $this->showSingleXmlStatus($this->notice);
+            } elseif ($this->format == 'json') {
+                $this->show_single_json_status($this->notice);
+            }
+        } else {
+
+            // XXX: Twitter just sets a 404 header and doens't bother
+            // to return an err msg
+
+            $deleted = Deleted_notice::staticGet($this->notice_id);
+
+            if (!empty($deleted)) {
+                $this->clientError(
+                    _('Status deleted.'),
+                    410,
+                    $this->format
+                );
+            } else {
+                $this->clientError(
+                    _('No status with that ID found.'),
+                    404,
+                    $this->format
+                );
+            }
+        }
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this notice last modified?
+     *
+     * @return string datestamp of the latest notice in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->notice)) {
+            return strtotime($this->notice->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this notice
+     *
+     * Returns an Etag based on the action name, language, and
+     * timestamps of the notice
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->notice)) {
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->notice->id,
+                      strtotime($this->notice->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php
new file mode 100644 (file)
index 0000000..0d71e15
--- /dev/null
@@ -0,0 +1,241 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Post a notice (update your status) through the API
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Tom Blankenship <mac65@mac65.com>
+ * @author    Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apiauth.php';
+
+/**
+ * Updates the authenticating user's status (posts a notice).
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   Tom Blankenship <mac65@mac65.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 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');
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->auth_user;
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return false;
+        }
+
+        $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)) {
+            $this->source = 'api';
+        }
+
+        $this->in_reply_to_status_id
+            = intval($this->trimmed('in_reply_to_status_id'));
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Make a new notice for the update, save it, and show it
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+            $this->clientError(
+                _('This method requires a POST.'),
+                400, $this->format
+            );
+            return;
+        }
+
+        $status_shortened = common_shorten_links($this->status);
+
+        if (Notice::contentTooLong($status_shortened)) {
+
+            // Note: Twitter truncates anything over 140, flags the status
+            // as "truncated."
+
+            $this->clientError(
+                sprintf(
+                    _('That\'s too long. Max notice size is %d chars.'),
+                    Notice::maxContent()
+                ),
+                406,
+                $this->format
+            );
+
+            return;
+        }
+
+        // Check for commands
+
+        $inter = new CommandInterpreter();
+        $cmd = $inter->handle_command($this->user, $status_shortened);
+
+        if ($cmd) {
+
+            if ($this->supported($cmd)) {
+                $cmd->execute(new Channel());
+            }
+
+            // Cmd not supported?  Twitter just returns your latest status.
+            // And, it returns your last status whether the cmd was successful
+            // or not!
+
+            $this->notice = $this->user->getCurrentNotice();
+
+        } else {
+
+            $reply_to = null;
+
+            if (!empty($this->in_reply_to_status_id)) {
+
+                // Check whether notice actually exists
+
+                $reply = Notice::staticGet($this->in_reply_to_status_id);
+
+                if ($reply) {
+                    $reply_to = $this->in_reply_to_status_id;
+                } else {
+                    $this->clientError(
+                        _('Not found'),
+                        $code = 404,
+                        $this->format
+                    );
+                    return;
+                }
+            }
+
+            $this->notice = Notice::saveNew(
+                $this->user->id,
+                html_entity_decode($this->status, ENT_NOQUOTES, 'UTF-8'),
+                $this->source,
+                1,
+                $reply_to
+            );
+
+            common_broadcast_notice($this->notice);
+        }
+
+        $this->showNotice();
+    }
+
+    /**
+     * Show the resulting notice
+     *
+     * @return void
+     */
+
+    function showNotice()
+    {
+        if (!empty($this->notice)) {
+            if ($this->format == 'xml') {
+                $this->showSingleXmlStatus($this->notice);
+            } elseif ($this->format == 'json') {
+                $this->show_single_json_status($this->notice);
+            }
+        }
+    }
+
+    /**
+     * Is this command supported when doing an update from the API?
+     *
+     * @param string $cmd the command to check for
+     *
+     * @return boolean true or false
+     */
+
+    function supported($cmd)
+    {
+        static $cmdlist = array('MessageCommand', 'SubCommand', 'UnsubCommand',
+            'FavCommand', 'OnCommand', 'OffCommand');
+
+        if (in_array(get_class($cmd), $cmdlist)) {
+            return true;
+        }
+
+        return false;
+    }
+
+}
diff --git a/actions/apistatusnetconfig.php b/actions/apistatusnetconfig.php
new file mode 100644 (file)
index 0000000..ed1d151
--- /dev/null
@@ -0,0 +1,142 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Dump of configuration variables
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Gives a full dump of configuration variables for this instance
+ * of StatusNet, minus variables that may be security-sensitive (like
+ * passwords).
+ * URL: http://identi.ca/api/statusnet/config.(xml|json)
+ * Formats: xml, json
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@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 ApiStatusnetConfigAction extends ApiAction
+{
+    var $keys = array(
+        'site' => array('name', 'server', 'theme', 'path', 'fancy', 'language',
+                        'email', 'broughtby', 'broughtbyurl', 'closed',
+                        'inviteonly', 'private'),
+        'license' => array('url', 'title', 'image'),
+        'nickname' => array('featured'),
+        'throttle' => array('enabled', 'count', 'timespan'),
+        'xmpp' => array('enabled', 'server', 'user')
+    );
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        switch ($this->format) {
+        case 'xml':
+            $this->initDocument('xml');
+            $this->elementStart('config');
+
+            // XXX: check that all sections and settings are legal XML elements
+
+            common_debug(var_export($this->keys, true));
+
+            foreach ($this->keys as $section => $settings) {
+                $this->elementStart($section);
+                foreach ($settings as $setting) {
+                    $value = common_config($section, $setting);
+                    if (is_array($value)) {
+                        $value = implode(',', $value);
+                    } else if ($value === false) {
+                        $value = 'false';
+                    } else if ($value === true) {
+                        $value = 'true';
+                    }
+                    $this->element($setting, null, $value);
+                }
+                $this->elementEnd($section);
+            }
+            $this->elementEnd('config');
+            $this->endDocument('xml');
+            break;
+        case 'json':
+            $result = array();
+            foreach ($this->keys as $section => $settings) {
+                $result[$section] = array();
+                foreach ($settings as $setting) {
+                    $result[$section][$setting]
+                        = common_config($section, $setting);
+                }
+            }
+            $this->initDocument('json');
+            $this->showJsonObjects($result);
+            $this->endDocument('json');
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+}
+
diff --git a/actions/apistatusnetversion.php b/actions/apistatusnetversion.php
new file mode 100644 (file)
index 0000000..e73ab98
--- /dev/null
@@ -0,0 +1,102 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * A version stamp for the API
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Returns a version number for this version of StatusNet, which
+ * should make things a bit easier for upgrades.
+ * URL: http://identi.ca/api/statusnet/version.(xml|json)
+ * Formats: xml, js
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@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 ApiStatusnetVersionAction extends ApiAction
+{
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        switch ($this->format) {
+        case 'xml':
+            $this->initDocument('xml');
+            $this->element('version', null, STATUSNET_VERSION);
+            $this->endDocument('xml');
+            break;
+        case 'json':
+            $this->initDocument('json');
+            print '"'.STATUSNET_VERSION.'"';
+            $this->endDocument('json');
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+}
+
diff --git a/actions/apisubscriptions.php b/actions/apisubscriptions.php
new file mode 100644 (file)
index 0000000..bc68dd1
--- /dev/null
@@ -0,0 +1,266 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Base class for showing subscription information in the API
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * This class outputs a list of profiles as Twitter-style user and status objects.
+ * It is used by the API methods /api/statuses/(friends|followers). To support the
+ * social graph methods it also can output a simple list of IDs.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@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 ApiSubscriptionsAction extends ApiBareAuthAction
+{
+    var $profiles = null;
+    var $tag      = null;
+    var $lite     = null;
+    var $ids_only = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->tag      = $this->arg('tag');
+
+        // Note: Twitter no longer supports 'lite'
+        $this->lite     = $this->arg('lite');
+
+        $this->ids_only = $this->arg('ids_only');
+
+        // If called as a social graph method, show 5000 per page, otherwise 100
+
+        $this->count    = isset($this->ids_only) ?
+            5000 : (int)$this->arg('count', 100);
+
+        $this->user = $this->getTargetUser($this->arg('id'));
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return false;
+        }
+
+        $this->profiles = $this->getProfiles();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Show the profiles
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(_('API method not found!'), $code = 404);
+            return;
+        }
+
+        $this->initDocument($this->format);
+
+        if (isset($this->ids_only)) {
+            $this->showIds();
+        } else {
+            $this->showProfiles(isset($this->lite) ? false : true);
+        }
+
+        $this->endDocument($this->format);
+    }
+
+    /**
+     * Get profiles - should get overrrided
+     *
+     * @return array Profiles
+     */
+
+    function getProfiles()
+    {
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this feed last modified?
+     *
+     * @return string datestamp of the latest profile in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->profiles) && (count($this->profiles) > 0)) {
+            return strtotime($this->profiles[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this action
+     *
+     * Returns an Etag based on the action name, language, user ID, and
+     * timestamps of the first and last profiles in the subscriptions list
+     * There's also an indicator to show whether this action is being called
+     * as /api/statuses/(friends|followers) or /api/(friends|followers)/ids
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->profiles) && (count($this->profiles) > 0)) {
+
+            $last = count($this->profiles) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->user->id,
+                      isset($this->ids_only) ? 'IDs' : 'Profiles',
+                      strtotime($this->profiles[0]->created),
+                      strtotime($this->profiles[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+    /**
+     * Show the profiles as Twitter-style useres and statuses
+     *
+     * @param boolean $include_statuses Whether to include the latest status
+     *                                  with each user. Default true.
+     *
+     * @return void
+     */
+
+    function showProfiles($include_statuses = true)
+    {
+        switch ($this->format) {
+        case 'xml':
+            $this->elementStart('users', array('type' => 'array'));
+            foreach ($this->profiles as $profile) {
+                $this->showProfile(
+                    $profile,
+                    $this->format,
+                    null,
+                    $include_statuses
+                );
+            }
+            $this->elementEnd('users');
+            break;
+        case 'json':
+            $arrays = array();
+            foreach ($this->profiles as $profile) {
+                $arrays[] = $this->twitterUserArray(
+                    $profile,
+                    $include_statuses
+                );
+            }
+            print json_encode($arrays);
+            break;
+        default:
+            $this->clientError(_('Unsupported format.'));
+            break;
+        }
+    }
+
+    /**
+     * Show the IDs of the profiles only. 5000 per page. To support
+     * the 'social graph' methods: /api/(friends|followers)/ids
+     *
+     * @return void
+     */
+
+    function showIds()
+    {
+        switch ($this->format) {
+        case 'xml':
+            $this->elementStart('ids');
+            foreach ($this->profiles as $profile) {
+                $this->element('id', null, $profile->id);
+            }
+            $this->elementEnd('ids');
+            break;
+        case 'json':
+            $ids = array();
+            foreach ($this->profiles as $profile) {
+                $ids[] = (int)$profile->id;
+            }
+            print json_encode($ids);
+            break;
+        default:
+            $this->clientError(_('Unsupported format.'));
+            break;
+        }
+    }
+
+}
diff --git a/actions/apitimelinefavorites.php b/actions/apitimelinefavorites.php
new file mode 100644 (file)
index 0000000..b8ae74f
--- /dev/null
@@ -0,0 +1,237 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a user's favorite 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR.'/lib/apibareauth.php';
+
+/**
+ * Returns the 20 most recent favorite notices for the authenticating user or user
+ * specified by the ID parameter in the requested format.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@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 ApiTimelineFavoritesAction extends ApiBareAuthAction
+{
+    var $notices  = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->getTargetUser($this->arg('id'));
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        $this->notices = $this->getNotices();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Just show the notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showTimeline();
+    }
+
+    /**
+     * Show the timeline of notices
+     *
+     * @return void
+     */
+
+    function showTimeline()
+    {
+        $profile = $this->user->getProfile();
+
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(
+            _('%s / Favorites from %s'),
+            $sitename,
+            $this->user->nickname
+        );
+
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:Favorites:" . $this->user->id;
+        $link       = common_local_url(
+            'favorites',
+            array('nickname' => $this->user->nickname)
+        );
+        $subtitle   = sprintf(
+            _('%s updates favorited by %s / %s.'),
+            $sitename,
+            $profile->getBestName(),
+            $this->user->nickname
+        );
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($this->notices);
+            break;
+        case 'rss':
+            $this->showRssTimeline($this->notices, $title, $link, $subtitle);
+            break;
+        case 'atom':
+            $selfuri = common_root_url() .
+                ltrim($_SERVER['QUERY_STRING'], 'p=');
+            $this->showAtomTimeline(
+                $this->notices, $title, $id, $link, $subtitle,
+                null, $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonTimeline($this->notices);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+            break;
+        }
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getNotices()
+    {
+        $notices = array();
+
+        if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) {
+            $notice = $this->user->favoriteNotices(
+                ($this->page-1) * $this->count,
+                $this->count,
+                true
+            );
+        } else {
+            $notice = $this->user->favoriteNotices(
+                ($this->page-1) * $this->count,
+                $this->count,
+                false
+            );
+        }
+
+        while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+        return $notices;
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this feed last modified?
+     *
+     * @return string datestamp of the latest notice in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+            return strtotime($this->notices[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this stream
+     *
+     * Returns an Etag based on the action name, language, user ID, and
+     * timestamps of the first and last notice in the timeline
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+
+            $last = count($this->notices) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->user->id,
+                      strtotime($this->notices[0]->created),
+                      strtotime($this->notices[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apitimelinefriends.php b/actions/apitimelinefriends.php
new file mode 100644 (file)
index 0000000..1ea3586
--- /dev/null
@@ -0,0 +1,247 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show the friends timeline
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    mac65 <mac65@mac65.com>
+ * @author    Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Returns the most recent notices (default 20) posted by the target user.
+ * This is the equivalent of 'You and friends' page accessed via Web.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   mac65 <mac65@mac65.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiTimelineFriendsAction extends ApiBareAuthAction
+{
+    var $notices  = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->getTargetUser($this->arg('id'));
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        $this->notices = $this->getNotices();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Just show the notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showTimeline();
+    }
+
+    /**
+     * Show the timeline of notices
+     *
+     * @return void
+     */
+
+    function showTimeline()
+    {
+        $profile    = $this->user->getProfile();
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s and friends"), $this->user->nickname);
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:FriendsTimeline:" . $this->user->id;
+        $link       = common_local_url(
+            'all', array('nickname' => $this->user->nickname)
+        );
+        $subtitle   = sprintf(
+            _('Updates from %1$s and friends on %2$s!'),
+            $this->user->nickname, $sitename
+        );
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($this->notices);
+            break;
+        case 'rss':
+            $this->showRssTimeline($this->notices, $title, $link, $subtitle);
+            break;
+        case 'atom':
+
+            $target_id = $this->arg('id');
+
+            if (isset($target_id)) {
+                $selfuri = common_root_url() .
+                    'api/statuses/friends_timeline/' .
+                    $target_id . '.atom';
+            } else {
+                $selfuri = common_root_url() .
+                    'api/statuses/friends_timeline.atom';
+            }
+
+            $this->showAtomTimeline(
+                $this->notices, $title, $id, $link,
+                $subtitle, null, $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonTimeline($this->notices);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+            break;
+        }
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getNotices()
+    {
+        $notices = array();
+
+        if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) {
+            $notice = $this->user->noticeInbox(
+                ($this->page-1) * $this->count,
+                $this->count, $this->since_id,
+                $this->max_id, $this->since
+            );
+        } else {
+            $notice = $this->user->noticesWithFriends(
+                ($this->page-1) * $this->count,
+                $this->count, $this->since_id,
+                $this->max_id, $this->since
+            );
+        }
+
+        while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+        return $notices;
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this feed last modified?
+     *
+     * @return string datestamp of the latest notice in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+            return strtotime($this->notices[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this stream
+     *
+     * Returns an Etag based on the action name, language, user ID, and
+     * timestamps of the first and last notice in the timeline
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+
+            $last = count($this->notices) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->user->id,
+                      strtotime($this->notices[0]->created),
+                      strtotime($this->notices[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apitimelinegroup.php b/actions/apitimelinegroup.php
new file mode 100644 (file)
index 0000000..5d05429
--- /dev/null
@@ -0,0 +1,231 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a group's 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Returns the most recent notices (default 20) posted to the group specified by ID
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @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 ApiTimelineGroupAction extends ApiAction
+{
+
+    var $group   = null;
+    var $notices = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->group   = $this->getTargetGroup($this->arg('id'));
+        $this->notices = $this->getNotices();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Just show the notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showTimeline();
+    }
+
+    /**
+     * Show the timeline of notices
+     *
+     * @return void
+     */
+
+    function showTimeline()
+    {
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s timeline"), $this->group->nickname);
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:GroupTimeline:" . $this->group->id;
+        $link       = common_local_url(
+            'showgroup',
+            array('nickname' => $this->group->nickname)
+        );
+        $subtitle   = sprintf(
+            _('Updates from %1$s on %2$s!'),
+            $this->group->nickname,
+            $sitename
+        );
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($this->notices);
+            break;
+        case 'rss':
+            $this->showRssTimeline($this->notices, $title, $link, $subtitle);
+            break;
+        case 'atom':
+            $selfuri = common_root_url() .
+                'api/statusnet/groups/timeline/' .
+                    $this->group->nickname . '.atom';
+            $this->showAtomTimeline(
+                $this->notices,
+                $title,
+                $id,
+                $link,
+                $subtitle,
+                null,
+                $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonTimeline($this->notices);
+            break;
+        default:
+            $this->clientError(
+                _('API method not found!'),
+                404,
+                $this->format
+            );
+            break;
+        }
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getNotices()
+    {
+        $notices = array();
+
+        $notice = $this->group->getNotices(
+            ($this->page-1) * $this->count,
+            $this->count,
+            $this->since_id,
+            $this->max_id,
+            $this->since
+        );
+
+        while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+        return $notices;
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this feed last modified?
+     *
+     * @return string datestamp of the latest notice in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+            return strtotime($this->notices[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this stream
+     *
+     * Returns an Etag based on the action name, language, group ID and
+     * timestamps of the first and last notice in the timeline
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+
+            $last = count($this->notices) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->group->id,
+                      strtotime($this->notices[0]->created),
+                      strtotime($this->notices[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apitimelinementions.php b/actions/apitimelinementions.php
new file mode 100644 (file)
index 0000000..fe5ff0f
--- /dev/null
@@ -0,0 +1,233 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show notices mentioning a user (@nickname)
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    mac65 <mac65@mac65.com>
+ * @author    Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Returns the most recent (default 20) mentions (status containing @nickname)
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   mac65 <mac65@mac65.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiTimelineMentionsAction extends ApiBareAuthAction
+{
+
+    var $notices = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->getTargetUser($this->arg('id'));
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        $this->notices = $this->getNotices();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Just show the notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showTimeline();
+    }
+
+    /**
+     * Show the timeline of notices
+     *
+     * @return void
+     */
+
+    function showTimeline()
+    {
+        $profile = $this->user->getProfile();
+
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(
+            _('%1$s / Updates mentioning %2$s'),
+            $sitename, $this->user->nickname
+        );
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:Mentions:" . $this->user->id;
+        $link       = common_local_url(
+            'replies',
+            array('nickname' => $this->user->nickname)
+        );
+        $subtitle   = sprintf(
+            _('%1$s updates that reply to updates from %2$s / %3$s.'),
+            $sitename, $this->user->nickname, $profile->getBestName()
+        );
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($this->notices);
+            break;
+        case 'rss':
+            $this->showRssTimeline($this->notices, $title, $link, $subtitle);
+            break;
+        case 'atom':
+            $selfuri = common_root_url() .
+                ltrim($_SERVER['QUERY_STRING'], 'p=');
+            $this->showAtomTimeline(
+                $this->notices, $title, $id, $link, $subtitle,
+                null, $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonTimeline($this->notices);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+            break;
+        }
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getNotices()
+    {
+        $notices = array();
+
+        $notice = $this->user->getReplies(
+            ($this->page - 1) * $this->count, $this->count,
+            $this->since_id, $this->max_id, $this->since
+        );
+
+        while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+        return $notices;
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this feed last modified?
+     *
+     * @return string datestamp of the latest notice in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+            return strtotime($this->notices[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this stream
+     *
+     * Returns an Etag based on the action name, language, user ID, and
+     * timestamps of the first and last notice in the timeline
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+
+            $last = count($this->notices) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->user->id,
+                      strtotime($this->notices[0]->created),
+                      strtotime($this->notices[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apitimelinepublic.php b/actions/apitimelinepublic.php
new file mode 100644 (file)
index 0000000..58e2677
--- /dev/null
@@ -0,0 +1,213 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show the public timeline
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    mac65 <mac65@mac65.com>
+ * @author    Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Returns the most recent notices (default 20) posted by everybody
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   mac65 <mac65@mac65.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiTimelinePublicAction extends ApiAction
+{
+
+    var $notices = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->notices = $this->getNotices();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Just show the notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showTimeline();
+    }
+
+    /**
+     * Show the timeline of notices
+     *
+     * @return void
+     */
+
+    function showTimeline()
+    {
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s public timeline"), $sitename);
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:PublicTimeline";
+        $link       = common_root_url();
+        $subtitle   = sprintf(_("%s updates from everyone!"), $sitename);
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($this->notices);
+            break;
+        case 'rss':
+            $this->showRssTimeline($this->notices, $title, $link, $subtitle);
+            break;
+        case 'atom':
+            $selfuri = common_root_url() . 'api/statuses/public_timeline.atom';
+            $this->showAtomTimeline(
+                $this->notices, $title, $id, $link,
+                $subtitle, null, $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonTimeline($this->notices);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+            break;
+        }
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getNotices()
+    {
+        $notices = array();
+
+        $notice = Notice::publicStream(
+            ($this->page - 1) * $this->count, $this->count, $this->since_id,
+            $this->max_id, $this->since
+        );
+
+        while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+        return $notices;
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this feed last modified?
+     *
+     * @return string datestamp of the latest notice in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+            return strtotime($this->notices[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this stream
+     *
+     * Returns an Etag based on the action name, language, and
+     * timestamps of the first and last notice in the timeline
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+
+            $last = count($this->notices) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      strtotime($this->notices[0]->created),
+                      strtotime($this->notices[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apitimelinetag.php b/actions/apitimelinetag.php
new file mode 100644 (file)
index 0000000..a274daa
--- /dev/null
@@ -0,0 +1,224 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show the latest notices for a given tag
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Returns the 20 most recent notices tagged by a given tag
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @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 ApiTimelineTagAction extends ApiAction
+{
+
+    var $notices = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->tag     = $this->arg('tag');
+        $this->notices = $this->getNotices();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Just show the notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showTimeline();
+    }
+
+    /**
+     * Show the timeline of notices
+     *
+     * @return void
+     */
+
+    function showTimeline()
+    {
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("Notices tagged with %s"), $this->tag);
+        $link       = common_local_url(
+            'tag',
+            array('tag' => $this->tag)
+        );
+        $subtitle   = sprintf(
+            _('Updates tagged with %1$s on %2$s!'),
+            $this->tag,
+            $sitename
+        );
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:TagTimeline:".$tag;
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($this->notices);
+            break;
+        case 'rss':
+            $this->showRssTimeline($this->notices, $title, $link, $subtitle);
+            break;
+        case 'atom':
+            $selfuri = common_root_url() .
+                'api/statusnet/tags/timeline/' .
+                    $this->tag . '.atom';
+            $this->showAtomTimeline(
+                $this->notices,
+                $title,
+                $id,
+                $link,
+                $subtitle,
+                null,
+                $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonTimeline($this->notices);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+            break;
+        }
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getNotices()
+    {
+        $notices = array();
+
+        $notice = Notice_tag::getStream(
+            $this->tag,
+            ($this->page - 1) * $this->count,
+            $this->count + 1
+        );
+
+        while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+        return $notices;
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this feed last modified?
+     *
+     * @return string datestamp of the latest notice in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+            return strtotime($this->notices[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this stream
+     *
+     * Returns an Etag based on the action name, language, and
+     * timestamps of the first and last notice in the timeline
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+
+            $last = count($this->notices) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->tag,
+                      strtotime($this->notices[0]->created),
+                      strtotime($this->notices[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php
new file mode 100644 (file)
index 0000000..285735f
--- /dev/null
@@ -0,0 +1,248 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a user's timeline
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    mac65 <mac65@mac65.com>
+ * @author    Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author    Robin Millette <robin@millette.info>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Returns the most recent notices (default 20) posted by the authenticating
+ * user. Another user's timeline can be requested via the id parameter. This
+ * is the API equivalent of the user profile web page.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   mac65 <mac65@mac65.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <robin@millette.info>
+ * @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 ApiTimelineUserAction extends ApiBareAuthAction
+{
+
+    var $notices = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user = $this->getTargetUser($this->arg('id'));
+
+        if (empty($this->user)) {
+            $this->clientError(_('No such user!'), 404, $this->format);
+            return;
+        }
+
+        $this->notices = $this->getNotices();
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Just show the notices
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showTimeline();
+    }
+
+    /**
+     * Show the timeline of notices
+     *
+     * @return void
+     */
+
+    function showTimeline()
+    {
+        $profile = $this->user->getProfile();
+
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s timeline"), $this->user->nickname);
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:UserTimeline:" . $this->user->id;
+        $link       = common_local_url(
+            'showstream',
+            array('nickname' => $this->user->nickname)
+        );
+        $subtitle   = sprintf(
+            _('Updates from %1$s on %2$s!'),
+            $this->user->nickname, $sitename
+        );
+
+        // FriendFeed's SUP protocol
+        // Also added RSS and Atom feeds
+
+        $suplink = common_local_url('sup', null, null, $this->user->id);
+        header('X-SUP-ID: ' . $suplink);
+
+        switch($this->format) {
+        case 'xml':
+            $this->showXmlTimeline($this->notices);
+            break;
+        case 'rss':
+            $this->showRssTimeline(
+                $this->notices, $title, $link,
+                $subtitle, $suplink
+            );
+            break;
+        case 'atom':
+            if (isset($apidata['api_arg'])) {
+                $selfuri = common_root_url() .
+                    'api/statuses/user_timeline/' .
+                    $apidata['api_arg'] . '.atom';
+            } else {
+                $selfuri = common_root_url() .
+                    'api/statuses/user_timeline.atom';
+            }
+            $this->showAtomTimeline(
+                $this->notices, $title, $id, $link,
+                $subtitle, $suplink, $selfuri
+            );
+            break;
+        case 'json':
+            $this->showJsonTimeline($this->notices);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+            break;
+        }
+
+    }
+
+    /**
+     * Get notices
+     *
+     * @return array notices
+     */
+
+    function getNotices()
+    {
+        $notices = array();
+
+        $notice = $this->user->getNotices(
+            ($this->page-1) * $this->count, $this->count,
+            $this->since_id, $this->max_id, $this->since
+        );
+
+        while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+        return $notices;
+    }
+
+    /**
+     * Is this action read only?
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * When was this feed last modified?
+     *
+     * @return string datestamp of the latest notice in the stream
+     */
+
+    function lastModified()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+            return strtotime($this->notices[0]->created);
+        }
+
+        return null;
+    }
+
+    /**
+     * An entity tag for this stream
+     *
+     * Returns an Etag based on the action name, language, user ID, and
+     * timestamps of the first and last notice in the timeline
+     *
+     * @return string etag
+     */
+
+    function etag()
+    {
+        if (!empty($this->notices) && (count($this->notices) > 0)) {
+
+            $last = count($this->notices) - 1;
+
+            return '"' . implode(
+                ':',
+                array($this->arg('action'),
+                      common_language(),
+                      $this->user->id,
+                      strtotime($this->notices[0]->created),
+                      strtotime($this->notices[$last]->created))
+            )
+            . '"';
+        }
+
+        return null;
+    }
+
+}
diff --git a/actions/apiuserfollowers.php b/actions/apiuserfollowers.php
new file mode 100644 (file)
index 0000000..e8d92a7
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a user's followers (subscribers)
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Ouputs the authenticating user's followers (subscribers), each with
+ * current Twitter-style status inline.  They are ordered by the order
+ * in which they subscribed to the user, 100 at a time.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@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 ApiUserFollowersAction extends ApiSubscriptionsAction
+{
+    /**
+     * Get the user's subscribers (followers) as an array of profiles
+     *
+     * @return array Profiles
+     */
+
+    function getProfiles()
+    {
+        $offset = ($this->page - 1) * $this->count;
+        $limit =  $this->count + 1;
+
+        $subs = null;
+
+        if (isset($this->tag)) {
+            $subs = $this->user->getTaggedSubscribers(
+                $this->tag, $offset, $limit
+            );
+        } else {
+            $subs = $this->user->getSubscribers(
+                $offset,
+                $limit
+            );
+        }
+
+        $profiles = array();
+
+        if (!empty($subs)) {
+            while ($subs->fetch()) {
+                $profiles[] = clone($subs);
+            }
+        }
+
+        return $profiles;
+    }
+
+}
diff --git a/actions/apiuserfriends.php b/actions/apiuserfriends.php
new file mode 100644 (file)
index 0000000..741a26e
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a user's friends (subscriptions)
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/apibareauth.php';
+
+/**
+ * Ouputs the authenticating user's friends (subscriptions), each with
+ * current Twitter-style status inline.  They are ordered by the date
+ * in which the user subscribed to them, 100 at a time.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@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 ApiUserFriendsAction extends ApiSubscriptionsAction
+{
+    /**
+     * Get the user's subscriptions (friends) as an array of profiles
+     *
+     * @return array Profiles
+     */
+
+    function getProfiles()
+    {
+        $offset = ($this->page - 1) * $this->count;
+        $limit =  $this->count + 1;
+
+        $subs = null;
+
+        if (isset($this->tag)) {
+            $subs = $this->user->getTaggedSubscriptions(
+                $this->tag, $offset, $limit
+            );
+        } else {
+            $subs = $this->user->getSubscriptions(
+                $offset,
+                $limit
+            );
+        }
+
+        $profiles = array();
+
+        if (!empty($subs)) {
+            while ($subs->fetch()) {
+                $profiles[] = clone($subs);
+            }
+        }
+
+        return $profiles;
+    }
+
+}
diff --git a/actions/apiusershow.php b/actions/apiusershow.php
new file mode 100644 (file)
index 0000000..b3a939b
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show a user's profile information
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    mac65 <mac65@mac65.com>
+ * @author    Zach Copley <zach@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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Ouputs information for a user, specified by ID or screen name.
+ * The user's most recent status will be returned inline.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   mac65 <mac65@mac65.com>
+ * @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 ApiUserShowAction extends ApiAction
+{
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $email = $this->arg('email');
+
+        // XXX: email field deprecated in Twitter's API
+
+        if (!empty($email)) {
+            $this->user = User::staticGet('email', $email);
+        } else {
+            $this->user = $this->getTargetUser($this->arg('id'));
+        }
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Check the format and show the user info
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (empty($this->user)) {
+            $this->clientError(_('Not found.'), 404, $this->format);
+            return;
+        }
+
+        if (!in_array($this->format, array('xml', 'json'))) {
+            $this->clientError(_('API method not found!'), $code = 404);
+            return;
+        }
+
+        $profile = $this->user->getProfile();
+
+        if (empty($profile)) {
+            $this->clientError(_('User has no profile.'));
+            return;
+        }
+
+        $twitter_user = $this->twitterUserArray($this->user->getProfile(), true);
+
+        if ($this->format == 'xml') {
+            $this->initDocument('xml');
+            $this->showTwitterXmlUser($twitter_user);
+            $this->endDocument('xml');
+        } elseif ($this->format == 'json') {
+            $this->initDocument('json');
+            $this->showJsonObjects($twitter_user);
+            $this->endDocument('json');
+        }
+
+    }
+
+}
index 2016942862197af7def453190a3429ee17436051..6fd74f3ff7889bf40e55d9df9343edecb0d10e6c 100644 (file)
@@ -67,11 +67,7 @@ class ConfirmaddressAction extends Action
         parent::handle($args);
         if (!common_logged_in()) {
             common_set_returnto($this->selfUrl());
-            if (!common_config('site', 'openidonly')) {
-                common_redirect(common_local_url('login'));
-            } else {
-                common_redirect(common_local_url('openidlogin'));
-            }
+            common_redirect(common_local_url('login'));
             return;
         }
         $code = $this->trimmed('code');
index 3d040f2fa91ed7b7e736c5be7d744d61667aeaea..4a48a9c346cba2b9714e30184c65273d8ae56632 100644 (file)
@@ -32,15 +32,45 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
     exit(1);
 }
 
-require_once INSTALLDIR.'/lib/deleteaction.php';
-
-class DeletenoticeAction extends DeleteAction
+class DeletenoticeAction extends Action
 {
-    var $error = null;
+    var $error        = null;
+    var $user         = null;
+    var $notice       = null;
+    var $profile      = null;
+    var $user_profile = null;
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->user   = common_current_user();
+        $notice_id    = $this->trimmed('notice');
+        $this->notice = Notice::staticGet($notice_id);
+
+        if (!$this->notice) {
+            common_user_error(_('No such notice.'));
+            exit;
+        }
+
+        $this->profile      = $this->notice->getProfile();
+        $this->user_profile = $this->user->getProfile();
+
+        return true;
+    }
 
     function handle($args)
     {
         parent::handle($args);
+
+        if (!common_logged_in()) {
+            common_user_error(_('Not logged in.'));
+            exit;
+        } else if ($this->notice->profile_id != $this->user_profile->id &&
+                   !$this->user->hasRight(Right::deleteOthersNotice)) {
+            common_user_error(_('Can\'t delete this notice.'));
+            exit;
+        }
         // XXX: Ajax!
 
         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
index 68295234c5ca1c7a2f6d74fe2f922ac730de9cc5..836f039d32128d9696d5bbc07b598155fd803218 100644 (file)
@@ -58,12 +58,24 @@ class DocAction extends Action
     function handle($args)
     {
         parent::handle($args);
-        $this->title    = $this->trimmed('title');
-        $this->filename = INSTALLDIR.'/doc-src/'.$this->title;
-        if (!file_exists($this->filename)) {
-            $this->clientError(_('No such document.'));
-            return;
+
+        $this->title  = $this->trimmed('title');
+        $this->output = null;
+
+        if (Event::handle('StartLoadDoc', array(&$this->title, &$this->output))) {
+
+            $this->filename = INSTALLDIR.'/doc-src/'.$this->title;
+            if (!file_exists($this->filename)) {
+                $this->clientError(_('No such document.'));
+                return;
+            }
+
+            $c = file_get_contents($this->filename);
+            $this->output = common_markup_to_html($c);
+
+            Event::handle('EndLoadDoc', array($this->title, &$this->output));
         }
+
         $this->showPage();
     }
 
@@ -93,9 +105,7 @@ class DocAction extends Action
      */
     function showContent()
     {
-        $c      = file_get_contents($this->filename);
-        $output = common_markup_to_html($c);
-        $this->raw($output);
+        $this->raw($this->output);
     }
 
     /**
index b8dac31cb180421a565e62c2e9d10e5cbdadaba7..cf160803563ddc651459fe60393a4feb9aeadecb 100644 (file)
@@ -64,11 +64,6 @@ class EditgroupAction extends GroupDesignAction
     {
         parent::prepare($args);
 
-        if (!common_config('inboxes','enabled')) {
-            $this->serverError(_('Inboxes must be enabled for groups to work'));
-            return false;
-        }
-
         if (!common_logged_in()) {
             $this->clientError(_('You must be logged in to create a group.'));
             return false;
@@ -202,8 +197,8 @@ class EditgroupAction extends GroupDesignAction
         } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
             $this->showForm(_('Full name is too long (max 255 chars).'));
             return;
-        } else if (!is_null($description) && mb_strlen($description) > 140) {
-            $this->showForm(_('description is too long (max 140 chars).'));
+        } else if (User_group::descriptionTooLong($description)) {
+            $this->showForm(sprintf(_('description is too long (max %d chars).'), User_group::maxDescription()));
             return;
         } else if (!is_null($location) && mb_strlen($location) > 255) {
             $this->showForm(_('Location is too long (max 255 chars).'));
index 5ba508cdf5e592bfb95ecd16444c0c180fd92cbe..150b67b0b09451f752d226486031de07852e32b7 100644 (file)
@@ -153,8 +153,7 @@ class FavoritedAction extends Action
             $message .= _('Be the first to add a notice to your favorites by clicking the fave button next to any notice you like.');
         }
         else {
-            $message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to add a notice to your favorites!'),
-                                (!common_config('site','openidonly')) ? 'register' : 'openidlogin');
+            $message .= _('Why not [register an account](%%action.register%%) and be the first to add a notice to your favorites!');
         }
 
         $this->elementStart('div', 'guide');
index 2d5ce985416d9e9d83f32b9f9d1a55082d7f6ebe..62f06e841b193ddd2cb4f94fbb47a3c6c151050e 100644 (file)
@@ -50,11 +50,11 @@ require_once INSTALLDIR.'/lib/rssaction.php';
  */
 class FavoritesrssAction extends Rss10Action
 {
-    
+
     /** The user whose favorites to display */
-    
+
     var $user = null;
-        
+
     /**
      * Find the user to display by supplied nickname
      *
@@ -66,7 +66,7 @@ class FavoritesrssAction extends Rss10Action
     function prepare($args)
     {
         parent::prepare($args);
-        
+
         $nickname   = $this->trimmed('nickname');
         $this->user = User::staticGet('nickname', $nickname);
 
@@ -74,10 +74,11 @@ class FavoritesrssAction extends Rss10Action
             $this->clientError(_('No such user.'));
             return false;
         } else {
+            $this->notices = $this->getNotices($this->limit);
             return true;
         }
     }
-    
+
     /**
      * Get notices
      *
diff --git a/actions/finishaddopenid.php b/actions/finishaddopenid.php
deleted file mode 100644 (file)
index b6de4f2..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Complete adding an 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    Evan Prodromou <evan@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.'/lib/openid.php';
-
-/**
- * Complete adding an OpenID
- *
- * Handle the return from an OpenID verification
- *
- * @category Settings
- * @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 FinishaddopenidAction extends Action
-{
-    var $msg = null;
-
-    /**
-     * Handle the redirect back from OpenID confirmation
-     *
-     * Check to see if the user's logged in, and then try
-     * to use the OpenID login system.
-     *
-     * @param array $args $_REQUEST arguments
-     *
-     * @return void
-     */
-
-    function handle($args)
-    {
-        parent::handle($args);
-        if (!common_logged_in()) {
-            $this->clientError(_('Not logged in.'));
-        } else {
-            $this->tryLogin();
-        }
-    }
-
-    /**
-     * Try to log in using OpenID
-     *
-     * Check the OpenID for validity; potentially store it.
-     *
-     * @return void
-     */
-
-    function tryLogin()
-    {
-        $consumer =& oid_consumer();
-
-        $response = $consumer->complete(common_local_url('finishaddopenid'));
-
-        if ($response->status == Auth_OpenID_CANCEL) {
-            $this->message(_('OpenID authentication cancelled.'));
-            return;
-        } else if ($response->status == Auth_OpenID_FAILURE) {
-            // Authentication failed; display the error message.
-            $this->message(sprintf(_('OpenID authentication failed: %s'),
-                                   $response->message));
-        } else if ($response->status == Auth_OpenID_SUCCESS) {
-
-            $display   = $response->getDisplayIdentifier();
-            $canonical = ($response->endpoint && $response->endpoint->canonicalID) ?
-              $response->endpoint->canonicalID : $display;
-
-            $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
-
-            if ($sreg_resp) {
-                $sreg = $sreg_resp->contents();
-            }
-
-            $cur =& common_current_user();
-
-            $other = oid_get_user($canonical);
-
-            if ($other) {
-                if ($other->id == $cur->id) {
-                    $this->message(_('You already have this OpenID!'));
-                } else {
-                    $this->message(_('Someone else already has this OpenID.'));
-                }
-                return;
-            }
-
-            // start a transaction
-
-            $cur->query('BEGIN');
-
-            $result = oid_link_user($cur->id, $canonical, $display);
-
-            if (!$result) {
-                $this->message(_('Error connecting user.'));
-                return;
-            }
-            if ($sreg) {
-                if (!oid_update_user($cur, $sreg)) {
-                    $this->message(_('Error updating profile'));
-                    return;
-                }
-            }
-
-            // success!
-
-            $cur->query('COMMIT');
-
-            oid_set_last($display);
-
-            common_redirect(common_local_url('openidsettings'), 303);
-        }
-    }
-
-    /**
-     * Show a failure message
-     *
-     * Something went wrong. Save the message, and show the page.
-     *
-     * @param string $msg Error message to show
-     *
-     * @return void
-     */
-
-    function message($msg)
-    {
-        $this->message = $msg;
-        $this->showPage();
-    }
-
-    /**
-     * Title of the page
-     *
-     * @return string title
-     */
-
-    function title()
-    {
-        return _('OpenID Login');
-    }
-
-    /**
-     * Show error message
-     *
-     * @return void
-     */
-
-    function showPageNotice()
-    {
-        if ($this->message) {
-            $this->element('p', 'error', $this->message);
-        }
-    }
-}
diff --git a/actions/finishopenidlogin.php b/actions/finishopenidlogin.php
deleted file mode 100644 (file)
index 9ac0369..0000000
+++ /dev/null
@@ -1,497 +0,0 @@
-<?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.'/lib/openid.php');
-
-class FinishopenidloginAction extends Action
-{
-    var $error = null;
-    var $username = null;
-    var $message = null;
-
-    function handle($args)
-    {
-        parent::handle($args);
-        if (!common_config('openid', 'enabled')) {
-            common_redirect(common_local_url('login'));
-        } else if (common_is_real_login()) {
-            $this->clientError(_('Already logged in.'));
-        } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
-            $token = $this->trimmed('token');
-            if (!$token || $token != common_session_token()) {
-                $this->showForm(_('There was a problem with your session token. Try again, please.'));
-                return;
-            }
-            if ($this->arg('create')) {
-                if (!$this->boolean('license')) {
-                    $this->showForm(_('You can\'t register if you don\'t agree to the license.'),
-                                    $this->trimmed('newname'));
-                    return;
-                }
-                $this->createNewUser();
-            } else if ($this->arg('connect')) {
-                $this->connectUser();
-            } else {
-                common_debug(print_r($this->args, true), __FILE__);
-                $this->showForm(_('Something weird happened.'),
-                                $this->trimmed('newname'));
-            }
-        } else {
-            $this->tryLogin();
-        }
-    }
-
-    function showPageNotice()
-    {
-        if ($this->error) {
-            $this->element('div', array('class' => 'error'), $this->error);
-        } else {
-            $this->element('div', 'instructions',
-                           sprintf(_('This is the first time you\'ve logged into %s so we must connect your OpenID to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name')));
-        }
-    }
-
-    function title()
-    {
-        return _('OpenID Account Setup');
-    }
-
-    function showForm($error=null, $username=null)
-    {
-        $this->error = $error;
-        $this->username = $username;
-
-        $this->showPage();
-    }
-
-    function showContent()
-    {
-        if (!empty($this->message_text)) {
-            $this->element('div', array('class' => 'error'), $this->message_text);
-            return;
-        }
-
-        $this->elementStart('form', array('method' => 'post',
-                                          'id' => 'account_connect',
-                                          'action' => common_local_url('finishopenidlogin')));
-        $this->hidden('token', common_session_token());
-        $this->element('h2', null,
-                       _('Create new account'));
-        $this->element('p', null,
-                       _('Create a new user with this nickname.'));
-        $this->input('newname', _('New nickname'),
-                     ($this->username) ? $this->username : '',
-                     _('1-64 lowercase letters or numbers, no punctuation or spaces'));
-        $this->elementStart('p');
-        $this->element('input', array('type' => 'checkbox',
-                                      'id' => 'license',
-                                      'name' => 'license',
-                                      'value' => 'true'));
-        $this->text(_('My text and files are available under '));
-        $this->element('a', array('href' => common_config('license', 'url')),
-                       common_config('license', 'title'));
-        $this->text(_(' except this private data: password, email address, IM address, phone number.'));
-        $this->elementEnd('p');
-        $this->submit('create', _('Create'));
-        $this->element('h2', null,
-                       _('Connect existing account'));
-        $this->element('p', null,
-                       _('If you already have an account, login with your username and password to connect it to your OpenID.'));
-        $this->input('nickname', _('Existing nickname'));
-        $this->password('password', _('Password'));
-        $this->submit('connect', _('Connect'));
-        $this->elementEnd('form');
-    }
-
-    function tryLogin()
-    {
-        $consumer = oid_consumer();
-
-        $response = $consumer->complete(common_local_url('finishopenidlogin'));
-
-        if ($response->status == Auth_OpenID_CANCEL) {
-            $this->message(_('OpenID authentication cancelled.'));
-            return;
-        } else if ($response->status == Auth_OpenID_FAILURE) {
-            // Authentication failed; display the error message.
-            $this->message(sprintf(_('OpenID authentication failed: %s'), $response->message));
-        } else if ($response->status == Auth_OpenID_SUCCESS) {
-            // This means the authentication succeeded; extract the
-            // identity URL and Simple Registration data (if it was
-            // returned).
-            $display = $response->getDisplayIdentifier();
-            $canonical = ($response->endpoint->canonicalID) ?
-              $response->endpoint->canonicalID : $response->getDisplayIdentifier();
-
-            $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
-
-            if ($sreg_resp) {
-                $sreg = $sreg_resp->contents();
-            }
-
-            $user = oid_get_user($canonical);
-
-            if ($user) {
-                oid_set_last($display);
-                # XXX: commented out at @edd's request until better
-                # control over how data flows from OpenID provider.
-                # oid_update_user($user, $sreg);
-                common_set_user($user);
-                common_real_login(true);
-                if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
-                    common_rememberme($user);
-                }
-                unset($_SESSION['openid_rememberme']);
-                $this->goHome($user->nickname);
-            } else {
-                $this->saveValues($display, $canonical, $sreg);
-                $this->showForm(null, $this->bestNewNickname($display, $sreg));
-            }
-        }
-    }
-
-    function message($msg)
-    {
-        $this->message_text = $msg;
-        $this->showPage();
-    }
-
-    function saveValues($display, $canonical, $sreg)
-    {
-        common_ensure_session();
-        $_SESSION['openid_display'] = $display;
-        $_SESSION['openid_canonical'] = $canonical;
-        $_SESSION['openid_sreg'] = $sreg;
-    }
-
-    function getSavedValues()
-    {
-        return array($_SESSION['openid_display'],
-                     $_SESSION['openid_canonical'],
-                     $_SESSION['openid_sreg']);
-    }
-
-    function createNewUser()
-    {
-        # FIXME: save invite code before redirect, and check here
-
-        if (common_config('site', 'closed')) {
-            $this->clientError(_('Registration not allowed.'));
-            return;
-        }
-
-        $invite = null;
-
-        if (common_config('site', 'inviteonly')) {
-            $code = $_SESSION['invitecode'];
-            if (empty($code)) {
-                $this->clientError(_('Registration not allowed.'));
-                return;
-            }
-
-            $invite = Invitation::staticGet($code);
-
-            if (empty($invite)) {
-                $this->clientError(_('Not a valid invitation code.'));
-                return;
-            }
-        }
-
-        $nickname = $this->trimmed('newname');
-
-        if (!Validate::string($nickname, array('min_length' => 1,
-                                               'max_length' => 64,
-                                               'format' => NICKNAME_FMT))) {
-            $this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
-            return;
-        }
-
-        if (!User::allowed_nickname($nickname)) {
-            $this->showForm(_('Nickname not allowed.'));
-            return;
-        }
-
-        if (User::staticGet('nickname', $nickname)) {
-            $this->showForm(_('Nickname already in use. Try another one.'));
-            return;
-        }
-
-        list($display, $canonical, $sreg) = $this->getSavedValues();
-
-        if (!$display || !$canonical) {
-            $this->serverError(_('Stored OpenID not found.'));
-            return;
-        }
-
-        # Possible race condition... let's be paranoid
-
-        $other = oid_get_user($canonical);
-
-        if ($other) {
-            $this->serverError(_('Creating new account for OpenID that already has a user.'));
-            return;
-        }
-
-        $location = '';
-        if (!empty($sreg['country'])) {
-            if ($sreg['postcode']) {
-                # XXX: use postcode to get city and region
-                # XXX: also, store postcode somewhere -- it's valuable!
-                $location = $sreg['postcode'] . ', ' . $sreg['country'];
-            } else {
-                $location = $sreg['country'];
-            }
-        }
-
-        if (!empty($sreg['fullname']) && mb_strlen($sreg['fullname']) <= 255) {
-            $fullname = $sreg['fullname'];
-        } else {
-            $fullname = '';
-        }
-
-        if (!empty($sreg['email']) && Validate::email($sreg['email'], true)) {
-            $email = $sreg['email'];
-        } else {
-            $email = '';
-        }
-
-        # XXX: add language
-        # XXX: add timezone
-
-        $args = array('nickname' => $nickname,
-                      'email' => $email,
-                      'fullname' => $fullname,
-                      'location' => $location);
-
-        if (!empty($invite)) {
-            $args['code'] = $invite->code;
-        }
-
-        $user = User::register($args);
-
-        $result = oid_link_user($user->id, $canonical, $display);
-
-        oid_set_last($display);
-        common_set_user($user);
-        common_real_login(true);
-        if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
-            common_rememberme($user);
-        }
-        unset($_SESSION['openid_rememberme']);
-        common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
-                        303);
-    }
-
-    function connectUser()
-    {
-        $nickname = $this->trimmed('nickname');
-        $password = $this->trimmed('password');
-
-        if (!common_check_user($nickname, $password)) {
-            $this->showForm(_('Invalid username or password.'));
-            return;
-        }
-
-        # They're legit!
-
-        $user = User::staticGet('nickname', $nickname);
-
-        list($display, $canonical, $sreg) = $this->getSavedValues();
-
-        if (!$display || !$canonical) {
-            $this->serverError(_('Stored OpenID not found.'));
-            return;
-        }
-
-        $result = oid_link_user($user->id, $canonical, $display);
-
-        if (!$result) {
-            $this->serverError(_('Error connecting user to OpenID.'));
-            return;
-        }
-
-        oid_update_user($user, $sreg);
-        oid_set_last($display);
-        common_set_user($user);
-        common_real_login(true);
-        if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
-            common_rememberme($user);
-        }
-        unset($_SESSION['openid_rememberme']);
-        $this->goHome($user->nickname);
-    }
-
-    function goHome($nickname)
-    {
-        $url = common_get_returnto();
-        if ($url) {
-            # We don't have to return to it again
-            common_set_returnto(null);
-        } else {
-            $url = common_local_url('all',
-                                    array('nickname' =>
-                                          $nickname));
-        }
-        common_redirect($url, 303);
-    }
-
-    function bestNewNickname($display, $sreg)
-    {
-
-        # Try the passed-in nickname
-
-        if (!empty($sreg['nickname'])) {
-            $nickname = $this->nicknamize($sreg['nickname']);
-            if ($this->isNewNickname($nickname)) {
-                return $nickname;
-            }
-        }
-
-        # Try the full name
-
-        if (!empty($sreg['fullname'])) {
-            $fullname = $this->nicknamize($sreg['fullname']);
-            if ($this->isNewNickname($fullname)) {
-                return $fullname;
-            }
-        }
-
-        # Try the URL
-
-        $from_url = $this->openidToNickname($display);
-
-        if ($from_url && $this->isNewNickname($from_url)) {
-            return $from_url;
-        }
-
-        # XXX: others?
-
-        return null;
-    }
-
-    function isNewNickname($str)
-    {
-        if (!Validate::string($str, array('min_length' => 1,
-                                          'max_length' => 64,
-                                          'format' => NICKNAME_FMT))) {
-            return false;
-        }
-        if (!User::allowed_nickname($str)) {
-            return false;
-        }
-        if (User::staticGet('nickname', $str)) {
-            return false;
-        }
-        return true;
-    }
-
-    function openidToNickname($openid)
-    {
-        if (Auth_Yadis_identifierScheme($openid) == 'XRI') {
-            return $this->xriToNickname($openid);
-        } else {
-            return $this->urlToNickname($openid);
-        }
-    }
-
-    # We try to use an OpenID URL as a legal StatusNet user name in this order
-    # 1. Plain hostname, like http://evanp.myopenid.com/
-    # 2. One element in path, like http://profile.typekey.com/EvanProdromou/
-    #    or http://getopenid.com/evanprodromou
-
-    function urlToNickname($openid)
-    {
-        static $bad = array('query', 'user', 'password', 'port', 'fragment');
-
-        $parts = parse_url($openid);
-
-        # If any of these parts exist, this won't work
-
-        foreach ($bad as $badpart) {
-            if (array_key_exists($badpart, $parts)) {
-                return null;
-            }
-        }
-
-        # We just have host and/or path
-
-        # If it's just a host...
-        if (array_key_exists('host', $parts) &&
-            (!array_key_exists('path', $parts) || strcmp($parts['path'], '/') == 0))
-        {
-            $hostparts = explode('.', $parts['host']);
-
-            # Try to catch common idiom of nickname.service.tld
-
-            if ((count($hostparts) > 2) &&
-                (strlen($hostparts[count($hostparts) - 2]) > 3) && # try to skip .co.uk, .com.au
-                (strcmp($hostparts[0], 'www') != 0))
-            {
-                return $this->nicknamize($hostparts[0]);
-            } else {
-                # Do the whole hostname
-                return $this->nicknamize($parts['host']);
-            }
-        } else {
-            if (array_key_exists('path', $parts)) {
-                # Strip starting, ending slashes
-                $path = preg_replace('@/$@', '', $parts['path']);
-                $path = preg_replace('@^/@', '', $path);
-                if (strpos($path, '/') === false) {
-                    return $this->nicknamize($path);
-                }
-            }
-        }
-
-        return null;
-    }
-
-    function xriToNickname($xri)
-    {
-        $base = $this->xriBase($xri);
-
-        if (!$base) {
-            return null;
-        } else {
-            # =evan.prodromou
-            # or @gratis*evan.prodromou
-            $parts = explode('*', substr($base, 1));
-            return $this->nicknamize(array_pop($parts));
-        }
-    }
-
-    function xriBase($xri)
-    {
-        if (substr($xri, 0, 6) == 'xri://') {
-            return substr($xri, 6);
-        } else {
-            return $xri;
-        }
-    }
-
-    # Given a string, try to make it work as a nickname
-
-    function nicknamize($str)
-    {
-        $str = preg_replace('/\W/', '', $str);
-        return strtolower($str);
-    }
-}
index 871bc3d2d131c3d8fbd7b1c33deb598ca461ebfc..b1cec66f48af55ac41de11b0fab041433cf3b093 100644 (file)
@@ -1,5 +1,16 @@
 <?php
-/*
+/**
+ * Handler for remote subscription finish callback
+ *
+ * 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.
  *
  *
  * 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.'/lib/omb.php');
+require_once INSTALLDIR.'/extlib/libomb/service_consumer.php';
+require_once INSTALLDIR.'/lib/omb.php';
 
+/**
+ * Handler for remote subscription finish callback
+ *
+ * When a remote user subscribes a local user, a redirect to this action is
+ * issued after the remote user authorized his service to subscribe.
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ */
 class FinishremotesubscribeAction extends Action
 {
 
+    /**
+     * Class handler.
+     *
+     * @param array $args query arguments
+     *
+     * @return nothing
+     *
+     **/
     function handle($args)
     {
-
         parent::handle($args);
 
-        if (common_logged_in()) {
-            $this->clientError(_('You can use the local subscription!'));
-            return;
-        }
-
-        $omb = $_SESSION['oauth_authorization_request'];
+        /* Restore session data. RemotesubscribeAction should have stored
+           this entry. */
+        $service  = unserialize($_SESSION['oauth_authorization_request']);
 
-        if (!$omb) {
+        if (!$service) {
             $this->clientError(_('Not expecting this response!'));
             return;
         }
 
-        common_debug('stored request: '.print_r($omb,true), __FILE__);
-
-        common_remove_magic_from_request();
-        $req = OAuthRequest::from_request('POST', common_local_url('finishuserauthorization'));
-
-        $token = $req->get_parameter('oauth_token');
-
-        # I think this is the success metric
-
-        if ($token != $omb['token']) {
-            $this->clientError(_('Not authorized.'));
-            return;
-        }
-
-        $version = $req->get_parameter('omb_version');
-
-        if ($version != OMB_VERSION_01) {
-            $this->clientError(_('Unknown version of OMB protocol.'));
-            return;
-        }
-
-        $nickname = $req->get_parameter('omb_listener_nickname');
-
-        if (!$nickname) {
-            $this->clientError(_('No nickname provided by remote server.'));
-            return;
-        }
-
-        $profile_url = $req->get_parameter('omb_listener_profile');
+        common_debug('stored request: '. print_r($service, true), __FILE__);
 
-        if (!$profile_url) {
-            $this->clientError(_('No profile URL returned by server.'));
-            return;
-        }
-
-        if (!Validate::uri($profile_url, array('allowed_schemes' => array('http', 'https')))) {
-            $this->clientError(_('Invalid profile URL returned by server.'));
-            return;
-        }
-
-        if ($profile_url == common_local_url('showstream', array('nickname' => $nickname))) {
-            $this->clientError(_('You can use the local subscription!'));
-            return;
-        }
-
-        common_debug('listenee: "'.$omb['listenee'].'"', __FILE__);
-
-        $user = User::staticGet('nickname', $omb['listenee']);
+        /* Create user objects for both users. Do it early for request
+           validation. */
+        $user = User::staticGet('uri', $service->getListeneeURI());
 
         if (!$user) {
-            $this->clientError(_('User being listened to doesn\'t exist.'));
+            $this->clientError(_('User being listened to does not exist.'));
             return;
         }
 
-        $other = User::staticGet('uri', $omb['listener']);
+        $other = User::staticGet('uri', $service->getListenerURI());
 
         if ($other) {
             $this->clientError(_('You can use the local subscription!'));
             return;
         }
 
-        $fullname = $req->get_parameter('omb_listener_fullname');
-        $homepage = $req->get_parameter('omb_listener_homepage');
-        $bio = $req->get_parameter('omb_listener_bio');
-        $location = $req->get_parameter('omb_listener_location');
-        $avatar_url = $req->get_parameter('omb_listener_avatar');
+        $remote = Remote_profile::staticGet('uri', $service->getListenerURI());
 
-        list($newtok, $newsecret) = $this->access_token($omb);
+        $profile = Profile::staticGet($remote->id);
 
-        if (!$newtok || !$newsecret) {
-            $this->clientError(_('Couldn\'t convert request tokens to access tokens.'));
+        if ($user->hasBlocked($profile)) {
+            $this->clientError(_('That user has blocked you from subscribing.'));
             return;
         }
 
-        # XXX: possible attack point; subscribe and return someone else's profile URI
-
-        $remote = Remote_profile::staticGet('uri', $omb['listener']);
-
-        if ($remote) {
-            $exists = true;
-            $profile = Profile::staticGet($remote->id);
-            $orig_remote = clone($remote);
-            $orig_profile = clone($profile);
-            # XXX: compare current postNotice and updateProfile URLs to the ones
-            # stored in the DB to avoid (possibly...) above attack
-        } else {
-            $exists = false;
-            $remote = new Remote_profile();
-            $remote->uri = $omb['listener'];
-            $profile = new Profile();
-        }
-
-        $profile->nickname = $nickname;
-        $profile->profileurl = $profile_url;
-
-        if (!is_null($fullname)) {
-            $profile->fullname = $fullname;
-        }
-        if (!is_null($homepage)) {
-            $profile->homepage = $homepage;
-        }
-        if (!is_null($bio)) {
-            $profile->bio = $bio;
-        }
-        if (!is_null($location)) {
-            $profile->location = $location;
-        }
-
-        if ($exists) {
-            $profile->update($orig_profile);
-        } else {
-            $profile->created = DB_DataObject_Cast::dateTime(); # current time
-            $id = $profile->insert();
-            if (!$id) {
-                $this->serverError(_('Error inserting new profile'));
+        /* Perform the handling itself via libomb. */
+        try {
+            $service->finishAuthorization();
+        } catch (OAuthException $e) {
+            if ($e->getMessage() == 'The authorized token does not equal the ' .
+                                    'submitted token.') {
+                $this->clientError(_('You are not authorized.'));
                 return;
-            }
-            $remote->id = $id;
-        }
-
-        if ($avatar_url) {
-            if (!$this->add_avatar($profile, $avatar_url)) {
-                $this->serverError(_('Error inserting avatar'));
-                return;
-            }
-        }
-
-        $remote->postnoticeurl = $omb['post_notice_url'];
-        $remote->updateprofileurl = $omb['update_profile_url'];
-
-        if ($exists) {
-            if (!$remote->update($orig_remote)) {
-                $this->serverError(_('Error updating remote profile'));
+            } else {
+                $this->clientError(_('Could not convert request token to ' .
+                                     'access token.'));
                 return;
             }
-        } else {
-            $remote->created = DB_DataObject_Cast::dateTime(); # current time
-            if (!$remote->insert()) {
-                $this->serverError(_('Error inserting remote profile'));
-                return;
-            }
-        }
-
-        if ($user->hasBlocked($profile)) {
-            $this->clientError(_('That user has blocked you from subscribing.'));
+        } catch (OMB_RemoteServiceException $e) {
+            $this->clientError(_('Remote service uses unknown version of ' .
+                                 'OMB protocol.'));
+            return;
+        } catch (Exception $e) {
+            common_debug('Got exception ' . print_r($e, true), __FILE__);
+            $this->clientError($e->getMessage());
             return;
         }
 
-        $sub = new Subscription();
-
-        $sub->subscriber = $remote->id;
-        $sub->subscribed = $user->id;
-
-        $sub_exists = false;
-
-        if ($sub->find(true)) {
-            $sub_exists = true;
-            $orig_sub = clone($sub);
-        } else {
-            $sub_exists = false;
-            $sub->created = DB_DataObject_Cast::dateTime(); # current time
-        }
-
-        $sub->token = $newtok;
-        $sub->secret = $newsecret;
+        /* The service URLs are not accessible from datastore, so setting them
+           after insertion of the profile. */
+        $orig_remote = clone($remote);
 
-        if ($sub_exists) {
-            $result = $sub->update($orig_sub);
-        } else {
-            $result = $sub->insert();
-        }
+        $remote->postnoticeurl    =
+                            $service->getServiceURI(OMB_ENDPOINT_POSTNOTICE);
+        $remote->updateprofileurl =
+                            $service->getServiceURI(OMB_ENDPOINT_UPDATEPROFILE);
 
-        if (!$result) {
-            common_log_db_error($sub, ($sub_exists) ? 'UPDATE' : 'INSERT', __FILE__);
-            $this->clientError(_('Couldn\'t insert new subscription.'));
-            return;
+        if (!$remote->update($orig_remote)) {
+                $this->serverError(_('Error updating remote profile'));
+                return;
         }
 
-        # Notify user, if necessary
-
-        mail_subscribe_notify_profile($user, $profile);
-
-        # Clear the data
+        /* Clear the session data. */
         unset($_SESSION['oauth_authorization_request']);
 
-        # If we show subscriptions in reverse chron order, this should
-        # show up close to the top of the page
-
+        /* If we show subscriptions in reverse chronological order, the new one
+           should show up close to the top of the page. */
         common_redirect(common_local_url('subscribers', array('nickname' =>
                                                              $user->nickname)),
                         303);
     }
-
-    function add_avatar($profile, $url)
-    {
-        $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
-        copy($url, $temp_filename);
-        $imagefile = new ImageFile($profile->id, $temp_filename);
-        $filename = Avatar::filename($profile->id,
-                                     image_type_to_extension($imagefile->type),
-                                     null,
-                                     common_timestamp());
-        rename($temp_filename, Avatar::path($filename));
-        return $profile->setOriginal($filename);
-    }
-
-    function access_token($omb)
-    {
-
-        common_debug('starting request for access token', __FILE__);
-
-        $con = omb_oauth_consumer();
-        $tok = new OAuthToken($omb['token'], $omb['secret']);
-
-        common_debug('using request token "'.$tok.'"', __FILE__);
-
-        $url = $omb['access_token_url'];
-
-        common_debug('using access token url "'.$url.'"', __FILE__);
-
-        # XXX: Is this the right thing to do? Strip off GET params and make them
-        # POST params? Seems wrong to me.
-
-        $parsed = parse_url($url);
-        $params = array();
-        parse_str($parsed['query'], $params);
-
-        $req = OAuthRequest::from_consumer_and_token($con, $tok, "POST", $url, $params);
-
-        $req->set_parameter('omb_version', OMB_VERSION_01);
-
-        # XXX: test to see if endpoint accepts this signature method
-
-        $req->sign_request(omb_hmac_sha1(), $con, $tok);
-
-        # We re-use this tool's fetcher, since it's pretty good
-
-        common_debug('posting to access token url "'.$req->get_normalized_http_url().'"', __FILE__);
-        common_debug('posting request data "'.$req->to_postdata().'"', __FILE__);
-
-        $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
-        $result = $fetcher->post($req->get_normalized_http_url(),
-                                 $req->to_postdata(),
-                                 array('User-Agent: StatusNet/' . STATUSNET_VERSION));
-
-        common_debug('got result: "'.print_r($result,true).'"', __FILE__);
-
-        if ($result->status != 200) {
-            return null;
-        }
-
-        parse_str($result->body, $return);
-
-        return array($return['oauth_token'], $return['oauth_token_secret']);
-    }
 }
diff --git a/actions/foafgroup.php b/actions/foafgroup.php
new file mode 100644 (file)
index 0000000..f5fd7fe
--- /dev/null
@@ -0,0 +1,173 @@
+<?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/>.
+ *
+ * @category  Mail
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Toby Inkster <mail@tobyinkster.co.uk>
+ * @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 FoafGroupAction extends Action
+{
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $nickname_arg = $this->arg('nickname');
+
+        if (empty($nickname_arg)) {
+            $this->clientError(_('No such group.'), 404);
+            return false;
+        }
+
+        $this->nickname = common_canonical_nickname($nickname_arg);
+
+        // Permanent redirect on non-canonical nickname
+
+        if ($nickname_arg != $this->nickname) {
+            common_redirect(common_local_url('foafgroup',
+                                             array('nickname' => $this->nickname)),
+                            301);
+            return false;
+        }
+
+        $this->group = User_group::staticGet('nickname', $this->nickname);
+
+        if (!$this->group) {
+            $this->clientError(_('No such group.'), 404);
+            return false;
+        }
+
+        common_set_returnto($this->selfUrl());
+
+        return true;
+    }
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        header('Content-Type: application/rdf+xml');
+
+        $this->startXML();
+        $this->elementStart('rdf:RDF', array('xmlns:rdf' =>
+                                              'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+                                              'xmlns:dcterms' =>
+                                              'http://purl.org/dc/terms/',
+                                              'xmlns:sioc' =>
+                                              'http://rdfs.org/sioc/ns#',
+                                              'xmlns:foaf' =>
+                                              'http://xmlns.com/foaf/0.1/',
+                                              'xmlns:statusnet' =>
+                                              'http://status.net/ont/',
+                                              'xmlns' => 'http://xmlns.com/foaf/0.1/'));
+
+        $this->showPpd(common_local_url('foafgroup', array('nickname' => $this->nickname)), $this->group->permalink());
+
+        $this->elementStart('Group', array('rdf:about' =>
+                                             $this->group->permalink()));
+        if ($this->group->fullname) {
+            $this->element('name', null, $this->group->fullname);
+        }
+        if ($this->group->description) {
+            $this->element('dcterms:description', null, $this->group->description);
+        }
+        if ($this->group->nickname) {
+            $this->element('dcterms:identifier', null, $this->group->nickname);
+            $this->element('nick', null, $this->group->nickname);
+        }
+        foreach ($this->group->getAliases() as $alias) {
+            $this->element('nick', null, $alias);
+        }
+        if ($this->group->homeUrl()) {
+            $this->element('weblog', array('rdf:resource' => $this->group->homeUrl()));
+        }
+        if ($this->group->homepage) {
+            $this->element('page', array('rdf:resource' => $this->group->homepage));
+        }
+        if ($this->group->homepage_logo) {
+            $this->element('depiction', array('rdf:resource' => $this->group->homepage_logo));
+        }
+        
+        $members = $this->group->getMembers();
+        $member_details = array();
+        while ($members->fetch()) {
+            $member_uri = common_local_url('userbyid', array('id'=>$members->id));
+            $member_details[$member_uri] = array(
+                                        'nickname' => $members->nickname
+                                        );
+            $this->element('member', array('rdf:resource' => $member_uri));
+        }
+        
+        $admins = $this->group->getAdmins();
+        while ($admins->fetch()) {
+            $admin_uri = common_local_url('userbyid', array('id'=>$admins->id));
+            $member_details[$admin_uri]['is_admin'] = true;
+            $this->element('statusnet:groupAdmin', array('rdf:resource' => $admin_uri));
+        }
+
+        $this->elementEnd('Group');
+        
+        ksort($member_details);
+        foreach ($member_details as $uri => $details) {
+            if ($details['is_admin'])
+            {
+                $this->elementStart('Agent', array('rdf:about' => $uri));
+                $this->element('nick', null, $details['nickname']);
+                $this->elementStart('holdsAccount');
+                $this->elementStart('sioc:User', array('rdf:about'=>$uri.'#acct'));
+                $this->elementStart('sioc:has_function');
+                $this->elementStart('statusnet:GroupAdminRole');
+                $this->element('sioc:scope', array('rdf:resource' => $this->group->permalink()));
+                $this->elementEnd('statusnet:GroupAdminRole');
+                $this->elementEnd('sioc:has_function');
+                $this->elementEnd('sioc:User');
+                $this->elementEnd('holdsAccount');
+                $this->elementEnd('Agent');
+            }
+            else
+            {
+                $this->element('Agent', array(
+                                        'foaf:nick' => $details['nickname'],
+                                        'rdf:about' => $uri,
+                                        ));
+            }
+        }
+        
+        $this->elementEnd('rdf:RDF');
+        $this->endXML();
+    }
+
+    function showPpd($foaf_url, $person_uri)
+    {
+        $this->elementStart('Document', array('rdf:about' => $foaf_url));
+        $this->element('primaryTopic', array('rdf:resource' => $person_uri));
+        $this->elementEnd('Document');
+    }
+
+}
\ No newline at end of file
index 52cfaddfc3298d7a520235e31ca9001146a7765e..f65bf511afb622d7d70b818f6b6d901f379da68a 100644 (file)
@@ -68,11 +68,6 @@ class GroupbyidAction extends Action
     {
         parent::prepare($args);
 
-        if (!common_config('inboxes','enabled')) {
-            $this->serverError(_('Inboxes must be enabled for groups to work'));
-            return false;
-        }
-
         $id = $this->arg('id');
 
         if (!$id) {
index cd86e3b0511d02f7b07728b835fe4870423ae0ba..b87b7d156a3f9ae511da3e037a1c2531f4fa5aa1 100644 (file)
@@ -64,11 +64,6 @@ class GroupDesignSettingsAction extends DesignSettingsAction
     {
         parent::prepare($args);
 
-        if (!common_config('inboxes', 'enabled')) {
-            $this->serverError(_('Inboxes must be enabled for groups to work'));
-            return false;
-        }
-
         if (!common_logged_in()) {
             $this->clientError(_('You must be logged in to edit a group.'));
             return false;
index 63ba769c7a61feb5a2e5eb71d513859496123cdf..a9dc7eb1d99a8e87adebf2e1f21e6c049f3e36f3 100644 (file)
@@ -66,11 +66,6 @@ class GrouplogoAction extends GroupDesignAction
     {
         parent::prepare($args);
 
-        if (!common_config('inboxes','enabled')) {
-            $this->serverError(_('Inboxes must be enabled for groups to work'));
-            return false;
-        }
-
         if (!common_logged_in()) {
             $this->clientError(_('You must be logged in to create a group.'));
             return false;
index 70c1ded488e89c46ff9397b5a3da65f27b30e923..50e48a67e9ea7121e462fdbcc5b2ef0a2063f9fe 100644 (file)
@@ -76,11 +76,6 @@ class groupRssAction extends Rss10Action
     {
         parent::prepare($args);
 
-        if (!common_config('inboxes','enabled')) {
-            $this->serverError(_('Inboxes must be enabled for groups to work'));
-            return false;
-        }
-
         $nickname_arg = $this->arg('nickname');
         $nickname = common_canonical_nickname($nickname_arg);
 
@@ -104,6 +99,7 @@ class groupRssAction extends Rss10Action
             return false;
         }
 
+        $this->notices = $this->getNotices($this->limit);
         return true;
     }
 
index 517f127895ca49f6a25c97f8f87e6d7e70ef115c..55f4cee625e37b5265ea40ef490945ceceb021da 100644 (file)
@@ -82,8 +82,7 @@ class GroupsearchAction extends SearchAction
                 $message = _('If you can\'t find the group you\'re looking for, you can [create it](%%action.newgroup%%) yourself.');
             }
             else {
-                $message = sprintf(_('Why not [register an account](%%%%action.%s%%%%) and [create the group](%%%%action.newgroup%%%%) yourself!'),
-                                   (!common_config('site','openidonly')) ? 'register' : 'openidlogin');
+                $message = _('Why not [register an account](%%action.register%%) and [create the group](%%action.newgroup%%) yourself!');
             }
             $this->elementStart('div', 'guide');
             $this->raw(common_markup_to_html($message));
index 9fa6a76f671f471a83b13b590db846401fd6b039..788130c5825e4989a9e7f93fc6baf0270fe184bd 100644 (file)
@@ -241,7 +241,7 @@ class InviteAction extends CurrentUserDesignAction
                         common_root_url(),
                         $personal,
                         common_local_url('showstream', array('nickname' => $user->nickname)),
-                        common_local_url((!common_config('site', 'openidonly')) ? 'register' : 'openidlogin', array('code' => $invite->code)));
+                        common_local_url('register', array('code' => $invite->code)));
 
         mail_send($recipients, $headers, $body);
     }
index 0209dd43fddcf3fc74b9ed70e7993752e96acce1..bf69b2ad1e40769df3b7c5c5278b3cd77303528b 100644 (file)
@@ -56,11 +56,6 @@ class JoingroupAction extends Action
     {
         parent::prepare($args);
 
-        if (!common_config('inboxes','enabled')) {
-            $this->serverError(_('Inboxes must be enabled for groups to work'));
-            return false;
-        }
-
         if (!common_logged_in()) {
             $this->clientError(_('You must be logged in to join a group.'));
             return false;
index 60b22e147d735978090f36d0ecc10644ee07b6c2..08fce150980eb406fce36bf46ceff307bf66ba32 100644 (file)
@@ -56,11 +56,6 @@ class LeavegroupAction extends Action
     {
         parent::prepare($args);
 
-        if (!common_config('inboxes','enabled')) {
-            $this->serverError(_('Inboxes must be enabled for groups to work.'));
-            return false;
-        }
-
         if (!common_logged_in()) {
             $this->clientError(_('You must be logged in to leave a group.'));
             return false;
index ac8c40c3e5f60e97cf98393204ff96ca79fc6fef..f6d0163105f42675a8667c55411ce4a8c96626e7 100644 (file)
@@ -67,8 +67,6 @@ class LoginAction extends Action
      *
      * Switches on request method; either shows the form or handles its input.
      *
-     * Checks if only OpenID is allowed and redirects to openidlogin if so.
-     *
      * @param array $args $_REQUEST data
      *
      * @return void
@@ -77,9 +75,7 @@ class LoginAction extends Action
     function handle($args)
     {
         parent::handle($args);
-        if (common_config('site', 'openidonly')) {
-            common_redirect(common_local_url('openidlogin'));
-        } else if (common_is_real_login()) {
+        if (common_is_real_login()) {
             $this->clientError(_('Already logged in.'));
         } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
             $this->checkLogin();
@@ -259,11 +255,6 @@ class LoginAction extends Action
             return _('For security reasons, please re-enter your ' .
                      'user name and password ' .
                      'before changing your settings.');
-        } else if (common_config('openid', 'enabled')) {
-            return _('Login with your username and password. ' .
-                     'Don\'t have a username yet? ' .
-                     '[Register](%%action.register%%) a new account, or ' .
-                     'try [OpenID](%%action.openidlogin%%). ');
         } else {
             return _('Login with your username and password. ' .
                      'Don\'t have a username yet? ' .
index 298b2a484b58088a76e7bf334a2dc55c746b7dd2..1e0adae57592a536bdaf7b6bbf3eb7ae60959f14 100644 (file)
@@ -32,8 +32,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
     exit(1);
 }
 
-require_once INSTALLDIR.'/lib/openid.php';
-
 /**
  * Logout action class.
  *
index 01cb636aaf80a43555221836eb1485bd7b7e0fa6..80da9861a0482a7b9b5c1308872c9e9ca9e5a254 100644 (file)
@@ -61,11 +61,6 @@ class NewgroupAction extends Action
     {
         parent::prepare($args);
 
-        if (!common_config('inboxes','enabled')) {
-            $this->serverError(_('Inboxes must be enabled for groups to work'));
-            return false;
-        }
-
         if (!common_logged_in()) {
             $this->clientError(_('You must be logged in to create a group.'));
             return false;
@@ -146,8 +141,8 @@ class NewgroupAction extends Action
         } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
             $this->showForm(_('Full name is too long (max 255 chars).'));
             return;
-        } else if (!is_null($description) && mb_strlen($description) > 140) {
-            $this->showForm(_('description is too long (max 140 chars).'));
+        } else if (User_group::descriptionTooLong($description)) {
+            $this->showForm(sprintf(_('description is too long (max %d chars).'), User_group::maxDescription()));
             return;
         } else if (!is_null($location) && mb_strlen($location) > 255) {
             $this->showForm(_('Location is too long (max 255 chars).'));
index 828a339cfe1db56578b9b22db06f88e71c9cbba0..a0b17fc18a5ec315eac3c25b13c5c7f1c271dcb7 100644 (file)
@@ -144,9 +144,10 @@ class NewmessageAction extends Action
         } else {
             $content_shortened = common_shorten_links($this->content);
 
-            if (mb_strlen($content_shortened) > 140) {
-                $this->showForm(_('That\'s too long. ' .
-                    'Max message size is 140 chars.'));
+            if (Message::contentTooLong($content_shortened)) {
+                $this->showForm(sprintf(_('That\'s too long. ' .
+                                          'Max message size is %d chars.'),
+                                        Message::maxContent()));
                 return;
             }
         }
index 8c0476f7057100e925d49bf0782158828adda59e..9ee031f93636b0003f5ae6937c4bc9e892f64d48 100644 (file)
@@ -162,9 +162,10 @@ class NewnoticeAction extends Action
             $this->clientError(_('No content!'));
         } else {
             $content_shortened = common_shorten_links($content);
-            if (mb_strlen($content_shortened) > 140) {
-                $this->clientError(_('That\'s too long. '.
-                                     'Max notice size is 140 chars.'));
+            if (Notice::contentTooLong($content_shortened)) {
+                $this->clientError(sprintf(_('That\'s too long. '.
+                                             'Max notice size is %d chars.'),
+                                           Notice::maxContent()));
             }
         }
 
@@ -245,9 +246,10 @@ class NewnoticeAction extends Action
             }
             $content_shortened .= ' ' . $short_fileurl;
 
-            if (mb_strlen($content_shortened) > 140) {
+            if (Notice::contentTooLong($content_shortened)) {
                 $this->deleteFile($filename);
-                $this->clientError(_('Max notice size is 140 chars, including attachment URL.'));
+                $this->clientError(sprintf(_('Max notice size is %d chars, including attachment URL.'),
+                                           Notice::maxContent()));
             }
 
             // Also, not sure this is necessary -- Zach
@@ -257,13 +259,6 @@ class NewnoticeAction extends Action
         $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
                                   ($replyto == 'false') ? null : $replyto);
 
-        if (is_string($notice)) {
-            if (isset($filename)) {
-                $this->deleteFile($filename);
-            }
-            $this->clientError($notice);
-        }
-
         if (isset($mimetype)) {
             $this->attachFile($notice, $fileRecord);
         }
index 69dcd1a46c08d815f988d04e2eb1feed0409f61d..79cf572ccaf59f2e4e2a5777966bd886c7625dc2 100644 (file)
@@ -121,9 +121,7 @@ class NoticesearchAction extends SearchAction
                 $message = sprintf(_('Be the first to [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'), urlencode($q));
             }
             else {
-                $message = sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'),
-                                   (!common_config('site','openidonly')) ? 'register' : 'openidlogin',
-                                   urlencode($q));
+                $message = sprintf(_('Why not [register an account](%%%%action.register%%%%) and be the first to  [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'), urlencode($q));
             }
 
             $this->elementStart('div', 'guide');
diff --git a/actions/openidlogin.php b/actions/openidlogin.php
deleted file mode 100644 (file)
index 9b7deef..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-<?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.'/lib/openid.php');
-
-class OpenidloginAction extends Action
-{
-    function handle($args)
-    {
-        parent::handle($args);
-        if (!common_config('openid', 'enabled')) {
-            common_redirect(common_local_url('login'));
-        } else if (common_is_real_login()) {
-            $this->clientError(_('Already logged in.'));
-        } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
-            $openid_url = $this->trimmed('openid_url');
-
-            # CSRF protection
-            $token = $this->trimmed('token');
-            if (!$token || $token != common_session_token()) {
-                $this->showForm(_('There was a problem with your session token. Try again, please.'), $openid_url);
-                return;
-            }
-
-            $rememberme = $this->boolean('rememberme');
-
-            common_ensure_session();
-
-            $_SESSION['openid_rememberme'] = $rememberme;
-
-            $result = oid_authenticate($openid_url,
-                                       'finishopenidlogin');
-
-            if (is_string($result)) { # error message
-                unset($_SESSION['openid_rememberme']);
-                $this->showForm($result, $openid_url);
-            }
-        } else {
-            $openid_url = oid_get_last();
-            $this->showForm(null, $openid_url);
-        }
-    }
-
-    function getInstructions()
-    {
-        if (common_logged_in() && !common_is_real_login() &&
-            common_get_returnto()) {
-            // rememberme logins have to reauthenticate before
-            // changing any profile settings (cookie-stealing protection)
-            return _('For security reasons, please re-login with your ' .
-                     '[OpenID](%%doc.openid%%) ' .
-                     'before changing your settings.');
-        } else {
-            return _('Login with an [OpenID](%%doc.openid%%) account.');
-        }
-    }
-
-    function showPageNotice()
-    {
-        if ($this->error) {
-            $this->element('div', array('class' => 'error'), $this->error);
-        } else {
-            $instr = $this->getInstructions();
-            $output = common_markup_to_html($instr);
-            $this->elementStart('div', 'instructions');
-            $this->raw($output);
-            $this->elementEnd('div');
-        }
-    }
-
-    function showScripts()
-    {
-        parent::showScripts();
-        $this->autofocus('openid_url');
-    }
-
-    function title()
-    {
-        return _('OpenID Login');
-    }
-
-    function showForm($error=null, $openid_url)
-    {
-        $this->error = $error;
-        $this->openid_url = $openid_url;
-        $this->showPage();
-    }
-
-    function showContent() {
-        $formaction = common_local_url('openidlogin');
-        $this->elementStart('form', array('method' => 'post',
-                                           'id' => 'form_openid_login',
-                                           'class' => 'form_settings',
-                                           'action' => $formaction));
-        $this->elementStart('fieldset');
-        $this->element('legend', null, _('OpenID login'));
-        $this->hidden('token', common_session_token());
-
-        $this->elementStart('ul', 'form_data');
-        $this->elementStart('li');
-        $this->input('openid_url', _('OpenID URL'),
-                     $this->openid_url,
-                     _('Your OpenID URL'));
-        $this->elementEnd('li');
-        $this->elementStart('li', array('id' => 'settings_rememberme'));
-        $this->checkbox('rememberme', _('Remember me'), false,
-                        _('Automatically login in the future; ' .
-                           'not for shared computers!'));
-        $this->elementEnd('li');
-        $this->elementEnd('ul');
-        $this->submit('submit', _('Login'));
-        $this->elementEnd('fieldset');
-        $this->elementEnd('form');
-    }
-
-    function showLocalNav()
-    {
-        $nav = new LoginGroupNav($this);
-        $nav->show();
-    }
-}
diff --git a/actions/openidsettings.php b/actions/openidsettings.php
deleted file mode 100644 (file)
index 30725fc..0000000
+++ /dev/null
@@ -1,246 +0,0 @@
-<?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    Evan Prodromou <evan@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.'/lib/accountsettingsaction.php';
-require_once INSTALLDIR.'/lib/openid.php';
-
-/**
- * Settings for OpenID
- *
- * Lets users add, edit and delete OpenIDs from their account
- *
- * @category Settings
- * @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 OpenidsettingsAction extends AccountSettingsAction
-{
-    /**
-     * Title of the page
-     *
-     * @return string Page title
-     */
-
-    function title()
-    {
-        return _('OpenID settings');
-    }
-
-    /**
-     * Instructions for use
-     *
-     * @return string Instructions for use
-     */
-
-    function getInstructions()
-    {
-        return _('[OpenID](%%doc.openid%%) lets you log into many sites' .
-                 ' with the same user account.'.
-                 ' Manage your associated OpenIDs from here.');
-    }
-
-    function showScripts()
-    {
-        parent::showScripts();
-        $this->autofocus('openid_url');
-    }
-
-    /**
-     * Show the form for OpenID management
-     *
-     * We have one form with a few different submit buttons to do different things.
-     *
-     * @return void
-     */
-
-    function showContent()
-    {
-        if (!common_config('openid', 'enabled')) {
-            $this->element('div', array('class' => 'error'),
-                           _('OpenID is not available.'));
-            return;
-        }
-
-        $user = common_current_user();
-
-        $this->elementStart('form', array('method' => 'post',
-                                          'id' => 'form_settings_openid_add',
-                                          'class' => 'form_settings',
-                                          'action' =>
-                                          common_local_url('openidsettings')));
-        $this->elementStart('fieldset', array('id' => 'settings_openid_add'));
-        $this->element('legend', null, _('Add OpenID'));
-        $this->hidden('token', common_session_token());
-        $this->element('p', 'form_guide',
-                       _('If you want to add an OpenID to your account, ' .
-                         'enter it in the box below and click "Add".'));
-        $this->elementStart('ul', 'form_data');
-        $this->elementStart('li');
-        $this->element('label', array('for' => 'openid_url'),
-                       _('OpenID URL'));
-        $this->element('input', array('name' => 'openid_url',
-                                      'type' => 'text',
-                                      'id' => 'openid_url'));
-        $this->elementEnd('li');
-        $this->elementEnd('ul');
-        $this->element('input', array('type' => 'submit',
-                                      'id' => 'settings_openid_add_action-submit',
-                                      'name' => 'add',
-                                      'class' => 'submit',
-                                      'value' => _('Add')));
-        $this->elementEnd('fieldset');
-        $this->elementEnd('form');
-
-        $oid = new User_openid();
-
-        $oid->user_id = $user->id;
-
-        $cnt = $oid->find();
-
-        if ($cnt > 0) {
-
-            $this->element('h2', null, _('Remove OpenID'));
-
-            if ($cnt == 1 && !$user->password) {
-
-                $this->element('p', 'form_guide',
-                               _('Removing your only OpenID '.
-                                 'would make it impossible to log in! ' .
-                                 'If you need to remove it, '.
-                                 'add another OpenID first.'));
-
-                if ($oid->fetch()) {
-                    $this->elementStart('p');
-                    $this->element('a', array('href' => $oid->canonical),
-                                   $oid->display);
-                    $this->elementEnd('p');
-                }
-
-            } else {
-
-                $this->element('p', 'form_guide',
-                               _('You can remove an OpenID from your account '.
-                                 'by clicking the button marked "Remove".'));
-                $idx = 0;
-
-                while ($oid->fetch()) {
-                    $this->elementStart('form',
-                                        array('method' => 'POST',
-                                              'id' => 'form_settings_openid_delete' . $idx,
-                                              'class' => 'form_settings',
-                                              'action' =>
-                                              common_local_url('openidsettings')));
-                    $this->elementStart('fieldset');
-                    $this->hidden('token', common_session_token());
-                    $this->element('a', array('href' => $oid->canonical),
-                                   $oid->display);
-                    $this->element('input', array('type' => 'hidden',
-                                                  'id' => 'openid_url'.$idx,
-                                                  'name' => 'openid_url',
-                                                  'value' => $oid->canonical));
-                    $this->element('input', array('type' => 'submit',
-                                                  'id' => 'remove'.$idx,
-                                                  'name' => 'remove',
-                                                  'class' => 'submit remove',
-                                                  'value' => _('Remove')));
-                    $this->elementEnd('fieldset');
-                    $this->elementEnd('form');
-                    $idx++;
-                }
-            }
-        }
-    }
-
-    /**
-     * Handle a POST request
-     *
-     * Muxes to different sub-functions based on which button was pushed
-     *
-     * @return void
-     */
-
-    function handlePost()
-    {
-        // CSRF protection
-        $token = $this->trimmed('token');
-        if (!$token || $token != common_session_token()) {
-            $this->showForm(_('There was a problem with your session token. '.
-                              'Try again, please.'));
-            return;
-        }
-
-        if ($this->arg('add')) {
-            $result = oid_authenticate($this->trimmed('openid_url'),
-                                       'finishaddopenid');
-            if (is_string($result)) { // error message
-                $this->showForm($result);
-            }
-        } else if ($this->arg('remove')) {
-            $this->removeOpenid();
-        } else {
-            $this->showForm(_('Something weird happened.'));
-        }
-    }
-
-    /**
-     * Handles a request to remove an OpenID from the user's account
-     *
-     * Validates input and, if everything is OK, deletes the OpenID.
-     * Reloads the form with a success or error notification.
-     *
-     * @return void
-     */
-
-    function removeOpenid()
-    {
-        $openid_url = $this->trimmed('openid_url');
-
-        $oid = User_openid::staticGet('canonical', $openid_url);
-
-        if (!$oid) {
-            $this->showForm(_('No such OpenID.'));
-            return;
-        }
-        $cur = common_current_user();
-        if (!$cur || $oid->user_id != $cur->id) {
-            $this->showForm(_('That OpenID does not belong to you.'));
-            return;
-        }
-        $oid->delete();
-        $this->showForm(_('OpenID removed.'), true);
-        return;
-    }
-}
index f898e220794e3c2c7bad536df3831f0fe6916681..011b4fc83832e7c39de789855ea44067318a871f 100644 (file)
@@ -97,19 +97,20 @@ class OthersettingsAction extends AccountSettingsAction
         $this->elementStart('fieldset');
         $this->hidden('token', common_session_token());
 
-        // I18N
-
-        $services = array(
-                          '' => 'None',
-                          'ur1.ca' => 'ur1.ca (free service)',
-                          '2tu.us' => '2tu.us (free service)',
-                          'ptiturl.com' => 'ptiturl.com',
-                          'bit.ly' => 'bit.ly',
-                          'tinyurl.com' => 'tinyurl.com',
-                          'is.gd' => 'is.gd',
-                          'snipr.com' => 'snipr.com',
-                          'metamark.net' => 'metamark.net'
-                          );
+        $services=array();
+        global $_shorteners;
+        if($_shorteners){
+            foreach($_shorteners as $name=>$value)
+            {
+                $services[$name]=$name;
+                if($value['info']['freeService']){
+                    // I18N
+                    $services[$name].=' (free service)';
+                }
+            }
+        }
+        asort($services);
+        $services['']='None';
 
         $this->elementStart('ul', 'form_data');
         $this->elementStart('li');
index e775ca17e83e6359ebefefe3711d86a13c485a8f..c2e1c44cae0b4a84099663aee85d954befa4edaf 100644 (file)
@@ -1,5 +1,16 @@
 <?php
-/*
+/**
+ * Handle postnotice action
+ *
+ * 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.
  *
 
 if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
 
-require_once(INSTALLDIR.'/lib/omb.php');
+require_once INSTALLDIR.'/lib/omb.php';
+require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
 
+/**
+ * Handler for postnotice action
+ *
+ * @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/
+ */
 class PostnoticeAction extends Action
 {
+    /**
+     * For initializing members of the class.
+     *
+     * @param array $argarray misc. arguments
+     *
+     * @return boolean true
+     */
+    function prepare($argarray)
+    {
+        parent::prepare($argarray);
+        try {
+            $this->checkNotice();
+        } catch (Exception $e) {
+            $this->clientError($e->getMessage());
+            return false;
+        }
+        return true;
+    }
+
     function handle($args)
     {
         parent::handle($args);
         try {
-            common_remove_magic_from_request();
-            $req = OAuthRequest::from_request('POST', common_local_url('postnotice'));
-            # Note: server-to-server function!
-            $server = omb_oauth_server();
-            list($consumer, $token) = $server->verify_request($req);
-            if ($this->save_notice($req, $consumer, $token)) {
-                print "omb_version=".OMB_VERSION_01;
-            }
-        } catch (OAuthException $e) {
+            $srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
+                                            omb_oauth_server());
+            $srv->handlePostNotice();
+        } catch (Exception $e) {
             $this->serverError($e->getMessage());
             return;
         }
     }
 
-    function save_notice(&$req, &$consumer, &$token)
+    function checkNotice()
     {
-        $version = $req->get_parameter('omb_version');
-        if ($version != OMB_VERSION_01) {
-            $this->clientError(_('Unsupported OMB version'), 400);
-            return false;
-        }
-        # First, check to see
-        $listenee =  $req->get_parameter('omb_listenee');
-        $remote_profile = Remote_profile::staticGet('uri', $listenee);
-        if (!$remote_profile) {
-            $this->clientError(_('Profile unknown'), 403);
-            return false;
-        }
-        $sub = Subscription::staticGet('token', $token->key);
-        if (!$sub) {
-            $this->clientError(_('No such subscription'), 403);
-            return false;
-        }
-        $content = $req->get_parameter('omb_notice_content');
-        $content_shortened = common_shorten_links($content);
-        if (mb_strlen($content_shortened) > 140) {
+        $content = common_shorten_links($_POST['omb_notice_content']);
+        if (Notice::contentTooLong($content)) {
             $this->clientError(_('Invalid notice content'), 400);
             return false;
         }
-        $notice_uri = $req->get_parameter('omb_notice');
-        if (!Validate::uri($notice_uri) &&
-            !common_valid_tag($notice_uri)) {
-            $this->clientError(_('Invalid notice uri'), 400);
-            return false;
-        }
-        $notice_url = $req->get_parameter('omb_notice_url');
-        if ($notice_url && !common_valid_http_url($notice_url)) {
-            $this->clientError(_('Invalid notice url'), 400);
-            return false;
+        $license      = $_POST['omb_notice_license'];
+        $site_license = common_config('license', 'url');
+        if ($license && !common_compatible_license($license, $site_license)) {
+            throw new Exception(sprintf(_('Notice license ‘%s’ is not ' .
+                                          'compatible with site license ‘%s’.'),
+                                        $license, $site_license));
         }
-        $notice = Notice::staticGet('uri', $notice_uri);
-        if (!$notice) {
-            $notice = Notice::saveNew($remote_profile->id, $content, 'omb', false, null, $notice_uri);
-            if (is_string($notice)) {
-                common_server_serror($notice, 500);
-                return false;
-            }
-            common_broadcast_notice($notice, true);
-        }
-        return true;
     }
 }
+?>
\ No newline at end of file
index 2d66e99469cf7692141724185669fdc7f742e027..5445d9bb257ee0c513ead7c2799a1f11958cbe81 100644 (file)
@@ -117,9 +117,16 @@ class ProfilesettingsAction extends AccountSettingsAction
                          _('URL of your homepage, blog, or profile on another site'));
             $this->elementEnd('li');
             $this->elementStart('li');
+            $maxBio = Profile::maxBio();
+            if ($maxBio > 0) {
+                $bioInstr = sprintf(_('Describe yourself and your interests in %d chars'),
+                                    $maxBio);
+            } else {
+                $bioInstr = _('Describe yourself and your interests');
+            }
             $this->textarea('bio', _('Bio'),
                             ($this->arg('bio')) ? $this->arg('bio') : $profile->bio,
-                            _('Describe yourself and your interests in 140 chars'));
+                            $bioInstr);
             $this->elementEnd('li');
             $this->elementStart('li');
             $this->input('location', _('Location'),
@@ -210,8 +217,9 @@ class ProfilesettingsAction extends AccountSettingsAction
             } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
                 $this->showForm(_('Full name is too long (max 255 chars).'));
                 return;
-            } else if (!is_null($bio) && mb_strlen($bio) > 140) {
-                $this->showForm(_('Bio is too long (max 140 chars).'));
+            } else if (Profile::bioTooLong($bio)) {
+                $this->showForm(sprintf(_('Bio is too long (max %d chars).'),
+                                        Profile::maxBio()));
                 return;
             } else if (!is_null($location) && mb_strlen($location) > 255) {
                 $this->showForm(_('Location is too long (max 255 chars).'));
index d426648f3d3623ad214adc00a3d748ef930749e9..73fad182a3f2ab71e7452f349637826dfb95d2d9 100644 (file)
@@ -114,8 +114,6 @@ class PublicAction extends Action
     {
         parent::handle($args);
 
-        header('X-XRDS-Location: '. common_local_url('publicxrds'));
-
         $this->showPage();
     }
 
@@ -156,22 +154,6 @@ class PublicAction extends Action
                               _('Public Stream Feed (Atom)')));
     }
 
-    /**
-     * Extra head elements
-     *
-     * We include a <meta> element linking to the publicxrds page, for OpenID
-     * client-side authentication.
-     *
-     * @return void
-     */
-
-    function extraHead()
-    {
-        // for client side of OpenID authentication
-        $this->element('meta', array('http-equiv' => 'X-XRDS-Location',
-                                     'content' => common_local_url('publicxrds')));
-    }
-
     /**
      * Show tabset for this page
      *
@@ -196,8 +178,7 @@ class PublicAction extends Action
         }
         else {
             if (! (common_config('site','closed') || common_config('site','inviteonly'))) {
-                $message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to post!'),
-                                    (!common_config('site','openidonly')) ? 'register' : 'openidlogin');
+                $message .= _('Why not [register an account](%%action.register%%) and be the first to post!');
             }
        }
 
@@ -244,11 +225,10 @@ class PublicAction extends Action
     function showAnonymousMessage()
     {
         if (! (common_config('site','closed') || common_config('site','inviteonly'))) {
-            $m = sprintf(_('This is %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
-                           'based on the Free Software [StatusNet](http://status.net/) tool. ' .
-                           '[Join now](%%%%action.%s%%%%) to share notices about yourself with friends, family, and colleagues! ' .
-                           '([Read more](%%%%doc.help%%%%))'),
-                         (!common_config('site','openidonly')) ? 'register' : 'openidlogin');
+            $m = _('This is %%site.name%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
+                   'based on the Free Software [StatusNet](http://status.net/) tool. ' .
+                   '[Join now](%%action.register%%) to share notices about yourself with friends, family, and colleagues! ' .
+                   '([Read more](%%doc.help%%))');
         } else {
             $m = _('This is %%site.name%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
                    'based on the Free Software [StatusNet](http://status.net/) tool.');
index 593888b9f66fd6fee0bd0f59333f4c174fc51dec..0c5d061cb65614148b6711eb0c2b60e24f1c25c9 100644 (file)
@@ -49,9 +49,23 @@ require_once INSTALLDIR.'/lib/rssaction.php';
  */
 class PublicrssAction extends Rss10Action
 {
+    /**
+     * Read arguments and initialize members
+     *
+     * @param array $args Arguments from $_REQUEST
+     * @return boolean success
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        $this->notices = $this->getNotices($this->limit);
+        return true;
+    }
+
     /**
      * Initialization.
-     * 
+     *
      * @return boolean true
      */
     function init()
@@ -73,7 +87,7 @@ class PublicrssAction extends Rss10Action
         while ($notice->fetch()) {
             $notices[] = clone($notice);
         }
-        
+
         return $notices;
     }
 
index 60bb53e27c934d9682b6ac616144d20d6c890294..e7f6ee36c736552bc33817bf5018940ea0e1d254 100644 (file)
@@ -72,8 +72,7 @@ class PublictagcloudAction extends Action
             $message .= _('Be the first to post one!');
         }
         else {
-            $message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to post one!'),
-                                (!common_config('site','openidonly')) ? 'register' : 'openidlogin');
+            $message .= _('Why not [register an account](%%action.register%%) and be the first to post one!');
         }
 
         $this->elementStart('div', 'guide');
diff --git a/actions/publicxrds.php b/actions/publicxrds.php
deleted file mode 100644 (file)
index 209a10e..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.'/lib/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 eefbc340a1f427f5484ff5a3f5978f007edf6e5e..100ab74242c74a4244739a3fcc46a56ca937c027 100644 (file)
@@ -116,8 +116,6 @@ class RegisterAction extends Action
      *
      * Checks if registration is closed and shows an error if so.
      *
-     * Checks if only OpenID is allowed and redirects to openidlogin if so.
-     *
      * @param array $args $_REQUEST data
      *
      * @return void
@@ -129,8 +127,6 @@ class RegisterAction extends Action
 
         if (common_config('site', 'closed')) {
             $this->clientError(_('Registration not allowed.'));
-        } else if (common_config('site', 'openidonly')) {
-            common_redirect(common_local_url('openidlogin'));
         } else if (common_logged_in()) {
             $this->clientError(_('Already logged in.'));
         } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
@@ -217,8 +213,9 @@ class RegisterAction extends Action
             } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
                 $this->showForm(_('Full name is too long (max 255 chars).'));
                 return;
-            } else if (!is_null($bio) && mb_strlen($bio) > 140) {
-                $this->showForm(_('Bio is too long (max 140 chars).'));
+            } else if (Profile::bioTooLong($bio)) {
+                $this->showForm(sprintf(_('Bio is too long (max %d chars).'),
+                                        Profile::maxBio()));
                 return;
             } else if (!is_null($location) && mb_strlen($location) > 255) {
                 $this->showForm(_('Location is too long (max 255 chars).'));
@@ -335,22 +332,11 @@ class RegisterAction extends Action
         } else if ($this->error) {
             $this->element('p', 'error', $this->error);
         } else {
-            if (common_config('openid', 'enabled')) {
-                $instr =
-                  common_markup_to_html(_('With this form you can create '.
-                                          ' a new account. ' .
-                                          'You can then post notices and '.
-                                          'link up to friends and colleagues. '.
-                                          '(Have an [OpenID](http://openid.net/)? ' .
-                                          'Try our [OpenID registration]'.
-                                          '(%%action.openidlogin%%)!)'));
-            } else {
-                $instr =
-                  common_markup_to_html(_('With this form you can create '.
-                                          ' a new account. ' .
-                                          'You can then post notices and '.
-                                          'link up to friends and colleagues.'));
-            }
+            $instr =
+              common_markup_to_html(_('With this form you can create '.
+                                      ' a new account. ' .
+                                      'You can then post notices and '.
+                                      'link up to friends and colleagues. '));
 
             $this->elementStart('div', 'instructions');
             $this->raw($instr);
@@ -463,10 +449,16 @@ class RegisterAction extends Action
                            'or profile on another site'));
             $this->elementEnd('li');
             $this->elementStart('li');
+            $maxBio = Profile::maxBio();
+            if ($maxBio > 0) {
+                $bioInstr = sprintf(_('Describe yourself and your interests in %d chars'),
+                                    $maxBio);
+            } else {
+                $bioInstr = _('Describe yourself and your interests');
+            }
             $this->textarea('bio', _('Bio'),
                             $this->trimmed('bio'),
-                            _('Describe yourself and your '.
-                              'interests in 140 chars'));
+                            $bioInstr);
             $this->elementEnd('li');
             $this->elementStart('li');
             $this->input('location', _('Location'),
index 374392d4a31df096599dc11cc2e59f002566aeaa..aee2a5d8e79f1d7a54a7c1f945eecf4752299344 100644 (file)
@@ -1,5 +1,16 @@
 <?php
-/*
+/**
+ * Handler for remote subscription
+ *
+ * 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.
  *
  *
  * 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.'/lib/omb.php');
+require_once INSTALLDIR.'/lib/omb.php';
+require_once INSTALLDIR.'/extlib/libomb/service_consumer.php';
+require_once INSTALLDIR.'/extlib/libomb/profile.php';
+
+/**
+ * Handler for remote subscription
+ *
+ * @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/
+ */
 
 class RemotesubscribeAction extends Action
 {
@@ -36,7 +60,7 @@ class RemotesubscribeAction extends Action
             return false;
         }
 
-        $this->nickname = $this->trimmed('nickname');
+        $this->nickname    = $this->trimmed('nickname');
         $this->profile_url = $this->trimmed('profile_url');
 
         return true;
@@ -47,7 +71,7 @@ class RemotesubscribeAction extends Action
         parent::handle($args);
 
         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
-            # CSRF protection
+            /* Use a session token for CSRF protection. */
             $token = $this->trimmed('token');
             if (!$token || $token != common_session_token()) {
                 $this->showForm(_('There was a problem with your session token. '.
@@ -71,13 +95,11 @@ class RemotesubscribeAction extends Action
         if ($this->err) {
             $this->element('div', 'error', $this->err);
         } else {
-            $inst = sprintf(_('To subscribe, you can [login](%%%%action.%s%%%%),' .
-                              ' or [register](%%%%action.%s%%%%) a new ' .
-                              ' account. If you already have an account ' .
-                              ' on a [compatible microblogging site](%%doc.openmublog%%), ' .
-                              ' enter your profile URL below.'),
-                            (!common_config('site','openidonly')) ? 'login' : 'openidlogin',
-                            (!common_config('site','openidonly')) ? 'register' : 'openidlogin');
+            $inst = _('To subscribe, you can [login](%%action.login%%),' .
+                      ' or [register](%%action.register%%) a new ' .
+                      ' account. If you already have an account ' .
+                      ' on a [compatible microblogging site](%%doc.openmublog%%), ' .
+                      ' enter your profile URL below.');
             $output = common_markup_to_html($inst);
             $this->elementStart('div', 'instructions');
             $this->raw($output);
@@ -92,8 +114,8 @@ class RemotesubscribeAction extends Action
 
     function showContent()
     {
-        # id = remotesubscribe conflicts with the
-        # button on profile page
+        /* The id 'remotesubscribe' conflicts with the
+           button on profile page. */
         $this->elementStart('form', array('id' => 'form_remote_subscribe',
                                           'method' => 'post',
                                           'class' => 'form_settings',
@@ -119,247 +141,50 @@ class RemotesubscribeAction extends Action
 
     function remoteSubscription()
     {
-        $user = $this->getUser();
-
-        if (!$user) {
+        if (!$this->nickname) {
             $this->showForm(_('No such user.'));
             return;
         }
 
+        $user = User::staticGet('nickname', $this->nickname);
+
         $this->profile_url = $this->trimmed('profile_url');
 
         if (!$this->profile_url) {
-            $this->showForm(_('No such user.'));
+            $this->showForm(_('No such user'));
             return;
         }
 
-        if (!Validate::uri($this->profile_url, array('allowed_schemes' => array('http', 'https')))) {
+        if (!common_valid_http_url($this->profile_url)) {
             $this->showForm(_('Invalid profile URL (bad format)'));
             return;
         }
 
-        $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
-        $yadis = Auth_Yadis_Yadis::discover($this->profile_url, $fetcher);
-
-        if (!$yadis || $yadis->failed) {
-            $this->showForm(_('Not a valid profile URL (no YADIS document).'));
-            return;
-        }
-
-        # XXX: a little liberal for sites that accidentally put whitespace before the xml declaration
-
-        $xrds =& Auth_Yadis_XRDS::parseXRDS(trim($yadis->response_text));
-
-        if (!$xrds) {
-            $this->showForm(_('Not a valid profile URL (no XRDS defined).'));
-            return;
-        }
-
-        $omb = $this->getOmb($xrds);
-
-        if (!$omb) {
-            $this->showForm(_('Not a valid profile URL (incorrect services).'));
-            return;
-        }
-
-        if (omb_service_uri($omb[OAUTH_ENDPOINT_REQUEST]) ==
-            common_local_url('requesttoken'))
-        {
-            $this->showForm(_('That\'s a local profile! Login to subscribe.'));
+        try {
+            $service = new OMB_Service_Consumer($this->profile_url,
+                                                common_root_url(),
+                                                omb_oauth_datastore());
+        } catch (OMB_InvalidYadisException $e) {
+            $this->showForm(_('Not a valid profile URL (no YADIS document or ' .
+                              'no or invalid XRDS defined).'));
             return;
         }
 
-        if (User::staticGet('uri', omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]))) {
-            $this->showForm(_('That\'s a local profile! Login to subscribe.'));
+        if ($service->getServiceURI(OAUTH_ENDPOINT_REQUEST) ==
+            common_local_url('requesttoken') ||
+            User::staticGet('uri', $service->getRemoteUserURI())) {
+            $this->showForm(_('That’s a local profile! Login to subscribe.'));
             return;
         }
 
-        list($token, $secret) = $this->requestToken($omb);
-
-        if (!$token || !$secret) {
-            $this->showForm(_('Couldn\'t get a request token.'));
+        try {
+            $service->requestToken();
+        } catch (OMB_RemoteServiceException $e) {
+            $this->showForm(_('Couldnt get a request token.'));
             return;
         }
 
-        $this->requestAuthorization($user, $omb, $token, $secret);
-    }
-
-    function getUser()
-    {
-        $user = null;
-        if ($this->nickname) {
-            $user = User::staticGet('nickname', $this->nickname);
-        }
-        return $user;
-    }
-
-    function getOmb($xrds)
-    {
-        static $omb_endpoints = array(OMB_ENDPOINT_UPDATEPROFILE, OMB_ENDPOINT_POSTNOTICE);
-        static $oauth_endpoints = array(OAUTH_ENDPOINT_REQUEST, OAUTH_ENDPOINT_AUTHORIZE,
-                                        OAUTH_ENDPOINT_ACCESS);
-        $omb = array();
-
-        # XXX: the following code could probably be refactored to eliminate dupes
-
-        $oauth_services = omb_get_services($xrds, OAUTH_DISCOVERY);
-
-        if (!$oauth_services) {
-            return null;
-        }
-
-        $oauth_service = $oauth_services[0];
-
-        $oauth_xrd = $this->getXRD($oauth_service, $xrds);
-
-        if (!$oauth_xrd) {
-            return null;
-        }
-
-        if (!$this->addServices($oauth_xrd, $oauth_endpoints, $omb)) {
-            return null;
-        }
-
-        $omb_services = omb_get_services($xrds, OMB_NAMESPACE);
-
-        if (!$omb_services) {
-            return null;
-        }
-
-        $omb_service = $omb_services[0];
-
-        $omb_xrd = $this->getXRD($omb_service, $xrds);
-
-        if (!$omb_xrd) {
-            return null;
-        }
-
-        if (!$this->addServices($omb_xrd, $omb_endpoints, $omb)) {
-            return null;
-        }
-
-        # XXX: check that we got all the services we needed
-
-        foreach (array_merge($omb_endpoints, $oauth_endpoints) as $type) {
-            if (!array_key_exists($type, $omb) || !$omb[$type]) {
-                return null;
-            }
-        }
-
-        if (!omb_local_id($omb[OAUTH_ENDPOINT_REQUEST])) {
-            return null;
-        }
-
-        return $omb;
-    }
-
-    function getXRD($main_service, $main_xrds)
-    {
-        $uri = omb_service_uri($main_service);
-        if (strpos($uri, "#") !== 0) {
-            # FIXME: more rigorous handling of external service definitions
-            return null;
-        }
-        $id = substr($uri, 1);
-        $nodes = $main_xrds->allXrdNodes;
-        $parser = $main_xrds->parser;
-        foreach ($nodes as $node) {
-            $attrs = $parser->attributes($node);
-            if (array_key_exists('xml:id', $attrs) &&
-                $attrs['xml:id'] == $id) {
-                # XXX: trick the constructor into thinking this is the only node
-                $bogus_nodes = array($node);
-                return new Auth_Yadis_XRDS($parser, $bogus_nodes);
-            }
-        }
-        return null;
-    }
-
-    function addServices($xrd, $types, &$omb)
-    {
-        foreach ($types as $type) {
-            $matches = omb_get_services($xrd, $type);
-            if ($matches) {
-                $omb[$type] = $matches[0];
-            } else {
-                # no match for type
-                return false;
-            }
-        }
-        return true;
-    }
-
-    function requestToken($omb)
-    {
-        $con = omb_oauth_consumer();
-
-        $url = omb_service_uri($omb[OAUTH_ENDPOINT_REQUEST]);
-
-        # XXX: Is this the right thing to do? Strip off GET params and make them
-        # POST params? Seems wrong to me.
-
-        $parsed = parse_url($url);
-        $params = array();
-        parse_str($parsed['query'], $params);
-
-        $req = OAuthRequest::from_consumer_and_token($con, null, "POST", $url, $params);
-
-        $listener = omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]);
-
-        if (!$listener) {
-            return null;
-        }
-
-        $req->set_parameter('omb_listener', $listener);
-        $req->set_parameter('omb_version', OMB_VERSION_01);
-
-        # XXX: test to see if endpoint accepts this signature method
-
-        $req->sign_request(omb_hmac_sha1(), $con, null);
-
-        # We re-use this tool's fetcher, since it's pretty good
-
-        $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
-
-        $result = $fetcher->post($req->get_normalized_http_url(),
-                                 $req->to_postdata(),
-                                 array('User-Agent: StatusNet/' . STATUSNET_VERSION));
-        if ($result->status != 200) {
-            return null;
-        }
-
-        parse_str($result->body, $return);
-
-        return array($return['oauth_token'], $return['oauth_token_secret']);
-    }
-
-    function requestAuthorization($user, $omb, $token, $secret)
-    {
-        $con = omb_oauth_consumer();
-        $tok = new OAuthToken($token, $secret);
-
-        $url = omb_service_uri($omb[OAUTH_ENDPOINT_AUTHORIZE]);
-
-        # XXX: Is this the right thing to do? Strip off GET params and make them
-        # POST params? Seems wrong to me.
-
-        $parsed = parse_url($url);
-        $params = array();
-        parse_str($parsed['query'], $params);
-
-        $req = OAuthRequest::from_consumer_and_token($con, $tok, 'GET', $url, $params);
-
-        # We send over a ton of information. This lets the other
-        # server store info about our user, and it lets the current
-        # user decide if they really want to authorize the subscription.
-
-        $req->set_parameter('omb_version', OMB_VERSION_01);
-        $req->set_parameter('omb_listener', omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]));
-        $req->set_parameter('omb_listenee', $user->uri);
-        $req->set_parameter('omb_listenee_profile', common_profile_url($user->nickname));
-        $req->set_parameter('omb_listenee_nickname', $user->nickname);
-        $req->set_parameter('omb_listenee_license', common_config('license', 'url'));
-
+        /* Create an OMB_Profile from $user. */
         $profile = $user->getProfile();
         if (!$profile) {
             common_log_db_error($user, 'SELECT', __FILE__);
@@ -367,49 +192,16 @@ class RemotesubscribeAction extends Action
             return;
         }
 
-        if (!is_null($profile->fullname)) {
-            $req->set_parameter('omb_listenee_fullname', $profile->fullname);
-        }
-        if (!is_null($profile->homepage)) {
-            $req->set_parameter('omb_listenee_homepage', $profile->homepage);
-        }
-        if (!is_null($profile->bio)) {
-            $req->set_parameter('omb_listenee_bio', $profile->bio);
-        }
-        if (!is_null($profile->location)) {
-            $req->set_parameter('omb_listenee_location', $profile->location);
-        }
-        $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
-        if ($avatar) {
-            $req->set_parameter('omb_listenee_avatar', $avatar->url);
-        }
-
-        # XXX: add a nonce to prevent replay attacks
-
-        $req->set_parameter('oauth_callback', common_local_url('finishremotesubscribe'));
-
-        # XXX: test to see if endpoint accepts this signature method
-
-        $req->sign_request(omb_hmac_sha1(), $con, $tok);
-
-        # store all our info here
-
-        $omb['listenee'] = $user->nickname;
-        $omb['listener'] = omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]);
-        $omb['token'] = $token;
-        $omb['secret'] = $secret;
-        # call doesn't work after bounce back so we cache; maybe serialization issue...?
-        $omb['access_token_url'] = omb_service_uri($omb[OAUTH_ENDPOINT_ACCESS]);
-        $omb['post_notice_url'] = omb_service_uri($omb[OMB_ENDPOINT_POSTNOTICE]);
-        $omb['update_profile_url'] = omb_service_uri($omb[OMB_ENDPOINT_UPDATEPROFILE]);
+        $target_url = $service->requestAuthorization(
+                                   profile_to_omb_profile($user->uri, $profile),
+                                   common_local_url('finishremotesubscribe'));
 
         common_ensure_session();
 
-        $_SESSION['oauth_authorization_request'] = $omb;
-
-        # Redirect to authorization service
+        $_SESSION['oauth_authorization_request'] = serialize($service);
 
-        common_redirect($req->to_url(), 303);
-        return;
+        /* Redirect to the remote service for authorization. */
+        common_redirect($target_url, 303);
     }
 }
+?>
index cca43023097c2f11ff5806cf629488cd88831bd9..6003ad30bde3e60b5a7fb5b9f953d20d1d634583 100644 (file)
@@ -192,9 +192,7 @@ class RepliesAction extends OwnerDesignAction
             }
         }
         else {
-            $message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and then nudge %s or post a notice to his or her attention.'),
-                                (!common_config('site','openidonly')) ? 'register' : 'openidlogin',
-                                $this->user->nickname);
+            $message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
         }
 
         $this->elementStart('div', 'guide');
index c71c9226fb59dbc0e9cba5d8c5c23ad2f1a30a99..76aae21adb994652a0fd16e8bbc090aeb95ccea3 100644 (file)
@@ -38,6 +38,7 @@ class RepliesrssAction extends Rss10Action
             $this->clientError(_('No such user.'));
             return false;
         } else {
+            $this->notices = $this->getNotices($this->limit);
             return true;
         }
     }
index a17efcdd50abaf55dbc5bc21b2f7daf52cd33a0d..e095161a7d01bf988604faba3ab9356f053bf191 100644 (file)
@@ -34,6 +34,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
 }
 
 require_once INSTALLDIR.'/lib/omb.php';
+require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
 
 /**
  * Request token action class.
@@ -49,17 +50,17 @@ class RequesttokenAction extends Action
 {
      /**
      * Is read only?
-     * 
+     *
      * @return boolean false
      */
-    function isReadOnly($args)
+    function isReadOnly()
     {
         return false;
     }
-    
+
     /**
      * Class handler.
-     * 
+     *
      * @param array $args array of arguments
      *
      * @return void
@@ -68,14 +69,12 @@ class RequesttokenAction extends Action
     {
         parent::handle($args);
         try {
-            common_remove_magic_from_request();
-            $req    = OAuthRequest::from_request('POST', common_local_url('requesttoken'));
-            $server = omb_oauth_server();
-            $token  = $server->fetch_request_token($req);
-            print $token.'&omb_version='.OMB_VERSION_01;
-        } catch (OAuthException $e) {
+            $srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
+                                            omb_oauth_server());
+            $srv->writeRequestToken();
+        } catch (Exception $e) {
             $this->serverError($e->getMessage());
         }
     }
 }
-
+?>
index 0f7a66330242be0a588c05f0b19a16b6e306e4b8..b96d2af37fc568a672132bba2a32280b488ecfc8 100644 (file)
@@ -196,9 +196,7 @@ class ShowfavoritesAction extends OwnerDesignAction
             }
         }
         else {
-            $message = sprintf(_('%s hasn\'t added any notices to his favorites yet. Why not [register an account](%%%%action.%s%%%%) and then post something interesting they would add to their favorites :)'),
-                               $this->user->nickname,
-                               (!common_config('site','openidonly')) ? 'register' : 'openidlogin');
+            $message = sprintf(_('%s hasn\'t added any notices to his favorites yet. Why not [register an account](%%%%action.register%%%%) and then post something interesting they would add to their favorites :)'), $this->user->nickname);
         }
 
         $this->elementStart('div', 'guide');
index 8157ee3c852f0884288e058c8e83bae90bc88806..bfe45ddad79a8d8194b679f510c058d7c2b68b71 100644 (file)
@@ -101,11 +101,6 @@ class ShowgroupAction extends GroupDesignAction
     {
         parent::prepare($args);
 
-        if (!common_config('inboxes','enabled')) {
-            $this->serverError(_('Inboxes must be enabled for groups to work'));
-            return false;
-        }
-
         $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
 
         $nickname_arg = $this->arg('nickname');
@@ -345,7 +340,12 @@ class ShowgroupAction extends GroupDesignAction
                                                      'method' => 'timeline',
                                                      'argument' => $this->group->nickname.'.atom')),
                               sprintf(_('Notice feed for %s group (Atom)'),
-                                      $this->group->nickname)));
+                                      $this->group->nickname)),
+                     new Feed(Feed::FOAF,
+                              common_local_url('foafgroup',
+                                               array('nickname' => $this->group->nickname)),
+                              sprintf(_('FOAF for %s group'),
+                                       $this->group->nickname)));
     }
 
     /**
@@ -450,9 +450,8 @@ class ShowgroupAction extends GroupDesignAction
             $m = sprintf(_('**%s** is a user group on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
                 'based on the Free Software [StatusNet](http://status.net/) tool. Its members share ' .
                 'short messages about their life and interests. '.
-                '[Join now](%%%%action.%s%%%%) to become part of this group and many more! ([Read more](%%%%doc.help%%%%))'),
-                     $this->group->nickname,
-                     (!common_config('site','openidonly')) ? 'register' : 'openidlogin');
+                '[Join now](%%%%action.register%%%%) to become part of this group and many more! ([Read more](%%%%doc.help%%%%))'),
+                     $this->group->nickname);
         } else {
             $m = sprintf(_('**%s** is a user group on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
                 'based on the Free Software [StatusNet](http://status.net/) tool. Its members share ' .
index 3bc52b2dbc7bae3fef45a4bf7cedc24df156bc38..41408c23ccb4a396d92d814366824a5871475ae5 100644 (file)
@@ -84,7 +84,13 @@ class ShownoticeAction extends OwnerDesignAction
         $this->notice = Notice::staticGet($id);
 
         if (empty($this->notice)) {
-            $this->clientError(_('No such notice.'), 404);
+            // Did we used to have it, and it got deleted?
+            $deleted = Deleted_notice::staticGet($id);
+            if (!empty($deleted)) {
+                $this->clientError(_('Notice deleted.'), 410);
+            } else {
+                $this->clientError(_('No such notice.'), 404);
+            }
             return false;
         }
 
index 89285b13c7aae1cd4982ce8f7e411f96bbd84f9f..b3a9b1f05c75da9fbf3ef8969c3fc093cda63494 100644 (file)
@@ -115,11 +115,11 @@ class ShowstreamAction extends ProfileAction
     {
         if (!empty($this->tag)) {
             return array(new Feed(Feed::RSS1,
-                common_local_url('userrss',
-                    array('nickname' => $this->user->nickname,
-                        'tag' => $this->tag)),
-                sprintf(_('Notice feed for %s tagged %s (RSS 1.0)'),
-                    $this->user->nickname, $this->tag)));
+                                  common_local_url('userrss',
+                                                   array('nickname' => $this->user->nickname,
+                                                         'tag' => $this->tag)),
+                                  sprintf(_('Notice feed for %s tagged %s (RSS 1.0)'),
+                                          $this->user->nickname, $this->tag)));
         }
 
         return array(new Feed(Feed::RSS1,
@@ -181,159 +181,251 @@ class ShowstreamAction extends ProfileAction
 
     function showProfile()
     {
-        $this->elementStart('div', 'entity_profile vcard author');
-        $this->element('h2', null, _('User profile'));
-
-        $avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
-        $this->elementStart('dl', 'entity_depiction');
-        $this->element('dt', null, _('Photo'));
-        $this->elementStart('dd');
-        $this->element('img', array('src' => ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE),
-                                    'class' => 'photo avatar',
-                                    'width' => AVATAR_PROFILE_SIZE,
-                                    'height' => AVATAR_PROFILE_SIZE,
-                                    'alt' => $this->profile->nickname));
-        $this->elementEnd('dd');
-
-        $user = User::staticGet('id', $this->profile->id);
-        $cur = common_current_user();
-        if ($cur && $cur->id == $user->id) {
-            $this->elementStart('dd');
-            $this->element('a', array('href' => common_local_url('avatarsettings')), _('Edit Avatar'));
-            $this->elementEnd('dd');
+        $this->showProfileData();
+        $this->showEntityActions();
+    }
+
+    function showProfileData()
+    {
+        if (Event::handle('StartProfilePageProfileSection', array(&$this, $this->profile))) {
+
+            $this->elementStart('div', 'entity_profile vcard author');
+            $this->element('h2', null, _('User profile'));
+
+            if (Event::handle('StartProfilePageProfileElements', array(&$this, $this->profile))) {
+
+                $this->showAvatar();
+                $this->showNickname();
+                $this->showFullName();
+                $this->showLocation();
+                $this->showHomepage();
+                $this->showBio();
+                $this->showProfileTags();
+
+                Event::handle('EndProfilePageProfileElements', array(&$this, $this->profile));
+            }
+
+            $this->elementEnd('div');
+            Event::handle('EndProfilePageProfileSection', array(&$this, $this->profile));
         }
+    }
+
+    function showAvatar()
+    {
+        if (Event::handle('StartProfilePageAvatar', array($this, $this->profile))) {
+
+            $avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
 
-        $this->elementEnd('dl');
-
-        $this->elementStart('dl', 'entity_nickname');
-        $this->element('dt', null, _('Nickname'));
-        $this->elementStart('dd');
-        $hasFN = ($this->profile->fullname) ? 'nickname url uid' : 'fn nickname url uid';
-        $this->element('a', array('href' => $this->profile->profileurl,
-                                  'rel' => 'me', 'class' => $hasFN),
-                       $this->profile->nickname);
-        $this->elementEnd('dd');
-        $this->elementEnd('dl');
-
-        if ($this->profile->fullname) {
-            $this->elementStart('dl', 'entity_fn');
-            $this->element('dt', null, _('Full name'));
+            $this->elementStart('dl', 'entity_depiction');
+            $this->element('dt', null, _('Photo'));
             $this->elementStart('dd');
-            $this->element('span', 'fn', $this->profile->fullname);
+            $this->element('img', array('src' => ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE),
+                                        'class' => 'photo avatar',
+                                        'width' => AVATAR_PROFILE_SIZE,
+                                        'height' => AVATAR_PROFILE_SIZE,
+                                        'alt' => $this->profile->nickname));
             $this->elementEnd('dd');
-            $this->elementEnd('dl');
-        }
 
-        if ($this->profile->location) {
-            $this->elementStart('dl', 'entity_location');
-            $this->element('dt', null, _('Location'));
-            $this->element('dd', 'label', $this->profile->location);
+            $user = User::staticGet('id', $this->profile->id);
+
+            $cur = common_current_user();
+            if ($cur && $cur->id == $user->id) {
+                $this->elementStart('dd');
+                $this->element('a', array('href' => common_local_url('avatarsettings')), _('Edit Avatar'));
+                $this->elementEnd('dd');
+            }
+
             $this->elementEnd('dl');
+
+            Event::handle('EndProfilePageAvatar', array($this, $this->profile));
         }
+    }
+
+    function showNickname()
+    {
+        if (Event::handle('StartProfilePageNickname', array($this, $this->profile))) {
 
-        if ($this->profile->homepage) {
-            $this->elementStart('dl', 'entity_url');
-            $this->element('dt', null, _('URL'));
+            $this->elementStart('dl', 'entity_nickname');
+            $this->element('dt', null, _('Nickname'));
             $this->elementStart('dd');
-            $this->element('a', array('href' => $this->profile->homepage,
-                                      'rel' => 'me', 'class' => 'url'),
-                           $this->profile->homepage);
+            $hasFN = ($this->profile->fullname) ? 'nickname url uid' : 'fn nickname url uid';
+            $this->element('a', array('href' => $this->profile->profileurl,
+                                      'rel' => 'me', 'class' => $hasFN),
+                           $this->profile->nickname);
             $this->elementEnd('dd');
             $this->elementEnd('dl');
+
+            Event::handle('EndProfilePageNickname', array($this, $this->profile));
         }
+    }
 
-        if ($this->profile->bio) {
-            $this->elementStart('dl', 'entity_note');
-            $this->element('dt', null, _('Note'));
-            $this->element('dd', 'note', $this->profile->bio);
-            $this->elementEnd('dl');
+    function showFullName()
+    {
+        if (Event::handle('StartProfilePageFullName', array($this, $this->profile))) {
+            if ($this->profile->fullname) {
+                $this->elementStart('dl', 'entity_fn');
+                $this->element('dt', null, _('Full name'));
+                $this->elementStart('dd');
+                $this->element('span', 'fn', $this->profile->fullname);
+                $this->elementEnd('dd');
+                $this->elementEnd('dl');
+            }
+            Event::handle('EndProfilePageFullName', array($this, $this->profile));
         }
+    }
 
-        $tags = Profile_tag::getTags($this->profile->id, $this->profile->id);
-        if (count($tags) > 0) {
-            $this->elementStart('dl', 'entity_tags');
-            $this->element('dt', null, _('Tags'));
-            $this->elementStart('dd');
-            $this->elementStart('ul', 'tags xoxo');
-            foreach ($tags as $tag) {
-                $this->elementStart('li');
-                // Avoid space by using raw output.
-                $pt = '<span class="mark_hash">#</span><a rel="tag" href="' .
-                      common_local_url('peopletag', array('tag' => $tag)) .
-                      '">' . $tag . '</a>';
-                $this->raw($pt);
-                $this->elementEnd('li');
+    function showLocation()
+    {
+        if (Event::handle('StartProfilePageLocation', array($this, $this->profile))) {
+            if ($this->profile->location) {
+                $this->elementStart('dl', 'entity_location');
+                $this->element('dt', null, _('Location'));
+                $this->element('dd', 'label', $this->profile->location);
+                $this->elementEnd('dl');
             }
-            $this->elementEnd('ul');
-            $this->elementEnd('dd');
-            $this->elementEnd('dl');
+            Event::handle('EndProfilePageLocation', array($this, $this->profile));
         }
-        $this->elementEnd('div');
+    }
 
-        $this->elementStart('div', 'entity_actions');
-        $this->element('h2', null, _('User actions'));
-        $this->elementStart('ul');
-        $cur = common_current_user();
-
-        if ($cur && $cur->id == $this->profile->id) {
-            $this->elementStart('li', 'entity_edit');
-            $this->element('a', array('href' => common_local_url('profilesettings'),
-                                      'title' => _('Edit profile settings')),
-                           _('Edit'));
-            $this->elementEnd('li');
+    function showHomepage()
+    {
+        if (Event::handle('StartProfilePageHomepage', array($this, $this->profile))) {
+            if ($this->profile->homepage) {
+                $this->elementStart('dl', 'entity_url');
+                $this->element('dt', null, _('URL'));
+                $this->elementStart('dd');
+                $this->element('a', array('href' => $this->profile->homepage,
+                                          'rel' => 'me', 'class' => 'url'),
+                               $this->profile->homepage);
+                $this->elementEnd('dd');
+                $this->elementEnd('dl');
+            }
+            Event::handle('EndProfilePageHomepage', array($this, $this->profile));
         }
+    }
 
-        if ($cur) {
-            if ($cur->id != $this->profile->id) {
-                $this->elementStart('li', 'entity_subscribe');
-                if ($cur->isSubscribed($this->profile)) {
-                    $usf = new UnsubscribeForm($this, $this->profile);
-                    $usf->show();
-                } else {
-                    $sf = new SubscribeForm($this, $this->profile);
-                    $sf->show();
-                }
-                $this->elementEnd('li');
+    function showBio()
+    {
+        if (Event::handle('StartProfilePageBio', array($this, $this->profile))) {
+            if ($this->profile->bio) {
+                $this->elementStart('dl', 'entity_note');
+                $this->element('dt', null, _('Note'));
+                $this->element('dd', 'note', $this->profile->bio);
+                $this->elementEnd('dl');
             }
-        } else {
-            $this->elementStart('li', 'entity_subscribe');
-            $this->showRemoteSubscribeLink();
-            $this->elementEnd('li');
+            Event::handle('EndProfilePageBio', array($this, $this->profile));
         }
+    }
 
-        if ($cur && $cur->id != $user->id && $cur->mutuallySubscribed($user)) {
-            $this->elementStart('li', 'entity_send-a-message');
-            $this->element('a', array('href' => common_local_url('newmessage', array('to' => $user->id)),
-                                      'title' => _('Send a direct message to this user')),
-                           _('Message'));
-            $this->elementEnd('li');
-
-            if ($user->email && $user->emailnotifynudge) {
-                $this->elementStart('li', 'entity_nudge');
-                $nf = new NudgeForm($this, $user);
-                $nf->show();
-                $this->elementEnd('li');
+    function showProfileTags()
+    {
+        if (Event::handle('StartProfilePageProfileTags', array($this, $this->profile))) {
+            $tags = Profile_tag::getTags($this->profile->id, $this->profile->id);
+
+            if (count($tags) > 0) {
+                $this->elementStart('dl', 'entity_tags');
+                $this->element('dt', null, _('Tags'));
+                $this->elementStart('dd');
+                $this->elementStart('ul', 'tags xoxo');
+                foreach ($tags as $tag) {
+                    $this->elementStart('li');
+                    // Avoid space by using raw output.
+                    $pt = '<span class="mark_hash">#</span><a rel="tag" href="' .
+                      common_local_url('peopletag', array('tag' => $tag)) .
+                      '">' . $tag . '</a>';
+                    $this->raw($pt);
+                    $this->elementEnd('li');
+                }
+                $this->elementEnd('ul');
+                $this->elementEnd('dd');
+                $this->elementEnd('dl');
             }
+            Event::handle('EndProfilePageProfileTags', array($this, $this->profile));
         }
+    }
 
-        if ($cur && $cur->id != $this->profile->id) {
-            $blocked = $cur->hasBlocked($this->profile);
-            $this->elementStart('li', 'entity_block');
-            if ($blocked) {
-                $ubf = new UnblockForm($this, $this->profile,
-                                       array('action' => 'showstream',
-                                             'nickname' => $this->profile->nickname));
-                $ubf->show();
-            } else {
-                $bf = new BlockForm($this, $this->profile,
-                                    array('action' => 'showstream',
-                                          'nickname' => $this->profile->nickname));
-                $bf->show();
+    function showEntityActions()
+    {
+        if (Event::handle('StartProfilePageActionsSection', array(&$this, $this->profile))) {
+
+            $this->elementStart('div', 'entity_actions');
+            $this->element('h2', null, _('User actions'));
+            $this->elementStart('ul');
+
+            if (Event::handle('StartProfilePageActionsElements', array(&$this, $this->profile))) {
+                if (empty($cur)) { // not logged in
+                    $this->elementStart('li', 'entity_subscribe');
+                    $this->showRemoteSubscribeLink();
+                    $this->elementEnd('li');
+                } else {
+                    if ($cur->id == $this->profile->id) { // your own page
+                        $this->elementStart('li', 'entity_edit');
+                        $this->element('a', array('href' => common_local_url('profilesettings'),
+                                                  'title' => _('Edit profile settings')),
+                                       _('Edit'));
+                        $this->elementEnd('li');
+                    } else { // someone else's page
+
+                        // subscribe/unsubscribe button
+
+                        $this->elementStart('li', 'entity_subscribe');
+
+                        if ($cur->isSubscribed($this->profile)) {
+                            $usf = new UnsubscribeForm($this, $this->profile);
+                            $usf->show();
+                        } else {
+                            $sf = new SubscribeForm($this, $this->profile);
+                            $sf->show();
+                        }
+                        $this->elementEnd('li');
+
+                        if ($cur->mutuallySubscribed($user)) {
+
+                            // message
+
+                            $this->elementStart('li', 'entity_send-a-message');
+                            $this->element('a', array('href' => common_local_url('newmessage', array('to' => $user->id)),
+                                                      'title' => _('Send a direct message to this user')),
+                                           _('Message'));
+                            $this->elementEnd('li');
+
+                            // nudge
+
+                            if ($user->email && $user->emailnotifynudge) {
+                                $this->elementStart('li', 'entity_nudge');
+                                $nf = new NudgeForm($this, $user);
+                                $nf->show();
+                                $this->elementEnd('li');
+                            }
+                        }
+
+                        // block/unblock
+
+                        $blocked = $cur->hasBlocked($this->profile);
+                        $this->elementStart('li', 'entity_block');
+                        if ($blocked) {
+                            $ubf = new UnblockForm($this, $this->profile,
+                                                   array('action' => 'showstream',
+                                                         'nickname' => $this->profile->nickname));
+                            $ubf->show();
+                        } else {
+                            $bf = new BlockForm($this, $this->profile,
+                                                array('action' => 'showstream',
+                                                      'nickname' => $this->profile->nickname));
+                            $bf->show();
+                        }
+                        $this->elementEnd('li');
+                    }
+                }
+
+                Event::handle('EndProfilePageActionsElements', array(&$this, $this->profile));
             }
-            $this->elementEnd('li');
+
+            $this->elementEnd('ul');
+            $this->elementEnd('div');
+
+            Event::handle('EndProfilePageActionsSection', array(&$this, $this->profile));
         }
-        $this->elementEnd('ul');
-        $this->elementEnd('div');
     }
 
     function showRemoteSubscribeLink()
@@ -358,9 +450,7 @@ class ShowstreamAction extends ProfileAction
             }
         }
         else {
-            $message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and then nudge %s or post a notice to his or her attention.'),
-                                (!common_config('site','openidonly')) ? 'register' : 'openidlogin',
-                                $this->user->nickname);
+            $message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
         }
 
         $this->elementStart('div', 'guide');
@@ -371,7 +461,7 @@ class ShowstreamAction extends ProfileAction
     function showNotices()
     {
         $notice = empty($this->tag)
-            ? $this->user->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1)
+          ? $this->user->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1)
             : $this->user->getTaggedNotices($this->tag, ($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1, 0, 0, null);
 
         $pnl = new ProfileNoticeList($notice, $this);
@@ -393,16 +483,14 @@ class ShowstreamAction extends ProfileAction
     {
         if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
             $m = sprintf(_('**%s** has an account on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
-                 'based on the Free Software [StatusNet](http://status.net/) tool. ' .
-                 '[Join now](%%%%action.%s%%%%) to follow **%s**\'s notices and many more! ([Read more](%%%%doc.help%%%%))'),
-                 $this->user->nickname,
-                 (!common_config('site','openidonly')) ? 'register' : 'openidlogin',
-                 $this->user->nickname);
+                           'based on the Free Software [StatusNet](http://status.net/) tool. ' .
+                           '[Join now](%%%%action.register%%%%) to follow **%s**\'s notices and many more! ([Read more](%%%%doc.help%%%%))'),
+                         $this->user->nickname, $this->user->nickname);
         } else {
             $m = sprintf(_('**%s** has an account on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
-                 'based on the Free Software [StatusNet](http://status.net/) tool. '),
-                 $this->user->nickname, $this->user->nickname);
-       }
+                           'based on the Free Software [StatusNet](http://status.net/) tool. '),
+                         $this->user->nickname, $this->user->nickname);
+        }
         $this->elementStart('div', array('id' => 'anon_notice'));
         $this->raw(common_markup_to_html($m));
         $this->elementEnd('div');
index f7d08d9d0b03d35493368982a18214c831af709c..df9ec996159abf64dfb4c04b26bdfb9b7c13e359 100644 (file)
@@ -111,9 +111,7 @@ class SubscribersAction extends GalleryAction
             }
         }
         else {
-            $message = sprintf(_('%s has no subscribers. Why not [register an account](%%%%action.%s%%%%) and be the first?'),
-                               $this->user->nickname,
-                               (!common_config('site','openidonly')) ? 'register' : 'openidlogin');
+            $message = sprintf(_('%s has no subscribers. Why not [register an account](%%%%action.register%%%%) and be the first?'), $this->user->nickname);
         }
 
         $this->elementStart('div', 'guide');
diff --git a/actions/twitapiaccount.php b/actions/twitapiaccount.php
deleted file mode 100644 (file)
index 93c8443..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-<?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.'/lib/twitterapi.php');
-
-class TwitapiaccountAction extends TwitterapiAction
-{
-    function verify_credentials($args, $apidata)
-    {
-        parent::handle($args);
-
-        switch ($apidata['content-type']) {
-        case 'xml':
-        case 'json':
-            $action_obj = new TwitapiusersAction();
-            $action_obj->prepare($args);
-            call_user_func(array($action_obj, 'show'), $args, $apidata);
-            break;
-        default:
-            header('Content-Type: text/html; charset=utf-8');
-            print 'Authorized';
-        }
-    }
-
-   function end_session($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
-    }
-
-    function update_location($args, $apidata)
-    {
-        parent::handle($args);
-
-        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
-            $this->clientError(_('This method requires a POST.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        $location = trim($this->arg('location'));
-
-        if (!is_null($location) && mb_strlen($location) > 255) {
-
-            // XXX: But Twitter just truncates and runs with it. -- Zach
-            $this->clientError(_('That\'s too long. Max notice size is 255 chars.'),
-                406, $apidate['content-type']);
-            return;
-        }
-
-        $user = $apidata['user']; // Always the auth user
-        $profile = $user->getProfile();
-
-        $orig_profile = clone($profile);
-        $profile->location = $location;
-
-        $result = $profile->update($orig_profile);
-
-        if (empty($result)) {
-            common_log_db_error($profile, 'UPDATE', __FILE__);
-            $this->serverError(_('Couldn\'t save profile.'));
-            return;
-        }
-
-        common_broadcast_profile($profile);
-        $type = $apidata['content-type'];
-
-        $this->init_document($type);
-        $this->show_profile($profile, $type);
-        $this->end_document($type);
-    }
-
-
-    function update_delivery_device($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
-    }
-
-    // We don't have a rate limit, but some clients check this method.
-    // It always returns the same thing: 100 hit left.
-    function rate_limit_status($args, $apidata)
-    {
-        parent::handle($args);
-
-        $type = $apidata['content-type'];
-        $this->init_document($type);
-
-        if ($apidata['content-type'] == 'xml') {
-            $this->elementStart('hash');
-            $this->element('remaining-hits', array('type' => 'integer'), 100);
-            $this->element('hourly-limit', array('type' => 'integer'), 100);
-            $this->element('reset-time', array('type' => 'datetime'), null);
-            $this->element('reset_time_in_seconds', array('type' => 'integer'), 0);
-            $this->elementEnd('hash');
-        } elseif ($apidata['content-type'] == 'json') {
-
-            $out = array('reset_time_in_seconds' => 0,
-                         'remaining_hits' => 100,
-                         'hourly_limit' => 100,
-                         'reset_time' => '');
-            print json_encode($out);
-        }
-
-        $this->end_document($type);
-    }
-}
diff --git a/actions/twitapiblocks.php b/actions/twitapiblocks.php
deleted file mode 100644 (file)
index ed17946..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-<?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.'/lib/twitterapi.php');
-
-class TwitapiblocksAction extends TwitterapiAction
-{
-
-    function create($args, $apidata)
-    {
-
-        parent::handle($args);
-
-        $blockee = $this->get_user($apidata['api_arg'], $apidata);
-
-        if (empty($blockee)) {
-            $this->clientError('Not Found', 404, $apidata['content-type']);
-            return;
-        }
-
-        $user = $apidata['user']; // Always the auth user
-
-        if ($user->hasBlocked($blockee) || $user->block($blockee)) {
-            $type = $apidata['content-type'];
-            $this->init_document($type);
-            $this->show_profile($blockee, $type);
-            $this->end_document($type);
-        } else {
-            $this->serverError(_('Block user failed.'));
-        }
-    }
-
-    function destroy($args, $apidata)
-    {
-        parent::handle($args);
-        $blockee = $this->get_user($apidata['api_arg'], $apidata);
-
-        if (empty($blockee)) {
-            $this->clientError('Not Found', 404, $apidata['content-type']);
-            return;
-        }
-
-        $user = $apidata['user'];
-
-        if (!$user->hasBlocked($blockee) || $user->unblock($blockee)) {
-            $type = $apidata['content-type'];
-            $this->init_document($type);
-            $this->show_profile($blockee, $type);
-            $this->end_document($type);
-        } else {
-            $this->serverError(_('Unblock user failed.'));
-        }
-    }
-}
\ No newline at end of file
diff --git a/actions/twitapidirect_messages.php b/actions/twitapidirect_messages.php
deleted file mode 100644 (file)
index dbe5580..0000000
+++ /dev/null
@@ -1,304 +0,0 @@
-<?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.'/lib/twitterapi.php');
-
-class Twitapidirect_messagesAction extends TwitterapiAction
-{
-
-    function direct_messages($args, $apidata)
-    {
-        parent::handle($args);
-        return $this->show_messages($args, $apidata, 'received');
-    }
-
-    function sent($args, $apidata)
-    {
-        parent::handle($args);
-        return $this->show_messages($args, $apidata, 'sent');
-    }
-
-    function show_messages($args, $apidata, $type)
-    {
-        $user = $apidata['user']; // Always the auth user
-
-        $message  = new Message();
-        $title    = null;
-        $subtitle = null;
-        $link     = null;
-        $server   = common_root_url();
-
-        if ($type == 'received') {
-            $message->to_profile = $user->id;
-            $title = sprintf(_("Direct messages to %s"), $user->nickname);
-            $subtitle = sprintf(_("All the direct messages sent to %s"),
-                $user->nickname);
-            $link = $server . $user->nickname . '/inbox';
-        } else {
-            $message->from_profile = $user->id;
-            $title = _('Direct Messages You\'ve Sent');
-            $subtitle = sprintf(_("All the direct messages sent from %s"),
-                $user->nickname);
-            $link = $server . $user->nickname . '/outbox';
-        }
-
-        $page     = (int)$this->arg('page', 1);
-        $count    = (int)$this->arg('count', 20);
-        $max_id   = (int)$this->arg('max_id', 0);
-        $since_id = (int)$this->arg('since_id', 0);
-        $since    = $this->arg('since');
-
-        if ($max_id) {
-            $message->whereAdd("id <= $max_id");
-        }
-
-        if ($since_id) {
-            $message->whereAdd("id > $since_id");
-        }
-
-        if ($since) {
-            $d = date('Y-m-d H:i:s', $since);
-            $message->whereAdd("created > '$d'");
-        }
-
-        $message->orderBy('created DESC, id DESC');
-        $message->limit((($page-1)*$count), $count);
-        $message->find();
-
-        switch($apidata['content-type']) {
-        case 'xml':
-            $this->show_xml_dmsgs($message);
-            break;
-        case 'rss':
-            $this->show_rss_dmsgs($message, $title, $link, $subtitle);
-            break;
-        case 'atom':
-            $selfuri = common_root_url() . 'api/direct_messages';
-            $selfuri .= ($type == 'received') ? '.atom' : '/sent.atom';
-            $taguribase = common_config('integration', 'taguri');
-
-            if ($type == 'sent') {
-                $id = "tag:$taguribase:SentDirectMessages:" . $user->id;
-            } else {
-                $id = "tag:$taguribase:DirectMessages:" . $user->id;
-            }
-
-            $this->show_atom_dmsgs($message, $title, $link, $subtitle,
-                $selfuri, $id);
-            break;
-        case 'json':
-            $this->show_json_dmsgs($message);
-            break;
-        default:
-            $this->clientError(_('API method not found!'), $code = 404);
-        }
-
-    }
-
-    // had to change this from "new" to "create" to avoid PHP reserved word
-    function create($args, $apidata)
-    {
-        parent::handle($args);
-
-        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
-            $this->clientError(_('This method requires a POST.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        $user = $apidata['user'];
-        $source = $this->trimmed('source'); // Not supported by Twitter.
-
-        $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api');
-        if (empty($source) || in_array($source, $reserved_sources)) {
-            $source = 'api';
-        }
-
-        $content = $this->trimmed('text');
-
-        if (empty($content)) {
-            $this->clientError(_('No message text!'),
-                $code = 406, $apidata['content-type']);
-        } else {
-            $content_shortened = common_shorten_links($content);
-            if (mb_strlen($content_shortened) > 140) {
-                $this->clientError(_('That\'s too long. Max message size is 140 chars.'),
-                    $code = 406, $apidata['content-type']);
-                return;
-            }
-        }
-
-        $other = $this->get_user($this->trimmed('user'));
-
-        if (empty($other)) {
-            $this->clientError(_('Recipient user not found.'),
-                $code = 403, $apidata['content-type']);
-            return;
-        } else if (!$user->mutuallySubscribed($other)) {
-            $this->clientError(_('Can\'t send direct messages to users who aren\'t your friend.'),
-                $code = 403, $apidata['content-type']);
-            return;
-        } else if ($user->id == $other->id) {
-            // Sending msgs to yourself is allowed by Twitter
-            $this->clientError(_('Don\'t send a message to yourself; just say it to yourself quietly instead.'),
-                $code = 403, $apidata['content-type']);
-            return;
-        }
-
-        $message = Message::saveNew($user->id, $other->id,
-            html_entity_decode($content, ENT_NOQUOTES, 'UTF-8'), $source);
-
-        if (is_string($message)) {
-            $this->serverError($message);
-            return;
-        }
-
-        $this->notify($user, $other, $message);
-
-        if ($apidata['content-type'] == 'xml') {
-            $this->show_single_xml_dmsg($message);
-        } elseif ($apidata['content-type'] == 'json') {
-            $this->show_single_json_dmsg($message);
-        }
-
-    }
-
-    function destroy($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
-    }
-
-    function show_xml_dmsgs($message)
-    {
-
-        $this->init_document('xml');
-        $this->elementStart('direct-messages', array('type' => 'array'));
-
-        if (is_array($message)) {
-            foreach ($message as $m) {
-                $twitter_dm = $this->twitter_dmsg_array($m);
-                $this->show_twitter_xml_dmsg($twitter_dm);
-            }
-        } else {
-            while ($message->fetch()) {
-                $twitter_dm = $this->twitter_dmsg_array($message);
-                $this->show_twitter_xml_dmsg($twitter_dm);
-            }
-        }
-
-        $this->elementEnd('direct-messages');
-        $this->end_document('xml');
-
-    }
-
-    function show_json_dmsgs($message)
-    {
-
-        $this->init_document('json');
-
-        $dmsgs = array();
-
-        if (is_array($message)) {
-            foreach ($message as $m) {
-                $twitter_dm = $this->twitter_dmsg_array($m);
-                array_push($dmsgs, $twitter_dm);
-            }
-        } else {
-            while ($message->fetch()) {
-                $twitter_dm = $this->twitter_dmsg_array($message);
-                array_push($dmsgs, $twitter_dm);
-            }
-        }
-
-        $this->show_json_objects($dmsgs);
-        $this->end_document('json');
-
-    }
-
-    function show_rss_dmsgs($message, $title, $link, $subtitle)
-    {
-
-        $this->init_document('rss');
-
-        $this->elementStart('channel');
-        $this->element('title', null, $title);
-
-        $this->element('link', null, $link);
-        $this->element('description', null, $subtitle);
-        $this->element('language', null, 'en-us');
-        $this->element('ttl', null, '40');
-
-        if (is_array($message)) {
-            foreach ($message as $m) {
-                $entry = $this->twitter_rss_dmsg_array($m);
-                $this->show_twitter_rss_item($entry);
-            }
-        } else {
-            while ($message->fetch()) {
-                $entry = $this->twitter_rss_dmsg_array($message);
-                $this->show_twitter_rss_item($entry);
-            }
-        }
-
-        $this->elementEnd('channel');
-        $this->end_twitter_rss();
-
-    }
-
-    function show_atom_dmsgs($message, $title, $link, $subtitle, $selfuri, $id)
-    {
-
-        $this->init_document('atom');
-
-        $this->element('title', null, $title);
-        $this->element('id', null, $id);
-        $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
-        $this->element('link', array('href' => $selfuri, 'rel' => 'self',
-            'type' => 'application/atom+xml'), null);
-        $this->element('updated', null, common_date_iso8601('now'));
-        $this->element('subtitle', null, $subtitle);
-
-        if (is_array($message)) {
-            foreach ($message as $m) {
-                $entry = $this->twitter_rss_dmsg_array($m);
-                $this->show_twitter_atom_entry($entry);
-            }
-        } else {
-            while ($message->fetch()) {
-                $entry = $this->twitter_rss_dmsg_array($message);
-                $this->show_twitter_atom_entry($entry);
-            }
-        }
-
-        $this->end_document('atom');
-    }
-
-    // swiped from MessageAction. Should it be place in util.php?
-    function notify($from, $to, $message)
-    {
-        mail_notify_message($message, $from, $to);
-        # XXX: Jabber, SMS notifications... probably queued
-    }
-
-}
diff --git a/actions/twitapifavorites.php b/actions/twitapifavorites.php
deleted file mode 100644 (file)
index f8943fe..0000000
+++ /dev/null
@@ -1,216 +0,0 @@
-<?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.'/lib/twitterapi.php');
-
-class TwitapifavoritesAction extends TwitterapiAction
-{
-
-    function favorites($args, $apidata)
-    {
-        parent::handle($args);
-
-        $this->auth_user = $apidata['user'];
-        $user = $this->get_user($apidata['api_arg'], $apidata);
-
-        if (empty($user)) {
-        if ($apidata['content-type'] == 'xml') {
-            $this->show_single_xml_status($notice);
-        } elseif ($apidata['content-type'] == 'json') {
-            $this->show_single_json_status($notice);
-        }
-            $this->clientError('Not Found', 404, $apidata['content-type']);
-            return;
-        }
-
-        $profile = $user->getProfile();
-
-        $sitename   = common_config('site', 'name');
-        $title      = sprintf(_('%s / Favorites from %s'), $sitename,
-            $user->nickname);
-        $taguribase = common_config('integration', 'taguri');
-        $id         = "tag:$taguribase:Favorites:".$user->id;
-        $link       = common_local_url('favorites',
-            array('nickname' => $user->nickname));
-        $subtitle   = sprintf(_('%s updates favorited by %s / %s.'), $sitename,
-            $profile->getBestName(), $user->nickname);
-
-        $page     = (int)$this->arg('page', 1);
-        $count    = (int)$this->arg('count', 20);
-        $max_id   = (int)$this->arg('max_id', 0);
-        $since_id = (int)$this->arg('since_id', 0);
-        $since    = $this->arg('since');
-
-        if (!empty($this->auth_user) && $this->auth_user->id == $user->id) {
-            $notice = $user->favoriteNotices(($page-1)*$count, $count, true);
-        } else {
-            $notice = $user->favoriteNotices(($page-1)*$count, $count, false);
-        }
-
-        switch($apidata['content-type']) {
-        case 'xml':
-            $this->show_xml_timeline($notice);
-            break;
-        case 'rss':
-            $this->show_rss_timeline($notice, $title, $link, $subtitle);
-            break;
-        case 'atom':
-            if (isset($apidata['api_arg'])) {
-                 $selfuri = $selfuri = common_root_url() .
-                     'api/favorites/' . $apidata['api_arg'] . '.atom';
-            } else {
-                 $selfuri = $selfuri = common_root_url() .
-                  'api/favorites.atom';
-            }
-            $this->show_atom_timeline($notice, $title, $id, $link,
-                $subtitle, null, $selfuri);
-            break;
-        case 'json':
-            $this->show_json_timeline($notice);
-            break;
-        default:
-            $this->clientError(_('API method not found!'), $code = 404);
-        }
-
-    }
-
-    function create($args, $apidata)
-    {
-        parent::handle($args);
-
-        // Check for RESTfulness
-        if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
-            $this->clientError(_('This method requires a POST or DELETE.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        $user      = $apidata['user']; // Always the auth user
-        $notice_id = $apidata['api_arg'];
-        $notice    = Notice::staticGet($notice_id);
-
-        if (empty($notice)) {
-            $this->clientError(_('No status found with that ID.'),
-                404, $apidata['content-type']);
-            return;
-        }
-
-        // XXX: Twitter lets you fave things repeatedly via api.
-        if ($user->hasFave($notice)) {
-            $this->clientError(_('This status is already a favorite!'),
-                403, $apidata['content-type']);
-            return;
-        }
-
-        $fave = Fave::addNew($user, $notice);
-
-        if (empty($fave)) {
-            $this->clientError(_('Could not create favorite.'));
-            return;
-        }
-
-        $this->notify($fave, $notice, $user);
-        $user->blowFavesCache();
-
-        if ($apidata['content-type'] == 'xml') {
-            $this->show_single_xml_status($notice);
-        } elseif ($apidata['content-type'] == 'json') {
-            $this->show_single_json_status($notice);
-        }
-
-    }
-
-    function destroy($args, $apidata)
-    {
-        parent::handle($args);
-
-        // Check for RESTfulness
-        if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
-            $this->clientError(_('This method requires a POST or DELETE.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        $user      = $apidata['user']; // Always the auth user
-        $notice_id = $apidata['api_arg'];
-        $notice    = Notice::staticGet($notice_id);
-
-        if (empty($notice)) {
-            $this->clientError(_('No status found with that ID.'),
-                404, $apidata['content-type']);
-            return;
-        }
-
-        $fave            = new Fave();
-        $fave->user_id   = $this->id;
-        $fave->notice_id = $notice->id;
-
-        if (!$fave->find(true)) {
-            $this->clientError(_('That status is not a favorite!'),
-                403, $apidata['content-type']);
-            return;
-        }
-
-        $result = $fave->delete();
-
-        if (!$result) {
-            common_log_db_error($fave, 'DELETE', __FILE__);
-            $this->clientError(_('Could not delete favorite.'), 404);
-            return;
-        }
-
-        $user->blowFavesCache();
-
-        if ($apidata['content-type'] == 'xml') {
-            $this->show_single_xml_status($notice);
-        } elseif ($apidata['content-type'] == 'json') {
-            $this->show_single_json_status($notice);
-        }
-
-    }
-
-    // XXX: these two funcs swiped from faves.
-    // Maybe put in util.php, or some common base class?
-
-    function notify($fave, $notice, $user)
-    {
-        $other = User::staticGet('id', $notice->profile_id);
-        if ($other && $other->id != $user->id) {
-            if ($other->email && $other->emailnotifyfav) {
-                mail_notify_fave($other, $user, $notice);
-            }
-            # XXX: notify by IM
-            # XXX: notify by SMS
-        }
-    }
-}
diff --git a/actions/twitapifriendships.php b/actions/twitapifriendships.php
deleted file mode 100644 (file)
index eea8945..0000000
+++ /dev/null
@@ -1,250 +0,0 @@
-<?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.'/lib/twitterapi.php');
-
-class TwitapifriendshipsAction extends TwitterapiAction
-{
-
-    function create($args, $apidata)
-    {
-        parent::handle($args);
-
-        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
-            $this->clientError(_('This method requires a POST.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        $id    = $apidata['api_arg'];
-        $other = $this->get_user($id);
-
-        if (empty($other)) {
-            $this->clientError(_('Could not follow user: User not found.'),
-                403, $apidata['content-type']);
-            return;
-        }
-
-        $user = $apidata['user'];
-
-        if ($user->isSubscribed($other)) {
-            $errmsg = sprintf(_('Could not follow user: %s is already on your list.'),
-                $other->nickname);
-            $this->clientError($errmsg, 403, $apidata['content-type']);
-            return;
-        }
-
-        $sub = new Subscription();
-
-        $sub->query('BEGIN');
-
-        $sub->subscriber = $user->id;
-        $sub->subscribed = $other->id;
-        $sub->created = DB_DataObject_Cast::dateTime(); # current time
-
-        $result = $sub->insert();
-
-        if (empty($result)) {
-            $errmsg = sprintf(_('Could not follow user: %s is already on your list.'),
-                $other->nickname);
-            $this->clientError($errmsg, 400, $apidata['content-type']);
-            return;
-        }
-
-        $sub->query('COMMIT');
-
-        mail_subscribe_notify($other, $user);
-
-        $type = $apidata['content-type'];
-        $this->init_document($type);
-        $this->show_profile($other, $type);
-        $this->end_document($type);
-
-    }
-
-    function destroy($args, $apidata)
-    {
-        parent::handle($args);
-
-        if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
-            $this->clientError(_('This method requires a POST or DELETE.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        $id = $apidata['api_arg'];
-
-        # We can't subscribe to a remote person, but we can unsub
-
-        $other = $this->get_profile($id);
-        $user = $apidata['user']; // Alwyas the auth user
-
-       if ($user->id == $other->id) {
-           $this->clientError(_("You cannot unfollow yourself!"),
-                              403, $apidata['content-type']);
-           return;
-       }
-
-        $sub = new Subscription();
-        $sub->subscriber = $user->id;
-        $sub->subscribed = $other->id;
-
-        if ($sub->find(true)) {
-            $sub->query('BEGIN');
-            $sub->delete();
-            $sub->query('COMMIT');
-        } else {
-            $this->clientError(_('You are not friends with the specified user.'),
-                403, $apidata['content-type']);
-            return;
-        }
-
-        $type = $apidata['content-type'];
-        $this->init_document($type);
-        $this->show_profile($other, $type);
-        $this->end_document($type);
-
-    }
-
-    function exists($args, $apidata)
-    {
-        parent::handle($args);
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        $user_a_id = $this->trimmed('user_a');
-        $user_b_id = $this->trimmed('user_b');
-
-        $user_a = $this->get_user($user_a_id);
-        $user_b = $this->get_user($user_b_id);
-
-        if (empty($user_a) || empty($user_b)) {
-            $this->clientError(_('Two user ids or screen_names must be supplied.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        $result = $user_a->isSubscribed($user_b);
-
-        switch ($apidata['content-type']) {
-         case 'xml':
-            $this->init_document('xml');
-            $this->element('friends', null, $result);
-            $this->end_document('xml');
-            break;
-         case 'json':
-            $this->init_document('json');
-            print json_encode($result);
-            $this->end_document('json');
-            break;
-         default:
-            break;
-        }
-
-    }
-
-    function show($args, $apidata)
-    {
-        parent::handle($args);
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        $source_id          = (int)$this->trimmed('source_id');
-        $source_screen_name = $this->trimmed('source_screen_name');
-
-        // If the source is not specified for an unauthenticated request,
-        // the method will return an HTTP 403.
-
-        if (empty($source_id) && empty($source_screen_name)) {
-            if (empty($apidata['user'])) {
-                $this->clientError(_('Could not determine source user.'),
-                        $code = 403);
-                return;
-            }
-        }
-
-        $source = null;
-
-        if (!empty($source_id)) {
-            $source = User::staticGet($source_id);
-        } elseif (!empty($source_screen_name)) {
-            $source = User::staticGet('nickname', $source_screen_name);
-        } else {
-            $source = $apidata['user'];
-        }
-
-        // If a source or target is specified but does not exist,
-        // the method will return an HTTP 404.
-
-        if (empty($source)) {
-            $this->clientError(_('Could not determine source user.'),
-                $code = 404);
-            return;
-        }
-
-        $target_id          = (int)$this->trimmed('target_id');
-        $target_screen_name = $this->trimmed('target_screen_name');
-
-        $target = null;
-
-        if (!empty($target_id)) {
-            $target = User::staticGet($target_id);
-        } elseif (!empty($target_screen_name)) {
-            $target = User::staticGet('nickname', $target_screen_name);
-        } else {
-            $this->clientError(_('Target user not specified.'),
-                $code = 403);
-            return;
-        }
-
-        if (empty($target)) {
-            $this->clientError(_('Could not find target user.'),
-                $code = 404);
-            return;
-        }
-
-        $result = $this->twitter_relationship_array($source, $target);
-
-        switch ($apidata['content-type']) {
-        case 'xml':
-            $this->init_document('xml');
-            $this->show_twitter_xml_relationship($result[relationship]);
-            $this->end_document('xml');
-            break;
-        case 'json':
-            $this->init_document('json');
-            print json_encode($result);
-            $this->end_document('json');
-            break;
-        default:
-            break;
-        }
-    }
-
-}
diff --git a/actions/twitapigroups.php b/actions/twitapigroups.php
deleted file mode 100644 (file)
index 4deb1b7..0000000
+++ /dev/null
@@ -1,329 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * StatusNet extensions to the Twitter-like API for groups
- *
- * 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  Twitter
- * @package   StatusNet
- * @author    Craig Andrews <candrews@integralblue.com>
- * @author    Zach Copley <zach@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);
-}
-
-require_once INSTALLDIR.'/lib/twitterapi.php';
-
-/**
- * Group-specific API methods
- *
- * This class handles StatusNet group API methods.
- *
- * @category  Twitter
- * @package   StatusNet
- * @author    Craig Andrews <candrews@integralblue.com>
- * @author    Zach Copley <zach@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/
- */
-
- class TwitapigroupsAction extends TwitterapiAction
- {
-
-     function list_groups($args, $apidata)
-     {
-         parent::handle($args);
-         
-         common_debug("in groups api action");
-         
-         $this->auth_user = $apidata['user'];
-         $user = $this->get_user($apidata['api_arg'], $apidata);
-
-         if (empty($user)) {
-             $this->clientError('Not Found', 404, $apidata['content-type']);
-             return;
-         }
-
-         $page     = (int)$this->arg('page', 1);
-         $count    = (int)$this->arg('count', 20);
-         $max_id   = (int)$this->arg('max_id', 0);
-         $since_id = (int)$this->arg('since_id', 0);
-         $since    = $this->arg('since');
-         $group = $user->getGroups(($page-1)*$count,
-             $count, $since_id, $max_id, $since);
-
-         $sitename   = common_config('site', 'name');
-         $title      = sprintf(_("%s's groups"), $user->nickname);
-         $taguribase = common_config('integration', 'taguri');
-         $id         = "tag:$taguribase:Groups";
-         $link       = common_root_url();
-         $subtitle   = sprintf(_("groups %s is a member of on %s"), $user->nickname, $sitename);
-
-         switch($apidata['content-type']) {
-         case 'xml':
-             $this->show_xml_groups($group);
-             break;
-         case 'rss':
-             $this->show_rss_groups($group, $title, $link, $subtitle);
-             break;
-         case 'atom':
-             $selfuri = common_root_url() . 'api/statusnet/groups/list/' . $user->id . '.atom';
-             $this->show_atom_groups($group, $title, $id, $link,
-                 $subtitle, $selfuri);
-             break;
-         case 'json':
-             $this->show_json_groups($group);
-             break;
-         default:
-             $this->clientError(_('API method not found!'), $code = 404);
-             break;
-         }
-     }
-
-     function list_all($args, $apidata)
-     {
-         parent::handle($args);
-         
-         common_debug("in groups api action");
-         
-         $page     = (int)$this->arg('page', 1);
-         $count    = (int)$this->arg('count', 20);
-         $max_id   = (int)$this->arg('max_id', 0);
-         $since_id = (int)$this->arg('since_id', 0);
-         $since    = $this->arg('since');
-
-         /*     TODO:
-         Use the $page, $count, $max_id, $since_id, and $since parameters
-         */
-         $group = new User_group();
-         $group->orderBy('created DESC');
-         $group->find();
-
-         $sitename   = common_config('site', 'name');
-         $title      = sprintf(_("%s groups"), $sitename);
-         $taguribase = common_config('integration', 'taguri');
-         $id         = "tag:$taguribase:Groups";
-         $link       = common_root_url();
-         $subtitle   = sprintf(_("groups on %s"), $sitename);
-
-         switch($apidata['content-type']) {
-         case 'xml':
-             $this->show_xml_groups($group);
-             break;
-         case 'rss':
-             $this->show_rss_groups($group, $title, $link, $subtitle);
-             break;
-         case 'atom':
-             $selfuri = common_root_url() . 'api/statusnet/groups/list_all.atom';
-             $this->show_atom_groups($group, $title, $id, $link,
-                 $subtitle, $selfuri);
-             break;
-         case 'json':
-             $this->show_json_groups($group);
-             break;
-         default:
-             $this->clientError(_('API method not found!'), $code = 404);
-             break;
-         }
-     }
-
-     function show($args, $apidata)
-     {
-         parent::handle($args);
-
-         common_debug("in groups api action");
-
-         $this->auth_user = $apidata['user'];
-         $group = $this->get_group($apidata['api_arg'], $apidata);
-
-         if (empty($group)) {
-             $this->clientError('Not Found', 404, $apidata['content-type']);
-             return;
-         }
-
-         switch($apidata['content-type']) {
-          case 'xml':
-             $this->show_single_xml_group($group);
-             break;
-          case 'json':
-             $this->show_single_json_group($group);
-             break;
-          default:
-             $this->clientError(_('API method not found!'), $code = 404);
-         }
-     }
-
-     function timeline($args, $apidata)
-     {
-         parent::handle($args);
-
-         common_debug("in groups api action");
-
-         $this->auth_user = $apidata['user'];
-         $group = $this->get_group($apidata['api_arg'], $apidata);
-
-         if (empty($group)) {
-             $this->clientError('Not Found', 404, $apidata['content-type']);
-             return;
-         }
-
-         $sitename   = common_config('site', 'name');
-         $title      = sprintf(_("%s timeline"), $group->nickname);
-         $taguribase = common_config('integration', 'taguri');
-         $id         = "tag:$taguribase:GroupTimeline:".$group->id;
-         $link       = common_local_url('showgroup',
-             array('nickname' => $group->nickname));
-         $subtitle   = sprintf(_('Updates from %1$s on %2$s!'),
-             $group->nickname, $sitename);
-
-         $page     = (int)$this->arg('page', 1);
-         $count    = (int)$this->arg('count', 20);
-         $max_id   = (int)$this->arg('max_id', 0);
-         $since_id = (int)$this->arg('since_id', 0);
-         $since    = $this->arg('since');
-
-         $notice = $group->getNotices(($page-1)*$count,
-             $count, $since_id, $max_id, $since);
-
-         switch($apidata['content-type']) {
-          case 'xml':
-             $this->show_xml_timeline($notice);
-             break;
-          case 'rss':
-             $this->show_rss_timeline($notice, $title, $link, $subtitle);
-             break;
-          case 'atom':
-             if (isset($apidata['api_arg'])) {
-                 $selfuri = common_root_url() .
-                     'api/statusnet/groups/timeline/' .
-                         $apidata['api_arg'] . '.atom';
-             } else {
-                 $selfuri = common_root_url() .
-                  'api/statusnet/groups/timeline.atom';
-             }
-             $this->show_atom_timeline($notice, $title, $id, $link,
-                 $subtitle, null, $selfuri);
-             break;
-          case 'json':
-             $this->show_json_timeline($notice);
-             break;
-          default:
-             $this->clientError(_('API method not found!'), $code = 404);
-         }
-     }
-
-     function membership($args, $apidata)
-     {
-         parent::handle($args);
-
-         common_debug("in groups api action");
-
-         $this->auth_user = $apidata['user'];
-         $group = $this->get_group($apidata['api_arg'], $apidata);
-         
-         if (empty($group)) {
-             $this->clientError('Not Found', 404, $apidata['content-type']);
-             return;
-         }
-
-         $sitename   = common_config('site', 'name');
-         $title      = sprintf(_("Members of %s group"), $group->nickname);
-         $taguribase = common_config('integration', 'taguri');
-         $id         = "tag:$taguribase:GroupMembership:".$group->id;
-         $link       = common_local_url('showgroup',
-             array('nickname' => $group->nickname));
-         $subtitle   = sprintf(_('Members of %1$s on %2$s'),
-             $group->nickname, $sitename);
-
-         $page     = (int)$this->arg('page', 1);
-         $count    = (int)$this->arg('count', 20);
-         $max_id   = (int)$this->arg('max_id', 0);
-         $since_id = (int)$this->arg('since_id', 0);
-         $since    = $this->arg('since');
-
-         $member = $group->getMembers(($page-1)*$count,
-             $count, $since_id, $max_id, $since);
-
-         switch($apidata['content-type']) {
-          case 'xml':
-             $this->show_twitter_xml_users($member);
-             break;
-          //TODO implement the RSS and ATOM content types
-          /*case 'rss':
-             $this->show_rss_users($member, $title, $link, $subtitle);
-             break;*/
-          /*case 'atom':
-             if (isset($apidata['api_arg'])) {
-                 $selfuri = common_root_url() .
-                     'api/statusnet/groups/membership/' .
-                         $apidata['api_arg'] . '.atom';
-             } else {
-                 $selfuri = common_root_url() .
-                  'api/statusnet/groups/membership.atom';
-             }
-             $this->show_atom_users($member, $title, $id, $link,
-                 $subtitle, null, $selfuri);
-             break;*/
-          case 'json':
-             $this->show_json_users($member);
-             break;
-          default:
-             $this->clientError(_('API method not found!'), $code = 404);
-         }
-     }
-
-     function is_member($args, $apidata)
-     {
-         parent::handle($args);
-
-         common_debug("in groups api action");
-
-         $this->auth_user = $apidata['user'];
-         $group = User_group::staticGet($args['group_id']);
-         if(! $group){
-            $this->clientError(_('Group not found'), $code = 500);
-         }
-         $user = User::staticGet('id', $args['user_id']);
-         if(! $user){
-            $this->clientError(_('User not found'), $code = 500);
-         }
-         
-         $is_member=$user->isMember($group);
-
-         switch($apidata['content-type']) {
-          case 'xml':
-             $this->init_document('xml');
-             $this->element('is_member', null, $is_member);
-             $this->end_document('xml');
-             break;
-          case 'json':
-             $this->init_document('json');
-             $this->show_json_objects(array('is_member'=>$is_member));
-             $this->end_document('json');
-             break;
-          default:
-             $this->clientError(_('API method not found!'), $code = 404);
-         }
-     }
-}
diff --git a/actions/twitapihelp.php b/actions/twitapihelp.php
deleted file mode 100644 (file)
index 8138162..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-<?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.'/lib/twitterapi.php');
-
-class TwitapihelpAction extends TwitterapiAction
-{
-
-    /* Returns the string "ok" in the requested format with a 200 OK HTTP status code.
-     * URL:http://identi.ca/api/help/test.format
-     * Formats: xml, json
-     */
-    function test($args, $apidata)
-    {
-        parent::handle($args);
-
-        if ($apidata['content-type'] == 'xml') {
-            $this->init_document('xml');
-            $this->element('ok', null, 'true');
-            $this->end_document('xml');
-        } elseif ($apidata['content-type'] == 'json') {
-            $this->init_document('json');
-            print '"ok"';
-            $this->end_document('json');
-        } else {
-            $this->clientError(_('API method not found!'), $code=404);
-        }
-
-    }
-
-    function downtime_schedule($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
-    }
-
-}
\ No newline at end of file
diff --git a/actions/twitapinotifications.php b/actions/twitapinotifications.php
deleted file mode 100644 (file)
index 0653e69..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?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.'/lib/twitterapi.php');
-
-# This naming convention looks real sick
-class TwitapinotificationsAction extends TwitterapiAction
-{
-
-    function follow($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
-    }
-
-    function leave($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
-    }
-
-}
\ No newline at end of file
index 2f587d604ffe5016258336183ffdbded64f61af9..7d618c471fe00dbc946a682b18383799176b73b4 100644 (file)
@@ -31,7 +31,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
     exit(1);
 }
 
-require_once INSTALLDIR.'/lib/twitterapi.php';
+require_once INSTALLDIR.'/lib/api.php';
 
 /**
  * Action for outputting search results in Twitter compatible Atom
@@ -46,10 +46,10 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link     http://status.net/
  *
- * @see      TwitterapiAction
+ * @see      ApiAction
  */
 
-class TwitapisearchatomAction extends TwitterapiAction
+class TwitapisearchatomAction extends ApiAction
 {
 
     var $cnt;
@@ -340,7 +340,7 @@ class TwitapisearchatomAction extends TwitterapiAction
         // TODO: Here is where we'd put in a link to an atom feed for threads
 
         $this->element("twitter:source", null,
-            htmlentities($this->source_link($notice->source)));
+            htmlentities($this->sourceLink($notice->source)));
 
         $this->elementStart('author');
 
index c628ee624a2a3498d7ff4a24cf329f6663a127f8..c7fa741a069477e797a89c55fb539f829c6b290f 100644 (file)
@@ -31,7 +31,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
     exit(1);
 }
 
-require_once INSTALLDIR.'/lib/twitterapi.php';
+require_once INSTALLDIR.'/lib/api.php';
 require_once INSTALLDIR.'/lib/jsonsearchresultslist.php';
 
 /**
@@ -42,10 +42,10 @@ require_once INSTALLDIR.'/lib/jsonsearchresultslist.php';
  * @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/
- * @see      TwitterapiAction
+ * @see      ApiAction
  */
 
-class TwitapisearchjsonAction extends TwitterapiAction
+class TwitapisearchjsonAction extends ApiAction
 {
     var $query;
     var $lang;
@@ -134,9 +134,9 @@ class TwitapisearchjsonAction extends TwitterapiAction
 
         $results = new JSONSearchResultsList($notice, $q, $this->rpp, $this->page);
 
-        $this->init_document('json');
+        $this->initDocument('json');
         $results->show();
-        $this->end_document('json');
+        $this->endDocument('json');
     }
 
     /**
diff --git a/actions/twitapistatuses.php b/actions/twitapistatuses.php
deleted file mode 100644 (file)
index 360dff2..0000000
+++ /dev/null
@@ -1,604 +0,0 @@
-<?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.'/lib/twitterapi.php');
-
-class TwitapistatusesAction extends TwitterapiAction
-{
-
-    function public_timeline($args, $apidata)
-    {
-        // XXX: To really live up to the spec we need to build a list
-        // of notices by users who have custom avatars, so fix this SQL -- Zach
-
-        parent::handle($args);
-
-        $sitename   = common_config('site', 'name');
-        $title      = sprintf(_("%s public timeline"), $sitename);
-        $taguribase = common_config('integration', 'taguri');
-        $id         = "tag:$taguribase:PublicTimeline";
-        $link       = common_root_url();
-        $subtitle   = sprintf(_("%s updates from everyone!"), $sitename);
-
-        $page     = (int)$this->arg('page', 1);
-        $count    = (int)$this->arg('count', 20);
-        $max_id   = (int)$this->arg('max_id', 0);
-        $since_id = (int)$this->arg('since_id', 0);
-        $since    = $this->arg('since');
-
-        $notice = Notice::publicStream(($page-1)*$count, $count, $since_id,
-            $max_id, $since);
-
-        switch($apidata['content-type']) {
-        case 'xml':
-            $this->show_xml_timeline($notice);
-            break;
-        case 'rss':
-            $this->show_rss_timeline($notice, $title, $link, $subtitle);
-            break;
-        case 'atom':
-            $selfuri = common_root_url() . 'api/statuses/public_timeline.atom';
-            $this->show_atom_timeline($notice, $title, $id, $link,
-                $subtitle, null, $selfuri);
-            break;
-        case 'json':
-            $this->show_json_timeline($notice);
-            break;
-        default:
-            $this->clientError(_('API method not found!'), $code = 404);
-            break;
-        }
-
-    }
-
-    function friends_timeline($args, $apidata)
-    {
-        parent::handle($args);
-
-        $this->auth_user = $apidata['user'];
-        $user = $this->get_user($apidata['api_arg'], $apidata);
-
-        if (empty($user)) {
-             $this->clientError(_('No such user!'), 404,
-             $apidata['content-type']);
-            return;
-        }
-
-        $profile    = $user->getProfile();
-        $sitename   = common_config('site', 'name');
-        $title      = sprintf(_("%s and friends"), $user->nickname);
-        $taguribase = common_config('integration', 'taguri');
-        $id         = "tag:$taguribase:FriendsTimeline:" . $user->id;
-        $link       = common_local_url('all',
-            array('nickname' => $user->nickname));
-        $subtitle   = sprintf(_('Updates from %1$s and friends on %2$s!'),
-            $user->nickname, $sitename);
-
-        $page     = (int)$this->arg('page', 1);
-        $count    = (int)$this->arg('count', 20);
-        $max_id   = (int)$this->arg('max_id', 0);
-        $since_id = (int)$this->arg('since_id', 0);
-        $since    = $this->arg('since');
-
-        if (!empty($this->auth_user) && $this->auth_user->id == $user->id) {
-            $notice = $user->noticeInbox(($page-1)*$count,
-                $count, $since_id, $max_id, $since);
-        } else {
-            $notice = $user->noticesWithFriends(($page-1)*$count,
-                $count, $since_id, $max_id, $since);
-        }
-
-        switch($apidata['content-type']) {
-        case 'xml':
-            $this->show_xml_timeline($notice);
-            break;
-        case 'rss':
-            $this->show_rss_timeline($notice, $title, $link, $subtitle);
-            break;
-        case 'atom':
-            if (isset($apidata['api_arg'])) {
-                $selfuri = common_root_url() .
-                    'api/statuses/friends_timeline/' .
-                        $apidata['api_arg'] . '.atom';
-            } else {
-                $selfuri = common_root_url() .
-                    'api/statuses/friends_timeline.atom';
-            }
-            $this->show_atom_timeline($notice, $title, $id, $link,
-                $subtitle, null, $selfuri);
-            break;
-        case 'json':
-            $this->show_json_timeline($notice);
-            break;
-        default:
-            $this->clientError(_('API method not found!'), $code = 404);
-        }
-
-    }
-
-    function home_timeline($args, $apidata)
-    {
-        call_user_func(array($this, 'friends_timeline'), $args, $apidata);
-    }
-
-    function user_timeline($args, $apidata)
-    {
-        parent::handle($args);
-
-        $this->auth_user = $apidata['user'];
-        $user = $this->get_user($apidata['api_arg'], $apidata);
-
-        if (empty($user)) {
-            $this->clientError('Not Found', 404, $apidata['content-type']);
-            return;
-        }
-
-        $profile = $user->getProfile();
-
-        $sitename   = common_config('site', 'name');
-        $title      = sprintf(_("%s timeline"), $user->nickname);
-        $taguribase = common_config('integration', 'taguri');
-        $id         = "tag:$taguribase:UserTimeline:".$user->id;
-        $link       = common_local_url('showstream',
-            array('nickname' => $user->nickname));
-        $subtitle   = sprintf(_('Updates from %1$s on %2$s!'),
-            $user->nickname, $sitename);
-
-        # FriendFeed's SUP protocol
-        # Also added RSS and Atom feeds
-
-        $suplink = common_local_url('sup', null, null, $user->id);
-        header('X-SUP-ID: '.$suplink);
-
-        $page     = (int)$this->arg('page', 1);
-        $count    = (int)$this->arg('count', 20);
-        $max_id   = (int)$this->arg('max_id', 0);
-        $since_id = (int)$this->arg('since_id', 0);
-        $since    = $this->arg('since');
-
-        $notice = $user->getNotices(($page-1)*$count,
-            $count, $since_id, $max_id, $since);
-
-        switch($apidata['content-type']) {
-         case 'xml':
-            $this->show_xml_timeline($notice);
-            break;
-         case 'rss':
-            $this->show_rss_timeline($notice, $title, $link,
-                $subtitle, $suplink);
-            break;
-         case 'atom':
-            if (isset($apidata['api_arg'])) {
-                $selfuri = common_root_url() .
-                    'api/statuses/user_timeline/' .
-                        $apidata['api_arg'] . '.atom';
-            } else {
-                $selfuri = common_root_url() .
-                 'api/statuses/user_timeline.atom';
-            }
-            $this->show_atom_timeline($notice, $title, $id, $link,
-                $subtitle, $suplink, $selfuri);
-            break;
-         case 'json':
-            $this->show_json_timeline($notice);
-            break;
-         default:
-            $this->clientError(_('API method not found!'), $code = 404);
-        }
-
-    }
-
-    function update($args, $apidata)
-    {
-        parent::handle($args);
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
-            $this->clientError(_('This method requires a POST.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        $user = $apidata['user'];  // Always the auth user
-
-        $status = $this->trimmed('status');
-        $source = $this->trimmed('source');
-        $in_reply_to_status_id =
-            intval($this->trimmed('in_reply_to_status_id'));
-        $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api');
-
-        if (empty($source) || in_array($source, $reserved_sources)) {
-            $source = 'api';
-        }
-
-        if (empty($status)) {
-
-            // XXX: Note: In this case, Twitter simply returns '200 OK'
-            // No error is given, but the status is not posted to the
-            // user's timeline.     Seems bad.     Shouldn't we throw an
-            // errror? -- Zach
-            return;
-
-        } else {
-
-            $status_shortened = common_shorten_links($status);
-
-            if (mb_strlen($status_shortened) > 140) {
-
-                // XXX: Twitter truncates anything over 140, flags the status
-                // as "truncated." Sending this error may screw up some clients
-                // that assume Twitter will truncate for them.    Should we just
-                // truncate too? -- Zach
-                $this->clientError(_('That\'s too long. Max notice size is 140 chars.'),
-                    $code = 406, $apidata['content-type']);
-                return;
-            }
-        }
-
-        // Check for commands
-        $inter = new CommandInterpreter();
-        $cmd = $inter->handle_command($user, $status_shortened);
-
-        if ($cmd) {
-
-            if ($this->supported($cmd)) {
-                $cmd->execute(new Channel());
-            }
-
-            // cmd not supported?  Twitter just returns your latest status.
-            // And, it returns your last status whether the cmd was successful
-            // or not!
-            $n = $user->getCurrentNotice();
-            $apidata['api_arg'] = $n->id;
-        } else {
-
-            $reply_to = null;
-
-            if ($in_reply_to_status_id) {
-
-                // check whether notice actually exists
-                $reply = Notice::staticGet($in_reply_to_status_id);
-
-                if ($reply) {
-                    $reply_to = $in_reply_to_status_id;
-                } else {
-                    $this->clientError(_('Not found'), $code = 404,
-                        $apidata['content-type']);
-                    return;
-                }
-            }
-
-            $notice = Notice::saveNew($user->id,
-                html_entity_decode($status, ENT_NOQUOTES, 'UTF-8'),
-                    $source, 1, $reply_to);
-
-            if (is_string($notice)) {
-                $this->serverError($notice, 500, $apidata['content-type']);
-                return;
-            }
-
-            common_broadcast_notice($notice);
-            $apidata['api_arg'] = $notice->id;
-        }
-
-        $this->show($args, $apidata);
-    }
-
-    function mentions($args, $apidata)
-    {
-        parent::handle($args);
-
-        $user = $this->get_user($apidata['api_arg'], $apidata);
-        $this->auth_user = $apidata['user'];
-
-        if (empty($user)) {
-             $this->clientError(_('No such user!'), 404,
-                 $apidata['content-type']);
-            return;
-        }
-
-        $profile = $user->getProfile();
-
-        $sitename   = common_config('site', 'name');
-        $title      = sprintf(_('%1$s / Updates mentioning %2$s'),
-            $sitename, $user->nickname);
-        $taguribase = common_config('integration', 'taguri');
-        $id         = "tag:$taguribase:Mentions:".$user->id;
-        $link       = common_local_url('replies',
-            array('nickname' => $user->nickname));
-        $subtitle   = sprintf(_('%1$s updates that reply to updates from %2$s / %3$s.'),
-            $sitename, $user->nickname, $profile->getBestName());
-
-        $page     = (int)$this->arg('page', 1);
-        $count    = (int)$this->arg('count', 20);
-        $max_id   = (int)$this->arg('max_id', 0);
-        $since_id = (int)$this->arg('since_id', 0);
-        $since    = $this->arg('since');
-
-        $notice = $user->getReplies(($page-1)*$count,
-            $count, $since_id, $max_id, $since);
-
-        switch($apidata['content-type']) {
-        case 'xml':
-            $this->show_xml_timeline($notice);
-            break;
-        case 'rss':
-            $this->show_rss_timeline($notice, $title, $link, $subtitle);
-            break;
-        case 'atom':
-            $selfuri = common_root_url() .
-                ltrim($_SERVER['QUERY_STRING'], 'p=');
-            $this->show_atom_timeline($notice, $title, $id, $link, $subtitle,
-                null, $selfuri);
-            break;
-        case 'json':
-            $this->show_json_timeline($notice);
-            break;
-        default:
-            $this->clientError(_('API method not found!'), $code = 404);
-        }
-
-    }
-
-    function replies($args, $apidata)
-    {
-        call_user_func(array($this, 'mentions'), $args, $apidata);
-    }
-
-    function show($args, $apidata)
-    {
-        parent::handle($args);
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        // 'id' is an undocumented parameter in Twitter's API. Several
-        // clients make use of it, so we support it too.
-
-        // show.json?id=12345 takes precedence over /show/12345.json
-
-        $this->auth_user = $apidata['user'];
-        $notice_id       = $this->trimmed('id');
-
-        if (empty($notice_id)) {
-            $notice_id   = $apidata['api_arg'];
-        }
-
-        $notice          = Notice::staticGet((int)$notice_id);
-
-        if ($notice) {
-            if ($apidata['content-type'] == 'xml') {
-                $this->show_single_xml_status($notice);
-            } elseif ($apidata['content-type'] == 'json') {
-                $this->show_single_json_status($notice);
-            }
-        } else {
-            // XXX: Twitter just sets a 404 header and doens't bother
-            // to return an err msg
-            $this->clientError(_('No status with that ID found.'),
-                404, $apidata['content-type']);
-        }
-    }
-
-    function destroy($args, $apidata)
-    {
-        parent::handle($args);
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        // Check for RESTfulness
-        if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
-            // XXX: Twitter just prints the err msg, no XML / JSON.
-            $this->clientError(_('This method requires a POST or DELETE.'),
-                400, $apidata['content-type']);
-            return;
-        }
-
-        $user      = $apidata['user']; // Always the auth user
-        $notice_id = $apidata['api_arg'];
-        $notice    = Notice::staticGet($notice_id);
-
-        if (empty($notice)) {
-            $this->clientError(_('No status found with that ID.'),
-                404, $apidata['content-type']);
-            return;
-        }
-
-        if ($user->id == $notice->profile_id) {
-            $replies = new Reply;
-            $replies->get('notice_id', $notice_id);
-            $replies->delete();
-            $notice->delete();
-
-            if ($apidata['content-type'] == 'xml') {
-                $this->show_single_xml_status($notice);
-            } elseif ($apidata['content-type'] == 'json') {
-                $this->show_single_json_status($notice);
-            }
-        } else {
-            $this->clientError(_('You may not delete another user\'s status.'),
-                403, $apidata['content-type']);
-        }
-
-    }
-
-    function friends($args, $apidata)
-    {
-        parent::handle($args);
-        $includeStatuses= !(array_key_exists('lite', $args) and $args['lite']);
-        return $this->subscriptions($apidata, 'subscribed', 'subscriber', false, $includeStatuses);
-    }
-
-    function friendsIDs($args, $apidata)
-    {
-        parent::handle($args);
-        return $this->subscriptions($apidata, 'subscribed', 'subscriber', true);
-    }
-
-    function followers($args, $apidata)
-    {
-        parent::handle($args);
-        $includeStatuses= !(array_key_exists('lite', $args) and $args['lite']);
-        return $this->subscriptions($apidata, 'subscriber', 'subscribed', false, $includeStatuses);
-    }
-
-    function followersIDs($args, $apidata)
-    {
-        parent::handle($args);
-        return $this->subscriptions($apidata, 'subscriber', 'subscribed', true);
-    }
-
-    function subscriptions($apidata, $other_attr, $user_attr, $onlyIDs=false, $includeStatuses=true)
-    {
-        $this->auth_user = $apidata['user'];
-        $user = $this->get_user($apidata['api_arg'], $apidata);
-
-        if (empty($user)) {
-            $this->clientError('Not Found', 404, $apidata['content-type']);
-            return;
-        }
-
-        $profile = $user->getProfile();
-
-        $sub = new Subscription();
-        $sub->$user_attr = $profile->id;
-
-        $sub->orderBy('created DESC');
-
-        // Normally, page 100 friends at a time
-
-        if (!$onlyIDs) {
-            $page  = $this->arg('page', 1);
-            $count = $this->arg('count', 100);
-            $sub->limit(($page-1)*$count, $count);
-        } else {
-
-            // If we're just looking at IDs, return
-            // ALL of them, unless the user specifies a page,
-            // in which case, return 500 per page.
-
-            $page = $this->arg('page');
-            if (!empty($page)) {
-                if ($page < 1) {
-                    $page = 1;
-                }
-                $count = 500;
-                $sub->limit(($page-1)*$count, $count);
-            }
-        }
-
-        $others = array();
-
-        if ($sub->find()) {
-            while ($sub->fetch()) {
-                $others[] = Profile::staticGet($sub->$other_attr);
-            }
-        } else {
-            // user has no followers
-        }
-
-        $type = $apidata['content-type'];
-
-        $this->init_document($type);
-
-        if ($onlyIDs) {
-            $this->showIDs($others, $type);
-        } else {
-            $this->show_profiles($others, $type, $includeStatuses);
-        }
-
-        $this->end_document($type);
-    }
-
-    function show_profiles($profiles, $type, $includeStatuses)
-    {
-        switch ($type) {
-        case 'xml':
-            $this->elementStart('users', array('type' => 'array'));
-            foreach ($profiles as $profile) {
-                $this->show_profile($profile,$type,null,$includeStatuses);
-            }
-            $this->elementEnd('users');
-            break;
-        case 'json':
-            $arrays = array();
-            foreach ($profiles as $profile) {
-                $arrays[] = $this->twitter_user_array($profile, $includeStatuses);
-            }
-            print json_encode($arrays);
-            break;
-        default:
-            $this->clientError(_('unsupported file type'));
-        }
-    }
-
-    function showIDs($profiles, $type)
-    {
-        switch ($type) {
-        case 'xml':
-            $this->elementStart('ids');
-            foreach ($profiles as $profile) {
-                $this->element('id', null, $profile->id);
-            }
-            $this->elementEnd('ids');
-            break;
-        case 'json':
-            $ids = array();
-            foreach ($profiles as $profile) {
-                $ids[] = (int)$profile->id;
-            }
-            print json_encode($ids);
-            break;
-        default:
-            $this->clientError(_('unsupported file type'));
-        }
-    }
-
-    function featured($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
-    }
-
-    function supported($cmd)
-    {
-        $cmdlist = array('MessageCommand', 'SubCommand', 'UnsubCommand',
-            'FavCommand', 'OnCommand', 'OffCommand');
-
-        if (in_array(get_class($cmd), $cmdlist)) {
-            return true;
-        }
-
-        return false;
-    }
-
-}
diff --git a/actions/twitapistatusnet.php b/actions/twitapistatusnet.php
deleted file mode 100644 (file)
index 490f11d..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * StatusNet-only extensions to the Twitter-like API
- *
- * 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  Twitter
- * @package   StatusNet
- * @author    Evan Prodromou <evan@status.net>
- * @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/twitterapi.php';
-
-/**
- * StatusNet-specific API methods
- *
- * This class handles all /statusnet/ API methods.
- *
- * @category  Twitter
- * @package   StatusNet
- * @author    Evan Prodromou <evan@status.net>
- * @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/
- */
-
-class TwitapistatusnetAction extends TwitterapiAction
-{
-    /**
-     * A version stamp for the API
-     *
-     * Returns a version number for this version of StatusNet, which
-     * should make things a bit easier for upgrades.
-     * URL: http://identi.ca/api/statusnet/version.(xml|json)
-     * Formats: xml, json
-     *
-     * @param array $args    Web arguments
-     * @param array $apidata Twitter API data
-     *
-     * @return void
-     *
-     * @see ApiAction::process_command()
-     */
-
-    function version($args, $apidata)
-    {
-        parent::handle($args);
-        switch ($apidata['content-type']) {
-         case 'xml':
-            $this->init_document('xml');
-            $this->element('version', null, STATUSNET_VERSION);
-            $this->end_document('xml');
-            break;
-         case 'json':
-            $this->init_document('json');
-            print '"'.STATUSNET_VERSION.'"';
-            $this->end_document('json');
-            break;
-         default:
-            $this->clientError(_('API method not found!'), $code=404);
-        }
-    }
-
-    /**
-     * Dump of configuration variables
-     *
-     * Gives a full dump of configuration variables for this instance
-     * of StatusNet, minus variables that may be security-sensitive (like
-     * passwords).
-     * URL: http://identi.ca/api/statusnet/config.(xml|json)
-     * Formats: xml, json
-     *
-     * @param array $args    Web arguments
-     * @param array $apidata Twitter API data
-     *
-     * @return void
-     *
-     * @see ApiAction::process_command()
-     */
-
-    function config($args, $apidata)
-    {
-        static $keys = array('site' => array('name', 'server', 'theme', 'path', 'fancy', 'language',
-                                             'email', 'broughtby', 'broughtbyurl', 'closed',
-                                             'inviteonly', 'private'),
-                             'license' => array('url', 'title', 'image'),
-                             'nickname' => array('featured'),
-                             'throttle' => array('enabled', 'count', 'timespan'),
-                             'xmpp' => array('enabled', 'server', 'user'));
-
-        parent::handle($args);
-
-        switch ($apidata['content-type']) {
-         case 'xml':
-            $this->init_document('xml');
-            $this->elementStart('config');
-            // XXX: check that all sections and settings are legal XML elements
-            foreach ($keys as $section => $settings) {
-                $this->elementStart($section);
-                foreach ($settings as $setting) {
-                    $value = common_config($section, $setting);
-                    if (is_array($value)) {
-                        $value = implode(',', $value);
-                    } else if ($value === false) {
-                        $value = 'false';
-                    } else if ($value === true) {
-                        $value = 'true';
-                    }
-                    $this->element($setting, null, $value);
-                }
-                $this->elementEnd($section);
-            }
-            $this->elementEnd('config');
-            $this->end_document('xml');
-            break;
-         case 'json':
-            $result = array();
-            foreach ($keys as $section => $settings) {
-                $result[$section] = array();
-                foreach ($settings as $setting) {
-                    $result[$section][$setting] = common_config($section, $setting);
-                }
-            }
-            $this->init_document('json');
-            $this->show_json_objects($result);
-            $this->end_document('json');
-            break;
-         default:
-            $this->clientError(_('API method not found!'), $code=404);
-        }
-    }
-
-    /**
-     * WADL description of the API
-     *
-     * Gives a WADL description of the API provided by this version of the
-     * software.
-     *
-     * @param array $args    Web arguments
-     * @param array $apidata Twitter API data
-     *
-     * @return void
-     *
-     * @see ApiAction::process_command()
-     */
-
-    function wadl($args, $apidata)
-    {
-        parent::handle($args);
-        $this->serverError(_('API method under construction.'), 501);
-    }
-
-}
diff --git a/actions/twitapitags.php b/actions/twitapitags.php
deleted file mode 100644 (file)
index 0bcc55d..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * StatusNet extensions to the Twitter-like API for groups
- *
- * 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  Twitter
- * @package   StatusNet
- * @author    Craig Andrews <candrews@integralblue.com>
- * @author    Zach Copley <zach@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);
-}
-
-require_once INSTALLDIR.'/lib/twitterapi.php';
-
-/**
- * Group-specific API methods
- *
- * This class handles StatusNet group API methods.
- *
- * @category  Twitter
- * @package   StatusNet
- * @author    Craig Andrews <candrews@integralblue.com>
- * @author    Zach Copley <zach@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/
- */
-
- class TwitapitagsAction extends TwitterapiAction
- {
-
-     function timeline($args, $apidata)
-     {
-         parent::handle($args);
-
-         common_debug("in tags api action");
-
-         $this->auth_user = $apidata['user'];
-         $tag = $apidata['api_arg'];
-
-         if (empty($tag)) {
-             $this->clientError('Not Found', 404, $apidata['content-type']);
-             return;
-         }
-
-         $sitename   = common_config('site', 'name');
-         $title      = sprintf(_("Notices tagged with %s"), $tag);
-         $taguribase = common_config('integration', 'taguri');
-         $id         = "tag:$taguribase:TagTimeline:".$tag;
-         $link       = common_local_url('tag',
-             array('tag' => $tag));
-         $subtitle   = sprintf(_('Updates tagged with %1$s on %2$s!'),
-             $tag, $sitename);
-
-         $page     = (int)$this->arg('page', 1);
-         $count    = (int)$this->arg('count', 20);
-         $max_id   = (int)$this->arg('max_id', 0);
-         $since_id = (int)$this->arg('since_id', 0);
-         $since    = $this->arg('since');
-
-         # XXX: support max_id, since_id, and since arguments
-         $notice = Notice_tag::getStream($tag, ($page-1)*$count, $count + 1);
-
-         switch($apidata['content-type']) {
-          case 'xml':
-             $this->show_xml_timeline($notice);
-             break;
-          case 'rss':
-             $this->show_rss_timeline($notice, $title, $link, $subtitle);
-             break;
-          case 'atom':
-             if (isset($apidata['api_arg'])) {
-                 $selfuri = common_root_url() .
-                     'api/statusnet/tags/timeline/' .
-                         $apidata['api_arg'] . '.atom';
-             } else {
-                 $selfuri = common_root_url() .
-                  'api/statusnet/tags/timeline.atom';
-             }
-             $this->show_atom_timeline($notice, $title, $id, $link,
-                 $subtitle, null, $selfuri);
-             break;
-          case 'json':
-             $this->show_json_timeline($notice);
-             break;
-          default:
-             $this->clientError(_('API method not found!'), $code = 404);
-         }
-     }
-
-}
index 83ab28f35d6236fa233c6f1f0a7b6aee5e82e039..779405e6d64c7a41d2e88f77e9a0915691ca5350 100644 (file)
@@ -31,7 +31,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
     exit(1);
 }
 
-require_once INSTALLDIR.'/lib/twitterapi.php';
+require_once INSTALLDIR.'/lib/api.php';
 
 /**
  *  Returns the top ten queries that are currently trending
@@ -42,10 +42,10 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link     http://status.net/
  *
- * @see      TwitterapiAction
+ * @see      ApiAction
  */
 
-class TwitapitrendsAction extends TwitterapiAction
+class TwitapitrendsAction extends ApiAction
 {
 
     var $callback;
diff --git a/actions/twitapiusers.php b/actions/twitapiusers.php
deleted file mode 100644 (file)
index 703fa67..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-<?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.'/lib/twitterapi.php');
-
-class TwitapiusersAction extends TwitterapiAction
-{
-
-    function show($args, $apidata)
-    {
-        parent::handle($args);
-
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
-            $this->clientError(_('API method not found!'), $code = 404);
-            return;
-        }
-
-        $user = null;
-        $email = $this->arg('email');
-
-        // XXX: email field deprecated in Twitter's API
-
-        if ($email) {
-            $user = User::staticGet('email', $email);
-        } else {
-            $user = $this->get_user($apidata['api_arg'], $apidata);
-        }
-
-        if (empty($user)) {
-            $this->clientError(_('Not found.'), 404, $apidata['content-type']);
-            return;
-        }
-
-        $profile = $user->getProfile();
-
-        if (!$profile) {
-            common_server_error(_('User has no profile.'));
-            return;
-        }
-
-        $twitter_user = $this->twitter_user_array($user->getProfile(), true);
-
-        if ($apidata['content-type'] == 'xml') {
-            $this->init_document('xml');
-            $this->show_twitter_xml_user($twitter_user);
-            $this->end_document('xml');
-        } elseif ($apidata['content-type'] == 'json') {
-            $this->init_document('json');
-            $this->show_json_objects($twitter_user);
-            $this->end_document('json');
-        } else {
-
-            // This is in case 'show' was called via /account/verify_credentials
-            // without a format (xml or json).
-            header('Content-Type: text/html; charset=utf-8');
-            print 'Authorized';
-        }
-
-    }
-}
index 9a4cf8e4659b6f51c09fb23a48993457efd52014..3cec9523cdff1fd3c1376592121e8cf21f52dacb 100644 (file)
@@ -1,5 +1,16 @@
 <?php
-/*
+/**
+ * Handle an updateprofile action
+ *
+ * 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.
  *
 
 if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
 
-require_once(INSTALLDIR.'/lib/omb.php');
+require_once INSTALLDIR.'/lib/omb.php';
+require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
 
+/**
+ * Handle an updateprofile action
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ */
 class UpdateprofileAction extends Action
 {
-    
-    function handle($args)
-    {
-        parent::handle($args);
-        try {
-            common_remove_magic_from_request();
-            $req = OAuthRequest::from_request('POST', common_local_url('updateprofile'));
-            # Note: server-to-server function!
-            $server = omb_oauth_server();
-            list($consumer, $token) = $server->verify_request($req);
-            if ($this->update_profile($req, $consumer, $token)) {
-                header('HTTP/1.1 200 OK');
-                header('Content-type: text/plain');
-                print "omb_version=".OMB_VERSION_01;
-            }
-        } catch (OAuthException $e) {
-            $this->serverError($e->getMessage());
-            return;
-        }
-    }
 
-    function update_profile($req, $consumer, $token)
+    /**
+     * For initializing members of the class.
+     *
+     * @param array $argarray misc. arguments
+     *
+     * @return boolean true
+     */
+    function prepare($argarray)
     {
-        $version = $req->get_parameter('omb_version');
-        if ($version != OMB_VERSION_01) {
-            $this->clientError(_('Unsupported OMB version'), 400);
+        parent::prepare($argarray);
+        $license      = $_POST['omb_listenee_license'];
+        $site_license = common_config('license', 'url');
+        if (!common_compatible_license($license, $site_license)) {
+            $this->clientError(sprintf(_('Listenee stream license ‘%s’ is not '.
+                                         'compatible with site license ‘%s’.'),
+                                       $license, $site_license));
             return false;
         }
-        # First, check to see if listenee exists
-        $listenee =  $req->get_parameter('omb_listenee');
-        $remote = Remote_profile::staticGet('uri', $listenee);
-        if (!$remote) {
-            $this->clientError(_('Profile unknown'), 404);
-            return false;
-        }
-        # Second, check to see if they should be able to post updates!
-        # We see if there are any subscriptions to that remote user with
-        # the given token.
-
-        $sub = new Subscription();
-        $sub->subscribed = $remote->id;
-        $sub->token = $token->key;
-        if (!$sub->find(true)) {
-            $this->clientError(_('You did not send us that profile'), 403);
-            return false;
-        }
-
-        $profile = Profile::staticGet('id', $remote->id);
-        if (!$profile) {
-            # This one is our fault
-            $this->serverError(_('Remote profile with no matching profile'), 500);
-            return false;
-        }
-        $nickname = $req->get_parameter('omb_listenee_nickname');
-        if ($nickname && !Validate::string($nickname, array('min_length' => 1,
-                                                            'max_length' => 64,
-                                                            'format' => NICKNAME_FMT))) {
-            $this->clientError(_('Nickname must have only lowercase letters and numbers and no spaces.'));
-            return false;
-        }
-        $license = $req->get_parameter('omb_listenee_license');
-        if ($license && !common_valid_http_url($license)) {
-            $this->clientError(sprintf(_("Invalid license URL '%s'"), $license));
-            return false;
-        }
-        $profile_url = $req->get_parameter('omb_listenee_profile');
-        if ($profile_url && !common_valid_http_url($profile_url)) {
-            $this->clientError(sprintf(_("Invalid profile URL '%s'."), $profile_url));
-            return false;
-        }
-        # optional stuff
-        $fullname = $req->get_parameter('omb_listenee_fullname');
-        if ($fullname && mb_strlen($fullname) > 255) {
-            $this->clientError(_("Full name is too long (max 255 chars)."));
-            return false;
-        }
-        $homepage = $req->get_parameter('omb_listenee_homepage');
-        if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) {
-            $this->clientError(sprintf(_("Invalid homepage '%s'"), $homepage));
-            return false;
-        }
-        $bio = $req->get_parameter('omb_listenee_bio');
-        if ($bio && mb_strlen($bio) > 140) {
-            $this->clientError(_("Bio is too long (max 140 chars)."));
-            return false;
-        }
-        $location = $req->get_parameter('omb_listenee_location');
-        if ($location && mb_strlen($location) > 255) {
-            $this->clientError(_("Location is too long (max 255 chars)."));
-            return false;
-        }
-        $avatar = $req->get_parameter('omb_listenee_avatar');
-        if ($avatar) {
-            if (!common_valid_http_url($avatar) || strlen($avatar) > 255) {
-                $this->clientError(sprintf(_("Invalid avatar URL '%s'"), $avatar));
-                return false;
-            }
-            $size = @getimagesize($avatar);
-            if (!$size) {
-                $this->clientError(sprintf(_("Can't read avatar URL '%s'"), $avatar));
-                return false;
-            }
-            if ($size[0] != AVATAR_PROFILE_SIZE || $size[1] != AVATAR_PROFILE_SIZE) {
-                $this->clientError(sprintf(_("Wrong size image at '%s'"), $avatar));
-                return false;
-            }
-            if (!in_array($size[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG,
-                                          IMAGETYPE_PNG))) {
-                $this->clientError(sprintf(_("Wrong image type for '%s'"), $avatar));
-                return false;
-            }
-        }
-
-        $orig_profile = clone($profile);
+        return true;
+    }
 
-        /* Use values even if they are an empty string. Parsing an empty string in
-           updateProfile is the specified way of clearing a parameter in OMB. */
-        if (!is_null($nickname)) {
-            $profile->nickname = $nickname;
-        }
-        if (!is_null($profile_url)) {
-            $profile->profileurl = $profile_url;
-        }
-        if (!is_null($fullname)) {
-            $profile->fullname = $fullname;
-        }
-        if (!is_null($homepage)) {
-            $profile->homepage = $homepage;
-        }
-        if (!is_null($bio)) {
-            $profile->bio = $bio;
-        }
-        if (!is_null($location)) {
-            $profile->location = $location;
-        }
+    function handle($args)
+    {
+        parent::handle($args);
 
-        if (!$profile->update($orig_profile)) {
-            $this->serverError(_('Could not save new profile info'), 500);
-            return false;
-        } else {
-            if ($avatar) {
-                $temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar');
-                copy($avatar, $temp_filename);
-                $imagefile = new ImageFile($profile->id, $temp_filename);
-                $filename = Avatar::filename($profile->id,
-                                     image_type_to_extension($imagefile->type),
-                                     null,
-                                     common_timestamp());
-                rename($temp_filename, Avatar::path($filename));
-                if (!$profile->setOriginal($filename)) {
-                    $this->serverError(_('Could not save avatar info'), 500);
-                    return false;
-                }
-            }
-            return true;
+        try {
+            $srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
+                                            omb_oauth_server());
+            $srv->handleUpdateProfile();
+        } catch (Exception $e) {
+            $this->serverError($e->getMessage());
+            return;
         }
     }
 }
index a9ac1f256f79eb3bef2e445fe2fbab668c33089e..dc59e6c94112ccec04d926c11647ce3c9b7a3e5b 100644 (file)
@@ -1,5 +1,16 @@
 <?php
-/*
+/**
+ * Let the user authorize a remote subscription request
+ *
+ * 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.
  *
@@ -19,7 +30,9 @@
 
 if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
 
-require_once(INSTALLDIR.'/lib/omb.php');
+require_once INSTALLDIR.'/lib/omb.php';
+require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
+require_once INSTALLDIR.'/extlib/libomb/profile.php';
 define('TIMESTAMP_THRESHOLD', 300);
 
 class UserauthorizationAction extends Action
@@ -32,46 +45,58 @@ class UserauthorizationAction extends Action
         parent::handle($args);
 
         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
-            # CSRF protection
+            /* Use a session token for CSRF protection. */
             $token = $this->trimmed('token');
             if (!$token || $token != common_session_token()) {
-                $params = $this->getStoredParams();
-                $this->showForm($params, _('There was a problem with your session token. '.
-                                        'Try again, please.'));
+                $srv = $this->getStoredParams();
+                $this->showForm($srv->getRemoteUser(), _('There was a problem ' .
+                                        'with your session token. Try again, ' .
+                                        'please.'));
                 return;
             }
-            # We've shown the form, now post user's choice
+            /* We've shown the form, now post user's choice. */
             $this->sendAuthorization();
         } else {
             if (!common_logged_in()) {
-                # Go log in, and then come back
+                /* Go log in, and then come back. */
                 common_set_returnto($_SERVER['REQUEST_URI']);
 
-                if (!common_config('site', 'openidonly')) {
-                    common_redirect(common_local_url('login'));
-                } else {
-                    common_redirect(common_local_url('openidlogin'));
-                }
+                common_redirect(common_local_url('login'));
+                return;
+            }
+
+            $user    = common_current_user();
+            $profile = $user->getProfile();
+            if (!$profile) {
+                common_log_db_error($user, 'SELECT', __FILE__);
+                $this->serverError(_('User without matching profile'));
                 return;
             }
 
+            /* TODO: If no token is passed the user should get a prompt to enter
+               it according to OAuth Core 1.0. */
             try {
-                $this->validateRequest();
-                $this->storeParams($_GET);
-                $this->showForm($_GET);
-            } catch (OAuthException $e) {
+                $this->validateOmb();
+                $srv = new OMB_Service_Provider(
+                        profile_to_omb_profile($user->uri, $profile),
+                        omb_oauth_datastore());
+
+                $remote_user = $srv->handleUserAuth();
+            } catch (Exception $e) {
                 $this->clearParams();
                 $this->clientError($e->getMessage());
                 return;
             }
 
+            $this->storeParams($srv);
+            $this->showForm($remote_user);
         }
     }
 
     function showForm($params, $error=null)
     {
         $this->params = $params;
-        $this->error = $error;
+        $this->error  = $error;
         $this->showPage();
     }
 
@@ -83,23 +108,24 @@ class UserauthorizationAction extends Action
     function showPageNotice()
     {
         $this->element('p', null, _('Please check these details to make sure '.
-                                    'that you want to subscribe to this user\'s notices. '.
-                                    'If you didn\'t just ask to subscribe to someone\'s notices, '.
-                                    'click "Reject".'));
+                                    'that you want to subscribe to this ' .
+                                    'user’s notices. If you didn’t just ask ' .
+                                    'to subscribe to someone’s notices, '.
+                                    'click “Reject”.'));
     }
 
     function showContent()
     {
         $params = $this->params;
 
-        $nickname = $params['omb_listenee_nickname'];
-        $profile = $params['omb_listenee_profile'];
-        $license = $params['omb_listenee_license'];
-        $fullname = $params['omb_listenee_fullname'];
-        $homepage = $params['omb_listenee_homepage'];
-        $bio = $params['omb_listenee_bio'];
-        $location = $params['omb_listenee_location'];
-        $avatar = $params['omb_listenee_avatar'];
+        $nickname = $params->getNickname();
+        $profile  = $params->getProfileURL();
+        $license  = $params->getLicenseURL();
+        $fullname = $params->getFullname();
+        $homepage = $params->getHomepage();
+        $bio      = $params->getBio();
+        $location = $params->getLocation();
+        $avatar   = $params->getAvatarURL();
 
         $this->elementStart('div', array('class' => 'profile'));
         $this->elementStart('div', 'entity_profile vcard');
@@ -176,11 +202,14 @@ class UserauthorizationAction extends Action
                                           'id' => 'userauthorization',
                                           'class' => 'form_user_authorization',
                                           'name' => 'userauthorization',
-                                          'action' => common_local_url('userauthorization')));
+                                          'action' => common_local_url(
+                                                         'userauthorization')));
         $this->hidden('token', common_session_token());
 
-        $this->submit('accept', _('Accept'), 'submit accept', null, _('Subscribe to this user'));
-        $this->submit('reject', _('Reject'), 'submit reject', null, _('Reject this subscription'));
+        $this->submit('accept', _('Accept'), 'submit accept', null,
+                      _('Subscribe to this user'));
+        $this->submit('reject', _('Reject'), 'submit reject', null,
+                      _('Reject this subscription'));
         $this->elementEnd('form');
         $this->elementEnd('li');
         $this->elementEnd('ul');
@@ -190,191 +219,27 @@ class UserauthorizationAction extends Action
 
     function sendAuthorization()
     {
-        $params = $this->getStoredParams();
+        $srv = $this->getStoredParams();
 
-        if (!$params) {
+        if (is_null($srv)) {
             $this->clientError(_('No authorization request!'));
             return;
         }
 
-        $callback = $params['oauth_callback'];
-
-        if ($this->arg('accept')) {
-            if (!$this->authorizeToken($params)) {
-                $this->clientError(_('Error authorizing token'));
-            }
-            if (!$this->saveRemoteProfile($params)) {
-                $this->clientError(_('Error saving remote profile'));
-            }
-            if (!$callback) {
-                $this->showAcceptMessage($params['oauth_token']);
-            } else {
-                $newparams = array();
-                $newparams['oauth_token'] = $params['oauth_token'];
-                $newparams['omb_version'] = OMB_VERSION_01;
-                $user = User::staticGet('uri', $params['omb_listener']);
-                $profile = $user->getProfile();
-                if (!$profile) {
-                    common_log_db_error($user, 'SELECT', __FILE__);
-                    $this->serverError(_('User without matching profile'));
-                    return;
-                }
-                $newparams['omb_listener_nickname'] = $user->nickname;
-                $newparams['omb_listener_profile'] = common_local_url('showstream',
-                                                                   array('nickname' => $user->nickname));
-                if (!is_null($profile->fullname)) {
-                    $newparams['omb_listener_fullname'] = $profile->fullname;
-                }
-                if (!is_null($profile->homepage)) {
-                    $newparams['omb_listener_homepage'] = $profile->homepage;
-                }
-                if (!is_null($profile->bio)) {
-                    $newparams['omb_listener_bio'] = $profile->bio;
-                }
-                if (!is_null($profile->location)) {
-                    $newparams['omb_listener_location'] = $profile->location;
-                }
-                $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
-                if ($avatar) {
-                    $newparams['omb_listener_avatar'] = $avatar->url;
-                }
-                $parts = array();
-                foreach ($newparams as $k => $v) {
-                    $parts[] = $k . '=' . OAuthUtil::urlencode_rfc3986($v);
-                }
-                $query_string = implode('&', $parts);
-                $parsed = parse_url($callback);
-                $url = $callback . (($parsed['query']) ? '&' : '?') . $query_string;
-                common_redirect($url, 303);
-            }
-        } else {
-            if (!$callback) {
-                $this->showRejectMessage();
-            } else {
-                # XXX: not 100% sure how to signal failure... just redirect without token?
-                common_redirect($callback, 303);
-            }
-        }
-    }
-
-    function authorizeToken(&$params)
-    {
-        $token_field = $params['oauth_token'];
-        $rt = new Token();
-        $rt->tok = $token_field;
-        $rt->type = 0;
-        $rt->state = 0;
-        if ($rt->find(true)) {
-            $orig_rt = clone($rt);
-            $rt->state = 1; # Authorized but not used
-            if ($rt->update($orig_rt)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    # XXX: refactor with similar code in finishremotesubscribe.php
-
-    function saveRemoteProfile(&$params)
-    {
-        # FIXME: we should really do this when the consumer comes
-        # back for an access token. If they never do, we've got stuff in a
-        # weird state.
-
-        $nickname = $params['omb_listenee_nickname'];
-        $fullname = $params['omb_listenee_fullname'];
-        $profile_url = $params['omb_listenee_profile'];
-        $homepage = $params['omb_listenee_homepage'];
-        $bio = $params['omb_listenee_bio'];
-        $location = $params['omb_listenee_location'];
-        $avatar_url = $params['omb_listenee_avatar'];
-
-        $listenee = $params['omb_listenee'];
-        $remote = Remote_profile::staticGet('uri', $listenee);
-
-        if ($remote) {
-            $exists = true;
-            $profile = Profile::staticGet($remote->id);
-            $orig_remote = clone($remote);
-            $orig_profile = clone($profile);
-        } else {
-            $exists = false;
-            $remote = new Remote_profile();
-            $remote->uri = $listenee;
-            $profile = new Profile();
-        }
-
-        $profile->nickname = $nickname;
-        $profile->profileurl = $profile_url;
-
-        if (!is_null($fullname)) {
-            $profile->fullname = $fullname;
-        }
-        if (!is_null($homepage)) {
-            $profile->homepage = $homepage;
-        }
-        if (!is_null($bio)) {
-            $profile->bio = $bio;
-        }
-        if (!is_null($location)) {
-            $profile->location = $location;
+        $accepted = $this->arg('accept');
+        try {
+            list($val, $token) = $srv->continueUserAuth($accepted);
+        } catch (Exception $e) {
+            $this->clientError($e->getMessage());
+            return;
         }
-
-        if ($exists) {
-            $profile->update($orig_profile);
+        if ($val !== false) {
+            common_redirect($val, 303);
+        } elseif ($accepted) {
+            $this->showAcceptMessage($token);
         } else {
-            $profile->created = DB_DataObject_Cast::dateTime(); # current time
-            $id = $profile->insert();
-            if (!$id) {
-                return false;
-            }
-            $remote->id = $id;
+            $this->showRejectMessage();
         }
-
-        if ($exists) {
-            if (!$remote->update($orig_remote)) {
-                return false;
-            }
-        } else {
-            $remote->created = DB_DataObject_Cast::dateTime(); # current time
-            if (!$remote->insert()) {
-                return false;
-            }
-        }
-
-        if ($avatar_url) {
-            if (!$this->addAvatar($profile, $avatar_url)) {
-                return false;
-            }
-        }
-
-        $user = common_current_user();
-
-        $sub = new Subscription();
-        $sub->subscriber = $user->id;
-        $sub->subscribed = $remote->id;
-        $sub->token = $params['oauth_token']; # NOTE: request token, not valid for use!
-        $sub->created = DB_DataObject_Cast::dateTime(); # current time
-
-        if (!$sub->insert()) {
-            return false;
-        }
-
-        return true;
-    }
-
-    function addAvatar($profile, $url)
-    {
-        $temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar');
-        copy($url, $temp_filename);
-        $imagefile = new ImageFile($profile->id, $temp_filename);
-        $filename = Avatar::filename($profile->id,
-                                     image_type_to_extension($imagefile->type),
-                                     null,
-                                     common_timestamp());
-        rename($temp_filename, Avatar::path($filename));
-        return $profile->setOriginal($filename);
     }
 
     function showAcceptMessage($tok)
@@ -382,26 +247,28 @@ class UserauthorizationAction extends Action
         common_show_header(_('Subscription authorized'));
         $this->element('p', null,
                        _('The subscription has been authorized, but no '.
-                         'callback URL was passed. Check with the site\'s instructions for '.
-                         'details on how to authorize the subscription. Your subscription token is:'));
+                         'callback URL was passed. Check with the site’s ' .
+                         'instructions for details on how to authorize the ' .
+                         'subscription. Your subscription token is:'));
         $this->element('blockquote', 'token', $tok);
         common_show_footer();
     }
 
-    function showRejectMessage($tok)
+    function showRejectMessage()
     {
         common_show_header(_('Subscription rejected'));
         $this->element('p', null,
                        _('The subscription has been rejected, but no '.
-                         'callback URL was passed. Check with the site\'s instructions for '.
-                         'details on how to fully reject the subscription.'));
+                         'callback URL was passed. Check with the site’s ' .
+                         'instructions for details on how to fully reject ' .
+                         'the subscription.'));
         common_show_footer();
     }
 
     function storeParams($params)
     {
         common_ensure_session();
-        $_SESSION['userauthorizationparams'] = $params;
+        $_SESSION['userauthorizationparams'] = serialize($params);
     }
 
     function clearParams()
@@ -413,138 +280,74 @@ class UserauthorizationAction extends Action
     function getStoredParams()
     {
         common_ensure_session();
-        $params = $_SESSION['userauthorizationparams'];
+        $params = unserialize($_SESSION['userauthorizationparams']);
         return $params;
     }
 
-    # Throws an OAuthException if anything goes wrong
-
-    function validateRequest()
-    {
-        /* Find token.
-           TODO: If no token is passed the user should get a prompt to enter it
-                 according to OAuth Core 1.0 */
-        $t = new Token();
-        $t->tok = $_GET['oauth_token'];
-        $t->type = 0;
-        if (!$t->find(true)) {
-            throw new OAuthException("Invalid request token: " . $_GET['oauth_token']);
-        }
-
-        $this->validateOmb();
-        return true;
-    }
-
     function validateOmb()
     {
-        foreach (array('omb_version', 'omb_listener', 'omb_listenee',
-                       'omb_listenee_profile', 'omb_listenee_nickname',
-                       'omb_listenee_license') as $param)
-        {
-            if (!isset($_GET[$param]) || is_null($_GET[$param])) {
-                throw new OAuthException("Required parameter '$param' not found");
-            }
-        }
-        # Now, OMB stuff
-        $version = $_GET['omb_version'];
-        if ($version != OMB_VERSION_01) {
-            throw new OAuthException("OpenMicroBlogging version '$version' not supported");
-        }
         $listener = $_GET['omb_listener'];
+        $listenee = $_GET['omb_listenee'];
+        $nickname = $_GET['omb_listenee_nickname'];
+        $profile  = $_GET['omb_listenee_profile'];
+
         $user = User::staticGet('uri', $listener);
         if (!$user) {
-            throw new OAuthException("Listener URI '$listener' not found here");
-        }
-        $cur = common_current_user();
-        if ($cur->id != $user->id) {
-            throw new OAuthException("Can't add for another user!");
-        }
-        $listenee = $_GET['omb_listenee'];
-        if (!Validate::uri($listenee) &&
-            !common_valid_tag($listenee)) {
-            throw new OAuthException("Listenee URI '$listenee' not a recognizable URI");
+            throw new Exception(sprintf(_('Listener URI ‘%s’ not found here'),
+                                        $listener));
         }
+
         if (strlen($listenee) > 255) {
-            throw new OAuthException("Listenee URI '$listenee' too long");
+            throw new Exception(sprintf(_('Listenee URI ‘%s’ is too long.'),
+                                        $listenee));
         }
 
         $other = User::staticGet('uri', $listenee);
         if ($other) {
-            throw new OAuthException("Listenee URI '$listenee' is local user");
+            throw new Exception(sprintf(_('Listenee URI ‘%s’ is a local user.'),
+                                        $listenee));
         }
 
         $remote = Remote_profile::staticGet('uri', $listenee);
         if ($remote) {
-            $sub = new Subscription();
+            $sub             = new Subscription();
             $sub->subscriber = $user->id;
             $sub->subscribed = $remote->id;
             if ($sub->find(true)) {
-                throw new OAuthException("Already subscribed to user!");
+                throw new Exception('You are already subscribed to this user.');
             }
         }
-        $nickname = $_GET['omb_listenee_nickname'];
-        if (!Validate::string($nickname, array('min_length' => 1,
-                                               'max_length' => 64,
-                                               'format' => NICKNAME_FMT))) {
-            throw new OAuthException('Nickname must have only letters and numbers and no spaces.');
-        }
-        $profile = $_GET['omb_listenee_profile'];
-        if (!common_valid_http_url($profile)) {
-            throw new OAuthException("Invalid profile URL '$profile'.");
-        }
 
-        if ($profile == common_local_url('showstream', array('nickname' => $nickname))) {
-            throw new OAuthException("Profile URL '$profile' is for a local user.");
-        }
+        if ($profile == common_profile_url($nickname)) {
+            throw new Exception(sprintf(_('Profile URL ‘%s’ is for a local user.'),
+                                        $profile));
 
-        $license = $_GET['omb_listenee_license'];
-        if (!common_valid_http_url($license)) {
-            throw new OAuthException("Invalid license URL '$license'.");
         }
+
+        $license      = $_GET['omb_listenee_license'];
         $site_license = common_config('license', 'url');
         if (!common_compatible_license($license, $site_license)) {
-            throw new OAuthException("Listenee stream license '$license' not compatible with site license '$site_license'.");
-        }
-        # optional stuff
-        $fullname = $_GET['omb_listenee_fullname'];
-        if ($fullname && mb_strlen($fullname) > 255) {
-            throw new OAuthException("Full name '$fullname' too long.");
-        }
-        $homepage = $_GET['omb_listenee_homepage'];
-        if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) {
-            throw new OAuthException("Invalid homepage '$homepage'");
-        }
-        $bio = $_GET['omb_listenee_bio'];
-        if ($bio && mb_strlen($bio) > 140) {
-            throw new OAuthException("Bio too long '$bio'");
-        }
-        $location = $_GET['omb_listenee_location'];
-        if ($location && mb_strlen($location) > 255) {
-            throw new OAuthException("Location too long '$location'");
+            throw new Exception(sprintf(_('Listenee stream license ‘%s’ is not ' .
+                                          'compatible with site license ‘%s’.'),
+                                        $license, $site_license));
         }
+
         $avatar = $_GET['omb_listenee_avatar'];
         if ($avatar) {
             if (!common_valid_http_url($avatar) || strlen($avatar) > 255) {
-                throw new OAuthException("Invalid avatar URL '$avatar'");
+                throw new Exception(sprintf(_('Avatar URL ‘%s’ is not valid.'),
+                                            $avatar));
             }
             $size = @getimagesize($avatar);
             if (!$size) {
-                throw new OAuthException("Can't read avatar URL '$avatar'");
-            }
-            if ($size[0] != AVATAR_PROFILE_SIZE || $size[1] != AVATAR_PROFILE_SIZE) {
-                throw new OAuthException("Wrong size image at '$avatar'");
+                throw new Exception(sprintf(_('Can’t read avatar URL ‘%s’.'),
+                                            $avatar));
             }
             if (!in_array($size[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG,
                                           IMAGETYPE_PNG))) {
-                throw new OAuthException("Wrong image type for '$avatar'");
+                throw new Exception(sprintf(_('Wrong image type for avatar URL '.
+                                              '‘%s’.'), $avatar));
             }
         }
-        $callback = $_GET['oauth_callback'];
-        if ($callback && !common_valid_http_url($callback)) {
-            throw new OAuthException("Invalid callback URL '$callback'");
-        }
-        if ($callback && $callback == common_local_url('finishremotesubscribe')) {
-            throw new OAuthException("Callback URL '$callback' is for local site.");
-        }
     }
-}
+}
\ No newline at end of file
index fa6d588cdf4358f7c2f99f37853bb10b7e3de8b7..19e610551d4bca2ec9cf65defde29b7e003a76de 100644 (file)
@@ -25,7 +25,6 @@ require_once(INSTALLDIR.'/lib/rssaction.php');
 
 class UserrssAction extends Rss10Action
 {
-    var $user = null;
     var $tag  = null;
 
     function prepare($args)
@@ -39,6 +38,7 @@ class UserrssAction extends Rss10Action
             $this->clientError(_('No such user.'));
             return false;
         } else {
+            $this->notices = $this->getNotices($this->limit);
             return true;
         }
     }
@@ -64,9 +64,8 @@ class UserrssAction extends Rss10Action
 
     function getNotices($limit=0)
     {
-
         $user = $this->user;
-
+        
         if (is_null($user)) {
             return null;
         }
index def10e4cf7bd90d8af6b6263eec7af093dfb1fbe..8ba89fec0ff7c56a262ebaa29374ba9a8c0b8359 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
- * XRDS for OpenID
+ * XRDS for OpenMicroBlogging
  *
  * PHP version 5
  *
@@ -34,9 +34,11 @@ 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';
 
 /**
- * XRDS for OpenID
+ * XRDS for OpenMicroBlogging
  *
  * @category Action
  * @package  StatusNet
@@ -52,7 +54,7 @@ class XrdsAction extends Action
      *
      * @return boolean true
      */
-    function isReadOnly($args)
+    function isReadOnly()
     {
         return true;
     }
@@ -85,89 +87,31 @@ class XrdsAction extends Action
      */
     function showXrds($user)
     {
-        header('Content-Type: application/xrds+xml');
-        $this->startXML();
-        $this->elementStart('XRDS', array('xmlns' => 'xri://$xrds'));
+        $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);
+    }
+}
 
-        $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
-                                          'xml:id' => 'oauth',
-                                          'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
-                                          'version' => '2.0'));
-        $this->element('Type', null, 'xri://$xrds*simple');
-        $this->showService(OAUTH_ENDPOINT_REQUEST,
-                            common_local_url('requesttoken'),
-                            array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
-                            array(OAUTH_HMAC_SHA1),
-                            $user->uri);
-        $this->showService(OAUTH_ENDPOINT_AUTHORIZE,
-                            common_local_url('userauthorization'),
-                            array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
-                            array(OAUTH_HMAC_SHA1));
-        $this->showService(OAUTH_ENDPOINT_ACCESS,
-                            common_local_url('accesstoken'),
-                            array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
-                            array(OAUTH_HMAC_SHA1));
-        $this->showService(OAUTH_ENDPOINT_RESOURCE,
-                            null,
-                            array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
-                            array(OAUTH_HMAC_SHA1));
-        $this->elementEnd('XRD');
+class Laconica_XRDS_Mapper implements OMB_XRDS_Mapper
+{
+    protected $urls;
 
-        // XXX: decide whether to include user's ID/nickname in postNotice URL
-        $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
-                                          'xml:id' => 'omb',
-                                          'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
-                                          'version' => '2.0'));
-        $this->element('Type', null, 'xri://$xrds*simple');
-        $this->showService(OMB_ENDPOINT_POSTNOTICE,
-                            common_local_url('postnotice'));
-        $this->showService(OMB_ENDPOINT_UPDATEPROFILE,
-                            common_local_url('updateprofile'));
-        $this->elementEnd('XRD');
-        $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
-                                          'version' => '2.0'));
-        $this->element('Type', null, 'xri://$xrds*simple');
-        $this->showService(OAUTH_DISCOVERY,
-                            '#oauth');
-        $this->showService(OMB_NAMESPACE,
-                            '#omb');
-        $this->elementEnd('XRD');
-        $this->elementEnd('XRDS');
-        $this->endXML();
+    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');
     }
 
-    /**
-     * 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)
+    public function getURL($action)
     {
-        $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');
+        return common_local_url($this->urls[$action]);
     }
 }
-
+?>
diff --git a/classes/Config.php b/classes/Config.php
new file mode 100644 (file)
index 0000000..92f237d
--- /dev/null
@@ -0,0 +1,131 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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')) {
+    exit(1);
+}
+
+/**
+ * Table Definition for config
+ */
+
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Config extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'config';                          // table name
+    public $section;                         // varchar(32)  primary_key not_null
+    public $setting;                         // varchar(32)  primary_key not_null
+    public $value;                           // varchar(255)
+
+    /* Static get */
+    function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Config',$k,$v); }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+
+    const settingsKey = 'config:settings';
+
+    static function loadSettings()
+    {
+        $settings = self::_getSettings();
+        if (!empty($settings)) {
+            self::_applySettings($settings);
+        }
+    }
+
+    static function _getSettings()
+    {
+        $c = self::memcache();
+
+        if (!empty($c)) {
+            $settings = $c->get(common_cache_key(self::settingsKey));
+            if (!empty($settings)) {
+                return $settings;
+            }
+        }
+
+        $settings = array();
+
+        $config = new Config();
+
+        $config->find();
+
+        while ($config->fetch()) {
+            $settings[] = array($config->section, $config->setting, $config->value);
+        }
+
+        $config->free();
+
+        if (!empty($c)) {
+            $c->set(common_cache_key(self::settingsKey), $settings);
+        }
+
+        return $settings;
+    }
+
+    static function _applySettings($settings)
+    {
+        global $config;
+
+        foreach ($settings as $s) {
+            list($section, $setting, $value) = $s;
+            $config[$section][$setting] = $value;
+        }
+    }
+
+    function insert()
+    {
+        $result = parent::insert();
+        if ($result) {
+            Config::_blowSettingsCache();
+        }
+        return $result;
+    }
+
+    function delete()
+    {
+        $result = parent::delete();
+        if ($result) {
+            Config::_blowSettingsCache();
+        }
+        return $result;
+    }
+
+    function update($orig=null)
+    {
+        $result = parent::update($orig);
+        if ($result) {
+            Config::_blowSettingsCache();
+        }
+        return $result;
+    }
+
+    function _blowSettingsCache()
+    {
+        $c = self::memcache();
+
+        if (!empty($c)) {
+            $c->delete(common_cache_key(self::settingsKey));
+        }
+    }
+}
diff --git a/classes/Deleted_notice.php b/classes/Deleted_notice.php
new file mode 100644 (file)
index 0000000..64dc85d
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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')) {
+    exit(1);
+}
+
+/**
+ * Table Definition for notice
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Deleted_notice extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'deleted_notice';                  // table name
+    public $id;                              // int(4)  primary_key not_null
+    public $profile_id;                      // int(4)   not_null
+    public $uri;                             // varchar(255)  unique_key
+    public $created;                         // datetime()   not_null
+    public $deleted;                         // datetime()   not_null
+
+    /* Static get */
+    function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Deleted_notice',$k,$v); }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+}
index 308d0a77176a63eb0ec563c8702096663c4d36b1..e04a9d5255ad50c85c840e3563e58ed0017610b9 100644 (file)
@@ -94,7 +94,13 @@ class File extends Memcached_DataObject
             $file_redir = File_redirection::staticGet('url', $given_url);
             if (empty($file_redir)) {
                 $redir_data = File_redirection::where($given_url);
-                $redir_url = $redir_data['url'];
+                if (is_array($redir_data)) {
+                    $redir_url = $redir_data['url'];
+                } elseif (is_string($redir_data)) {
+                    $redir_url = $redir_data;
+                } else {
+                    throw new ServerException("Can't process url '$given_url'");
+                }
                 // TODO: max field length
                 if ($redir_url === $given_url || strlen($redir_url) > 255) {
                     $x = File::saveNew($redir_data, $given_url);
@@ -197,17 +203,44 @@ class File extends Memcached_DataObject
         return 'http://'.$server.$path.$filename;
     }
 
-    function isEnclosure(){
-        if(isset($this->filename)){
-            return true;
-        }
-        $notEnclosureMimeTypes = array('text/html','application/xhtml+xml',null);
-        $mimetype = strtolower($this->mimetype);
-        $semicolon = strpos($mimetype,';');
-        if($semicolon){
-            $mimetype = substr($mimetype,0,$semicolon);
+    function getEnclosure(){
+        $enclosure = (object) array();
+        $enclosure->title=$this->title;
+        $enclosure->url=$this->url;
+        $enclosure->title=$this->title;
+        $enclosure->date=$this->date;
+        $enclosure->modified=$this->modified;
+        $enclosure->size=$this->size;
+        $enclosure->mimetype=$this->mimetype;
+
+        if(! isset($this->filename)){
+            $notEnclosureMimeTypes = array('text/html','application/xhtml+xml');
+            $mimetype = strtolower($this->mimetype);
+            $semicolon = strpos($mimetype,';');
+            if($semicolon){
+                $mimetype = substr($mimetype,0,$semicolon);
+            }
+            if(in_array($mimetype,$notEnclosureMimeTypes)){
+                $oembed = File_oembed::staticGet('file_id',$this->id);
+                if($oembed){
+                    $mimetype = strtolower($oembed->mimetype);
+                    $semicolon = strpos($mimetype,';');
+                    if($semicolon){
+                        $mimetype = substr($mimetype,0,$semicolon);
+                    }
+                    if(in_array($mimetype,$notEnclosureMimeTypes)){
+                        return false;
+                    }else{
+                        if($oembed->mimetype) $enclosure->mimetype=$oembed->mimetype;
+                        if($oembed->url) $enclosure->url=$oembed->url;
+                        if($oembed->title) $enclosure->title=$oembed->title;
+                        if($oembed->modified) $enclosure->modified=$oembed->modified;
+                        unset($oembed->size);
+                    }
+                }
+            }
         }
-        return(! in_array($mimetype,$notEnclosureMimeTypes));
+        return $enclosure;
     }
 }
 
index 6be6518152f7f9dcebe9e3e71e06b33d0d0b2386..e41ccfd097eb4d415d02d587f2fc08d6d7e57ecc 100644 (file)
@@ -20,6 +20,7 @@
 if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
 
 require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+require_once INSTALLDIR.'/classes/File_redirection.php';
 
 /**
  * Table Definition for file_oembed
@@ -34,6 +35,7 @@ class File_oembed extends Memcached_DataObject
     public $file_id;                         // int(4)  primary_key not_null
     public $version;                         // varchar(20)
     public $type;                            // varchar(20)
+    public $mimetype;                        // varchar(50)
     public $provider;                        // varchar(50)
     public $provider_url;                    // varchar(255)
     public $width;                           // int(4)
@@ -93,7 +95,24 @@ class File_oembed extends Memcached_DataObject
         if (!empty($data->title)) $file_oembed->title = $data->title;
         if (!empty($data->author_name)) $file_oembed->author_name = $data->author_name;
         if (!empty($data->author_url)) $file_oembed->author_url = $data->author_url;
-        if (!empty($data->url)) $file_oembed->url = $data->url;
+        if (!empty($data->url)){
+            $file_oembed->url = $data->url;
+            $given_url = File_redirection::_canonUrl($file_oembed->url);
+            if (! empty($given_url)){
+                $file = File::staticGet('url', $given_url);
+                if (empty($file)) {
+                    $file_redir = File_redirection::staticGet('url', $given_url);
+                    if (empty($file_redir)) {
+                        $redir_data = File_redirection::where($given_url);
+                        $file_oembed->mimetype = $redir_data['type'];
+                    } else {
+                        $file_id = $file_redir->file_id;
+                    }
+                } else {
+                    $file_oembed->mimetype=$file->mimetype;
+                }
+            }
+        }
         $file_oembed->insert();
         if (!empty($data->thumbnail_url)) {
             File_thumbnail::saveNew($data, $file_id);
index 76b18f672d3e23d969b50f69052e29f6f08acf81..79052bf7d3e80ca6e5b9347ee2062e5e9cc8ad22 100644 (file)
@@ -79,6 +79,9 @@ 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);
index 4806057b4ca1183fa6c752fde2e0ce03c4f20ee0..979e6e87ccbdcb4d79de3c0fec07a19a239a6812 100644 (file)
@@ -4,7 +4,7 @@
  */
 require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
 
-class Message extends Memcached_DataObject 
+class Message extends Memcached_DataObject
 {
     ###START_AUTOCODE
     /* the code below is auto generated do not remove the above tag */
@@ -14,58 +14,73 @@ class Message extends Memcached_DataObject
     public $uri;                             // varchar(255)  unique_key
     public $from_profile;                    // int(4)   not_null
     public $to_profile;                      // int(4)   not_null
-    public $content;                         // varchar(140)  
-    public $rendered;                        // text()  
-    public $url;                             // varchar(255)  
+    public $content;                         // text()
+    public $rendered;                        // text()
+    public $url;                             // varchar(255)
     public $created;                         // datetime()   not_null
     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
-    public $source;                          // varchar(32)  
+    public $source;                          // varchar(32)
 
     /* Static get */
-    function staticGet($k,$v=null)
-    { return Memcached_DataObject::staticGet('Message',$k,$v); }
+    function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Message',$k,$v); }
 
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
-    
+
     function getFrom()
     {
         return Profile::staticGet('id', $this->from_profile);
     }
-    
+
     function getTo()
     {
         return Profile::staticGet('id', $this->to_profile);
     }
-    
+
     static function saveNew($from, $to, $content, $source) {
-        
+
         $msg = new Message();
-        
+
         $msg->from_profile = $from;
         $msg->to_profile = $to;
         $msg->content = common_shorten_links($content);
         $msg->rendered = common_render_text($content);
         $msg->created = common_sql_now();
         $msg->source = $source;
-        
+
         $result = $msg->insert();
-        
+
         if (!$result) {
             common_log_db_error($msg, 'INSERT', __FILE__);
             return _('Could not insert message.');
         }
-        
+
         $orig = clone($msg);
         $msg->uri = common_local_url('showmessage', array('message' => $msg->id));
-        
+
         $result = $msg->update($orig);
-        
+
         if (!$result) {
             common_log_db_error($msg, 'UPDATE', __FILE__);
             return _('Could not update message with new URI.');
         }
-        
+
         return $msg;
     }
+
+    static function maxContent()
+    {
+        $desclimit = common_config('message', 'contentlimit');
+        // null => use global limit (distinct from 0!)
+        if (is_null($desclimit)) {
+            $desclimit = common_config('site', 'textlimit');
+        }
+        return $desclimit;
+    }
+
+    static function contentTooLong($content)
+    {
+        $contentlimit = self::maxContent();
+        return ($contentlimit > 0 && !empty($content) && (mb_strlen($content) > $contentlimit));
+    }
 }
index 7d050262674265f13dfe0cbeb577aa1a41e62c9a..cd3906ca17fb2a7f201056495ac9647392eb3beb 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-/*
+/**
  * StatusNet - the distributed open-source microblogging tool
  * Copyright (C) 2008, 2009, StatusNet, Inc.
  *
  *
  * 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 Notices
+ * @package  StatusNet
+ * @author   Brenda Wallace <shiny@cpan.org>
+ * @author   Christopher Vollick <psycotica0@gmail.com>
+ * @author   CiaranG <ciaran@ciarang.com>
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@controlezvous.ca>
+ * @author   Gina Haeussge <osd@foosel.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @author   Sarven Capadisli <csarven@controlyourself.ca>
+ * @author   Tom Adams <tom@holizz.com>
+ * @license  GNU Affero General Public License http://www.gnu.org/licenses/
  */
 
-if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
 
 /**
  * Table Definition for notice
@@ -40,7 +57,7 @@ class Notice extends Memcached_DataObject
     public $id;                              // int(4)  primary_key not_null
     public $profile_id;                      // int(4)   not_null
     public $uri;                             // varchar(255)  unique_key
-    public $content;                         // varchar(140)
+    public $content;                         // text()
     public $rendered;                        // text()
     public $url;                             // varchar(255)
     public $created;                         // datetime()   not_null
@@ -51,9 +68,7 @@ class Notice extends Memcached_DataObject
     public $conversation;                    // 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
@@ -75,17 +90,30 @@ class Notice extends Memcached_DataObject
         $this->blowFavesCache(true);
         $this->blowSubsCache(true);
 
+        // For auditing purposes, save a record that the notice
+        // was deleted.
+
+        $deleted = new Deleted_notice();
+
+        $deleted->id         = $this->id;
+        $deleted->profile_id = $this->profile_id;
+        $deleted->uri        = $this->uri;
+        $deleted->created    = $this->created;
+        $deleted->deleted    = common_sql_now();
+
         $this->query('BEGIN');
+
+        $deleted->insert();
+
         //Null any notices that are replies to this notice
         $this->query(sprintf("UPDATE notice set reply_to = null WHERE reply_to = %d", $this->id));
         $related = array('Reply',
                          'Fave',
                          'Notice_tag',
                          'Group_inbox',
-                         'Queue_item');
-        if (common_config('inboxes', 'enabled')) {
-            $related[] = 'Notice_inbox';
-        }
+                         'Queue_item',
+                         'Notice_inbox');
+
         foreach ($related as $cls) {
             $inst = new $cls();
             $inst->notice_id = $this->id;
@@ -140,31 +168,31 @@ class Notice extends Memcached_DataObject
 
         $final = common_shorten_links($content);
 
-        if (mb_strlen($final) > 140) {
-            common_log(LOG_INFO, 'Rejecting notice that is too long.');
-            return _('Problem saving notice. Too long.');
+        if (Notice::contentTooLong($final)) {
+            throw new ClientException(_('Problem saving notice. Too long.'));
         }
 
         if (!$profile) {
-            common_log(LOG_ERR, 'Problem saving notice. Unknown user.');
-            return _('Problem saving notice. Unknown user.');
+            throw new ClientException(_('Problem saving notice. Unknown user.'));
         }
 
         if (common_config('throttle', 'enabled') && !Notice::checkEditThrottle($profile_id)) {
             common_log(LOG_WARNING, 'Excessive posting by profile #' . $profile_id . '; throttled.');
-            return _('Too many notices too fast; take a breather and post again in a few minutes.');
+            throw new ClientException(_('Too many notices too fast; take a breather '.
+                                        'and post again in a few minutes.'));
         }
 
         if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $final)) {
             common_log(LOG_WARNING, 'Dupe posting by profile #' . $profile_id . '; throttled.');
-                       return _('Too many duplicate messages too quickly; take a breather and post again in a few minutes.');
+            throw new ClientException(_('Too many duplicate messages too quickly;'.
+                                        ' take a breather and post again in a few minutes.'));
         }
 
-               $banned = common_config('profile', 'banned');
+        $banned = common_config('profile', 'banned');
 
         if ( in_array($profile_id, $banned) || in_array($profile->nickname, $banned)) {
             common_log(LOG_WARNING, "Attempted post from banned user: $profile->nickname (user id = $profile_id).");
-            return _('You are banned from posting notices on this site.');
+            throw new ClientException(_('You are banned from posting notices on this site.'));
         }
 
         $notice = new Notice();
@@ -188,12 +216,12 @@ class Notice extends Memcached_DataObject
             $notice->created = common_sql_now();
         }
 
-               $notice->content = $final;
-               $notice->rendered = common_render_content($final, $notice);
-               $notice->source = $source;
-               $notice->uri = $uri;
+        $notice->content = $final;
+        $notice->rendered = common_render_content($final, $notice);
+        $notice->source = $source;
+        $notice->uri = $uri;
 
-               $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final);
+        $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final);
 
         if (!empty($notice->reply_to)) {
             $reply = Notice::staticGet('id', $notice->reply_to);
@@ -210,7 +238,7 @@ class Notice extends Memcached_DataObject
 
             if (!$id) {
                 common_log_db_error($notice, 'INSERT', __FILE__);
-                return _('Problem saving notice.');
+                throw new ServerException(_('Problem saving notice.'));
             }
 
             // Update ID-dependent columns: URI, conversation
@@ -235,7 +263,7 @@ class Notice extends Memcached_DataObject
             if ($changed) {
                 if (!$notice->update($orig)) {
                     common_log_db_error($notice, 'UPDATE', __FILE__);
-                    return _('Problem saving notice.');
+                    throw new ServerException(_('Problem saving notice.'));
                 }
             }
 
@@ -854,66 +882,61 @@ class Notice extends Memcached_DataObject
 
     function addToInboxes()
     {
-        $enabled = common_config('inboxes', 'enabled');
+        // XXX: loads constants
 
-        if ($enabled === true || $enabled === 'transitional') {
+        $inbox = new Notice_inbox();
 
-            // XXX: loads constants
+        $users = $this->getSubscribedUsers();
 
-            $inbox = new Notice_inbox();
+        // FIXME: kind of ignoring 'transitional'...
+        // we'll probably stop supporting inboxless mode
+        // in 0.9.x
 
-            $users = $this->getSubscribedUsers();
+        $ni = array();
 
-            // FIXME: kind of ignoring 'transitional'...
-            // we'll probably stop supporting inboxless mode
-            // in 0.9.x
+        foreach ($users as $id) {
+            $ni[$id] = NOTICE_INBOX_SOURCE_SUB;
+        }
 
-            $ni = array();
+        $groups = $this->saveGroups();
 
+        foreach ($groups as $group) {
+            $users = $group->getUserMembers();
             foreach ($users as $id) {
-                $ni[$id] = NOTICE_INBOX_SOURCE_SUB;
-            }
-
-            $groups = $this->saveGroups();
-
-            foreach ($groups as $group) {
-                $users = $group->getUserMembers();
-                foreach ($users as $id) {
-                    if (!array_key_exists($id, $ni)) {
-                        $ni[$id] = NOTICE_INBOX_SOURCE_GROUP;
-                    }
+                if (!array_key_exists($id, $ni)) {
+                    $ni[$id] = NOTICE_INBOX_SOURCE_GROUP;
                 }
             }
+        }
 
-            $cnt = 0;
-
-            $qryhdr = 'INSERT INTO notice_inbox (user_id, notice_id, source, created) VALUES ';
-            $qry = $qryhdr;
+        $cnt = 0;
 
-            foreach ($ni as $id => $source) {
-                if ($cnt > 0) {
-                    $qry .= ', ';
-                }
-                $qry .= '('.$id.', '.$this->id.', '.$source.", '".$this->created. "') ";
-                $cnt++;
-                if (rand() % NOTICE_INBOX_SOFT_LIMIT == 0) {
-                    // FIXME: Causes lag in replicated servers
-                    // Notice_inbox::gc($id);
-                }
-                if ($cnt >= MAX_BOXCARS) {
-                    $inbox = new Notice_inbox();
-                    $inbox->query($qry);
-                    $qry = $qryhdr;
-                    $cnt = 0;
-                }
-            }
+        $qryhdr = 'INSERT INTO notice_inbox (user_id, notice_id, source, created) VALUES ';
+        $qry = $qryhdr;
 
+        foreach ($ni as $id => $source) {
             if ($cnt > 0) {
+                $qry .= ', ';
+            }
+            $qry .= '('.$id.', '.$this->id.', '.$source.", '".$this->created. "') ";
+            $cnt++;
+            if (rand() % NOTICE_INBOX_SOFT_LIMIT == 0) {
+                // FIXME: Causes lag in replicated servers
+                // Notice_inbox::gc($id);
+            }
+            if ($cnt >= MAX_BOXCARS) {
                 $inbox = new Notice_inbox();
                 $inbox->query($qry);
+                $qry = $qryhdr;
+                $cnt = 0;
             }
         }
 
+        if ($cnt > 0) {
+            $inbox = new Notice_inbox();
+            $inbox->query($qry);
+        }
+
         return;
     }
 
@@ -948,11 +971,6 @@ class Notice extends Memcached_DataObject
     {
         $groups = array();
 
-        $enabled = common_config('inboxes', 'enabled');
-        if ($enabled !== true && $enabled !== 'transitional') {
-            return $groups;
-        }
-
         /* extract all !group */
         $count = preg_match_all('/(?:^|\s)!([A-Za-z0-9]{1,64})/',
                                 strtolower($this->content),
@@ -1181,10 +1199,11 @@ class Notice extends Memcached_DataObject
         $attachments = $this->attachments();
         if($attachments){
             foreach($attachments as $attachment){
-                if ($attachment->isEnclosure()) {
-                    $attributes = array('rel'=>'enclosure','href'=>$attachment->url,'type'=>$attachment->mimetype,'length'=>$attachment->size);
-                    if($attachment->title){
-                        $attributes['title']=$attachment->title;
+                $enclosure=$attachment->getEnclosure();
+                if ($enclosure) {
+                    $attributes = array('rel'=>'enclosure','href'=>$enclosure->url,'type'=>$enclosure->mimetype,'length'=>$enclosure->size);
+                    if($enclosure->title){
+                        $attributes['title']=$enclosure->title;
                     }
                     $xs->element('link', $attributes, null);
                 }
@@ -1335,4 +1354,20 @@ class Notice extends Memcached_DataObject
             return $last->id;
         }
     }
+
+    static function maxContent()
+    {
+        $contentlimit = common_config('notice', 'contentlimit');
+        // null => use global limit (distinct from 0!)
+        if (is_null($contentlimit)) {
+            $contentlimit = common_config('site', 'textlimit');
+        }
+        return $contentlimit;
+    }
+
+    static function contentTooLong($content)
+    {
+        $contentlimit = self::maxContent();
+        return ($contentlimit > 0 && !empty($content) && (mb_strlen($content) > $contentlimit));
+    }
 }
index 8385ebf8890bb5e6f7a229b0d059dfedfbed2168..4a069ee84eb6141465d27f2a9ac205432d41cfd5 100644 (file)
@@ -35,14 +35,13 @@ class Profile extends Memcached_DataObject
     public $fullname;                        // varchar(255)  multiple_key
     public $profileurl;                      // varchar(255)
     public $homepage;                        // varchar(255)  multiple_key
-    public $bio;                             // varchar(140)  multiple_key
+    public $bio;                             // text()  multiple_key
     public $location;                        // varchar(255)  multiple_key
     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
@@ -462,6 +461,22 @@ class Profile extends Memcached_DataObject
         }
     }
 
+    static function maxBio()
+    {
+        $biolimit = common_config('profile', 'biolimit');
+        // null => use global limit (distinct from 0!)
+        if (is_null($biolimit)) {
+            $biolimit = common_config('site', 'textlimit');
+        }
+        return $biolimit;
+    }
+
+    static function bioTooLong($bio)
+    {
+        $biolimit = self::maxBio();
+        return ($biolimit > 0 && !empty($bio) && (mb_strlen($bio) > $biolimit));
+    }
+
     function delete()
     {
         $this->_deleteNotices();
index 007662131c70a0a44702f1caa114e38f680d1ba5..0a70c9801402f8038d1dc59bb72bf0204ce1f3c7 100644 (file)
@@ -103,10 +103,7 @@ class User extends Memcached_DataObject
         }
         $toupdate = implode(', ', $parts);
 
-        $table = $this->tableName();
-        if(common_config('db','quote_identifiers')) {
-            $table = '"' . $table . '"';
-        }
+        $table = common_database_tablename($this->tableName());
         $qry = 'UPDATE ' . $table . ' SET ' . $toupdate .
           ' WHERE id = ' . $this->id;
         $orig->decache();
@@ -230,11 +227,9 @@ class User extends Memcached_DataObject
             }
         }
 
-        $inboxes = common_config('inboxes', 'enabled');
+        // This flag is ignored but still set to 1
 
-        if ($inboxes === true || $inboxes == 'transitional') {
-            $user->inboxed = 1;
-        }
+        $user->inboxed = 1;
 
         $user->created = common_sql_now();
         $user->uri = common_user_uri($user);
@@ -436,55 +431,16 @@ class User extends Memcached_DataObject
 
     function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
     {
-        $enabled = common_config('inboxes', 'enabled');
-
-        // Complicated code, depending on whether we support inboxes yet
-        // XXX: make this go away when inboxes become mandatory
-
-        if ($enabled === false ||
-            ($enabled == 'transitional' && $this->inboxed == 0)) {
-            $qry =
-              'SELECT notice.* ' .
-              'FROM notice JOIN subscription ON notice.profile_id = subscription.subscribed ' .
-              'WHERE subscription.subscriber = %d ' .
-              'AND notice.is_local != ' . Notice::GATEWAY;
-            return Notice::getStream(sprintf($qry, $this->id),
-                                     'user:notices_with_friends:' . $this->id,
-                                     $offset, $limit, $since_id, $before_id,
-                                     $order, $since);
-        } else if ($enabled === true ||
-                   ($enabled == 'transitional' && $this->inboxed == 1)) {
+        $ids = Notice_inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, false);
 
-            $ids = Notice_inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, false);
-
-            return Notice::getStreamByIds($ids);
-        }
+        return Notice::getStreamByIds($ids);
     }
 
     function noticeInbox($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
     {
-        $enabled = common_config('inboxes', 'enabled');
+        $ids = Notice_inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, true);
 
-        // Complicated code, depending on whether we support inboxes yet
-        // XXX: make this go away when inboxes become mandatory
-
-        if ($enabled === false ||
-            ($enabled == 'transitional' && $this->inboxed == 0)) {
-            $qry =
-              'SELECT notice.* ' .
-              'FROM notice JOIN subscription ON notice.profile_id = subscription.subscribed ' .
-              'WHERE subscription.subscriber = %d ';
-            return Notice::getStream(sprintf($qry, $this->id),
-                                     'user:notices_with_friends:' . $this->id,
-                                     $offset, $limit, $since_id, $before_id,
-                                     $order, $since);
-        } else if ($enabled === true ||
-                   ($enabled == 'transitional' && $this->inboxed == 1)) {
-
-            $ids = Notice_inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, true);
-
-            return Notice::getStreamByIds($ids);
-        }
+        return Notice::getStreamByIds($ids);
     }
 
     function blowFavesCache()
@@ -634,11 +590,7 @@ class User extends Memcached_DataObject
           'ORDER BY subscription.created DESC ';
 
         if ($offset) {
-            if (common_config('db','type') == 'pgsql') {
-                $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
-            } else {
-                $qry .= ' LIMIT ' . $offset . ', ' . $limit;
-            }
+            $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
         }
 
         $profile = new Profile();
@@ -661,11 +613,7 @@ class User extends Memcached_DataObject
           'AND subscription.subscribed != subscription.subscriber ' .
           'ORDER BY subscription.created DESC ';
 
-        if (common_config('db','type') == 'pgsql') {
-            $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
-        } else {
-            $qry .= ' LIMIT ' . $offset . ', ' . $limit;
-        }
+        $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
 
         $profile = new Profile();
 
@@ -674,20 +622,82 @@ class User extends Memcached_DataObject
         return $profile;
     }
 
-    function hasOpenID()
+    function getDesign()
     {
-        $oid = new User_openid();
+        return Design::staticGet('id', $this->design_id);
+    }
 
-        $oid->user_id = $this->id;
+    function hasRole($name)
+    {
+        $role = User_role::pkeyGet(array('user_id' => $this->id,
+                                         'role' => $name));
+        return (!empty($role));
+    }
 
-        $cnt = $oid->find();
+    function grantRole($name)
+    {
+        $role = new User_role();
+
+        $role->user_id = $this->id;
+        $role->role    = $name;
+        $role->created = common_sql_now();
+
+        $result = $role->insert();
+
+        if (!$result) {
+            common_log_db_error($role, 'INSERT', __FILE__);
+            return false;
+        }
 
-        return ($cnt > 0);
+        return true;
     }
 
-    function getDesign()
+    function revokeRole($name)
     {
-        return Design::staticGet('id', $this->design_id);
+        $role = User_role::pkeyGet(array('user_id' => $this->id,
+                                         'role' => $name));
+
+        if (empty($role)) {
+            throw new Exception('Cannot revoke role "'.$name.'" for user #'.$this->id.'; does not exist.');
+        }
+
+        $result = $role->delete();
+
+        if (!$result) {
+            common_log_db_error($role, 'DELETE', __FILE__);
+            throw new Exception('Cannot revoke role "'.$name.'" for user #'.$this->id.'; database error.');
+        }
+
+        return true;
+    }
+
+    /**
+     * Does this user have the right to do X?
+     *
+     * With our role-based authorization, this is merely a lookup for whether the user
+     * has a particular role. The implementation currently uses a switch statement
+     * to determine if the user has the pre-defined role to exercise the right. Future
+     * implementations may allow per-site roles, and different mappings of roles to rights.
+     *
+     * @param $right string Name of the right, usually a constant in class Right
+     * @return boolean whether the user has the right in question
+     */
+
+    function hasRight($right)
+    {
+        $result = false;
+        if (Event::handle('UserRightsCheck', array($this, $right, &$result))) {
+            switch ($right)
+            {
+             case Right::deleteOthersNotice:
+                $result = $this->hasRole('moderator');
+                break;
+             default:
+                $result = false;
+                break;
+            }
+        }
+        return $result;
     }
 
     function delete()
@@ -701,12 +711,9 @@ class User extends Memcached_DataObject
                          'Remember_me',
                          'Foreign_link',
                          'Invitation',
+                         'Notice_inbox',
                          );
 
-        if (common_config('inboxes', 'enabled')) {
-            $related[] = 'Notice_inbox';
-        }
-
         foreach ($related as $cls) {
             $inst = new $cls();
             $inst->user_id = $this->id;
index ea19cbb97acff0b2a752ebc1793d02e81a94903b..310ecff1ef766726197eaa06457b6a3c5cfcc405 100644 (file)
@@ -13,7 +13,7 @@ class User_group extends Memcached_DataObject
     public $nickname;                        // varchar(64)  unique_key
     public $fullname;                        // varchar(255)
     public $homepage;                        // varchar(255)
-    public $description;                     // varchar(140)
+    public $description;                     // text()
     public $location;                        // varchar(255)
     public $original_logo;                   // varchar(255)
     public $homepage_logo;                   // varchar(255)
@@ -298,6 +298,22 @@ class User_group extends Memcached_DataObject
         return $ids;
     }
 
+    static function maxDescription()
+    {
+        $desclimit = common_config('group', 'desclimit');
+        // null => use global limit (distinct from 0!)
+        if (is_null($desclimit)) {
+            $desclimit = common_config('site', 'textlimit');
+        }
+        return $desclimit;
+    }
+
+    static function descriptionTooLong($desc)
+    {
+        $desclimit = self::maxDescription();
+        return ($desclimit > 0 && !empty($desc) && (mb_strlen($desc) > $desclimit));
+    }
+
     function asAtomEntry($namespace=false, $source=false)
     {
         $xs = new XMLStringer(true);
diff --git a/classes/User_openid.php b/classes/User_openid.php
deleted file mode 100644 (file)
index f4fda1c..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-/**
- * Table Definition for user_openid
- */
-require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
-
-class User_openid extends Memcached_DataObject 
-{
-    ###START_AUTOCODE
-    /* the code below is auto generated do not remove the above tag */
-
-    public $__table = 'user_openid';                     // table name
-    public $canonical;                       // varchar(255)  primary_key not_null
-    public $display;                         // varchar(255)  unique_key not_null
-    public $user_id;                         // int(4)   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',$k,$v); }
-
-    /* the code above is auto generated do not remove the tag below */
-    ###END_AUTOCODE
-}
diff --git a/classes/User_role.php b/classes/User_role.php
new file mode 100644 (file)
index 0000000..85ecfb4
--- /dev/null
@@ -0,0 +1,48 @@
+<?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); }
+
+/**
+ * Table Definition for user_role
+ */
+
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class User_role extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'user_role';                       // table name
+    public $user_id;                         // int(4)  primary_key not_null
+    public $role;                            // varchar(32)  primary_key not_null
+    public $created;                         // datetime()   not_null
+
+    /* Static get */
+    function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User_role',$k,$v); }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+
+    function &pkeyGet($kv)
+    {
+        return Memcached_DataObject::pkeyGet('User_role', $kv);
+    }
+}
index 766bed75deff2a8af0134b0c6773dac420895844..453981cd64f7ad34f1c74891b1e734330ad100e3 100644 (file)
@@ -1,4 +1,3 @@
-
 [avatar]
 profile_id = 129
 original = 17
@@ -16,6 +15,15 @@ width = K
 height = K
 url = U
 
+[config]
+section = 130
+setting = 130
+value = 2
+
+[config__keys]
+section = K
+setting = K
+
 [confirm_address]
 code = 130
 user_id = 129
@@ -38,6 +46,17 @@ modified = 384
 [consumer__keys]
 consumer_key = K
 
+[deleted_notice]
+id = 129
+profile_id = 129
+uri = 2
+created = 142
+deleted = 142
+
+[deleted_notice__keys]
+id = K
+uri = U
+
 [design]
 id = 129
 backgroundcolor = 1
@@ -78,6 +97,7 @@ id = N
 file_id = 129
 version = 2
 type = 2
+mimetype = 2
 provider = 2
 provider_url = 2
 width = 1
@@ -228,7 +248,7 @@ id = 129
 uri = 2
 from_profile = 129
 to_profile = 129
-content = 2
+content = 34
 rendered = 34
 url = 2
 created = 142
@@ -255,7 +275,7 @@ ts = K
 id = 129
 profile_id = 129
 uri = 2
-content = 2
+content = 34
 rendered = 34
 url = 2
 created = 142
@@ -303,7 +323,7 @@ nickname = 130
 fullname = 2
 profileurl = 2
 homepage = 2
-bio = 2
+bio = 34
 location = 2
 created = 142
 modified = 384
@@ -475,7 +495,7 @@ id = 129
 nickname = 2
 fullname = 2
 homepage = 2
-description = 2
+description = 34
 location = 2
 original_logo = 2
 homepage_logo = 2
@@ -498,3 +518,12 @@ modified = 384
 [user_openid__keys]
 canonical = K
 display = U
+
+[user_role]
+user_id = 129
+role = 130
+created = 142
+
+[user_role__keys]
+user_id = K
+role = K
index bd3776a47f4a0620c4a3a3c02e588f58af3d911a..997c9d6b0bccb971157c6cc50f5689fc17419f87 100644 (file)
@@ -38,8 +38,6 @@ $config['site']['path'] = 'statusnet';
 // $config['site']['closed'] = true;
 // Only allow registration for people invited by another user
 // $config['site']['inviteonly'] = true;
-// Only allow registrations and logins through OpenID
-// $config['site']['openidonly'] = true;
 // Make the site invisible to  non-logged-in users
 // $config['site']['private'] = true;
 
@@ -99,9 +97,6 @@ $config['sphinx']['port'] = 3312;
 // $config['xmpp']['public'][] = 'someindexer@example.net';
 // $config['xmpp']['debug'] = false;
 
-// Disable OpenID
-// $config['openid']['enabled'] = false;
-
 // Turn off invites
 // $config['invite']['enabled'] = false;
 
diff --git a/db/08to09.sql b/db/08to09.sql
new file mode 100644 (file)
index 0000000..953e0e5
--- /dev/null
@@ -0,0 +1,34 @@
+alter table notice
+     modify column content text comment 'update content';
+
+alter table message
+     modify column content text comment 'message content';
+
+alter table profile
+     modify column bio text comment 'descriptive biography';
+
+alter table user_group
+     modify column description text comment 'group description';
+
+alter table file_oembed
+     add column mimetype varchar(50) comment 'mime type of resource';
+
+create table config (
+
+    section varchar(32) comment 'configuration section',
+    setting varchar(32) comment 'configuration setting',
+    value varchar(255) comment 'configuration value',
+
+    constraint primary key (section, setting)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table user_role (
+
+    user_id integer not null comment 'user having the role' references user (id),
+    role    varchar(32) not null comment 'string representing the role',
+    created datetime not null comment 'date the role was granted',
+
+    constraint primary key (user_id, role)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
diff --git a/db/08to09_pg.sql b/db/08to09_pg.sql
new file mode 100644 (file)
index 0000000..9e37314
--- /dev/null
@@ -0,0 +1,40 @@
+-- SQL commands to update an 0.8.x version of Laconica
+-- to 0.9.x.
+
+--these are just comments
+/*
+alter table notice
+     modify column content text comment 'update content';
+
+alter table message
+     modify column content text comment 'message content';
+
+alter table profile
+     modify column bio text comment 'descriptive biography';
+
+alter table user_group
+     modify column description text comment 'group description';
+*/
+
+alter table file_oembed
+     add column mimetype varchar(50) /*comment 'mime type of resource'*/;
+
+create table config (
+
+    section varchar(32) /* comment 'configuration section'*/,
+    setting varchar(32) /* comment 'configuration setting'*/,
+    value varchar(255) /* comment 'configuration value'*/,
+
+    primary key (section, setting)
+
+);
+
+create table user_role (
+
+    user_id integer not null /* comment 'user having the role'*/ references "user" (id),
+    role    varchar(32) not null /* comment 'string representing the role'*/,
+    created timestamp /* not null comment 'date the role was granted'*/,
+
+    primary key (user_id, role)
+
+);
index 2c04f680a85d587a032cd06fada5c63c8488eaa5..dfcddb643c8b8004d1043c73053329c69c45852a 100644 (file)
@@ -6,7 +6,7 @@ create table profile (
     fullname varchar(255) comment 'display name',
     profileurl varchar(255) comment 'URL, cached so we dont regenerate',
     homepage varchar(255) comment 'identifying URL',
-    bio varchar(140) comment 'descriptive biography',
+    bio text comment 'descriptive biography',
     location varchar(255) comment 'physical location',
     created datetime not null comment 'date this record was created',
     modified timestamp comment 'date this record was modified',
@@ -110,7 +110,7 @@ create table notice (
     id integer auto_increment primary key comment 'unique identifier',
     profile_id integer not null comment 'who made the update' references profile (id),
     uri varchar(255) unique key comment 'universally unique identifier, usually a tag URI',
-    content varchar(140) comment 'update content',
+    content text comment 'update content',
     rendered text comment 'HTML version of the content',
     url varchar(255) comment 'URL of any attachment (image, video, bookmark, whatever)',
     created datetime not null comment 'date this record was created',
@@ -195,18 +195,6 @@ create table nonce (
     constraint primary key (consumer_key, ts, nonce)
 ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
 
-/* One-to-many relationship of user to openid_url */
-
-create table user_openid (
-    canonical varchar(255) primary key comment 'Canonical true URL',
-    display varchar(255) not null unique key comment 'URL for viewing, may be different from canonical',
-    user_id integer not null comment 'user owning this URL' references user (id),
-    created datetime not null comment 'date this record was created',
-    modified timestamp comment 'date this record was modified',
-
-    index user_openid_user_id_idx (user_id)
-) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
-
 /* These are used by JanRain OpenID library */
 
 create table oid_associations (
@@ -331,7 +319,7 @@ create table message (
     uri varchar(255) unique key comment 'universally unique identifier',
     from_profile integer not null comment 'who the message is from' references profile (id),
     to_profile integer not null comment 'who the message is to' references profile (id),
-    content varchar(140) comment 'message content',
+    content text comment 'message content',
     rendered text comment 'HTML version of the content',
     url varchar(255) comment 'URL of any attachment (image, video, bookmark, whatever)',
     created datetime not null comment 'date this record was created',
@@ -380,7 +368,7 @@ create table user_group (
     nickname varchar(64) unique key comment 'nickname for addressing',
     fullname varchar(255) comment 'display name',
     homepage varchar(255) comment 'URL, cached so we dont regenerate',
-    description varchar(140) comment 'descriptive biography',
+    description text comment 'group description',
     location varchar(255) comment 'related physical location, if any',
 
     original_logo varchar(255) comment 'original size logo',
@@ -450,6 +438,7 @@ create table file_oembed (
     file_id integer primary key comment 'oEmbed for that URL/file' references file (id),
     version varchar(20) comment 'oEmbed spec. version',
     type varchar(20) comment 'oEmbed type: photo, video, link, rich',
+    mimetype varchar(50) comment 'mime type of resource',
     provider varchar(50) comment 'name of this oEmbed provider',
     provider_url varchar(255) comment 'URL of this oEmbed provider',
     width integer comment 'width of oEmbed resource when available',
@@ -534,4 +523,36 @@ create table session (
 
     index session_modified_idx (modified)
 
-) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
\ No newline at end of file
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table deleted_notice (
+
+    id integer primary key comment 'identity of notice',
+    profile_id integer not null comment 'author of the notice',
+    uri varchar(255) unique key comment 'universally unique identifier, usually a tag URI',
+    created datetime not null comment 'date the notice record was created',
+    deleted datetime not null comment 'date the notice record was created',
+
+    index deleted_notice_profile_id_idx (profile_id)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table config (
+
+    section varchar(32) comment 'configuration section',
+    setting varchar(32) comment 'configuration setting',
+    value varchar(255) comment 'configuration value',
+
+    constraint primary key (section, setting)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table user_role (
+
+    user_id integer not null comment 'user having the role' references user (id),
+    role    varchar(32) not null comment 'string representing the role',
+    created datetime not null comment 'date the role was granted',
+
+    constraint primary key (user_id, role)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
index ad34720a23357e405c99a7ff433303385d4df041..672877ddf0bac25ba3bdfdbcb0c18e8a14460470 100644 (file)
@@ -465,6 +465,7 @@ create table file_oembed (
     file_id bigint default nextval('file_oembed_seq') primary key /* comment 'unique identifier' */,
     version varchar(20),
     type varchar(20),
+    mimetype varchar(50), 
     provider varchar(50),
     provider_url varchar(255),
     width integer,
@@ -529,6 +530,17 @@ create table session (
 
 create index session_modified_idx on session (modified);
 
+create table deleted_notice (
+
+    id integer primary key /* comment 'identity of notice'*/ ,
+    profile_id integer /* not null comment 'author of the notice'*/,
+    uri varchar(255) unique /* comment 'universally unique identifier, usually a tag URI'*/,
+    created timestamp not null  /* comment 'date the notice record was created'*/ ,
+    deleted timestamp not null DEFAULT CURRENT_TIMESTAMP /* comment 'date the notice record was created'*/
+);
+
+CREATE index deleted_notice_profile_id_idx on deleted_notice (profile_id);
+
 
 /* Textsearch stuff */
 
@@ -537,3 +549,23 @@ create index noticecontent_idx on notice using gist(to_tsvector('english',conten
 create trigger textsearchupdate before insert or update on profile for each row
 execute procedure tsvector_update_trigger(textsearch, 'pg_catalog.english', nickname, fullname, location, bio, homepage);
 
+
+create table config (
+
+    section varchar(32) /* comment 'configuration section'*/,
+    setting varchar(32) /* comment 'configuration setting'*/,
+    value varchar(255) /* comment 'configuration value'*/,
+
+    primary key (section, setting)
+
+);
+
+create table user_role (
+
+    user_id integer not null /* comment 'user having the role'*/ references "user" (id),
+    role    varchar(32) not null /* comment 'string representing the role'*/,
+    created timestamp /* not null comment 'date the role was granted'*/,
+
+    primary key (user_id, role)
+
+);
index 8d7acf63b4738e65bfe53fc98b43515bf322b4a5..93300ab242e3c517cc4f25b54d5c3ad898ef7e9c 100644 (file)
@@ -26,7 +26,6 @@ Here are some documents that you might find helpful in understanding
 * [SMS](%%doc.sms%%) - tying your cellphone to %%site.name%%
 * [tags](%%doc.tags%%) - different ways to use tagging
 * [Groups](%%doc.groups%%) - joining together in groups
-* [OpenID](%%doc.openid%%) - what OpenID is and how to use it with this service
 * [OpenMicroBlogging](%%doc.openmublog%%) - subscribing to remote users
 * [Privacy](%%doc.privacy%%) - %%site.name%%'s privacy policy
 * [Source](%%doc.source%%) - How to get the StatusNet source code
diff --git a/doc-src/openid b/doc-src/openid
deleted file mode 100644 (file)
index c741e36..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-%%site.name%% supports the [OpenID](http://openid.net/) standard for single signon between Web sites. OpenID lets you log into many different Web sites without using a different password for each. (See [Wikipedia's OpenID article](http://en.wikipedia.org/wiki/OpenID) for more information.)
-
-If you already have an account on %%site.name%%, you can [login](%%action.login%%) with your username and password as usual.
-To use OpenID in the future, you can [add an OpenID to your account](%%action.openidsettings%%) after you have logged in normally.
-
-There are many [Public OpenID providers](http://wiki.openid.net/Public_OpenID_providers), and you may already have an OpenID-enabled account on another service.
-
-* On wikis: If you have an account on an OpenID-enabled wiki, like [Wikitravel](http://wikitravel.org/), [wikiHow](http://www.wikihow.com/), [Vinismo](http://vinismo.com/), [AboutUs](http://aboutus.org/) or [Keiki](http://kei.ki/), you can log in to %%site.name%% by entering the **full URL** of your user page on that other wiki in the box above. For example, *http://kei.ki/en/User:Evan*.
-* [Yahoo!](http://openid.yahoo.com/) : If you have an account with Yahoo!, you can log in to this site by entering your Yahoo!-provided OpenID in the box above. Yahoo! OpenID URLs have the form *https://me.yahoo.com/yourusername*.
-* [AOL](http://dev.aol.com/aol-and-63-million-openids) : If you have an account with [AOL](http://www.aol.com/), like an [AIM](http://www.aim.com/) account, you can log in to %%site.name%% by entering your AOL-provided OpenID in the box above. AOL OpenID URLs have the form *http://openid.aol.com/yourusername*. Your username should be all lowercase, no spaces.
-* [Blogger](http://bloggerindraft.blogspot.com/2008/01/new-feature-blogger-as-openid-provider.html), [Wordpress.com](http://faq.wordpress.com/2007/03/06/what-is-openid/), [LiveJournal](http://www.livejournal.com/openid/about.bml), [Vox](http://bradfitz.vox.com/library/post/openid-for-vox.html) : If you have a blog on any of these services, enter your blog URL in the box above. For example, *http://yourusername.blogspot.com/*, *http://yourusername.wordpress.com/*, *http://yourusername.livejournal.com/*, or *http://yourusername.vox.com/*.
diff --git a/extlib/libomb/base_url_xrds_mapper.php b/extlib/libomb/base_url_xrds_mapper.php
new file mode 100755 (executable)
index 0000000..6454595
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+require_once 'xrds_mapper.php';
+require_once 'constants.php';
+
+/**
+ * Map XRDS actions to URLs using base URLs.
+ *
+ * This interface specifies classes which write the XRDS file announcing
+ * the OMB server. An instance of an implementing class should be passed to
+ * OMB_Service_Provider->writeXRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Base_URL_XRDS_Mapper implements OMB_XRDS_Mapper {
+
+  protected $urls;
+
+  public function __construct($oauth_base, $omb_base) {
+    $this->urls = array(
+        OAUTH_ENDPOINT_REQUEST => $oauth_base . 'requesttoken',
+        OAUTH_ENDPOINT_AUTHORIZE => $oauth_base . 'userauthorization',
+        OAUTH_ENDPOINT_ACCESS => $oauth_base . 'accesstoken',
+        OMB_ENDPOINT_POSTNOTICE => $omb_base . 'postnotice',
+        OMB_ENDPOINT_UPDATEPROFILE => $omb_base . 'updateprofile');
+  }
+
+  public function getURL($action) {
+    return $this->urls[$action];
+  }
+}
+?>
diff --git a/extlib/libomb/constants.php b/extlib/libomb/constants.php
new file mode 100644 (file)
index 0000000..a097443
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Constants for libomb
+ *
+ * This file contains constant definitions for libomb. The defined constants
+ * are service and namespace URIs for OAuth and OMB as used in XRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+/**
+ * The OMB constants.
+ **/
+
+define('OMB_VERSION_01', 'http://openmicroblogging.org/protocol/0.1');
+
+/* The OMB version supported by this libomb version. */
+define('OMB_VERSION', OMB_VERSION_01);
+
+define('OMB_ENDPOINT_UPDATEPROFILE', OMB_VERSION . '/updateProfile');
+define('OMB_ENDPOINT_POSTNOTICE', OMB_VERSION . '/postNotice');
+
+/**
+ * The OAuth constants.
+ **/
+
+define('OAUTH_NAMESPACE', 'http://oauth.net/core/1.0/');
+
+define('OAUTH_ENDPOINT_REQUEST', OAUTH_NAMESPACE.'endpoint/request');
+define('OAUTH_ENDPOINT_AUTHORIZE', OAUTH_NAMESPACE.'endpoint/authorize');
+define('OAUTH_ENDPOINT_ACCESS', OAUTH_NAMESPACE.'endpoint/access');
+define('OAUTH_ENDPOINT_RESOURCE', OAUTH_NAMESPACE.'endpoint/resource');
+
+define('OAUTH_AUTH_HEADER', OAUTH_NAMESPACE.'parameters/auth-header');
+define('OAUTH_POST_BODY', OAUTH_NAMESPACE.'parameters/post-body');
+
+define('OAUTH_HMAC_SHA1', OAUTH_NAMESPACE.'signature/HMAC-SHA1');
+
+define('OAUTH_DISCOVERY', 'http://oauth.net/discovery/1.0');
+?>
diff --git a/extlib/libomb/datastore.php b/extlib/libomb/datastore.php
new file mode 100755 (executable)
index 0000000..ab52de5
--- /dev/null
@@ -0,0 +1,200 @@
+<?php
+
+require_once 'OAuth.php';
+
+/**
+ * Data access interface
+ *
+ * This interface specifies data access methods libomb needs. It should be
+ * implemented by libomb users. OMB_Datastore is libomb’s main interface to the
+ * application’s data. Objects corresponding to this interface are used in
+ * OMB_Service_Provider and OMB_Service_Consumer.
+ *
+ * Note that it’s implemented as a class since OAuthDataStore is as well a
+ * class, though only declaring methods.
+ *
+ * OMB_Datastore extends OAuthDataStore with two OAuth-related methods for token
+ * revoking and authorizing and all OMB-related methods.
+ * Refer to OAuth.php for a complete specification of OAuth-related methods.
+ *
+ * It is the user’s duty to signal and handle errors. libomb does not check
+ * return values nor handle exceptions. It is suggested to use exceptions.
+ * Note that lookup_token and getProfile return null if the requested object
+ * is not available. This is NOT an error and should not raise an exception.
+ * Same applies for lookup_nonce which returns a boolean value. These methods
+ * may nevertheless throw an exception, for example in case of a storage errors.
+ *
+ * Most of the parameters passed to these methods are unescaped and unverified
+ * user input. Therefore they should be handled with extra care to avoid
+ * security problems like SQL injections.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Datastore extends OAuthDataStore {
+
+  /*********
+   * OAUTH *
+   *********/
+
+  /**
+   * Revoke specified OAuth token
+   *
+   * Revokes the authorization token specified by $token_key.
+   * Throws exceptions in case of error.
+   *
+   * @param string $token_key The key of the token to be revoked
+   *
+   * @access public
+   **/
+  public function revoke_token($token_key) {
+    throw new Exception();
+  }
+
+  /**
+   * Authorize specified OAuth token
+   *
+   * Authorizes the authorization token specified by $token_key.
+   * Throws exceptions in case of error.
+   *
+   * @param string $token_key The key of the token to be authorized
+   *
+   * @access public
+   **/
+  public function authorize_token($token_key) {
+    throw new Exception();
+  }
+
+  /*********
+   *  OMB  *
+   *********/
+
+  /**
+   * Get profile by identifying URI
+   *
+   * Returns an OMB_Profile object representing the OMB profile identified by
+   * $identifier_uri.
+   * Returns null if there is no such OMB profile.
+   * Throws exceptions in case of other error.
+   *
+   * @param string $identifier_uri The OMB identifier URI specifying the
+   *                               requested profile
+   *
+   * @access public
+   *
+   * @return OMB_Profile The corresponding profile
+   **/
+  public function getProfile($identifier_uri) {
+    throw new Exception();
+  }
+
+  /**
+   * Save passed profile
+   *
+   * Stores the OMB profile $profile. Overwrites an existing entry.
+   * Throws exceptions in case of error.
+   *
+   * @param OMB_Profile $profile   The OMB profile which should be saved
+   *
+   * @access public
+   **/
+  public function saveProfile($profile) {
+    throw new Exception();
+  }
+
+  /**
+   * Save passed notice
+   *
+   * Stores the OMB notice $notice. The datastore may change the passed notice.
+   * This might by neccessary for URIs depending on a database key. Note that
+   * it is the user’s duty to present a mechanism for his OMB_Datastore to
+   * appropriately change his OMB_Notice. TODO: Ugly.
+   * Throws exceptions in case of error.
+   *
+   * @param OMB_Notice $notice The OMB notice which should be saved
+   *
+   * @access public
+   **/
+  public function saveNotice(&$notice) {
+    throw new Exception();
+  }
+
+  /**
+   * Get subscriptions of a given profile
+   *
+   * Returns an array containing subscription informations for the specified
+   * profile. Every array entry should in turn be an array with keys
+   *   'uri´: The identifier URI of the subscriber
+   *   'token´: The subscribe token
+   *   'secret´: The secret token
+   * Throws exceptions in case of error.
+   *
+   * @param string $subscribed_user_uri The OMB identifier URI specifying the
+   *                                    subscribed profile
+   *
+   * @access public
+   *
+   * @return mixed An array containing the subscriptions or 0 if no
+   *               subscription has been found.
+   **/
+  public function getSubscriptions($subscribed_user_uri) {
+    throw new Exception();
+  }
+
+  /**
+   * Delete a subscription
+   *
+   * Deletes the subscription from $subscriber_uri to $subscribed_user_uri.
+   * Throws exceptions in case of error.
+   *
+   * @param string $subscriber_uri      The OMB identifier URI specifying the
+   *                                    subscribing profile
+   *
+   * @param string $subscribed_user_uri The OMB identifier URI specifying the
+   *                                    subscribed profile
+   *
+   * @access public
+   **/
+  public function deleteSubscription($subscriber_uri, $subscribed_user_uri) {
+    throw new Exception();
+  }
+
+  /**
+   * Save a subscription
+   *
+   * Saves the subscription from $subscriber_uri to $subscribed_user_uri.
+   * Throws exceptions in case of error.
+   *
+   * @param string     $subscriber_uri      The OMB identifier URI specifying
+   *                                        the subscribing profile
+   *
+   * @param string     $subscribed_user_uri The OMB identifier URI specifying
+   *                                        the subscribed profile
+   * @param OAuthToken $token               The access token
+   *
+   * @access public
+   **/
+  public function saveSubscription($subscriber_uri, $subscribed_user_uri,
+                                                                       $token) {
+    throw new Exception();
+  }
+}
+?>
diff --git a/extlib/libomb/helper.php b/extlib/libomb/helper.php
new file mode 100644 (file)
index 0000000..a1f21f2
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+
+require_once 'Validate.php';
+
+/**
+ * Helper functions for libomb
+ *
+ * This file contains helper functions for libomb.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Helper {
+
+  /**
+   * Non-scalar constants
+   *
+   * The set of OMB and OAuth Services an OMB Server has to implement.
+   */
+
+  public static $OMB_SERVICES =
+    array(OMB_ENDPOINT_UPDATEPROFILE, OMB_ENDPOINT_POSTNOTICE);
+  public static $OAUTH_SERVICES =
+    array(OAUTH_ENDPOINT_REQUEST, OAUTH_ENDPOINT_AUTHORIZE, OAUTH_ENDPOINT_ACCESS);
+
+  /**
+   * Validate URL
+   *
+   * Basic URL validation. Currently http, https, ftp and gopher are supported
+   * schemes.
+   *
+   * @param string $url The URL which is to be validated.
+   *
+   * @return bool Whether URL is valid.
+   *
+   * @access public
+   */
+  public static function validateURL($url) {
+    return Validate::uri($url, array('allowed_schemes' => array('http', 'https',
+            'gopher', 'ftp')));
+  }
+
+  /**
+   * Validate Media type
+   *
+   * Basic Media type validation. Checks for valid maintype and correct format.
+   *
+   * @param string $mediatype The Media type which is to be validated.
+   *
+   * @return bool Whether media type is valid.
+   *
+   * @access public
+   */
+  public static function validateMediaType($mediatype) {
+    if (0 === preg_match('/^(\w+)\/([\w\d-+.]+)$/', $mediatype, $subtypes)) {
+      return false;
+    }
+    if (!in_array(strtolower($subtypes[1]), array('application', 'audio', 'image',
+              'message', 'model', 'multipart', 'text', 'video'))) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Remove escaping from request parameters
+   *
+   * Neutralise the evil effects of magic_quotes_gpc in the current request.
+   * This is used before handing a request off to OAuthRequest::from_request.
+   * Many thanks to Ciaran Gultnieks for this fix.
+   *
+   * @access public
+   */
+  public static function removeMagicQuotesFromRequest() {
+    if(get_magic_quotes_gpc() == 1) {
+      $_POST = array_map('stripslashes', $_POST);
+      $_GET = array_map('stripslashes', $_GET);
+    }
+  }
+}
+?>
diff --git a/extlib/libomb/invalidparameterexception.php b/extlib/libomb/invalidparameterexception.php
new file mode 100755 (executable)
index 0000000..163e1dd
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Exception stating that a passed parameter is invalid
+ *
+ * This exception is raised when a parameter does not obey the OMB standard.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+class OMB_InvalidParameterException extends Exception {
+  public function __construct($value, $type, $parameter) {
+    parent::__construct("Invalid value $value for parameter $parameter in $type");
+  }
+}
+?>
diff --git a/extlib/libomb/invalidyadisexception.php b/extlib/libomb/invalidyadisexception.php
new file mode 100755 (executable)
index 0000000..797b7b9
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Exception stating that a requested url does not resolve to a valid yadis
+ *
+ * This exception is raised when OMB_Service is not able to discover a valid
+ * yadis location with XRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+class OMB_InvalidYadisException extends Exception {
+
+}
+?>
diff --git a/extlib/libomb/notice.php b/extlib/libomb/notice.php
new file mode 100755 (executable)
index 0000000..9ac3664
--- /dev/null
@@ -0,0 +1,272 @@
+<?php
+require_once 'invalidparameterexception.php';
+require_once 'Validate.php';
+require_once 'helper.php';
+
+/**
+ * OMB Notice representation
+ *
+ * This class represents an OMB notice.
+ *
+ * Do not call the setters with null values. Instead, if you want to delete a
+ * field, pass an empty string. The getters will return null for empty fields.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Notice {
+  protected $author;
+  protected $uri;
+  protected $content;
+  protected $url;
+  protected $license_url; /* url is an own addition for clarification. */
+  protected $seealso_url; /* url is an own addition for clarification. */
+  protected $seealso_disposition;
+  protected $seealso_mediatype;
+  protected $seealso_license_url; /* url is an addition for clarification. */
+
+  /* The notice as OMB param array. Cached and rebuild on usage.
+     false while outdated. */
+  protected $param_array;
+
+  /**
+   * Constructor for OMB_Notice
+   *
+   * Initializes the OMB_Notice object with author, uri and content.
+   * These parameters are mandatory for postNotice.
+   *
+   * @param object $author  An OMB_Profile object representing the author of the
+   *                        notice.
+   * @param string $uri     The notice URI as defined by the OMB. A unique and
+   *                        unchanging identifier for a notice.
+   * @param string $content The content of the notice. 140 chars recommended,
+   *                        but there is no limit.
+   *
+   * @access public
+   */
+  public function __construct($author, $uri, $content) {
+    $this->content = $content;
+    if (is_null($author)) {
+      throw new OMB_InvalidParameterException('', 'notice', 'omb_listenee');
+    }
+    $this->author = $author;
+
+    if (!Validate::uri($uri)) {
+      throw new OMB_InvalidParameterException($uri, 'notice', 'omb_notice');
+    }
+    $this->uri = $uri;
+
+    $this->param_array = false;
+  }
+
+  /**
+   * Returns the notice as array
+   *
+   * The method returns an array which contains the whole notice as array. The
+   * array is cached and only rebuilt on changes of the notice.
+   * Empty optional values are not passed.
+   *
+   *  @access  public
+   *  @returns array The notice as parameter array
+   */
+  public function asParameters() {
+    if ($this->param_array !== false) {
+      return $this->param_array;
+    }
+
+    $this->param_array = array(
+                 'omb_notice' => $this->uri,
+                 'omb_notice_content' => $this->content);
+
+    if (!is_null($this->url))
+      $this->param_array['omb_notice_url'] = $this->url;
+
+    if (!is_null($this->license_url))
+      $this->param_array['omb_notice_license'] = $this->license_url;
+
+    if (!is_null($this->seealso_url)) {
+      $this->param_array['omb_seealso'] = $this->seealso_url;
+
+      /* This is actually a free interpretation of the OMB standard. We assume
+         that additional seealso parameters are not of any use if seealso itself
+         is not set. */
+      if (!is_null($this->seealso_disposition))
+        $this->param_array['omb_seealso_disposition'] =
+                                                     $this->seealso_disposition;
+
+      if (!is_null($this->seealso_mediatype))
+        $this->param_array['omb_seealso_mediatype'] = $this->seealso_mediatype;
+
+      if (!is_null($this->seealso_license_url))
+        $this->param_array['omb_seealso_license'] = $this->seealso_license_url;
+    }
+    return $this->param_array;
+  }
+
+  /**
+   * Builds an OMB_Notice object from array
+   *
+   * The method builds an OMB_Notice object from the passed parameters array.
+   * The array MUST provide a notice URI and content. The array fields HAVE TO
+   * be named according to the OMB standard, i. e. omb_notice_* and
+   * omb_seealso_*. Values are handled as not passed if the corresponding array
+   * fields are not set or the empty string.
+   *
+   * @param object $author     An OMB_Profile object representing the author of
+   *                           the notice.
+   * @param string $parameters An array containing the notice parameters.
+   *
+   * @access public
+   *
+   * @returns OMB_Notice The built OMB_Notice.
+   */
+  public static function fromParameters($author, $parameters) {
+    $notice = new OMB_Notice($author, $parameters['omb_notice'],
+                             $parameters['omb_notice_content']);
+
+    if (isset($parameters['omb_notice_url'])) {
+      $notice->setURL($parameters['omb_notice_url']);
+    }
+
+    if (isset($parameters['omb_notice_license'])) {
+      $notice->setLicenseURL($parameters['omb_notice_license']);
+    }
+
+    if (isset($parameters['omb_seealso'])) {
+      $notice->setSeealsoURL($parameters['omb_seealso']);
+    }
+
+    if (isset($parameters['omb_seealso_disposition'])) {
+      $notice->setSeealsoDisposition($parameters['omb_seealso_disposition']);
+    }
+
+    if (isset($parameters['omb_seealso_mediatype'])) {
+      $notice->setSeealsoMediatype($parameters['omb_seealso_mediatype']);
+    }
+
+    if (isset($parameters['omb_seealso_license'])) {
+      $notice->setSeealsoLicenseURL($parameters['omb_seealso_license']);
+    }
+    return $notice;
+  }
+
+  public function getAuthor() {
+    return $this->author;
+  }
+
+  public function getIdentifierURI() {
+    return $this->uri;
+  }
+
+  public function getContent() {
+    return $this->content;
+  }
+
+  public function getURL() {
+    return $this->url;
+  }
+
+  public function getLicenseURL() {
+    return $this->license_url;
+  }
+
+  public function getSeealsoURL() {
+    return $this->seealso_url;
+  }
+
+  public function getSeealsoDisposition() {
+    return $this->seealso_disposition;
+  }
+
+  public function getSeealsoMediatype() {
+    return $this->seealso_mediatype;
+  }
+
+  public function getSeealsoLicenseURL() {
+    return $this->seealso_license_url;
+  }
+
+  public function setURL($url) {
+    if ($url === '') {
+      $url = null;
+    } elseif (!OMB_Helper::validateURL($url)) {
+      throw new OMB_InvalidParameterException($url, 'notice', 'omb_notice_url');
+    }
+    $this->url = $url;
+    $this->param_array = false;
+  }
+
+  public function setLicenseURL($license_url) {
+    if ($license_url === '') {
+      $license_url = null;
+    } elseif (!OMB_Helper::validateURL($license_url)) {
+      throw new OMB_InvalidParameterException($license_url, 'notice',
+                                              'omb_notice_license');
+    }
+    $this->license_url = $license_url;
+    $this->param_array = false;
+  }
+
+  public function setSeealsoURL($seealso_url) {
+    if ($seealso_url === '') {
+      $seealso_url = null;
+    } elseif (!OMB_Helper::validateURL($seealso_url)) {
+      throw new OMB_InvalidParameterException($seealso_url, 'notice',
+                                              'omb_seealso');
+    }
+    $this->seealso_url = $seealso_url;
+    $this->param_array = false;
+  }
+
+  public function setSeealsoDisposition($seealso_disposition) {
+    if ($seealso_disposition === '') {
+      $seealso_disposition = null;
+    } elseif ($seealso_disposition !== 'link' && $seealso_disposition !== 'inline') {
+      throw new OMB_InvalidParameterException($seealso_disposition, 'notice',
+                                              'omb_seealso_disposition');
+    }
+    $this->seealso_disposition = $seealso_disposition;
+    $this->param_array = false;
+  }
+
+  public function setSeealsoMediatype($seealso_mediatype) {
+    if ($seealso_mediatype === '') {
+      $seealso_mediatype = null;
+    } elseif (!OMB_Helper::validateMediaType($seealso_mediatype)) {
+      throw new OMB_InvalidParameterException($seealso_mediatype, 'notice',
+                                              'omb_seealso_mediatype');
+    }
+    $this->seealso_mediatype = $seealso_mediatype;
+    $this->param_array = false;
+  }
+
+  public function setSeealsoLicenseURL($seealso_license_url) {
+    if ($seealso_license_url === '') {
+      $seealso_license_url = null;
+    } elseif (!OMB_Helper::validateURL($seealso_license_url)) {
+      throw new OMB_InvalidParameterException($seealso_license_url, 'notice',
+                                              'omb_seealso_license');
+    }
+    $this->seealso_license_url = $seealso_license_url;
+    $this->param_array = false;
+  }
+}
+?>
diff --git a/extlib/libomb/omb_yadis_xrds.php b/extlib/libomb/omb_yadis_xrds.php
new file mode 100755 (executable)
index 0000000..8992120
--- /dev/null
@@ -0,0 +1,196 @@
+<?php
+
+require_once 'Auth/Yadis/Yadis.php';
+require_once 'unsupportedserviceexception.php';
+require_once 'invalidyadisexception.php';
+
+/**
+ * OMB XRDS representation
+ *
+ * This class represents a Yadis XRDS file for OMB. It adds some useful methods to
+ * Auth_Yadis_XRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Yadis_XRDS extends Auth_Yadis_XRDS {
+
+  protected $fetcher;
+
+  /**
+   * Create an instance from URL
+   *
+   * Constructs an OMB_Yadis_XRDS object from a given URL. A full Yadis
+   * discovery is performed on the URL and the XRDS is parsed.
+   * Throws an OMB_InvalidYadisException when no Yadis is discovered or the
+   * detected XRDS file is broken.
+   *
+   * @param string                 $url     The URL on which Yadis discovery
+   *                                        should be performed on
+   * @param Auth_Yadis_HTTPFetcher $fetcher A fetcher used to get HTTP
+   *                                        resources
+   *
+   * @access public
+   *
+   * @return OMB_Yadis_XRDS The initialized object representing the given
+   *                        resource
+   **/
+  public static function fromYadisURL($url, $fetcher) {
+    /* Perform a Yadis discovery. */
+    $yadis = Auth_Yadis_Yadis::discover($url, $fetcher);
+    if ($yadis->failed) {
+      throw new OMB_InvalidYadisException($url);
+    }
+
+    /* Parse the XRDS file. */
+    $xrds = OMB_Yadis_XRDS::parseXRDS($yadis->response_text);
+    if ($xrds === null) {
+      throw new OMB_InvalidYadisException($url);
+    }
+    $xrds->fetcher = $fetcher;
+    return $xrds;
+  }
+
+  /**
+   * Get a specific service
+   *
+   * Returns the Auth_Yadis_Service object corresponding to the given service
+   * URI.
+   * Throws an OMB_UnsupportedServiceException if the service is not available.
+   *
+   * @param string $service URI specifier of the requested service
+   *
+   * @access public
+   *
+   * @return Auth_Yadis_Service The object representing the requested service
+   **/
+  public function getService($service) {
+    $match = $this->services(array( create_function('$s',
+                           "return in_array('$service', \$s->getTypes());")));
+    if ($match === array()) {
+      throw new OMB_UnsupportedServiceException($service);
+    }
+    return $match[0];
+  }
+
+  /**
+   * Get a specific XRD
+   *
+   * Returns the OMB_Yadis_XRDS object corresponding to the given URI.
+   * Throws an OMB_UnsupportedServiceException if the XRD is not available.
+   * Note that getXRD tries to resolve external XRD parts as well.
+   *
+   * @param string $uri URI specifier of the requested XRD
+   *
+   * @access public
+   *
+   * @return OMB_Yadis_XRDS The object representing the requested XRD
+   **/
+  public function getXRD($uri) {
+    $nexthash = strpos($uri, '#');
+    if ($nexthash !== 0) {
+      if ($nexthash !== false) {
+        $cururi = substr($uri, 0, $nexthash);
+        $nexturi = substr($uri, $nexthash);
+      }
+      return
+        OMB_Yadis_XRDS::fromYadisURL($cururi, $this->fetcher)->getXRD($nexturi);
+    }
+
+    $id = substr($uri, 1);
+    foreach ($this->allXrdNodes as $node) {
+      $attrs = $this->parser->attributes($node);
+      if (array_key_exists('xml:id', $attrs) && $attrs['xml:id'] == $id) {
+        /* Trick the constructor into thinking this is the only node. */
+        $bogus_nodes = array($node);
+        return new OMB_Yadis_XRDS($this->parser, $bogus_nodes);
+      }
+    }
+    throw new OMB_UnsupportedServiceException($uri);
+  }
+
+  /**
+   * Parse an XML string containing a XRDS document
+   *
+   * Parse an XML string (XRDS document) and return either a
+   * Auth_Yadis_XRDS object or null, depending on whether the
+   * XRDS XML is valid.
+   * Copy and paste from parent to select correct constructor.
+   *
+   * @param string $xml_string An XRDS XML string.
+   *
+   * @access public
+   *
+   * @return mixed An instance of OMB_Yadis_XRDS or null,
+   *               depending on the validity of $xml_string
+   **/
+
+  public function &parseXRDS($xml_string, $extra_ns_map = null) {
+    $_null = null;
+
+    if (!$xml_string) {
+      return $_null;
+    }
+
+    $parser = Auth_Yadis_getXMLParser();
+
+    $ns_map = Auth_Yadis_getNSMap();
+
+    if ($extra_ns_map && is_array($extra_ns_map)) {
+      $ns_map = array_merge($ns_map, $extra_ns_map);
+    }
+
+    if (!($parser && $parser->init($xml_string, $ns_map))) {
+      return $_null;
+    }
+
+    // Try to get root element.
+    $root = $parser->evalXPath('/xrds:XRDS[1]');
+    if (!$root) {
+      return $_null;
+    }
+
+    if (is_array($root)) {
+      $root = $root[0];
+    }
+
+    $attrs = $parser->attributes($root);
+
+    if (array_key_exists('xmlns:xrd', $attrs) &&
+          $attrs['xmlns:xrd'] != Auth_Yadis_XMLNS_XRDS) {
+      return $_null;
+    } else if (array_key_exists('xmlns', $attrs) &&
+                   preg_match('/xri/', $attrs['xmlns']) &&
+                   $attrs['xmlns'] != Auth_Yadis_XMLNS_XRD_2_0) {
+      return $_null;
+    }
+
+    // Get the last XRD node.
+    $xrd_nodes = $parser->evalXPath('/xrds:XRDS[1]/xrd:XRD');
+
+    if (!$xrd_nodes) {
+      return $_null;
+    }
+
+    $xrds = new OMB_Yadis_XRDS($parser, $xrd_nodes);
+    return $xrds;
+  }
+}
diff --git a/extlib/libomb/plain_xrds_writer.php b/extlib/libomb/plain_xrds_writer.php
new file mode 100755 (executable)
index 0000000..b4a6e99
--- /dev/null
@@ -0,0 +1,124 @@
+<?php
+
+require_once 'xrds_writer.php';
+
+/**
+ * Write OMB-specific XRDS using XMLWriter.
+ *
+ * This class writes the XRDS file announcing the OMB server. It uses
+ * OMB_XMLWriter, which is a subclass of XMLWriter. An instance of
+ * OMB_Plain_XRDS_Writer should be passed to OMB_Service_Provider->writeXRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Plain_XRDS_Writer implements OMB_XRDS_Writer {
+  public function writeXRDS($user, $mapper) {
+    header('Content-Type: application/xrds+xml');
+    $xw = new XMLWriter();
+    $xw->openURI('php://output');
+    $xw->setIndent(true);
+
+    $xw->startDocument('1.0', 'UTF-8');
+    $this->writeFullElement($xw, 'XRDS',  array('xmlns' => 'xri://$xrds'), array(
+        array('XRD',  array('xmlns' => 'xri://$xrd*($v*2.0)',
+                                          'xml:id' => 'oauth',
+                                          'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+                                          'version' => '2.0'), array(
+          array('Type', null, 'xri://$xrds*simple'),
+          array('Service', null, array(
+            array('Type', null, OAUTH_ENDPOINT_REQUEST),
+            array('URI', null, $mapper->getURL(OAUTH_ENDPOINT_REQUEST)),
+            array('Type', null, OAUTH_AUTH_HEADER),
+            array('Type', null, OAUTH_POST_BODY),
+            array('Type', null, OAUTH_HMAC_SHA1),
+            array('LocalID', null, $user->getIdentifierURI())
+          )),
+          array('Service', null, array(
+            array('Type', null, OAUTH_ENDPOINT_AUTHORIZE),
+            array('URI', null, $mapper->getURL(OAUTH_ENDPOINT_AUTHORIZE)),
+            array('Type', null, OAUTH_AUTH_HEADER),
+            array('Type', null, OAUTH_POST_BODY),
+            array('Type', null, OAUTH_HMAC_SHA1)
+          )),
+          array('Service', null, array(
+            array('Type', null, OAUTH_ENDPOINT_ACCESS),
+            array('URI', null, $mapper->getURL(OAUTH_ENDPOINT_ACCESS)),
+            array('Type', null, OAUTH_AUTH_HEADER),
+            array('Type', null, OAUTH_POST_BODY),
+            array('Type', null, OAUTH_HMAC_SHA1)
+          )),
+          array('Service', null, array(
+            array('Type', null, OAUTH_ENDPOINT_RESOURCE),
+            array('Type', null, OAUTH_AUTH_HEADER),
+            array('Type', null, OAUTH_POST_BODY),
+            array('Type', null, OAUTH_HMAC_SHA1)
+          ))
+        )),
+        array('XRD',  array('xmlns' => 'xri://$xrd*($v*2.0)',
+                                          'xml:id' => 'omb',
+                                          'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+                                          'version' => '2.0'), array(
+          array('Type', null, 'xri://$xrds*simple'),
+          array('Service', null, array(
+            array('Type', null, OMB_ENDPOINT_POSTNOTICE),
+            array('URI', null, $mapper->getURL(OMB_ENDPOINT_POSTNOTICE))
+          )),
+          array('Service', null, array(
+            array('Type', null, OMB_ENDPOINT_UPDATEPROFILE),
+            array('URI', null, $mapper->getURL(OMB_ENDPOINT_UPDATEPROFILE))
+          ))
+        )),
+        array('XRD',  array('xmlns' => 'xri://$xrd*($v*2.0)',
+                                          'version' => '2.0'), array(
+          array('Type', null, 'xri://$xrds*simple'),
+          array('Service', null, array(
+            array('Type', null, OAUTH_DISCOVERY),
+            array('URI', null, '#oauth')
+          )),
+          array('Service', null, array(
+            array('Type', null, OMB_VERSION),
+            array('URI', null, '#omb')
+          ))
+        ))
+      ));
+    $xw->endDocument();
+    $xw->flush();
+  }
+
+  public static function writeFullElement($xw, $tag, $attributes, $content) {
+    $xw->startElement($tag);
+    if (!is_null($attributes)) {
+      foreach ($attributes as $name => $value) {
+        $xw->writeAttribute($name, $value);
+      }
+    }
+    if (is_array($content)) {
+      foreach ($content as $values) {
+        OMB_Plain_XRDS_Writer::writeFullElement($xw, $values[0], $values[1], $values[2]);
+      }
+    } else {
+      $xw->text($content);
+    }
+    $xw->fullEndElement();
+  }
+}
+?>
diff --git a/extlib/libomb/profile.php b/extlib/libomb/profile.php
new file mode 100755 (executable)
index 0000000..13314d3
--- /dev/null
@@ -0,0 +1,317 @@
+<?php
+require_once 'invalidparameterexception.php';
+require_once 'Validate.php';
+require_once 'helper.php';
+
+/**
+ * OMB profile representation
+ *
+ * This class represents an OMB profile.
+ *
+ * Do not call the setters with null values. Instead, if you want to delete a
+ * field, pass an empty string. The getters will return null for empty fields.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Profile {
+  protected $identifier_uri;
+  protected $profile_url;
+  protected $nickname;
+  protected $license_url;
+  protected $fullname;
+  protected $homepage;
+  protected $bio;
+  protected $location;
+  protected $avatar_url;
+
+  /* The profile as OMB param array. Cached and rebuild on usage.
+     false while outdated. */
+  protected $param_array;
+
+  /**
+   * Constructor for OMB_Profile
+   *
+   * Initializes the OMB_Profile object with an identifier uri.
+   *
+   * @param string $identifier_uri The profile URI as defined by the OMB. A unique
+   *                               and unchanging identifier for a profile.
+   *
+   * @access public
+   */
+  public function __construct($identifier_uri) {
+    if (!Validate::uri($identifier_uri)) {
+      throw new OMB_InvalidParameterException($identifier_uri, 'profile',
+                                                'omb_listenee or omb_listener');
+    }
+    $this->identifier_uri = $identifier_uri;
+    $this->param_array = false;
+  }
+
+  /**
+   * Returns the profile as array
+   *
+   * The method returns an array which contains the whole profile as array. The
+   * array is cached and only rebuilt on changes of the profile.
+   *
+   * @param bool   $force_all Specifies whether empty fields should be added to
+   *                          the array as well. This is neccessary to clear
+   *                          fields via updateProfile.
+   *
+   * @param string $prefix    The common prefix to the key for all parameters.
+   *
+   * @access public
+   *
+   * @return array The profile as parameter array
+   */
+  public function asParameters($prefix, $force_all = false) {
+    if ($this->param_array === false) {
+      $this->param_array = array('' => $this->identifier_uri);
+
+      if ($force_all || !is_null($this->profile_url)) {
+        $this->param_array['_profile'] = $this->profile_url;
+      }
+
+      if ($force_all || !is_null($this->homepage)) {
+        $this->param_array['_homepage'] = $this->homepage;
+      }
+
+      if ($force_all || !is_null($this->nickname)) {
+        $this->param_array['_nickname'] = $this->nickname;
+      }
+
+      if ($force_all || !is_null($this->license_url)) {
+        $this->param_array['_license'] = $this->license_url;
+      }
+
+      if ($force_all || !is_null($this->fullname)) {
+        $this->param_array['_fullname'] = $this->fullname;
+      }
+
+      if ($force_all || !is_null($this->bio)) {
+        $this->param_array['_bio'] = $this->bio;
+      }
+
+      if ($force_all || !is_null($this->location)) {
+        $this->param_array['_location'] = $this->location;
+      }
+
+      if ($force_all || !is_null($this->avatar_url)) {
+        $this->param_array['_avatar'] = $this->avatar_url;
+      }
+
+    }
+    $ret = array();
+    foreach ($this->param_array as $k => $v) {
+      $ret[$prefix . $k] = $v;
+    }
+    return $ret;
+  }
+
+  /**
+   * Builds an OMB_Profile object from array
+   *
+   * The method builds an OMB_Profile object from the passed parameters array. The
+   * array MUST provide a profile URI. The array fields HAVE TO be named according
+   * to the OMB standard. The prefix (omb_listener or omb_listenee) is passed as a
+   * parameter.
+   *
+   * @param string $parameters An array containing the profile parameters.
+   * @param string $prefix     The common prefix of the profile parameter keys.
+   *
+   * @access public
+   *
+   * @returns OMB_Profile The built OMB_Profile.
+   */
+  public static function fromParameters($parameters, $prefix) {
+    if (!isset($parameters[$prefix])) {
+      throw new OMB_InvalidParameterException('', 'profile', $prefix);
+    }
+
+    $profile = new OMB_Profile($parameters[$prefix]);
+    $profile->updateFromParameters($parameters, $prefix);
+    return $profile;
+  }
+
+  /**
+   * Update from array
+   *
+   * Updates from the passed parameters array. The array does not have to
+   * provide a profile URI. The array fields HAVE TO be named according to the
+   * OMB standard. The prefix (omb_listener or omb_listenee) is passed as a
+   * parameter.
+   *
+   * @param string $parameters An array containing the profile parameters.
+   * @param string $prefix     The common prefix of the profile parameter keys.
+   *
+   * @access public
+   */
+  public function updateFromParameters($parameters, $prefix) {
+    if (isset($parameters[$prefix.'_profile'])) {
+      $this->setProfileURL($parameters[$prefix.'_profile']);
+    }
+
+    if (isset($parameters[$prefix.'_license'])) {
+      $this->setLicenseURL($parameters[$prefix.'_license']);
+    }
+
+    if (isset($parameters[$prefix.'_nickname'])) {
+      $this->setNickname($parameters[$prefix.'_nickname']);
+    }
+
+    if (isset($parameters[$prefix.'_fullname'])) {
+      $this->setFullname($parameters[$prefix.'_fullname']);
+    }
+
+    if (isset($parameters[$prefix.'_homepage'])) {
+      $this->setHomepage($parameters[$prefix.'_homepage']);
+    }
+
+    if (isset($parameters[$prefix.'_bio'])) {
+      $this->setBio($parameters[$prefix.'_bio']);
+    }
+
+    if (isset($parameters[$prefix.'_location'])) {
+      $this->setLocation($parameters[$prefix.'_location']);
+    }
+
+    if (isset($parameters[$prefix.'_avatar'])) {
+      $this->setAvatarURL($parameters[$prefix.'_avatar']);
+    }
+  }
+
+  public function getIdentifierURI() {
+    return $this->identifier_uri;
+  }
+
+  public function getProfileURL() {
+    return $this->profile_url;
+  }
+
+  public function getHomepage() {
+    return $this->homepage;
+  }
+
+  public function getNickname() {
+    return $this->nickname;
+  }
+
+  public function getLicenseURL() {
+    return $this->license_url;
+  }
+
+  public function getFullname() {
+    return $this->fullname;
+  }
+
+  public function getBio() {
+    return $this->bio;
+  }
+
+  public function getLocation() {
+    return $this->location;
+  }
+
+  public function getAvatarURL() {
+    return $this->avatar_url;
+  }
+
+  public function setProfileURL($profile_url) {
+    if (!OMB_Helper::validateURL($profile_url)) {
+      throw new OMB_InvalidParameterException($profile_url, 'profile',
+                                    'omb_listenee_profile or omb_listener_profile');
+    }
+    $this->profile_url = $profile_url;
+    $this->param_array = false;
+  }
+
+  public function setNickname($nickname) {
+    if (!Validate::string($nickname,
+                          array('min_length' => 1,
+                                'max_length' => 64,
+                                'format' => VALIDATE_NUM . VALIDATE_ALPHA))) {
+      throw new OMB_InvalidParameterException($nickname, 'profile', 'nickname');
+    }
+
+    $this->nickname = $nickname;
+    $this->param_array = false;
+  }
+
+  public function setLicenseURL($license_url) {
+    if (!OMB_Helper::validateURL($license_url)) {
+      throw new OMB_InvalidParameterException($license_url, 'profile',
+                                    'omb_listenee_license or omb_listener_license');
+    }
+    $this->license_url = $license_url;
+    $this->param_array = false;
+  }
+
+  public function setFullname($fullname) {
+    if ($fullname === '') {
+      $fullname = null;
+    } elseif (!Validate::string($fullname, array('max_length' => 255))) {
+      throw new OMB_InvalidParameterException($fullname, 'profile', 'fullname');
+    }
+    $this->fullname = $fullname;
+    $this->param_array = false;
+  }
+
+  public function setHomepage($homepage) {
+    if ($homepage === '') {
+      $homepage = null;
+    }
+    $this->homepage = $homepage;
+    $this->param_array = false;
+  }
+
+  public function setBio($bio) {
+    if ($bio === '') {
+      $bio = null;
+    } elseif (!Validate::string($bio, array('max_length' => 140))) {
+      throw new OMB_InvalidParameterException($bio, 'profile', 'fullname');
+    }
+    $this->bio = $bio;
+    $this->param_array = false;
+  }
+
+  public function setLocation($location) {
+    if ($location === '') {
+      $location = null;
+    } elseif (!Validate::string($location, array('max_length' => 255))) {
+      throw new OMB_InvalidParameterException($location, 'profile', 'fullname');
+    }
+    $this->location = $location;
+    $this->param_array = false;
+  }
+
+  public function setAvatarURL($avatar_url) {
+    if ($avatar_url === '') {
+      $avatar_url = null;
+    } elseif (!OMB_Helper::validateURL($avatar_url)) {
+      throw new OMB_InvalidParameterException($avatar_url, 'profile',
+                                      'omb_listenee_avatar or omb_listener_avatar');
+    }
+    $this->avatar_url = $avatar_url;
+    $this->param_array = false;
+  }
+
+}
+?>
diff --git a/extlib/libomb/remoteserviceexception.php b/extlib/libomb/remoteserviceexception.php
new file mode 100755 (executable)
index 0000000..374d159
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Exception stating that the remote service had a failure
+ *
+ * This exception is raised when a remote service failed to return a valid
+ * response to a request or send a valid request.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+class OMB_RemoteServiceException extends Exception {
+  public static function fromYadis($request_uri, $result) {
+    if ($result->status == 200) {
+        $err = 'Got wrong response ' . $result->body;
+    } else {
+        $err = 'Got error code ' . $result->status . ' with response ' . $result->body;
+    }
+    return new OMB_RemoteServiceException($request_uri . ': ' .  $err);
+  }
+
+  public static function forRequest($action_uri, $failure) {
+    return new OMB_RemoteServiceException("Handler for $action_uri: " .  $failure);
+  }
+}
+?>
diff --git a/extlib/libomb/service_consumer.php b/extlib/libomb/service_consumer.php
new file mode 100755 (executable)
index 0000000..273fd05
--- /dev/null
@@ -0,0 +1,430 @@
+<?php
+
+require_once 'constants.php';
+require_once 'Validate.php';
+require_once 'Auth/Yadis/Yadis.php';
+require_once 'OAuth.php';
+require_once 'unsupportedserviceexception.php';
+require_once 'remoteserviceexception.php';
+require_once 'omb_yadis_xrds.php';
+require_once 'helper.php';
+
+/**
+ * OMB service representation
+ *
+ * This class represents a complete remote OMB service. It provides discovery
+ * and execution of the service’s methods.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Service_Consumer {
+  protected $url; /* The service URL */
+  protected $services; /* An array of strings mapping service URI to
+                          service URL */
+
+  protected $token; /* An OAuthToken */
+
+  protected $listener_uri; /* The URI identifying the listener, i. e. the
+                              remote user. */
+
+  protected $listenee_uri; /* The URI identifying the listenee, i. e. the
+                              local user during an auth request. */
+
+  /**
+   * According to OAuth Core 1.0, an user authorization request is no full-blown
+   * OAuth request. nonce, timestamp, consumer_key and signature are not needed
+   * in this step. See http://laconi.ca/trac/ticket/827 for more informations.
+   *
+   * Since Laconica up to version 0.7.2 performs a full OAuth request check, a
+   * correct request would fail.
+   **/
+  public $performLegacyAuthRequest = true;
+
+  /* Helper stuff we are going to need. */
+  protected $fetcher;
+  protected $oauth_consumer;
+  protected $datastore;
+
+  /**
+   * Constructor for OMB_Service_Consumer
+   *
+   * Initializes an OMB_Service_Consumer object representing the OMB service
+   * specified by $service_url. Performs a complete service discovery using
+   * Yadis.
+   * Throws OMB_UnsupportedServiceException if XRDS file does not specify a
+   * complete OMB service.
+   *
+   * @param string        $service_url  The URL of the service
+   * @param string        $consumer_url An URL representing the consumer
+   * @param OMB_Datastore $datastore    An instance of a class implementing
+   *                                    OMB_Datastore
+   *
+   * @access public
+   **/
+  public function __construct ($service_url, $consumer_url, $datastore) {
+    $this->url = $service_url;
+    $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+    $this->datastore = $datastore;
+    $this->oauth_consumer = new OAuthConsumer($consumer_url, '');
+
+    $xrds = OMB_Yadis_XRDS::fromYadisURL($service_url, $this->fetcher);
+
+    /* Detect our services. This performs a validation as well, since
+       getService und getXRD throw exceptions on failure. */
+    $this->services = array();
+
+    foreach (array(OAUTH_DISCOVERY => OMB_Helper::$OAUTH_SERVICES,
+                   OMB_VERSION     => OMB_Helper::$OMB_SERVICES)
+             as $service_root => $targetservices) {
+      $uris = $xrds->getService($service_root)->getURIs();
+      $xrd = $xrds->getXRD($uris[0]);
+      foreach ($targetservices as $targetservice) {
+        $yadis_service = $xrd->getService($targetservice);
+        if ($targetservice == OAUTH_ENDPOINT_REQUEST) {
+            $localid = $yadis_service->getElements('xrd:LocalID');
+            $this->listener_uri = $yadis_service->parser->content($localid[0]);
+        }
+        $uris = $yadis_service->getURIs();
+        $this->services[$targetservice] = $uris[0];
+      }
+    }
+  }
+
+  /**
+   * Get the handler URI for a service
+   *
+   * Returns the URI the remote web service has specified for the given
+   * service.
+   *
+   * @param string $service The URI identifying the service
+   *
+   * @access public
+   *
+   * @return string The service handler URI
+   **/
+  public function getServiceURI($service) {
+    return $this->services[$service];
+  }
+
+  /**
+   * Get the remote user’s URI
+   *
+   * Returns the URI of the remote user, i. e. the listener.
+   *
+   * @access public
+   *
+   * @return string The remote user’s URI
+   **/
+  public function getRemoteUserURI() {
+    return $this->listener_uri;
+  }
+
+  /**
+   * Get the listenee’s URI
+   *
+   * Returns the URI of the user being subscribed to, i. e. the local user.
+   *
+   * @access public
+   *
+   * @return string The local user’s URI
+   **/
+  public function getListeneeURI() {
+    return $this->listenee_uri;
+  }
+
+  /**
+   * Request a request token
+   *
+   * Performs a token request on the service. Returns an OAuthToken on success.
+   * Throws an exception if the request fails.
+   *
+   * @access public
+   *
+   * @return OAuthToken An unauthorized request token
+   **/
+  public function requestToken() {
+    /* Set the token to null just in case the user called setToken. */
+    $this->token = null;
+
+    $result = $this->performAction(OAUTH_ENDPOINT_REQUEST,
+                                  array('omb_listener' => $this->listener_uri));
+    if ($result->status != 200) {
+      throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST,
+                                                  $result);
+    }
+    parse_str($result->body, $return);
+    if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) {
+      throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST,
+                                                  $result);
+    }
+    $this->setToken($return['oauth_token'], $return['oauth_token_secret']);
+    return $this->token;
+  }
+
+  /**
+   *
+   * Request authorization
+   *
+   * Returns an URL which equals to an authorization request. The end user
+   * should be redirected to this location to perform authorization.
+   * The $finish_url should be a local resource which invokes
+   * OMB_Consumer::finishAuthorization on request.
+   *
+   * @param OMB_Profile $profile    An OMB_Profile object representing the
+   *                                soon-to-be subscribed (i. e. local) user
+   * @param string      $finish_url Target location after successful
+   *                                authorization
+   *
+   * @access public
+   *
+   * @return string An URL representing an authorization request
+   **/
+  public function requestAuthorization($profile, $finish_url) {
+    if ($this->performLegacyAuthRequest) {
+      $params = $profile->asParameters('omb_listenee', false);
+      $params['omb_listener'] = $this->listener_uri;
+      $params['oauth_callback'] = $finish_url;
+
+      $url = $this->prepareAction(OAUTH_ENDPOINT_AUTHORIZE, $params, 'GET')->to_url();
+    } else {
+
+      $params = array(
+                'oauth_callback' => $finish_url,
+                'oauth_token'    => $this->token->key,
+                'omb_version'    => OMB_VERSION,
+                'omb_listener'   => $this->listener_uri);
+
+      $params = array_merge($profile->asParameters('omb_listenee', false). $params);
+
+      /* Build result URL. */
+      $url = $this->services[OAUTH_ENDPOINT_AUTHORIZE];
+      $url .= (strrpos($url, '?') === false ? '?' : '&');
+      foreach ($params as $k => $v) {
+        $url .= OAuthUtil::urlencode_rfc3986($k) . '=' . OAuthUtil::urlencode_rfc3986($v) . '&';
+      }
+    }
+
+    $this->listenee_uri = $profile->getIdentifierURI();
+
+    return $url;
+  }
+
+  /**
+   * Finish authorization
+   *
+   * Finish the subscription process by converting the received and authorized
+   * request token into an access token. After that, the subscriber’s profile
+   * and the subscription are stored in the database.
+   * Expects an OAuthRequest in query parameters.
+   * Throws exceptions on failure.
+   *
+   * @access public
+   **/
+  public function finishAuthorization() {
+    OMB_Helper::removeMagicQuotesFromRequest();
+    $req = OAuthRequest::from_request();
+    if ($req->get_parameter('oauth_token') !=
+          $this->token->key) {
+      /* That’s not the token I wanted to get authorized. */
+      throw new OAuthException('The authorized token does not equal the ' .
+                               'submitted token.');
+    }
+
+    if ($req->get_parameter('omb_version') != OMB_VERSION) {
+      throw new OMB_RemoteServiceException('The remote service uses an ' .
+                                           'unsupported OMB version');
+    }
+
+    /* Construct the profile to validate it. */
+
+    /* Fix OMB bug. Listener URI is not passed. */
+    if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+      $params = $_POST;
+    } else {
+      $params = $_GET;
+    }
+    $params['omb_listener'] = $this->listener_uri;
+
+    require_once 'profile.php';
+    $listener = OMB_Profile::fromParameters($params, 'omb_listener');
+
+    /* Ask the remote service to convert the authorized request token into an
+       access token. */
+
+    $result = $this->performAction(OAUTH_ENDPOINT_ACCESS, array());
+    if ($result->status != 200) {
+      throw new OAuthException('Could not get access token');
+    }
+
+    parse_str($result->body, $return);
+    if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) {
+      throw new OAuthException('Could not get access token');
+    }
+    $this->setToken($return['oauth_token'], $return['oauth_token_secret']);
+
+    /* Subscription is finished and valid. Now store the new subscriber and the
+       subscription in the database. */
+
+    $this->datastore->saveProfile($listener);
+    $this->datastore->saveSubscription($this->listener_uri,
+                                       $this->listenee_uri,
+                                       $this->token);
+  }
+
+  /**
+   * Return the URI identifying the listener
+   *
+   * Returns the URI for the OMB user who tries to subscribe or already has
+   * subscribed our user. This method is a workaround for a serious OMB flaw:
+   * The Listener URI is not passed in the finishauthorization call.
+   *
+   * @access public
+   *
+   * @return string the listener’s URI
+   **/
+  public function getListenerURI() {
+    return $this->listener_uri;
+  }
+
+  /**
+   * Inform the service about a profile update
+   *
+   * Sends an updated profile to the service.
+   *
+   * @param OMB_Profile $profile The profile that has changed
+   *
+   * @access public
+   **/
+  public function updateProfile($profile) {
+    $params = $profile->asParameters('omb_listenee', true);
+    $this->performOMBAction(OMB_ENDPOINT_UPDATEPROFILE, $params, $profile->getIdentifierURI());
+  }
+
+  /**
+   * Inform the service about a new notice
+   *
+   * Sends a notice to the service.
+   *
+   * @param OMB_Notice $notice The notice
+   *
+   * @access public
+   **/
+  public function postNotice($notice) {
+    $params = $notice->asParameters();
+    $params['omb_listenee'] = $notice->getAuthor()->getIdentifierURI();
+    $this->performOMBAction(OMB_ENDPOINT_POSTNOTICE, $params, $params['omb_listenee']);
+  }
+
+  /**
+   * Set the token member variable
+   *
+   * Initializes the token based on given token and secret token.
+   *
+   * @param string $token  The token
+   * @param string $secret The secret token
+   *
+   * @access public
+   **/
+  public function setToken($token, $secret) {
+    $this->token = new OAuthToken($token, $secret);
+  }
+
+  /**
+   * Prepare an OAuthRequest object
+   *
+   * Creates an OAuthRequest object mapping the request specified by the
+   * parameters.
+   *
+   * @param string $action_uri The URI specifying the target service
+   * @param array  $params     Additional parameters for the service call
+   * @param string $method     The HTTP method used to call the service
+   *                           ('POST' or 'GET', usually)
+   *
+   * @access protected
+   *
+   * @return OAuthRequest the prepared request
+   **/
+  protected function prepareAction($action_uri, $params, $method) {
+    $url = $this->services[$action_uri];
+
+    $url_params = array();
+    parse_str(parse_url($url, PHP_URL_QUERY), $url_params);
+
+    /* Add OMB version. */
+    $url_params['omb_version'] = OMB_VERSION;
+
+    /* Add user-defined parameters. */
+    $url_params = array_merge($url_params, $params);
+
+    $req = OAuthRequest::from_consumer_and_token($this->oauth_consumer,
+                                      $this->token, $method, $url, $url_params);
+
+    /* Sign the request. */
+    $req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(),
+                                           $this->oauth_consumer, $this->token);
+
+    return $req;
+  }
+
+  /**
+   * Perform a service call
+   *
+   * Creates an OAuthRequest object and execute the mapped call as POST request.
+   *
+   * @param string $action_uri The URI specifying the target service
+   * @param array  $params     Additional parameters for the service call
+   *
+   * @access protected
+   *
+   * @return Auth_Yadis_HTTPResponse The POST request response
+   **/
+  protected function performAction($action_uri, $params) {
+    $req = $this->prepareAction($action_uri, $params, 'POST');
+
+    /* Return result page. */
+    return $this->fetcher->post($req->get_normalized_http_url(), $req->to_postdata(), array());
+  }
+
+  /**
+   * Perform an OMB action
+   *
+   * Executes an OMB action – to date, it’s one of updateProfile or postNotice.
+   *
+   * @param string $action_uri   The URI specifying the target service
+   * @param array  $params       Additional parameters for the service call
+   * @param string $listenee_uri The URI identifying the local user for whom
+   *                             the action is performed
+   *
+   * @access protected
+   **/
+  protected function performOMBAction($action_uri, $params, $listenee_uri) {
+    $result = $this->performAction($action_uri, $params);
+    if ($result->status == 403) {
+      /* The remote user unsubscribed us. */
+      $this->datastore->deleteSubscription($this->listener_uri, $listenee_uri);
+    } else if ($result->status != 200 ||
+               strpos($result->body, 'omb_version=' . OMB_VERSION) === false) {
+      /* The server signaled an error or sent an incorrect response. */
+      throw OMB_RemoteServiceException::fromYadis($action_uri, $result);
+    }
+  }
+}
diff --git a/extlib/libomb/service_provider.php b/extlib/libomb/service_provider.php
new file mode 100755 (executable)
index 0000000..7531527
--- /dev/null
@@ -0,0 +1,425 @@
+<?php
+
+require_once 'constants.php';
+require_once 'remoteserviceexception.php';
+require_once 'helper.php';
+
+/**
+ * OMB service realization
+ *
+ * This class realizes a complete, simple OMB service.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Service_Provider {
+  protected $user; /* An OMB_Profile representing the user */
+  protected $datastore; /* AN OMB_Datastore */
+
+  protected $remote_user; /* An OMB_Profile representing the remote user during
+                            the authorization process */
+
+  protected $oauth_server; /* An OAuthServer; should only be accessed via
+                              getOAuthServer. */
+
+  /**
+   * Initialize an OMB_Service_Provider object
+   *
+   * Constructs an OMB_Service_Provider instance that provides OMB services
+   * referring to a particular user.
+   *
+   * @param OMB_Profile   $user         An OMB_Profile; mandatory for XRDS
+   *                                    output, user auth handling and OMB
+   *                                    action performing
+   * @param OMB_Datastore $datastore    An OMB_Datastore; mandatory for
+   *                                    everything but XRDS output
+   * @param OAuthServer   $oauth_server An OAuthServer; used for token writing
+   *                                    and OMB action handling; will use
+   *                                    default value if not set
+   *
+   * @access public
+   **/
+  public function __construct ($user = null, $datastore = null, $oauth_server = null) {
+    $this->user = $user;
+    $this->datastore = $datastore;
+    $this->oauth_server = $oauth_server;
+  }
+
+  public function getRemoteUser() {
+    return $this->remote_user;
+  }
+
+  /**
+   * Write a XRDS document
+   *
+   * Writes a XRDS document specifying the OMB service. Optionally uses a
+   * given object of a class implementing OMB_XRDS_Writer for output. Else
+   * OMB_Plain_XRDS_Writer is used.
+   *
+   * @param OMB_XRDS_Mapper $xrds_mapper An object mapping actions to URLs
+   * @param OMB_XRDS_Writer $xrds_writer Optional; The OMB_XRDS_Writer used to
+   *                                     write the XRDS document
+   *
+   * @access public
+   *
+   * @return mixed Depends on the used OMB_XRDS_Writer; OMB_Plain_XRDS_Writer
+   *               returns nothing.
+   **/
+  public function writeXRDS($xrds_mapper, $xrds_writer = null) {
+    if ($xrds_writer == null) {
+        require_once 'plain_xrds_writer.php';
+        $xrds_writer = new OMB_Plain_XRDS_Writer();
+    }
+    return $xrds_writer->writeXRDS($this->user, $xrds_mapper);
+  }
+
+  /**
+   * Echo a request token
+   *
+   * Outputs an unauthorized request token for the query found in $_GET or
+   * $_POST.
+   *
+   * @access public
+   **/
+  public function writeRequestToken() {
+    OMB_Helper::removeMagicQuotesFromRequest();
+    echo $this->getOAuthServer()->fetch_request_token(OAuthRequest::from_request());
+  }
+
+  /**
+   * Handle an user authorization request.
+   *
+   * Parses an authorization request. This includes OAuth and OMB verification.
+   * Throws exceptions on failures. Returns an OMB_Profile object representing
+   * the remote user.
+   *
+   * The OMB_Profile passed to the constructor of OMB_Service_Provider should
+   * not represent the user specified in the authorization request, but the one
+   * currently logged in to the service. This condition being satisfied,
+   * handleUserAuth will check whether the listener specified in the request is
+   * identical to the logged in user.
+   *
+   * @access public
+   *
+   * @return OMB_Profile The profile of the soon-to-be subscribed, i. e. remote
+   *                     user
+   **/
+  public function handleUserAuth() {
+    OMB_Helper::removeMagicQuotesFromRequest();
+
+    /* Verify the request token. */
+
+    $this->token = $this->datastore->lookup_token(null, "request", $_GET['oauth_token']);
+    if (is_null($this->token)) {
+      throw new OAuthException('The given request token has not been issued ' .
+                               'by this service.');
+    }
+
+    /* Verify the OMB part. */
+
+    if ($_GET['omb_version'] !== OMB_VERSION) {
+      throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
+                                   'Wrong OMB version ' . $_GET['omb_version']);
+    }
+
+    if ($_GET['omb_listener'] !== $this->user->getIdentifierURI()) {
+      throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
+                                 'Wrong OMB listener ' . $_GET['omb_listener']);
+    }
+
+    foreach (array('omb_listenee', 'omb_listenee_profile',
+                   'omb_listenee_nickname', 'omb_listenee_license') as $param) {
+      if (!isset($_GET[$param]) || is_null($_GET[$param])) {
+        throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
+                                       "Required parameter '$param' not found");
+      }
+    }
+
+    /* Store given callback for later use. */
+    if (isset($_GET['oauth_callback']) && $_GET['oauth_callback'] !== '') {
+      $this->callback = $_GET['oauth_callback'];
+      if (!OMB_Helper::validateURL($this->callback)) {
+        throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
+                                              'Invalid callback URL specified');
+      }
+    }
+    $this->remote_user = OMB_Profile::fromParameters($_GET, 'omb_listenee');
+
+    return $this->remote_user;
+  }
+
+  /**
+   * Continue the OAuth dance after user authorization
+   *
+   * Performs the appropriate actions after user answered the authorization
+   * request.
+   *
+   * @param bool $accepted Whether the user granted authorization
+   *
+   * @access public
+   *
+   * @return array A two-component array with the values:
+   *                - callback The callback URL or null if none given
+   *                - token    The authorized request token or null if not
+   *                           authorized.
+   **/
+  public function continueUserAuth($accepted) {
+    $callback = $this->callback;
+    if (!$accepted) {
+      $this->datastore->revoke_token($this->token->key);
+      $this->token = null;
+      /* TODO: The handling is probably wrong in terms of OAuth 1.0 but the way
+               laconica works. Moreover I don’t know the right way either. */
+
+    } else {
+      $this->datastore->authorize_token($this->token->key);
+      $this->datastore->saveProfile($this->remote_user);
+      $this->datastore->saveSubscription($this->user->getIdentifierURI(),
+                          $this->remote_user->getIdentifierURI(), $this->token);
+
+      if (!is_null($this->callback)) {
+        /* Callback wants to get some informations as well. */
+        $params = $this->user->asParameters('omb_listener', false);
+
+        $params['oauth_token'] = $this->token->key;
+        $params['omb_version'] = OMB_VERSION;
+
+        $callback .= (parse_url($this->callback, PHP_URL_QUERY) ? '&' : '?');
+        foreach ($params as $k => $v) {
+          $callback .= OAuthUtil::urlencode_rfc3986($k) . '=' .
+                       OAuthUtil::urlencode_rfc3986($v) . '&';
+        }
+      }
+    }
+    return array($callback, $this->token);
+  }
+
+  /**
+   * Echo an access token
+   *
+   * Outputs an access token for the query found in $_POST. OMB 0.1 specifies
+   * that the access token request has to be a POST even if OAuth allows GET as
+   * well.
+   *
+   * @access public
+   **/
+  public function writeAccessToken() {
+    OMB_Helper::removeMagicQuotesFromRequest();
+    echo $this->getOAuthServer()->fetch_access_token(
+                                            OAuthRequest::from_request('POST'));
+  }
+
+  /**
+   * Handle an updateprofile request
+   *
+   * Handles an updateprofile request posted to this service. Updates the
+   * profile through the OMB_Datastore.
+   *
+   * @access public
+   *
+   * @return OMB_Profile The updated profile
+   **/
+  public function handleUpdateProfile() {
+    list($req, $profile) = $this->handleOMBRequest(OMB_ENDPOINT_UPDATEPROFILE);
+    $profile->updateFromParameters($req->get_parameters(), 'omb_listenee');
+    $this->datastore->saveProfile($profile);
+    $this->finishOMBRequest();
+    return $profile;
+  }
+
+  /**
+   * Handle a postnotice request
+   *
+   * Handles a postnotice request posted to this service. Saves the notice
+   * through the OMB_Datastore.
+   *
+   * @access public
+   *
+   * @return OMB_Notice The received notice
+   **/
+  public function handlePostNotice() {
+    list($req, $profile) = $this->handleOMBRequest(OMB_ENDPOINT_POSTNOTICE);
+    require_once 'notice.php';
+    $notice = OMB_Notice::fromParameters($profile, $req->get_parameters());
+    $this->datastore->saveNotice($notice);
+    $this->finishOMBRequest();
+    return $notice;
+  }
+
+  /**
+   * Handle an OMB request
+   *
+   * Performs common OMB request handling.
+   *
+   * @param string $uri The URI defining the OMB endpoint being served
+   *
+   * @access protected
+   *
+   * @return array(OAuthRequest, OMB_Profile)
+   **/
+  protected function handleOMBRequest($uri) {
+
+    OMB_Helper::removeMagicQuotesFromRequest();
+    $req = OAuthRequest::from_request('POST');
+    $listenee =  $req->get_parameter('omb_listenee');
+
+    try {
+        list($consumer, $token) = $this->getOAuthServer()->verify_request($req);
+    } catch (OAuthException $e) {
+      header('HTTP/1.1 403 Forbidden');
+      throw OMB_RemoteServiceException::forRequest($uri,
+                                   'Revoked accesstoken for ' . $listenee);
+    }
+
+    $version = $req->get_parameter('omb_version');
+    if ($version !== OMB_VERSION) {
+      header('HTTP/1.1 400 Bad Request');
+      throw OMB_RemoteServiceException::forRequest($uri,
+                                   'Wrong OMB version ' . $version);
+    }
+
+    $profile = $this->datastore->getProfile($listenee);
+    if (is_null($profile)) {
+      header('HTTP/1.1 400 Bad Request');
+      throw OMB_RemoteServiceException::forRequest($uri,
+                                   'Unknown remote profile ' . $listenee);
+    }
+
+    $subscribers = $this->datastore->getSubscriptions($listenee);
+    if (count($subscribers) === 0) {
+      header('HTTP/1.1 403 Forbidden');
+      throw OMB_RemoteServiceException::forRequest($uri,
+                                   'No subscriber for ' . $listenee);
+    }
+
+    return array($req, $profile);
+  }
+
+  /**
+   * Finishes an OMB request handling
+   *
+   * Performs common OMB request handling finishing.
+   *
+   * @access protected
+   **/
+  protected function finishOMBRequest() {
+    header('HTTP/1.1 200 OK');
+    header('Content-type: text/plain');
+    /* There should be no clutter but the version. */
+    echo "omb_version=" . OMB_VERSION;
+  }
+
+  /**
+   * Return an OAuthServer
+   *
+   * Checks whether the OAuthServer is null. If so, initializes it with a
+   * default value. Returns the OAuth server.
+   *
+   * @access protected
+   **/
+  protected function getOAuthServer() {
+    if (is_null($this->oauth_server)) {
+      $this->oauth_server = new OAuthServer($this->datastore);
+      $this->oauth_server->add_signature_method(
+                                          new OAuthSignatureMethod_HMAC_SHA1());
+    }
+    return $this->oauth_server;
+  }
+
+  /**
+   * Publish a notice
+   *
+   * Posts an OMB notice. This includes storing the notice and posting it to
+   * subscribed users.
+   *
+   * @param OMB_Notice $notice The new notice
+   *
+   * @access public
+   *
+   * @return array An array mapping subscriber URIs to the exception posting to
+   *               them has raised; Empty array if no exception occured
+   **/
+  public function postNotice($notice) {
+    $uri = $this->user->getIdentifierURI();
+
+    /* $notice is passed by reference and may change. */
+    $this->datastore->saveNotice($notice);
+    $subscribers = $this->datastore->getSubscriptions($uri);
+
+    /* No one to post to. */
+    if (is_null($subscribers)) {
+        return array();
+    }
+
+    require_once 'service_consumer.php';
+
+    $err = array();
+    foreach($subscribers as $subscriber) {
+      try {
+        $service = new OMB_Service_Consumer($subscriber['uri'], $uri, $this->datastore);
+        $service->setToken($subscriber['token'], $subscriber['secret']);
+        $service->postNotice($notice);
+      } catch (Exception $e) {
+        $err[$subscriber['uri']] = $e;
+        continue;
+      }
+    }
+    return $err;
+  }
+
+  /**
+   * Publish a profile update
+   *
+   * Posts the current profile as an OMB profile update. This includes updating
+   * the stored profile and posting it to subscribed users.
+   *
+   * @access public
+   *
+   * @return array An array mapping subscriber URIs to the exception posting to
+   *               them has raised; Empty array if no exception occured
+   **/
+  public function updateProfile() {
+    $uri = $this->user->getIdentifierURI();
+
+    $this->datastore->saveProfile($this->user);
+    $subscribers = $this->datastore->getSubscriptions($uri);
+
+    /* No one to post to. */
+    if (is_null($subscribers)) {
+        return array();
+    }
+
+    require_once 'service_consumer.php';
+
+    $err = array();
+    foreach($subscribers as $subscriber) {
+      try {
+        $service = new OMB_Service_Consumer($subscriber['uri'], $uri, $this->datastore);
+        $service->setToken($subscriber['token'], $subscriber['secret']);
+        $service->updateProfile($this->user);
+      } catch (Exception $e) {
+        $err[$subscriber['uri']] = $e;
+        continue;
+      }
+    }
+    return $err;
+  }
+}
diff --git a/extlib/libomb/unsupportedserviceexception.php b/extlib/libomb/unsupportedserviceexception.php
new file mode 100755 (executable)
index 0000000..4dab63e
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Exception stating that a requested service is not available
+ *
+ * This exception is raised when OMB_Service is asked to call a service the remote
+ * server does not provide.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+class OMB_UnsupportedServiceException extends Exception {
+
+}
+?>
diff --git a/extlib/libomb/xrds_mapper.php b/extlib/libomb/xrds_mapper.php
new file mode 100755 (executable)
index 0000000..7552154
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Map XRDS actions to URLs
+ *
+ * This interface specifies classes which write the XRDS file announcing
+ * the OMB server. An instance of an implementing class should be passed to
+ * OMB_Service_Provider->writeXRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+interface OMB_XRDS_Mapper {
+  public function getURL($action);
+}
+?>
diff --git a/extlib/libomb/xrds_writer.php b/extlib/libomb/xrds_writer.php
new file mode 100755 (executable)
index 0000000..31b451b
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Write OMB-specific XRDS
+ *
+ * This interface specifies classes which write the XRDS file announcing
+ * the OMB server. An instance of an implementing class should be passed to
+ * OMB_Service_Provider->writeXRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: 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/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+interface OMB_XRDS_Writer {
+  public function writeXRDS($user, $mapper);
+}
+?>
index 362ab3cd37e1bef5cd189703f949063c99c6309c..644812bd55ac1c8b1549fd4446189266f3c121af 100644 (file)
--- a/index.php
+++ b/index.php
  *
  * 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 StatusNet
+ * @package  StatusNet
+ * @author   Brenda Wallace <shiny@cpan.org>
+ * @author   Christopher Vollick <psycotica0@gmail.com>
+ * @author   CiaranG <ciaran@ciarang.com>
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Evan Prodromou <evan@controlezvous.ca>
+ * @author   Gina Haeussge <osd@foosel.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   Mike Cochrane <mikec@mikenz.geek.nz>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @author   Sarven Capadisli <csarven@controlyourself.ca>
+ * @author   Tom Adams <tom@holizz.com>
+ * 
+ * @license  GNU Affero General Public License http://www.gnu.org/licenses/
  */
 
 define('INSTALLDIR', dirname(__FILE__));
@@ -29,7 +45,8 @@ $action = null;
 function getPath($req)
 {
     if ((common_config('site', 'fancy') || !array_key_exists('PATH_INFO', $_SERVER))
-        && array_key_exists('p', $req)) {
+        && array_key_exists('p', $req)
+    ) {
         return $req['p'];
     } else if (array_key_exists('PATH_INFO', $_SERVER)) {
         $path = $_SERVER['PATH_INFO'];
@@ -44,6 +61,11 @@ function getPath($req)
     }
 }
 
+/**
+ * logs and then displays error messages
+ *
+ * @return void
+ */
 function handleError($error)
 {
     if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) {
@@ -51,28 +73,35 @@ function handleError($error)
     }
 
     $logmsg = "PEAR error: " . $error->getMessage();
-    if(common_config('site', 'logdebug')) {
+    if (common_config('site', 'logdebug')) {
         $logmsg .= " : ". $error->getDebugInfo();
     }
     common_log(LOG_ERR, $logmsg);
-    if(common_config('site', 'logdebug')) {
+    if (common_config('site', 'logdebug')) {
         $bt = $error->getBacktrace();
         foreach ($bt as $line) {
             common_log(LOG_ERR, $line);
         }
     }
-    if ($error instanceof DB_DataObject_Error ||
-        $error instanceof DB_Error) {
-        $msg = sprintf(_('The database for %s isn\'t responding correctly, '.
-                         'so the site won\'t work properly. '.
-                         'The site admins probably know about the problem, '.
-                         'but you can contact them at %s to make sure. '.
-                         'Otherwise, wait a few minutes and try again.'),
-                       common_config('site', 'name'),
-                       common_config('site', 'email'));
+    if ($error instanceof DB_DataObject_Error
+        || $error instanceof DB_Error
+    ) {
+        $msg = sprintf(
+            _(
+                'The database for %s isn\'t responding correctly, '.
+                'so the site won\'t work properly. '.
+                'The site admins probably know about the problem, '.
+                'but you can contact them at %s to make sure. '.
+                'Otherwise, wait a few minutes and try again.'
+            ),
+            common_config('site', 'name'),
+            common_config('site', 'email')
+        );
     } else {
-        $msg = _('An important error occured, probably related to email setup. '.
-                 'Check logfiles for more info..');
+        $msg = _(
+            'An important error occured, probably related to email setup. '.
+            'Check logfiles for more info..'
+        );
     }
 
     $dac = new DBErrorAction($msg, 500);
@@ -112,6 +141,19 @@ function checkMirror($action_obj, $args)
     }
 }
 
+function isLoginAction($action)
+{
+    static $loginActions =  array('login', 'recoverpassword', 'api', 'doc', 'register');
+
+    $login = null;
+
+    if (Event::handle('LoginAction', array($action, &$login))) {
+        $login = in_array($action, $loginActions);
+    }
+
+    return $login;
+}
+
 function main()
 {
     // fake HTTP redirects using lighttpd's 404 redirects
@@ -120,10 +162,11 @@ function main()
         $_lighty_url = @parse_url($_lighty_url);
 
         if ($_lighty_url['path'] != '/index.php' && $_lighty_url['path'] != '/') {
-            $_lighty_path = preg_replace('/^'.preg_quote(common_config('site','path')).'\//', '', substr($_lighty_url['path'], 1));
+            $_lighty_path = preg_replace('/^'.preg_quote(common_config('site', 'path')).'\//', '', substr($_lighty_url['path'], 1));
             $_SERVER['QUERY_STRING'] = 'p='.$_lighty_path;
-            if ($_lighty_url['query'])
+            if ($_lighty_url['query']) {
                 $_SERVER['QUERY_STRING'] .= '&'.$_lighty_url['query'];
+            }
             parse_str($_lighty_url['query'], $_lighty_query);
             foreach ($_lighty_query as $key => $val) {
                 $_GET[$key] = $_REQUEST[$key] = $val;
@@ -134,7 +177,7 @@ function main()
     $_SERVER['REDIRECT_URL'] = preg_replace("/\?.+$/", "", $_SERVER['REQUEST_URI']);
 
     // quick check for fancy URL auto-detection support in installer.
-    if (isset($_SERVER['REDIRECT_URL']) && (preg_replace("/^\/$/","",(dirname($_SERVER['REQUEST_URI']))) . '/check-fancy') === $_SERVER['REDIRECT_URL']) {
+    if (isset($_SERVER['REDIRECT_URL']) && (preg_replace("/^\/$/", "", (dirname($_SERVER['REQUEST_URI']))) . '/check-fancy') === $_SERVER['REDIRECT_URL']) {
         die("Fancy URL support detection succeeded. We suggest you enable this to get fancy (pretty) URLs.");
     }
     global $user, $action;
@@ -142,8 +185,12 @@ function main()
     Snapshot::check();
 
     if (!_have_config()) {
-        $msg = sprintf(_("No configuration file found. Try running ".
-                         "the installation program first."));
+        $msg = sprintf(
+            _(
+                "No configuration file found. Try running ".
+                "the installation program first."
+            )
+        );
         $sac = new ServerErrorAction($msg);
         $sac->showPage();
         return;
@@ -189,36 +236,12 @@ function main()
     // If the site is private, and they're not on one of the "public"
     // parts of the site, redirect to login
 
-    if (!$user && common_config('site', 'private')) {
-        $public_actions = array('openidlogin', 'finishopenidlogin',
-                                'recoverpassword', 'api', 'doc',
-                                'opensearch');
-        $login_action = 'openidlogin';
-        if (!common_config('site', 'openidonly')) {
-            $public_actions[] = 'login';
-            $public_actions[] = 'register';
-            $login_action = 'login';
-        }
-        if (!in_array($action, $public_actions) &&
-            !preg_match('/rss$/', $action)) {
-
-            // set returnto
-            $rargs =& common_copy_args($args);
-            unset($rargs['action']);
-            if (common_config('site', 'fancy')) {
-                unset($rargs['p']);
-            }
-            if (array_key_exists('submit', $rargs)) {
-                unset($rargs['submit']);
-            }
-            foreach (array_keys($_COOKIE) as $cookie) {
-                unset($rargs[$cookie]);
-            }
-            common_set_returnto(common_local_url($action, $rargs));
-
-            common_redirect(common_local_url($login_action));
-            return;
-        }
+    if (!$user && common_config('site', 'private')
+        && !isLoginAction($action)
+        && !preg_match('/rss$/', $action)
+    ) {
+        common_redirect(common_local_url('login'));
+        return;
     }
 
     $action_class = ucfirst($action).'Action';
index 425ea91ef8f18399cf6d0b84df7dc552de05346f..319c261e418514f890e87152c709bc652945accc 100644 (file)
@@ -1,3 +1,4 @@
+
 <?php
 /**
  * StatusNet - the distributed open-source microblogging tool
  *
  * 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 Installation
+ * @package  Installation
+ *
+ * @author   Adrian Lang <mail@adrianlang.de>
+ * @author   Brenda Wallace <shiny@cpan.org>
+ * @author   Brett Taylor <brett@webfroot.co.nz>
+ * @author   Brion Vibber <brion@pobox.com>
+ * @author   CiaranG <ciaran@ciarang.com>
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Eric Helgeson <helfire@Erics-MBP.local>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @author   Sarven Capadisli <csarven@status.net>
+ * @author   Tom Adams <tom@holizz.com>
+ * @license  GNU Affero General Public License http://www.gnu.org/licenses/
+ * @version  0.9.x
+ * @link     http://status.net
  */
 
 define('INSTALLDIR', dirname(__FILE__));
@@ -181,15 +200,32 @@ $external_libraries=array(
         'check_class'=>'Validate'
     )
 );
+$dbModules = array(
+    'mysql' => array(
+        'name' => 'MySQL',
+        'check_module' => 'mysql', // mysqli?
+        'installer' => 'mysql_db_installer',
+    ),
+    'pgsql' => array(
+        'name' => 'PostgreSQL',
+        'check_module' => 'pgsql',
+        'installer' => 'pgsql_db_installer',
+    ),
+);
 
+/**
+ * the actual installation.
+ * If call libraries are present, then install
+ *
+ * @return void
+ */
 function main()
 {
-    if (!checkPrereqs())
-    {
+    if (!checkPrereqs()) {
         return;
     }
-    
-    if (isset($_GET['checklibs'])) {
+
+    if (!empty($_GET['checklibs'])) {
         showLibs();
     } else {
         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
@@ -200,15 +236,22 @@ function main()
     }
 }
 
+/**
+ * checks if an external libary is present
+ *
+ * @param string $external_library Name of library
+ *
+ * @return boolean indicates if library present
+ */
 function haveExternalLibrary($external_library)
 {
-    if(isset($external_library['include']) && ! haveIncludeFile($external_library['include'])){
+    if (isset($external_library['include']) && !haveIncludeFile($external_library['include'])) {
         return false;
     }
-    if(isset($external_library['check_function']) && ! function_exists($external_library['check_function'])){
+    if (isset($external_library['check_function']) && ! function_exists($external_library['check_function'])) {
         return false;
     }
-    if(isset($external_library['check_class']) && ! class_exists($external_library['check_class'])){
+    if (isset($external_library['check_class']) && ! class_exists($external_library['check_class'])) {
         return false;
     }
     return true;
@@ -223,19 +266,23 @@ function haveIncludeFile($filename) {
     return $ok;
 }
 
+/**
+ * Check if all is ready for installation
+ *
+ * @return void
+ */
 function checkPrereqs()
 {
-       $pass = true;
+    $pass = true;
 
     if (file_exists(INSTALLDIR.'/config.php')) {
-         ?><p class="error">Config file &quot;config.php&quot; already exists.</p>
-         <?php
+         printf('<p class="error">Config file &quot;config.php&quot; already exists.</p>');
         $pass = false;
     }
 
     if (version_compare(PHP_VERSION, '5.2.3', '<')) {
-            ?><p class="error">Require PHP version 5.2.3 or greater.</p><?php
-                   $pass = false;
+        printf('<p class="error">Require PHP version 5.2.3 or greater.</p>');
+        $pass = false;
     }
 
     $reqs = array('gd', 'curl',
@@ -243,63 +290,83 @@ function checkPrereqs()
 
     foreach ($reqs as $req) {
         if (!checkExtension($req)) {
-            ?><p class="error">Cannot load required extension: <code><?php echo $req; ?></code></p><?php
-                   $pass = false;
+            printf('<p class="error">Cannot load required extension: <code>%s</code></p>', $req);
+            $pass = false;
         }
     }
-    if (!checkExtension('pgsql') && !checkExtension('mysql')) {
-      ?><p class="error">Cannot find mysql or pgsql extension. You need one or the other: <code><?php echo $req; ?></code></p><?php
-                    $pass = false;
-    }
-
-       if (!is_writable(INSTALLDIR)) {
-         ?><p class="error">Cannot write config file to: <code><?php echo INSTALLDIR; ?></code></p>
-              <p>On your server, try this command: <code>chmod a+w <?php echo INSTALLDIR; ?></code>
-         <?php
-            $pass = false;
-       }
-
-       // Check the subdirs used for file uploads
-       $fileSubdirs = array('avatar', 'background', 'file');
-       foreach ($fileSubdirs as $fileSubdir) {
-               $fileFullPath = INSTALLDIR."/$fileSubdir/";
-               if (!is_writable($fileFullPath)) {
-            ?><p class="error">Cannot write <?php echo $fileSubdir; ?> directory: <code><?php echo $fileFullPath; ?></code></p>
-                      <p>On your server, try this command: <code>chmod a+w <?php echo $fileFullPath; ?></code></p>
-            <?php
-                    $pass = false;
-               }
-       }
-
-       return $pass;
+    // Make sure we have at least one database module available
+    global $dbModules;
+    $missingExtensions = array();
+    foreach ($dbModules as $type => $info) {
+        if (!checkExtension($info['check_module'])) {
+            $missingExtensions[] = $info['check_module'];
+        }
+    }
+
+    if (count($missingExtensions) == count($dbModules)) {
+        $req = implode(', ', $missingExtensions);
+        printf('<p class="error">Cannot find mysql or pgsql extension. You need one or the other.');
+        $pass = false;
+    }
+
+    if (!is_writable(INSTALLDIR)) {
+        printf('<p class="error">Cannot write config file to: <code>%s</code></p>', INSTALLDIR);
+        printf('<p>On your server, try this command: <code>chmod a+w %s</code>', INSTALLDIR);
+        $pass = false;
+    }
+
+    // Check the subdirs used for file uploads
+    $fileSubdirs = array('avatar', 'background', 'file');
+    foreach ($fileSubdirs as $fileSubdir) {
+        $fileFullPath = INSTALLDIR."/$fileSubdir/";
+        if (!is_writable($fileFullPath)) {
+            printf('<p class="error">Cannot write to %s directory: <code>%s</code></p>', $fileSubdir, $fileFullPath);
+            printf('<p>On your server, try this command: <code>chmod a+w %s</code></p>', $fileFullPath);
+            $pass = false;
+        }
+    }
+
+    return $pass;
 }
 
+/**
+ * Checks if a php extension is both installed and loaded
+ *
+ * @param string $name of extension to check
+ *
+ * @return boolean whether extension is installed and loaded
+ */
 function checkExtension($name)
 {
     if (extension_loaded($name)) {
         return true;
     } elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) {
-       // dl will throw a fatal error if it's disabled or we're in safe mode.
-       // More fun, it may not even exist under some SAPIs in 5.3.0 or later...
-       $soname = $name . '.' . PHP_SHLIB_SUFFIX;
-       if (PHP_SHLIB_SUFFIX == 'dll') {
-               $soname = "php_" . $soname;
-       }
-       return @dl($soname);
+        // dl will throw a fatal error if it's disabled or we're in safe mode.
+        // More fun, it may not even exist under some SAPIs in 5.3.0 or later...
+        $soname = $name . '.' . PHP_SHLIB_SUFFIX;
+        if (PHP_SHLIB_SUFFIX == 'dll') {
+            $soname = "php_" . $soname;
+        }
+        return @dl($soname);
     } else {
         return false;
     }
 }
 
+/**
+ * Show list of libraries
+ *
+ * @return void
+ */
 function showLibs()
 {
     global $external_libraries;
     $present_libraries=array();
     $absent_libraries=array();
-    foreach($external_libraries as $external_library){
-        if(haveExternalLibrary($external_library)){
+    foreach ($external_libraries as $external_library) {
+        if (haveExternalLibrary($external_library)) {
             $present_libraries[]=$external_library;
-        }else{
+        } else {
             $absent_libraries[]=$external_library;
         }
     }
@@ -314,22 +381,21 @@ function showLibs()
     <h2>Absent Libraries</h2>
     <ul id="absent_libraries">
 E_O_T;
-    foreach($absent_libraries as $library)
-    {
+    foreach ($absent_libraries as $library) {
         echo '<li>';
-        if(isset($library['url'])){
+        if (isset($library['url'])) {
             echo '<a href="'.$library['url'].'">'.htmlentities($library['name']).'</a>';
-        }else{
+        } else {
             echo htmlentities($library['name']);
         }
         echo '<ul>';
-        if(isset($library['deb'])){
+        if (isset($library['deb'])) {
             echo '<li class="deb package">deb: <a href="apt:' . urlencode($library['deb']) . '">' . htmlentities($library['deb']) . '</a></li>';
         }
-        if(isset($library['rpm'])){
+        if (isset($library['rpm'])) {
             echo '<li class="rpm package">rpm: ' . htmlentities($library['rpm']) . '</li>';
         }
-        if(isset($library['pear'])){
+        if (isset($library['pear'])) {
             echo '<li class="pear package">pear: ' . htmlentities($library['pear']) . '</li>';
         }
         echo '</ul>';
@@ -339,12 +405,11 @@ E_O_T;
     <h2>Installed Libraries</h2>
     <ul id="present_libraries">
 E_O_T;
-    foreach($present_libraries as $library)
-    {
+    foreach ($present_libraries as $library) {
         echo '<li>';
-        if(isset($library['url'])){
+        if (isset($library['url'])) {
             echo '<a href="'.$library['url'].'">'.htmlentities($library['name']).'</a>';
-        }else{
+        } else {
             echo htmlentities($library['name']);
         }
         echo '</li>';
@@ -356,6 +421,15 @@ E_O_T;
 
 function showForm()
 {
+    global $dbModules;
+    $dbRadios = '';
+    $checked = 'checked="checked" '; // Check the first one which exists
+    foreach ($dbModules as $type => $info) {
+        if (checkExtension($info['check_module'])) {
+            $dbRadios .= "<input type=\"radio\" name=\"dbtype\" id=\"dbtype-$type\" value=\"$type\" $checked/> $info[name]<br />\n";
+            $checked = '';
+        }
+    }
     echo<<<E_O_T
         </ul>
     </dd>
@@ -392,8 +466,7 @@ function showForm()
             <li>
 
                 <label for="dbtype">Type</label>
-                <input type="radio" name="dbtype" id="fancy-mysql" value="mysql" checked='checked' /> MySQL<br />
-                <input type="radio" name="dbtype" id="dbtype-pgsql" value="pgsql" /> PostgreSQL<br />
+                $dbRadios
                 <p class="form_guide">Database type</p>
             </li>
 
@@ -422,17 +495,11 @@ E_O_T;
 
 function updateStatus($status, $error=false)
 {
-?>
-                <li <?php echo ($error) ? 'class="error"': ''; ?>><?php echo $status;?></li>
-
-<?php
+    echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>";
 }
 
 function handlePost()
 {
-?>
-
-<?php
     $host     = $_POST['host'];
     $dbtype   = $_POST['dbtype'];
     $database = $_POST['database'];
@@ -443,55 +510,41 @@ function handlePost()
     $server = $_SERVER['HTTP_HOST'];
     $path = substr(dirname($_SERVER['PHP_SELF']), 1);
 
-?>
+    echo <<<STR
     <dl class="system_notice">
         <dt>Page notice</dt>
         <dd>
             <ul>
-<?php
-       $fail = false;
+STR;
+    $fail = false;
 
     if (empty($host)) {
         updateStatus("No hostname specified.", true);
-               $fail = true;
+        $fail = true;
     }
 
     if (empty($database)) {
         updateStatus("No database specified.", true);
-               $fail = true;
+        $fail = true;
     }
 
     if (empty($username)) {
         updateStatus("No username specified.", true);
-               $fail = true;
+        $fail = true;
     }
 
-//     if (empty($password)) {
-//         updateStatus("No password specified.", true);
-//             $fail = true;
-//     }
-
     if (empty($sitename)) {
         updateStatus("No sitename specified.", true);
-               $fail = true;
+        $fail = true;
     }
 
-    if($fail){
-            showForm();
+    if ($fail) {
+        showForm();
         return;
     }
 
-    // FIXME: use PEAR::DB or PDO instead of our own switch
-
-    switch($dbtype) {
-        case 'mysql':
-            $db = mysql_db_installer($host, $database, $username, $password);
-            break;
-        case 'pgsql':
-            $db = pgsql_db_installer($host, $database, $username, $password);
-            break;
-        default:
-    }
+    global $dbModules;
+    $db = call_user_func($dbModules[$dbtype]['installer'], $host, $database, $username, $password);
 
     if (!$db) {
         // database connection failed, do not move on to create config file.
@@ -514,112 +567,110 @@ function handlePost()
 
     updateStatus("StatusNet has been installed at $link");
     updateStatus("You can visit your <a href='$link'>new StatusNet site</a>.");
-?>
-
-<?php
 }
 
-function pgsql_db_installer($host, $database, $username, $password) {
-  $connstring = "dbname=$database host=$host user=$username";
-
-  //No password would mean trust authentication used.
-  if (!empty($password)) {
-    $connstring .= " password=$password";
-  }
-  updateStatus("Starting installation...");
-  updateStatus("Checking database...");
-  $conn = pg_connect($connstring);
-
-  if ($conn ===false) {
-    updateStatus("Failed to connect to database: $connstring");
-    showForm();
-    return false;
-  }
-
-  //ensure database encoding is UTF8
-  $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
-  if ($record->server_encoding != 'UTF8') {
-    updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
-    showForm();
-    return false;
-  }
-
-  updateStatus("Running database script...");
-  //wrap in transaction;
-  pg_query($conn, 'BEGIN');
-  $res = runDbScript(INSTALLDIR.'/db/statusnet_pg.sql', $conn, 'pgsql');
-
-  if ($res === false) {
-      updateStatus("Can't run database script.", true);
-      showForm();
-      return false;
-  }
-  foreach (array('sms_carrier' => 'SMS carrier',
+function Pgsql_Db_installer($host, $database, $username, $password)
+{
+    $connstring = "dbname=$database host=$host user=$username";
+
+    //No password would mean trust authentication used.
+    if (!empty($password)) {
+        $connstring .= " password=$password";
+    }
+    updateStatus("Starting installation...");
+    updateStatus("Checking database...");
+    $conn = pg_connect($connstring);
+
+    if ($conn ===false) {
+        updateStatus("Failed to connect to database: $connstring");
+        showForm();
+        return false;
+    }
+
+    //ensure database encoding is UTF8
+    $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
+    if ($record->server_encoding != 'UTF8') {
+        updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
+        showForm();
+        return false;
+    }
+
+    updateStatus("Running database script...");
+    //wrap in transaction;
+    pg_query($conn, 'BEGIN');
+    $res = runDbScript(INSTALLDIR.'/db/statusnet_pg.sql', $conn, 'pgsql');
+
+    if ($res === false) {
+        updateStatus("Can't run database script.", true);
+        showForm();
+        return false;
+    }
+    foreach (array('sms_carrier' => 'SMS carrier',
                 'notice_source' => 'notice source',
                 'foreign_services' => 'foreign service')
           as $scr => $name) {
-      updateStatus(sprintf("Adding %s data to database...", $name));
-      $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn, 'pgsql');
-      if ($res === false) {
-          updateStatus(sprintf("Can't run %d script.", $name), true);
-          showForm();
-          return false;
-      }
-  }
-  pg_query($conn, 'COMMIT');
-
-  if (empty($password)) {
-    $sqlUrl = "pgsql://$username@$host/$database";
-  }
-  else {
-    $sqlUrl = "pgsql://$username:$password@$host/$database";
-  }
-
-  $db = array('type' => 'pgsql', 'database' => $sqlUrl);
-
-  return $db;
+        updateStatus(sprintf("Adding %s data to database...", $name));
+        $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn, 'pgsql');
+        if ($res === false) {
+            updateStatus(sprintf("Can't run %d script.", $name), true);
+            showForm();
+            return false;
+        }
+    }
+    pg_query($conn, 'COMMIT');
+
+    if (empty($password)) {
+        $sqlUrl = "pgsql://$username@$host/$database";
+    } else {
+        $sqlUrl = "pgsql://$username:$password@$host/$database";
+    }
+
+    $db = array('type' => 'pgsql', 'database' => $sqlUrl);
+
+    return $db;
 }
 
-function mysql_db_installer($host, $database, $username, $password) {
-  updateStatus("Starting installation...");
-  updateStatus("Checking database...");
-
-  $conn = mysql_connect($host, $username, $password);
-  if (!$conn) {
-      updateStatus("Can't connect to server '$host' as '$username'.", true);
-      showForm();
-      return false;
-  }
-  updateStatus("Changing to database...");
-  $res = mysql_select_db($database, $conn);
-  if (!$res) {
-      updateStatus("Can't change to database.", true);
-      showForm();
-      return false;
-  }
-  updateStatus("Running database script...");
-  $res = runDbScript(INSTALLDIR.'/db/statusnet.sql', $conn);
-  if ($res === false) {
-      updateStatus("Can't run database script.", true);
-      showForm();
-      return false;
-  }
-  foreach (array('sms_carrier' => 'SMS carrier',
+function Mysql_Db_installer($host, $database, $username, $password)
+{
+    updateStatus("Starting installation...");
+    updateStatus("Checking database...");
+
+    $conn = mysql_connect($host, $username, $password);
+    if (!$conn) {
+        updateStatus("Can't connect to server '$host' as '$username'.", true);
+        showForm();
+        return false;
+    }
+    updateStatus("Changing to database...");
+    $res = mysql_select_db($database, $conn);
+    if (!$res) {
+        updateStatus("Can't change to database.", true);
+        showForm();
+        return false;
+    }
+    updateStatus("Running database script...");
+    $res = runDbScript(INSTALLDIR.'/db/statusnet.sql', $conn);
+    if ($res === false) {
+        updateStatus("Can't run database script.", true);
+        showForm();
+        return false;
+    }
+    foreach (array('sms_carrier' => 'SMS carrier',
                 'notice_source' => 'notice source',
                 'foreign_services' => 'foreign service')
           as $scr => $name) {
-      updateStatus(sprintf("Adding %s data to database...", $name));
-      $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
-      if ($res === false) {
-          updateStatus(sprintf("Can't run %d script.", $name), true);
-          showForm();
-          return false;
-      }
-  }
-
-      $sqlUrl = "mysqli://$username:$password@$host/$database";
-      $db = array('type' => 'mysql', 'database' => $sqlUrl);
-      return $db;
+        updateStatus(sprintf("Adding %s data to database...", $name));
+        $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
+        if ($res === false) {
+            updateStatus(sprintf("Can't run %d script.", $name), true);
+            showForm();
+            return false;
+        }
+    }
+
+    $sqlUrl = "mysqli://$username:$password@$host/$database";
+    $db = array('type' => 'mysql', 'database' => $sqlUrl);
+    return $db;
 }
 
 function writeConf($sitename, $server, $path, $fancy, $db)
@@ -650,7 +701,16 @@ function writeConf($sitename, $server, $path, $fancy, $db)
     return $res;
 }
 
-function runDbScript($filename, $conn, $type = 'mysql')
+/**
+ * Install schema into the database
+ *
+ * @param string $filename location of database schema file
+ * @param dbconn $conn     connection to database
+ * @param string $type     type of database, currently mysql or pgsql
+ *
+ * @return boolean - indicating success or failure
+ */
+function runDbScript($filename, $conn, $type = 'mysqli')
 {
     $sql = trim(file_get_contents($filename));
     $stmts = explode(';', $sql);
@@ -661,7 +721,7 @@ function runDbScript($filename, $conn, $type = 'mysql')
         }
         // FIXME: use PEAR::DB or PDO instead of our own switch
         switch ($type) {
-        case 'mysql':
+        case 'mysqli':
             $res = mysql_query($stmt, $conn);
             if ($res === false) {
                 $error = mysql_error();
@@ -686,7 +746,9 @@ function runDbScript($filename, $conn, $type = 'mysql')
 
 ?>
 <?php echo"<?"; ?> xml version="1.0" encoding="UTF-8" <?php echo "?>"; ?>
-<!DOCTYPE html>
+<!DOCTYPE html
+PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en_US" lang="en_US">
     <head>
         <title>Install StatusNet</title>
index 2165957c3b925a929e121826811c8a541ea9e0dd..0a943512f2a33c4bd7c551a43c6f71d07e47c278 100644 (file)
@@ -21,7 +21,9 @@ $(document).ready(function(){
        
        // count character on keyup
        function counter(event){
-               var maxLength = 140;
+         if (maxLength <= 0) {
+              return;
+         }
                var currentLength = $("#notice_data-text").val().length;
                var remaining = maxLength - currentLength;
                var counter = $("#notice_text-count");
@@ -67,12 +69,20 @@ $(document).ready(function(){
                return true;
        }
 
+     // define maxLength if it wasn't defined already
+
+    if (typeof(maxLength) == "undefined") {
+         maxLength = 140;
+    }
+
        if ($("#notice_data-text").length) {
-               $("#notice_data-text").bind("keyup", counter);
-               $("#notice_data-text").bind("keydown", submitonreturn);
+         if (maxLength > 0) {
+              $("#notice_data-text").bind("keyup", counter);
+              // run once in case there's something in there
+              counter();
+         }
 
-               // run once in case there's something in there
-               counter();
+               $("#notice_data-text").bind("keydown", submitonreturn);
 
         if($('body')[0].id != 'conversation') {
             $("#notice_data-text").focus();
@@ -218,7 +228,9 @@ $(document).ready(function(){
                                                                                                                                                                }
                                                                                                                                                                else {
                                                                                                                                                                        $("#notice_data-text").val("");
-                                                                                                                                                                       counter();
+                                                                                     if (maxLength > 0) {
+                                                                                          counter();
+                                                                                     }
                                                                                                                                                                }
                                                                                                                                                        }
                                                                                                                                                }
@@ -258,7 +270,9 @@ $(document).ready(function(){
                                                                                                $("#notice_data-attach").val("");
                                                                                                $("#notice_in-reply-to").val("");
                                                     $('#notice_data-attach_selected').remove();
-                                                    counter();
+                                                     if (maxLength > 0) {
+                                                          counter();
+                                                     }
                                                                                                }
                                                                                                $("#form_notice").removeClass("processing");
                                                                                                $("#notice_action-submit").removeAttr("disabled");
index 6402dbc099615918d7ee777330b61abfe3c8873b..18ae7719b25c9df14146079ece46afade3fd362f 100644 (file)
@@ -19,7 +19,7 @@
 
 if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
 
-class ShortUrlApi
+abstract class ShortUrlApi
 {
     protected $service_url;
     protected $long_limit = 27;
@@ -35,11 +35,9 @@ class ShortUrlApi
         return $url;
     }
 
-    protected function shorten_imp($url) {
-        return "To Override";
-    }
+    protected  abstract function shorten_imp($url);
 
-    private function is_long($url) {
+    protected function is_long($url) {
         return strlen($url) >= common_config('site', 'shorturllength');
     }
 
@@ -71,61 +69,3 @@ class ShortUrlApi
     }
 }
 
-class LilUrl extends ShortUrlApi
-{
-    function __construct()
-    {
-        parent::__construct('http://ur1.ca/');
-    }
-
-    protected function shorten_imp($url) {
-        $data['longurl'] = $url;
-        $response = $this->http_post($data);
-        if (!$response) return $url;
-        $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'];
-        return $url;
-    }
-}
-
-
-class PtitUrl extends ShortUrlApi
-{
-    function __construct()
-    {
-        parent::__construct('http://ptiturl.com/?creer=oui&action=Reduire&url=');
-    }
-
-    protected function shorten_imp($url) {
-        $response = $this->http_get($url);
-        if (!$response) return $url;
-        $response = $this->tidy($response);
-        $y = @simplexml_load_string($response);
-        if (!isset($y->body)) return $url;
-        $xml = $y->body->center->table->tr->td->pre->a->attributes();
-        if (isset($xml['href'])) return $xml['href'];
-        return $url;
-    }
-}
-
-class TightUrl extends ShortUrlApi
-{
-    function __construct()
-    {
-        parent::__construct('http://2tu.us/?save=y&url=');
-    }
-
-    protected function shorten_imp($url) {
-        $response = $this->http_get($url);
-        if (!$response) return $url;
-        $response = $this->tidy($response);
-        $y = @simplexml_load_string($response);
-        if (!isset($y->body)) return $url;
-        $xml = $y->body->p[0]->code[0]->a->attributes();
-        if (isset($xml['href'])) return $xml['href'];
-        return $url;
-    }
-}
-
index 798116163798b728fd2df6ae335dd57c2b944629..a004a3ed992f08a507e71763ab4f61887df302ba 100644 (file)
@@ -98,42 +98,39 @@ class AccountSettingsNav extends Widget
 
     function show()
     {
-        # action => array('prompt', 'title')
-        $menu =
-          array('profilesettings' =>
-                array(_('Profile'),
-                      _('Change your profile settings')),
-                'avatarsettings' =>
-                array(_('Avatar'),
-                      _('Upload an avatar')),
-                'passwordsettings' =>
-                array(_('Password'),
-                      _('Change your password')),
-                'emailsettings' =>
-                array(_('Email'),
-                      _('Change email handling')),
-                'openidsettings' =>
-                array(_('OpenID'),
-                      _('Add or remove OpenIDs')),
-                'userdesignsettings' =>
-                array(_('Design'),
-                      _('Design your profile')),
-                'othersettings' =>
-                array(_('Other'),
-                      _('Other options')));
-
         $action_name = $this->action->trimmed('action');
         $this->action->elementStart('ul', array('class' => 'nav'));
 
-        foreach ($menu as $menuaction => $menudesc) {
-            if ($menuaction == 'openidsettings' &&
-                !common_config('openid', 'enabled')) {
-                continue;
+        if (Event::handle('StartAccountSettingsNav', array(&$this->action))) {
+
+            $menu =
+              array('profilesettings' =>
+                    array(_('Profile'),
+                          _('Change your profile settings')),
+                    'avatarsettings' =>
+                    array(_('Avatar'),
+                          _('Upload an avatar')),
+                    'passwordsettings' =>
+                    array(_('Password'),
+                          _('Change your password')),
+                    'emailsettings' =>
+                    array(_('Email'),
+                          _('Change email handling')),
+                    'userdesignsettings' =>
+                    array(_('Design'),
+                          _('Design your profile')),
+                    'othersettings' =>
+                    array(_('Other'),
+                          _('Other options')));
+
+            foreach ($menu as $menuaction => $menudesc) {
+                $this->action->menuItem(common_local_url($menuaction),
+                                        $menudesc[0],
+                                        $menudesc[1],
+                                        $action_name === $menuaction);
             }
-            $this->action->menuItem(common_local_url($menuaction),
-                                   $menudesc[0],
-                                   $menudesc[1],
-                                   $action_name === $menuaction);
+
+            Event::handle('EndAccountSettingsNav', array(&$this->action));
         }
 
         $this->action->elementEnd('ul');
index 670eb498c1a63375af9c37b882c4a61b87f8af45..1b2f7375211364561a9f05409d55758294b5c325 100644 (file)
@@ -120,14 +120,16 @@ class Action extends HTMLOutputter // lawsuit
     {
         // XXX: attributes (profile?)
         $this->elementStart('head');
-        $this->showTitle();
-        $this->showShortcutIcon();
-        $this->showStylesheets();
-        $this->showScripts();
-        $this->showOpenSearch();
-        $this->showFeeds();
-        $this->showDescription();
-        $this->extraHead();
+        if (Event::handle('StartShowHeadElements', array($this))) {
+            $this->showTitle();
+            $this->showShortcutIcon();
+            $this->showStylesheets();
+            $this->showOpenSearch();
+            $this->showFeeds();
+            $this->showDescription();
+            $this->extraHead();
+            Event::handle('EndShowHeadElements', array($this));
+        }
         $this->elementEnd('head');
     }
 
@@ -352,6 +354,7 @@ class Action extends HTMLOutputter // lawsuit
             Event::handle('EndShowFooter', array($this));
         }
         $this->elementEnd('div');
+        $this->showScripts();
         $this->elementEnd('body');
     }
 
@@ -442,17 +445,12 @@ class Action extends HTMLOutputter // lawsuit
                                 _('Logout'), _('Logout from the site'), false, 'nav_logout');
             }
             else {
-                if (!common_config('site', 'openidonly')) {
-                    if (!common_config('site', 'closed')) {
-                        $this->menuItem(common_local_url('register'),
-                                        _('Register'), _('Create an account'), false, 'nav_register');
-                    }
-                    $this->menuItem(common_local_url('login'),
-                                    _('Login'), _('Login to the site'), false, 'nav_login');
-                } else {
-                    $this->menuItem(common_local_url('openidlogin'),
-                                    _('OpenID'), _('Login with OpenID'), false, 'nav_openid');
+                if (!common_config('site', 'closed')) {
+                    $this->menuItem(common_local_url('register'),
+                                    _('Register'), _('Create an account'), false, 'nav_register');
                 }
+                $this->menuItem(common_local_url('login'),
+                                _('Login'), _('Login to the site'), false, 'nav_login');
             }
             $this->menuItem(common_local_url('doc', array('title' => 'help')),
                             _('Help'), _('Help me!'), false, 'nav_help');
@@ -530,7 +528,10 @@ class Action extends HTMLOutputter // lawsuit
             $this->showContentBlock();
             Event::handle('EndShowContentBlock', array($this));
         }
-        $this->showAside();
+        if (Event::handle('StartShowAside', array($this))) {
+            $this->showAside();
+            Event::handle('EndShowAside', array($this));
+        }
         $this->elementEnd('div');
     }
 
diff --git a/lib/api.php b/lib/api.php
new file mode 100644 (file)
index 0000000..7a63a4a
--- /dev/null
@@ -0,0 +1,1216 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Base API action
+ *
+ * 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  API
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @author    Dan Moore <dan@moore.cx>
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Jeffery To <jeffery.to@gmail.com>
+ * @author    Toby Inkster <mail@tobyinkster.co.uk>
+ * @author    Zach Copley <zach@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);
+}
+
+/**
+ * Contains most of the Twitter-compatible API output functions.
+ *
+ * @category API
+ * @package  StatusNet
+ * @author   Craig Andrews <candrews@integralblue.com>
+ * @author   Dan Moore <dan@moore.cx>
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Jeffery To <jeffery.to@gmail.com>
+ * @author   Toby Inkster <mail@tobyinkster.co.uk>
+ * @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 ApiAction extends Action
+{
+     var $format   = null;
+     var $user     = null;
+     var $page     = null;
+     var $count    = null;
+     var $max_id   = null;
+     var $since_id = null;
+     var $since    = null;
+     
+    /**
+     * Initialization.
+     *
+     * @param array $args Web and URL arguments
+     *
+     * @return boolean false if user doesn't exist
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        
+        $this->format   = $this->arg('format');
+        $this->page     = (int)$this->arg('page', 1);
+        $this->count    = (int)$this->arg('count', 20);
+        $this->max_id   = (int)$this->arg('max_id', 0);
+        $this->since_id = (int)$this->arg('since_id', 0);
+        $this->since    = $this->arg('since');
+        
+        return true;
+    }
+
+    /**
+     * Handle a request
+     *
+     * @param array $args Arguments from $_REQUEST
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+    }
+
+    /**
+     * Overrides XMLOutputter::element to write booleans as strings (true|false).
+     * See that method's documentation for more info.
+     *
+     * @param string $tag     Element type or tagname
+     * @param array  $attrs   Array of element attributes, as
+     *                        key-value pairs
+     * @param string $content string content of the element
+     *
+     * @return void
+     */
+    function element($tag, $attrs=null, $content=null)
+    {
+        if (is_bool($content)) {
+            $content = ($content ? 'true' : 'false');
+        }
+
+        return parent::element($tag, $attrs, $content);
+    }
+
+    function twitterUserArray($profile, $get_notice=false)
+    {
+        $twitter_user = array();
+
+        $twitter_user['id'] = intval($profile->id);
+        $twitter_user['name'] = $profile->getBestName();
+        $twitter_user['screen_name'] = $profile->nickname;
+        $twitter_user['location'] = ($profile->location) ? $profile->location : null;
+        $twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
+
+        $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+        $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() :
+            Avatar::defaultImage(AVATAR_STREAM_SIZE);
+
+        $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
+        $twitter_user['protected'] = false; # not supported by StatusNet yet
+        $twitter_user['followers_count'] = $profile->subscriberCount();
+
+        // To be supported soon...
+        $twitter_user['profile_background_color'] = '';
+        $twitter_user['profile_text_color'] = '';
+        $twitter_user['profile_link_color'] = '';
+        $twitter_user['profile_sidebar_fill_color'] = '';
+        $twitter_user['profile_sidebar_border_color'] = '';
+
+        $twitter_user['friends_count'] = $profile->subscriptionCount();
+
+        $twitter_user['created_at'] = $this->dateTwitter($profile->created);
+
+        $twitter_user['favourites_count'] = $profile->faveCount(); // British spelling!
+
+        // Need to pull up the user for some of this
+        $user = User::staticGet($profile->id);
+
+        $timezone = 'UTC';
+
+        if ($user->timezone) {
+            $timezone = $user->timezone;
+        }
+
+        $t = new DateTime;
+        $t->setTimezone(new DateTimeZone($timezone));
+
+        $twitter_user['utc_offset'] = $t->format('Z');
+        $twitter_user['time_zone'] = $timezone;
+
+        // To be supported some day, perhaps
+        $twitter_user['profile_background_image_url'] = '';
+        $twitter_user['profile_background_tile'] = false;
+
+        $twitter_user['statuses_count'] = $profile->noticeCount();
+
+        // Is the requesting user following this user?
+        $twitter_user['following'] = false;
+        $twitter_user['notifications'] = false;
+
+        if (isset($apidata['user'])) {
+
+            $twitter_user['following'] = $apidata['user']->isSubscribed($profile);
+
+            // Notifications on?
+            $sub = Subscription::pkeyGet(array('subscriber' =>
+                $apidata['user']->id, 'subscribed' => $profile->id));
+
+            if ($sub) {
+                $twitter_user['notifications'] = ($sub->jabber || $sub->sms);
+            }
+        }
+
+        if ($get_notice) {
+            $notice = $profile->getCurrentNotice();
+            if ($notice) {
+                # don't get user!
+                $twitter_user['status'] = $this->twitterStatusArray($notice, false);
+            }
+        }
+
+        return $twitter_user;
+    }
+
+    function twitterStatusArray($notice, $include_user=true)
+    {
+        $profile = $notice->getProfile();
+
+        $twitter_status = array();
+        $twitter_status['text'] = $notice->content;
+        $twitter_status['truncated'] = false; # Not possible on StatusNet
+        $twitter_status['created_at'] = $this->dateTwitter($notice->created);
+        $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ?
+            intval($notice->reply_to) : null;
+        $twitter_status['source'] = $this->sourceLink($notice->source);
+        $twitter_status['id'] = intval($notice->id);
+
+        $replier_profile = null;
+
+        if ($notice->reply_to) {
+            $reply = Notice::staticGet(intval($notice->reply_to));
+            if ($reply) {
+                $replier_profile = $reply->getProfile();
+            }
+        }
+
+        $twitter_status['in_reply_to_user_id'] =
+            ($replier_profile) ? intval($replier_profile->id) : null;
+        $twitter_status['in_reply_to_screen_name'] =
+            ($replier_profile) ? $replier_profile->nickname : null;
+
+        if (isset($this->auth_user)) {
+            $twitter_status['favorited'] = $this->auth_user->hasFave($notice);
+        } else {
+            $twitter_status['favorited'] = false;
+        }
+
+        // Enclosures
+        $attachments = $notice->attachments();
+
+        if (!empty($attachments)) {
+
+            $twitter_status['attachments'] = array();
+
+            foreach ($attachments as $attachment) {
+                if ($attachment->isEnclosure()) {
+                    $enclosure = array();
+                    $enclosure['url'] = $attachment->url;
+                    $enclosure['mimetype'] = $attachment->mimetype;
+                    $enclosure['size'] = $attachment->size;
+                    $twitter_status['attachments'][] = $enclosure;
+                }
+            }
+        }
+
+        if ($include_user) {
+            # Don't get notice (recursive!)
+            $twitter_user = $this->twitterUserArray($profile, false);
+            $twitter_status['user'] = $twitter_user;
+        }
+
+        return $twitter_status;
+    }
+
+    function twitterGroupArray($group)
+    {
+        $twitter_group=array();
+        $twitter_group['id']=$group->id;
+        $twitter_group['url']=$group->permalink();
+        $twitter_group['nickname']=$group->nickname;
+        $twitter_group['fullname']=$group->fullname;
+        $twitter_group['homepage_url']=$group->homepage_url;
+        $twitter_group['original_logo']=$group->original_logo;
+        $twitter_group['homepage_logo']=$group->homepage_logo;
+        $twitter_group['stream_logo']=$group->stream_logo;
+        $twitter_group['mini_logo']=$group->mini_logo;
+        $twitter_group['homepage']=$group->homepage;
+        $twitter_group['description']=$group->description;
+        $twitter_group['location']=$group->location;
+        $twitter_group['created']=$this->dateTwitter($group->created);
+        $twitter_group['modified']=$this->dateTwitter($group->modified);
+        return $twitter_group;
+    }
+
+    function twitterRssGroupArray($group)
+    {
+        $entry = array();
+        $entry['content']=$group->description;
+        $entry['title']=$group->nickname;
+        $entry['link']=$group->permalink();
+        $entry['published']=common_date_iso8601($group->created);
+        $entry['updated']==common_date_iso8601($group->modified);
+        $taguribase = common_config('integration', 'groupuri');
+        $entry['id'] = "group:$groupuribase:$entry[link]";
+
+        $entry['description'] = $entry['content'];
+        $entry['pubDate'] = common_date_rfc2822($group->created);
+        $entry['guid'] = $entry['link'];
+
+        return $entry;
+    }
+
+    function twitterRssEntryArray($notice)
+    {
+        $profile = $notice->getProfile();
+        $entry = array();
+
+        // We trim() to avoid extraneous whitespace in the output
+
+        $entry['content'] = common_xml_safe_str(trim($notice->rendered));
+        $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
+        $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id));
+        $entry['published'] = common_date_iso8601($notice->created);
+
+        $taguribase = common_config('integration', 'taguri');
+        $entry['id'] = "tag:$taguribase:$entry[link]";
+
+        $entry['updated'] = $entry['published'];
+        $entry['author'] = $profile->getBestName();
+
+        // Enclosures
+        $attachments = $notice->attachments();
+        $enclosures = array();
+
+        foreach ($attachments as $attachment) {
+            $enclosure_o=$attachment->getEnclosure();
+            if ($enclosure_o) {
+                 $enclosure = array();
+                 $enclosure['url'] = $enclosure_o->url;
+                 $enclosure['mimetype'] = $enclosure_o->mimetype;
+                 $enclosure['size'] = $enclosure_o->size;
+                 $enclosures[] = $enclosure;
+            }
+        }
+
+        if (!empty($enclosures)) {
+            $entry['enclosures'] = $enclosures;
+        }
+
+        // Tags/Categories
+        $tag = new Notice_tag();
+        $tag->notice_id = $notice->id;
+        if ($tag->find()) {
+            $entry['tags']=array();
+            while ($tag->fetch()) {
+                $entry['tags'][]=$tag->tag;
+            }
+        }
+        $tag->free();
+
+        // RSS Item specific
+        $entry['description'] = $entry['content'];
+        $entry['pubDate'] = common_date_rfc2822($notice->created);
+        $entry['guid'] = $entry['link'];
+
+        return $entry;
+    }
+
+
+    function twitterRelationshipArray($source, $target)
+    {
+        $relationship = array();
+
+        $relationship['source'] =
+            $this->relationshipDetailsArray($source, $target);
+        $relationship['target'] =
+            $this->relationshipDetailsArray($target, $source);
+
+        return array('relationship' => $relationship);
+    }
+
+    function relationshipDetailsArray($source, $target)
+    {
+        $details = array();
+
+        $details['screen_name'] = $source->nickname;
+        $details['followed_by'] = $target->isSubscribed($source);
+        $details['following'] = $source->isSubscribed($target);
+
+        $notifications = false;
+
+        if ($source->isSubscribed($target)) {
+
+            $sub = Subscription::pkeyGet(array('subscriber' =>
+                $source->id, 'subscribed' => $target->id));
+
+            if (!empty($sub)) {
+                $notifications = ($sub->jabber || $sub->sms);
+            }
+        }
+
+        $details['notifications_enabled'] = $notifications;
+        $details['blocking'] = $source->hasBlocked($target);
+        $details['id'] = $source->id;
+
+        return $details;
+    }
+
+    function showTwitterXmlRelationship($relationship)
+    {
+        $this->elementStart('relationship');
+
+        foreach($relationship as $element => $value) {
+            if ($element == 'source' || $element == 'target') {
+                $this->elementStart($element);
+                $this->showXmlRelationshipDetails($value);
+                $this->elementEnd($element);
+            }
+        }
+
+        $this->elementEnd('relationship');
+    }
+
+    function showXmlRelationshipDetails($details)
+    {
+        foreach($details as $element => $value) {
+            $this->element($element, null, $value);
+        }
+    }
+
+    function showTwitterXmlStatus($twitter_status)
+    {
+        $this->elementStart('status');
+        foreach($twitter_status as $element => $value) {
+            switch ($element) {
+            case 'user':
+                $this->showTwitterXmlUser($twitter_status['user']);
+                break;
+            case 'text':
+                $this->element($element, null, common_xml_safe_str($value));
+                break;
+            case 'attachments':
+                $this->showXmlAttachments($twitter_status['attachments']);
+                break;
+            default:
+                $this->element($element, null, $value);
+            }
+        }
+        $this->elementEnd('status');
+    }
+
+    function showTwitterXmlGroup($twitter_group)
+    {
+        $this->elementStart('group');
+        foreach($twitter_group as $element => $value) {
+            $this->element($element, null, $value);
+        }
+        $this->elementEnd('group');
+    }
+
+    function showTwitterXmlUser($twitter_user, $role='user')
+    {
+        $this->elementStart($role);
+        foreach($twitter_user as $element => $value) {
+            if ($element == 'status') {
+                $this->showTwitterXmlStatus($twitter_user['status']);
+            } else {
+                $this->element($element, null, $value);
+            }
+        }
+        $this->elementEnd($role);
+    }
+
+    function showXmlAttachments($attachments) {
+        if (!empty($attachments)) {
+            $this->elementStart('attachments', array('type' => 'array'));
+            foreach ($attachments as $attachment) {
+                $attrs = array();
+                $attrs['url'] = $attachment['url'];
+                $attrs['mimetype'] = $attachment['mimetype'];
+                $attrs['size'] = $attachment['size'];
+                $this->element('enclosure', $attrs, '');
+            }
+            $this->elementEnd('attachments');
+        }
+    }
+
+    function showTwitterRssItem($entry)
+    {
+        $this->elementStart('item');
+        $this->element('title', null, $entry['title']);
+        $this->element('description', null, $entry['description']);
+        $this->element('pubDate', null, $entry['pubDate']);
+        $this->element('guid', null, $entry['guid']);
+        $this->element('link', null, $entry['link']);
+
+        # RSS only supports 1 enclosure per item
+        if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){
+            $enclosure = $entry['enclosures'][0];
+            $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null);
+        }
+
+        if(array_key_exists('tags', $entry)){
+            foreach($entry['tags'] as $tag){
+                $this->element('category', null,$tag);
+            }
+        }
+
+        $this->elementEnd('item');
+    }
+
+    function showJsonObjects($objects)
+    {
+        print(json_encode($objects));
+    }
+
+    function showSingleXmlStatus($notice)
+    {
+        $this->initDocument('xml');
+        $twitter_status = $this->twitterStatusArray($notice);
+        $this->showTwitterXmlStatus($twitter_status);
+        $this->endDocument('xml');
+    }
+
+    function show_single_json_status($notice)
+    {
+        $this->initDocument('json');
+        $status = $this->twitterStatusArray($notice);
+        $this->showJsonObjects($status);
+        $this->endDocument('json');
+    }
+
+
+    function showXmlTimeline($notice)
+    {
+
+        $this->initDocument('xml');
+        $this->elementStart('statuses', array('type' => 'array'));
+
+        if (is_array($notice)) {
+            foreach ($notice as $n) {
+                $twitter_status = $this->twitterStatusArray($n);
+                $this->showTwitterXmlStatus($twitter_status);
+            }
+        } else {
+            while ($notice->fetch()) {
+                $twitter_status = $this->twitterStatusArray($notice);
+                $this->showTwitterXmlStatus($twitter_status);
+            }
+        }
+
+        $this->elementEnd('statuses');
+        $this->endDocument('xml');
+    }
+
+    function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null)
+    {
+
+        $this->initDocument('rss');
+
+        $this->element('title', null, $title);
+        $this->element('link', null, $link);
+        if (!is_null($suplink)) {
+            // For FriendFeed's SUP protocol
+            $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
+                                         'rel' => 'http://api.friendfeed.com/2008/03#sup',
+                                         'href' => $suplink,
+                                         'type' => 'application/json'));
+        }
+        $this->element('description', null, $subtitle);
+        $this->element('language', null, 'en-us');
+        $this->element('ttl', null, '40');
+
+        if (is_array($notice)) {
+            foreach ($notice as $n) {
+                $entry = $this->twitterRssEntryArray($n);
+                $this->showTwitterRssItem($entry);
+            }
+        } else {
+            while ($notice->fetch()) {
+                $entry = $this->twitterRssEntryArray($notice);
+                $this->showTwitterRssItem($entry);
+            }
+        }
+
+        $this->endTwitterRss();
+    }
+
+    function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null)
+    {
+
+        $this->initDocument('atom');
+
+        $this->element('title', null, $title);
+        $this->element('id', null, $id);
+        $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
+
+        if (!is_null($suplink)) {
+            # For FriendFeed's SUP protocol
+            $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
+                                         'href' => $suplink,
+                                         'type' => 'application/json'));
+        }
+
+        if (!is_null($selfuri)) {
+            $this->element('link', array('href' => $selfuri,
+                'rel' => 'self', 'type' => 'application/atom+xml'), null);
+        }
+
+        $this->element('updated', null, common_date_iso8601('now'));
+        $this->element('subtitle', null, $subtitle);
+
+        if (is_array($notice)) {
+            foreach ($notice as $n) {
+                $this->raw($n->asAtomEntry());
+            }
+        } else {
+            while ($notice->fetch()) {
+                $this->raw($notice->asAtomEntry());
+            }
+        }
+
+        $this->endDocument('atom');
+
+    }
+
+    function showRssGroups($group, $title, $link, $subtitle)
+    {
+
+        $this->initDocument('rss');
+
+        $this->element('title', null, $title);
+        $this->element('link', null, $link);
+        $this->element('description', null, $subtitle);
+        $this->element('language', null, 'en-us');
+        $this->element('ttl', null, '40');
+
+        if (is_array($group)) {
+            foreach ($group as $g) {
+                $twitter_group = $this->twitterRssGroupArray($g);
+                $this->showTwitterRssItem($twitter_group);
+            }
+        } else {
+            while ($group->fetch()) {
+                $twitter_group = $this->twitterRssGroupArray($group);
+                $this->showTwitterRssItem($twitter_group);
+            }
+        }
+
+        $this->endTwitterRss();
+    }
+
+
+    function showTwitterAtomEntry($entry)
+    {
+        $this->elementStart('entry');
+        $this->element('title', null, $entry['title']);
+        $this->element('content', array('type' => 'html'), $entry['content']);
+        $this->element('id', null, $entry['id']);
+        $this->element('published', null, $entry['published']);
+        $this->element('updated', null, $entry['updated']);
+        $this->element('link', array('type' => 'text/html',
+                                     'href' => $entry['link'],
+                                     'rel' => 'alternate'));
+        $this->element('link', array('type' => $entry['avatar-type'],
+                                     'href' => $entry['avatar'],
+                                     'rel' => 'image'));
+        $this->elementStart('author');
+
+        $this->element('name', null, $entry['author-name']);
+        $this->element('uri', null, $entry['author-uri']);
+
+        $this->elementEnd('author');
+        $this->elementEnd('entry');
+    }
+
+    function showXmlDirectMessage($dm)
+    {
+        $this->elementStart('direct_message');
+        foreach($dm as $element => $value) {
+            switch ($element) {
+            case 'sender':
+            case 'recipient':
+                $this->showTwitterXmlUser($value, $element);
+                break;
+            case 'text':
+                $this->element($element, null, common_xml_safe_str($value));
+                break;
+            default:
+                $this->element($element, null, $value);
+                break;
+            }
+        }
+        $this->elementEnd('direct_message');
+    }
+
+    function directMessageArray($message)
+    {
+        $dmsg = array();
+
+        $from_profile = $message->getFrom();
+        $to_profile = $message->getTo();
+
+        $dmsg['id'] = $message->id;
+        $dmsg['sender_id'] = $message->from_profile;
+        $dmsg['text'] = trim($message->content);
+        $dmsg['recipient_id'] = $message->to_profile;
+        $dmsg['created_at'] = $this->dateTwitter($message->created);
+        $dmsg['sender_screen_name'] = $from_profile->nickname;
+        $dmsg['recipient_screen_name'] = $to_profile->nickname;
+        $dmsg['sender'] = $this->twitterUserArray($from_profile, false);
+        $dmsg['recipient'] = $this->twitterUserArray($to_profile, false);
+
+        return $dmsg;
+    }
+
+    function rssDirectMessageArray($message)
+    {
+        $entry = array();
+
+        $from = $message->getFrom();
+
+        $entry['title'] = sprintf('Message from %s to %s',
+            $from->nickname, $message->getTo()->nickname);
+
+        $entry['content'] = common_xml_safe_str($message->rendered);
+        $entry['link'] = common_local_url('showmessage', array('message' => $message->id));
+        $entry['published'] = common_date_iso8601($message->created);
+
+        $taguribase = common_config('integration', 'taguri');
+
+        $entry['id'] = "tag:$taguribase:$entry[link]";
+        $entry['updated'] = $entry['published'];
+
+        $entry['author-name'] = $from->getBestName();
+        $entry['author-uri'] = $from->homepage;
+
+        $avatar = $from->getAvatar(AVATAR_STREAM_SIZE);
+
+        $entry['avatar']      = (!empty($avatar)) ? $avatar->url : Avatar::defaultImage(AVATAR_STREAM_SIZE);
+        $entry['avatar-type'] = (!empty($avatar)) ? $avatar->mediatype : 'image/png';
+
+        // RSS item specific
+
+        $entry['description'] = $entry['content'];
+        $entry['pubDate'] = common_date_rfc2822($message->created);
+        $entry['guid'] = $entry['link'];
+
+        return $entry;
+    }
+
+    function showSingleXmlDirectMessage($message)
+    {
+        $this->initDocument('xml');
+        $dmsg = $this->directMessageArray($message);
+        $this->showXmlDirectMessage($dmsg);
+        $this->endDocument('xml');
+    }
+
+    function showSingleJsonDirectMessage($message)
+    {
+        $this->initDocument('json');
+        $dmsg = $this->directMessageArray($message);
+        $this->showJsonObjects($dmsg);
+        $this->endDocument('json');
+    }
+
+    function showAtomGroups($group, $title, $id, $link, $subtitle=null, $selfuri=null)
+    {
+
+        $this->initDocument('atom');
+
+        $this->element('title', null, $title);
+        $this->element('id', null, $id);
+        $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
+
+        if (!is_null($selfuri)) {
+            $this->element('link', array('href' => $selfuri,
+                'rel' => 'self', 'type' => 'application/atom+xml'), null);
+        }
+
+        $this->element('updated', null, common_date_iso8601('now'));
+        $this->element('subtitle', null, $subtitle);
+
+        if (is_array($group)) {
+            foreach ($group as $g) {
+                $this->raw($g->asAtomEntry());
+            }
+        } else {
+            while ($group->fetch()) {
+                $this->raw($group->asAtomEntry());
+            }
+        }
+
+        $this->endDocument('atom');
+
+    }
+
+    function showJsonTimeline($notice)
+    {
+
+        $this->initDocument('json');
+
+        $statuses = array();
+
+        if (is_array($notice)) {
+            foreach ($notice as $n) {
+                $twitter_status = $this->twitterStatusArray($n);
+                array_push($statuses, $twitter_status);
+            }
+        } else {
+            while ($notice->fetch()) {
+                $twitter_status = $this->twitterStatusArray($notice);
+                array_push($statuses, $twitter_status);
+            }
+        }
+
+        $this->showJsonObjects($statuses);
+
+        $this->endDocument('json');
+    }
+
+    function showJsonGroups($group)
+    {
+
+        $this->initDocument('json');
+
+        $groups = array();
+
+        if (is_array($group)) {
+            foreach ($group as $g) {
+                $twitter_group = $this->twitterGroupArray($g);
+                array_push($groups, $twitter_group);
+            }
+        } else {
+            while ($group->fetch()) {
+                $twitter_group = $this->twitterGroupArray($group);
+                array_push($groups, $twitter_group);
+            }
+        }
+
+        $this->showJsonObjects($groups);
+
+        $this->endDocument('json');
+    }
+
+    function showXmlGroups($group)
+    {
+
+        $this->initDocument('xml');
+        $this->elementStart('groups', array('type' => 'array'));
+
+        if (is_array($group)) {
+            foreach ($group as $g) {
+                $twitter_group = $this->twitterGroupArray($g);
+                $this->showTwitterXmlGroup($twitter_group);
+            }
+        } else {
+            while ($group->fetch()) {
+                $twitter_group = $this->twitterGroupArray($group);
+                $this->showTwitterXmlGroup($twitter_group);
+            }
+        }
+
+        $this->elementEnd('groups');
+        $this->endDocument('xml');
+    }
+
+    function showTwitterXmlUsers($user)
+    {
+
+        $this->initDocument('xml');
+        $this->elementStart('users', array('type' => 'array'));
+
+        if (is_array($user)) {
+            foreach ($user as $u) {
+                $twitter_user = $this->twitterUserArray($u);
+                $this->showTwitterXmlUser($twitter_user);
+            }
+        } else {
+            while ($user->fetch()) {
+                $twitter_user = $this->twitterUserArray($user);
+                $this->showTwitterXmlUser($twitter_user);
+            }
+        }
+
+        $this->elementEnd('users');
+        $this->endDocument('xml');
+    }
+
+    function showJsonUsers($user)
+    {
+
+        $this->initDocument('json');
+
+        $users = array();
+
+        if (is_array($user)) {
+            foreach ($user as $u) {
+                $twitter_user = $this->twitterUserArray($u);
+                array_push($users, $twitter_user);
+            }
+        } else {
+            while ($user->fetch()) {
+                $twitter_user = $this->twitterUserArray($user);
+                array_push($users, $twitter_user);
+            }
+        }
+
+        $this->showJsonObjects($users);
+
+        $this->endDocument('json');
+    }
+
+    function showSingleJsonGroup($group)
+    {
+        $this->initDocument('json');
+        $twitter_group = $this->twitterGroupArray($group);
+        $this->showJsonObjects($twitter_group);
+        $this->endDocument('json');
+    }
+
+    function showSingleXmlGroup($group)
+    {
+        $this->initDocument('xml');
+        $twitter_group = $this->twitterGroupArray($group);
+        $this->showTwitterXmlGroup($twitter_group);
+        $this->endDocument('xml');
+    }
+
+    function dateTwitter($dt)
+    {
+        $dateStr = date('d F Y H:i:s', strtotime($dt));
+        $d = new DateTime($dateStr, new DateTimeZone('UTC'));
+        $d->setTimezone(new DateTimeZone(common_timezone()));
+        return $d->format('D M d H:i:s O Y');
+    }
+
+    function initDocument($type='xml')
+    {
+        switch ($type) {
+        case 'xml':
+            header('Content-Type: application/xml; charset=utf-8');
+            $this->startXML();
+            break;
+        case 'json':
+            header('Content-Type: application/json; charset=utf-8');
+
+            // Check for JSONP callback
+            $callback = $this->arg('callback');
+            if ($callback) {
+                print $callback . '(';
+            }
+            break;
+        case 'rss':
+            header("Content-Type: application/rss+xml; charset=utf-8");
+            $this->initTwitterRss();
+            break;
+        case 'atom':
+            header('Content-Type: application/atom+xml; charset=utf-8');
+            $this->initTwitterAtom();
+            break;
+        default:
+            $this->clientError(_('Not a supported data format.'));
+            break;
+        }
+
+        return;
+    }
+
+    function endDocument($type='xml')
+    {
+        switch ($type) {
+        case 'xml':
+            $this->endXML();
+            break;
+        case 'json':
+
+            // Check for JSONP callback
+            $callback = $this->arg('callback');
+            if ($callback) {
+                print ')';
+            }
+            break;
+        case 'rss':
+            $this->endTwitterRss();
+            break;
+        case 'atom':
+            $this->endTwitterRss();
+            break;
+        default:
+            $this->clientError(_('Not a supported data format.'));
+            break;
+        }
+        return;
+    }
+
+    function clientError($msg, $code = 400, $format = 'xml')
+    {
+        $action = $this->trimmed('action');
+
+        common_debug("User error '$code' on '$action': $msg", __FILE__);
+
+        if (!array_key_exists($code, ClientErrorAction::$status)) {
+            $code = 400;
+        }
+
+        $status_string = ClientErrorAction::$status[$code];
+
+        header('HTTP/1.1 '.$code.' '.$status_string);
+
+        if ($format == 'xml') {
+            $this->initDocument('xml');
+            $this->elementStart('hash');
+            $this->element('error', null, $msg);
+            $this->element('request', null, $_SERVER['REQUEST_URI']);
+            $this->elementEnd('hash');
+            $this->endDocument('xml');
+        } elseif ($format == 'json'){
+            $this->initDocument('json');
+            $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
+            print(json_encode($error_array));
+            $this->endDocument('json');
+        } else {
+
+            // If user didn't request a useful format, throw a regular client error
+            throw new ClientException($msg, $code);
+        }
+    }
+
+    function serverError($msg, $code = 500, $content_type = 'json')
+    {
+        $action = $this->trimmed('action');
+
+        common_debug("Server error '$code' on '$action': $msg", __FILE__);
+
+        if (!array_key_exists($code, ServerErrorAction::$status)) {
+            $code = 400;
+        }
+
+        $status_string = ServerErrorAction::$status[$code];
+
+        header('HTTP/1.1 '.$code.' '.$status_string);
+
+        if ($content_type == 'xml') {
+            $this->initDocument('xml');
+            $this->elementStart('hash');
+            $this->element('error', null, $msg);
+            $this->element('request', null, $_SERVER['REQUEST_URI']);
+            $this->elementEnd('hash');
+            $this->endDocument('xml');
+        } else {
+            $this->initDocument('json');
+            $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
+            print(json_encode($error_array));
+            $this->endDocument('json');
+        }
+    }
+
+    function initTwitterRss()
+    {
+        $this->startXML();
+        $this->elementStart('rss', array('version' => '2.0', 'xmlns:atom'=>'http://www.w3.org/2005/Atom'));
+        $this->elementStart('channel');
+        Event::handle('StartApiRss', array($this));
+    }
+
+    function endTwitterRss()
+    {
+        $this->elementEnd('channel');
+        $this->elementEnd('rss');
+        $this->endXML();
+    }
+
+    function initTwitterAtom()
+    {
+        $this->startXML();
+        // FIXME: don't hardcode the language here!
+        $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom',
+                                          'xml:lang' => 'en-US',
+                                          'xmlns:thr' => 'http://purl.org/syndication/thread/1.0'));
+        Event::handle('StartApiAtom', array($this));
+    }
+
+    function endTwitterAtom()
+    {
+        $this->elementEnd('feed');
+        $this->endXML();
+    }
+
+    function showProfile($profile, $content_type='xml', $notice=null, $includeStatuses=true)
+    {
+        $profile_array = $this->twitterUserArray($profile, $includeStatuses);
+        switch ($content_type) {
+        case 'xml':
+            $this->showTwitterXmlUser($profile_array);
+            break;
+        case 'json':
+            $this->showJsonObjects($profile_array);
+            break;
+        default:
+            $this->clientError(_('Not a supported data format.'));
+            return;
+        }
+        return;
+    }
+
+    function getTargetUser($id)
+    {
+        if (empty($id)) {
+
+            // Twitter supports these other ways of passing the user ID
+            if (is_numeric($this->arg('id'))) {
+                return User::staticGet($this->arg('id'));
+            } else if ($this->arg('id')) {
+                $nickname = common_canonical_nickname($this->arg('id'));
+                return User::staticGet('nickname', $nickname);
+            } else if ($this->arg('user_id')) {
+                // This is to ensure that a non-numeric user_id still
+                // overrides screen_name even if it doesn't get used
+                if (is_numeric($this->arg('user_id'))) {
+                    return User::staticGet('id', $this->arg('user_id'));
+                }
+            } else if ($this->arg('screen_name')) {
+                $nickname = common_canonical_nickname($this->arg('screen_name'));
+                return User::staticGet('nickname', $nickname);
+            } else {
+                // Fall back to trying the currently authenticated user
+                return $this->auth_user;
+            }
+
+        } else if (is_numeric($id)) {
+            return User::staticGet($id);
+        } else {
+            $nickname = common_canonical_nickname($id);
+            return User::staticGet('nickname', $nickname);
+        }
+    }
+
+    function getTargetGroup($id)
+    {
+        if (empty($id)) {
+            if (is_numeric($this->arg('id'))) {
+                return User_group::staticGet($this->arg('id'));
+            } else if ($this->arg('id')) {
+                $nickname = common_canonical_nickname($this->arg('id'));
+                return User_group::staticGet('nickname', $nickname);
+            } else if ($this->arg('group_id')) {
+                // This is to ensure that a non-numeric user_id still
+                // overrides screen_name even if it doesn't get used
+                if (is_numeric($this->arg('group_id'))) {
+                    return User_group::staticGet('id', $this->arg('group_id'));
+                }
+            } else if ($this->arg('group_name')) {
+                $nickname = common_canonical_nickname($this->arg('group_name'));
+                return User_group::staticGet('nickname', $nickname);
+            }
+
+        } else if (is_numeric($id)) {
+            return User_group::staticGet($id);
+        } else {
+            $nickname = common_canonical_nickname($id);
+            return User_group::staticGet('nickname', $nickname);
+        }
+    }
+
+    function sourceLink($source)
+    {
+        $source_name = _($source);
+        switch ($source) {
+        case 'web':
+        case 'xmpp':
+        case 'mail':
+        case 'omb':
+        case 'api':
+            break;
+        default:
+            $ns = Notice_source::staticGet($source);
+            if ($ns) {
+                $source_name = '<a href="' . $ns->url . '">' . $ns->name . '</a>';
+            }
+            break;
+        }
+        return $source_name;
+    }
+
+    /**
+     * Returns query argument or default value if not found. Certain
+     * parameters used throughout the API are lightly scrubbed and
+     * bounds checked.  This overrides Action::arg().
+     *
+     * @param string $key requested argument
+     * @param string $def default value to return if $key is not provided
+     *
+     * @return var $var
+     */
+    function arg($key, $def=null)
+    {
+
+        // XXX: Do even more input validation/scrubbing?
+
+        if (array_key_exists($key, $this->args)) {
+            switch($key) {
+            case 'page':
+                $page = (int)$this->args['page'];
+                return ($page < 1) ? 1 : $page;
+            case 'count':
+                $count = (int)$this->args['count'];
+                if ($count < 1) {
+                    return 20;
+                } elseif ($count > 200) {
+                    return 200;
+                } else {
+                    return $count;
+                }
+            case 'since_id':
+                $since_id = (int)$this->args['since_id'];
+                return ($since_id < 1) ? 0 : $since_id;
+            case 'max_id':
+                $max_id = (int)$this->args['max_id'];
+                return ($max_id < 1) ? 0 : $max_id;
+            case 'since':
+                return strtotime($this->args['since']);
+            default:
+                return parent::arg($key, $def);
+            }
+        } else {
+            return $def;
+        }
+    }
+
+}
diff --git a/lib/apiauth.php b/lib/apiauth.php
new file mode 100644 (file)
index 0000000..2f2e44a
--- /dev/null
@@ -0,0 +1,203 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Base class for API actions that require authentication
+ *
+ * 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  API
+ * @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> 
+ * @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);
+}
+
+require_once INSTALLDIR . '/lib/api.php';
+
+/**
+ * Actions extending this class will require auth
+ *
+ * @category API
+ * @package  StatusNet
+ * @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 ApiAuthAction extends ApiAction
+{
+
+    var $auth_user = null;
+
+    /**
+     * Take arguments for running, and output basic auth header if needed
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        if ($this->requiresAuth()) {
+            $this->checkBasicAuthUser();
+        }
+
+        return true;
+    }
+
+    /**
+     * Does this API resource require authentication?
+     *
+     * @return boolean true
+     */
+
+    function requiresAuth()
+    {
+        return true;
+    }
+
+    /**
+     * Check for a user specified via HTTP basic auth. If there isn't
+     * one, try to get one by outputting the basic auth header.
+     *
+     * @return boolean true or false
+     */
+
+    function checkBasicAuthUser()
+    {
+        $this->basicAuthProcessHeader();
+
+        $realm = common_config('site', 'name') . ' API';
+
+        if (!isset($this->auth_user)) {
+            header('WWW-Authenticate: Basic realm="' . $realm . '"');
+
+            // show error if the user clicks 'cancel'
+
+            $this->showBasicAuthError();
+            exit;
+
+        } else {
+            $nickname = $this->auth_user;
+            $password = $this->auth_pw;
+            $this->auth_user = common_check_user($nickname, $password);
+
+            if (empty($this->auth_user)) {
+
+                // basic authentication failed
+
+                list($proxy, $ip) = common_client_ip();
+                common_log(
+                    LOG_WARNING,
+                    'Failed API auth attempt, nickname = ' .
+                    "$nickname, proxy = $proxy, ip = $ip."
+                );
+                $this->showBasicAuthError();
+                exit;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Read the HTTP headers and set the auth user.  Decodes HTTP_AUTHORIZATION
+     * param to support basic auth when PHP is running in CGI mode.
+     *
+     * @return void
+     */
+
+    function basicAuthProcessHeader()
+    {
+        if (isset($_SERVER['AUTHORIZATION'])
+            || isset($_SERVER['HTTP_AUTHORIZATION'])
+        ) {
+                $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION'])
+                ? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION'];
+        }
+
+        if (isset($_SERVER['PHP_AUTH_USER'])) {
+            $this->auth_user = $_SERVER['PHP_AUTH_USER'];
+            $this->auth_pw = $_SERVER['PHP_AUTH_PW'];
+        } elseif (isset($authorization_header)
+            && strstr(substr($authorization_header, 0, 5), 'Basic')) {
+
+            // decode the HTTP_AUTHORIZATION header on php-cgi server self
+            // on fcgid server the header name is AUTHORIZATION
+
+            $auth_hash = base64_decode(substr($authorization_header, 6));
+            list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash);
+
+            // set all to null on a empty basic auth request
+
+            if ($this->auth_user == "") {
+                $this->auth_user = null;
+                $this->auth_pw = null;
+            }
+        } else {
+            $this->auth_user = null;
+            $this->auth_pw = null;
+        }
+    }
+
+    /**
+     * Output an authentication error message.  Use XML or JSON if one
+     * of those formats is specified, otherwise output plain text
+     *
+     * @return void
+     */
+
+    function showBasicAuthError()
+    {
+        header('HTTP/1.1 401 Unauthorized');
+        $msg = 'Could not authenticate you.';
+
+        if ($this->format == 'xml') {
+            header('Content-Type: application/xml; charset=utf-8');
+            $this->startXML();
+            $this->elementStart('hash');
+            $this->element('error', null, $msg);
+            $this->element('request', null, $_SERVER['REQUEST_URI']);
+            $this->elementEnd('hash');
+            $this->endXML();
+        } elseif ($this->format == 'json') {
+            header('Content-Type: application/json; charset=utf-8');
+            $error_array = array('error' => $msg,
+                                 'request' => $_SERVER['REQUEST_URI']);
+            print(json_encode($error_array));
+        } else {
+            header('Content-type: text/plain');
+            print "$msg\n";
+        }
+    }
+
+}
diff --git a/lib/apibareauth.php b/lib/apibareauth.php
new file mode 100644 (file)
index 0000000..2d29c1d
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Base class for API actions that require "bare auth". Bare auth means
+ * authentication is required only if the action is called without an argument
+ * or query param specifying user id.
+ *
+ * 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  API
+ * @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> 
+ * @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);
+}
+
+require_once INSTALLDIR.'/lib/apiauth.php';
+
+/**
+ * Actions extending this class will require auth unless a target
+ * user ID has been specified
+ *
+ * @category API
+ * @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 ApiBareAuthAction extends ApiAuthAction
+{
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     *
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        return true;
+    }
+
+    /**
+     * Does this API resource require authentication?
+     *
+     * @return boolean true or false
+     */
+
+    function requiresAuth()
+    {
+        // If the site is "private", all API methods except statusnet/config
+        // need authentication
+
+        if (common_config('site', 'private')) {
+            return true;
+        }
+
+        // check whether a user has been specified somehow
+
+        $id           = $this->arg('id');
+        $user_id      = $this->arg('user_id');
+        $screen_name  = $this->arg('screen_name');
+
+        if (empty($id) && empty($user_id) && empty($screen_name)) {
+            return true;
+        }
+
+        return false;
+    }
+
+}
\ No newline at end of file
index 01b14f83e7a367cbe2a9ab8544a690e7a111f783..11d40b8e156da190df5b33aa0a45f8aa36a34041 100644 (file)
@@ -312,16 +312,20 @@ class MessageCommand extends Command
     function execute($channel)
     {
         $other = User::staticGet('nickname', common_canonical_nickname($this->other));
+
         $len = mb_strlen($this->text);
+
         if ($len == 0) {
             $channel->error($this->user, _('No content!'));
             return;
-        } else if ($len > 140) {
-            $content = common_shorten_links($content);
-            if (mb_strlen($content) > 140) {
-                $channel->error($this->user, sprintf(_('Message too long - maximum is 140 characters, you sent %d'), $len));
-                return;
-            }
+        }
+
+        $this->text = common_shorten_links($this->text);
+
+        if (Message::contentTooLong($this->text)) {
+            $channel->error($this->user, sprintf(_('Message too long - maximum is %d characters, you sent %d'),
+                                                 Message::maxContent(), mb_strlen($this->text)));
+            return;
         }
 
         if (!$other) {
index 6e4340e5dcac05529218c6246b2aff02e35a62fa..60fc4c3c44802755af6e8bc1c6a5e4ea54bd92f1 100644 (file)
@@ -28,7 +28,7 @@ class CommandInterpreter
         # XXX: localise
 
         $text = preg_replace('/\s+/', ' ', trim($text));
-        list($cmd, $arg) = explode(' ', $text, 2);
+        list($cmd, $arg) = $this->split_arg($text);
 
         # We try to support all the same commands as Twitter, see
         # http://getsatisfaction.com/twitter/topics/what_are_the_twitter_commands
@@ -43,7 +43,7 @@ class CommandInterpreter
             return new HelpCommand($user);
          case 'on':
             if ($arg) {
-                list($other, $extra) = explode(' ', $arg, 2);
+                list($other, $extra) = $this->split_arg($arg);
                 if ($extra) {
                     return null;
                 } else {
@@ -54,7 +54,7 @@ class CommandInterpreter
             }
          case 'off':
             if ($arg) {
-                list($other, $extra) = explode(' ', $arg, 2);
+                list($other, $extra) = $this->split_arg($arg);
                 if ($extra) {
                     return null;
                 } else {
@@ -74,7 +74,7 @@ class CommandInterpreter
              if (!$arg) {
                 return null;
             }
-            list($other, $extra) = explode(' ', $arg, 2);
+            list($other, $extra) = $this->split_arg($arg);
             if ($extra) {
                 return null;
             } else {
@@ -84,7 +84,7 @@ class CommandInterpreter
             if (!$arg) {
                 return null;
             }
-            list($other, $extra) = explode(' ', $arg, 2);
+            list($other, $extra) = $this->split_arg($arg);
             if ($extra) {
                 return null;
             } else {
@@ -95,7 +95,7 @@ class CommandInterpreter
             if (!$arg) {
                 return null;
             }
-            list($other, $extra) = explode(' ', $arg, 2);
+            list($other, $extra) = $this->split_arg($arg);
             if ($extra) {
                 return null;
             } else {
@@ -106,7 +106,7 @@ class CommandInterpreter
             if (!$arg) {
                 return null;
             }
-            list($other, $extra) = explode(' ', $arg, 2);
+            list($other, $extra) = $this->split_arg($arg);
             if ($extra) {
                 return null;
             } else {
@@ -117,7 +117,7 @@ class CommandInterpreter
             if (!$arg) {
                 return null;
             }
-            list($other, $extra) = explode(' ', $arg, 2);
+            list($other, $extra) = $this->split_arg($arg);
             if ($extra) {
                 return null;
             } else {
@@ -128,7 +128,7 @@ class CommandInterpreter
             if (!$arg) {
                 return null;
             }
-            list($other, $extra) = explode(' ', $arg, 2);
+            list($other, $extra) = $this->split_arg($arg);
             if (!$extra) {
                 return null;
             } else {
@@ -138,7 +138,7 @@ class CommandInterpreter
             if (!$arg) {
                 return null;
             }
-            list($other, $extra) = explode(' ', $arg, 2);
+            list($other, $extra) = $this->split_arg($arg);
             if ($extra) {
                 return null;
             } else {
@@ -148,7 +148,7 @@ class CommandInterpreter
             if (!$arg) {
                 return null;
             }
-            list($other, $extra) = explode(' ', $arg, 2);
+            list($other, $extra) = $this->split_arg($arg);
             if ($extra) {
                 return null;
             } else {
@@ -158,7 +158,7 @@ class CommandInterpreter
             if (!$arg) {
                 return null;
             }
-            list($other, $extra) = explode(' ', $arg, 2);
+            list($other, $extra) = $this->split_arg($arg);
             if ($extra) {
                 return null;
             } else {
@@ -173,7 +173,7 @@ class CommandInterpreter
             if (!$arg) {
                 return null;
             }
-            list($other, $extra) = explode(' ', $arg, 2);
+            list($other, $extra) = $this->split_arg($arg);
             if ($extra) {
                 return null;
             } else {
@@ -183,7 +183,7 @@ class CommandInterpreter
             if (!$arg) {
                 return null;
             }
-            list($word, $extra) = explode(' ', $arg, 2);
+            list($word, $extra) = $this->split_arg($arg);
             if ($extra) {
                 return null;
             } else if ($word == 'off') {
@@ -195,7 +195,7 @@ class CommandInterpreter
             if (!$arg) {
                 return null;
             }
-            list($word, $extra) = explode(' ', $arg, 2);
+            list($word, $extra) = $this->split_arg($arg);
             if ($extra) {
                 return null;
             } else if ($word == 'all') {
@@ -213,5 +213,17 @@ class CommandInterpreter
             return false;
         }
     }
+    
+    /**
+     * Split arguments without triggering a PHP notice warning
+     */
+    function split_arg($text)
+    {
+        $pieces = explode(' ', $text, 2);
+        if (count($pieces) == 1) {
+            $pieces[] = null;
+        }
+        return $pieces;
+    }
 }
 
index 0b4e031845b287723eeeceb0ae3b72a5f855b2fc..ce33c871bfb15b83d1c0e4b207952c8a7b61cb54 100644 (file)
 
 if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
 
-define('STATUSNET_VERSION', '0.8.2dev');
+define('STATUSNET_VERSION', '0.9.0dev');
 define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility
 
-define('STATUSNET_CODENAME', 'Second Guessing');
+define('STATUSNET_CODENAME', 'Stand');
 
 define('AVATAR_PROFILE_SIZE', 96);
 define('AVATAR_STREAM_SIZE', 48);
@@ -53,6 +53,7 @@ require_once('DB/DataObject/Cast.php'); # for dates
 if (!function_exists('gettext')) {
     require_once("php-gettext/gettext.inc");
 }
+
 require_once(INSTALLDIR.'/lib/language.php');
 
 // This gets included before the config file, so that admin code and plugins
@@ -93,206 +94,17 @@ if (isset($path)) {
     null;
 }
 
-// default configuration, overwritten in config.php
+require_once(INSTALLDIR.'/lib/default.php');
+
+// Set config values initially to default values
 
-$config =
-  array('site' =>
-        array('name' => 'Just another StatusNet microblog',
-              'server' => $_server,
-              'theme' => 'default',
-              'path' => $_path,
-              'logfile' => null,
-              'logo' => null,
-              'logdebug' => false,
-              'fancy' => false,
-              'locale_path' => INSTALLDIR.'/locale',
-              'language' => 'en_US',
-              'languages' => get_all_languages(),
-              'email' =>
-              array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : null,
-              'broughtby' => null,
-              'timezone' => 'UTC',
-              'broughtbyurl' => null,
-              'closed' => false,
-              'inviteonly' => false,
-              'openidonly' => false,
-              'private' => false,
-              'ssl' => 'never',
-              'sslserver' => null,
-              'shorturllength' => 30,
-              'dupelimit' => 60), # default for same person saying the same thing
-        'syslog' =>
-        array('appname' => 'statusnet', # for syslog
-              'priority' => 'debug', # XXX: currently ignored
-              'facility' => LOG_USER),
-        'queue' =>
-        array('enabled' => false,
-              'subsystem' => 'db', # default to database, or 'stomp'
-              'stomp_server' => null,
-              'queue_basename' => 'statusnet',
-              'stomp_username' => null,
-              'stomp_password' => null,
-              ),
-        'license' =>
-        array('url' => 'http://creativecommons.org/licenses/by/3.0/',
-              'title' => 'Creative Commons Attribution 3.0',
-              'image' => 'http://i.creativecommons.org/l/by/3.0/80x15.png'),
-        'mail' =>
-        array('backend' => 'mail',
-              'params' => null),
-        'nickname' =>
-        array('blacklist' => array(),
-              'featured' => array()),
-        'profile' =>
-        array('banned' => array()),
-        'avatar' =>
-        array('server' => null,
-              'dir' => INSTALLDIR . '/avatar/',
-              'path' => $_path . '/avatar/'),
-        'background' =>
-        array('server' => null,
-              'dir' => INSTALLDIR . '/background/',
-              'path' => $_path . '/background/'),
-        'public' =>
-        array('localonly' => true,
-              'blacklist' => array(),
-              'autosource' => array()),
-        'theme' =>
-        array('server' => null,
-              'dir' => null,
-              'path'=> null),
-        'throttle' =>
-        array('enabled' => false, // whether to throttle edits; false by default
-              'count' => 20, // number of allowed messages in timespan
-              'timespan' => 600), // timespan for throttling
-        'xmpp' =>
-        array('enabled' => false,
-              'server' => 'INVALID SERVER',
-              'port' => 5222,
-              'user' => 'update',
-              'encryption' => true,
-              'resource' => 'uniquename',
-              'password' => 'blahblahblah',
-              'host' => null, # only set if != server
-              'debug' => false, # print extra debug info
-              'public' => array()), # JIDs of users who want to receive the public stream
-        'openid' =>
-        array('enabled' => true),
-        'invite' =>
-        array('enabled' => true),
-        'sphinx' =>
-        array('enabled' => false,
-              'server' => 'localhost',
-              'port' => 3312),
-        'tag' =>
-        array('dropoff' => 864000.0),
-        'popular' =>
-        array('dropoff' => 864000.0),
-        'daemon' =>
-        array('piddir' => '/var/run',
-              'user' => false,
-              'group' => false),
-        'emailpost' =>
-        array('enabled' => true),
-        'sms' =>
-        array('enabled' => true),
-        'twitterbridge' =>
-        array('enabled' => false),
-        'integration' =>
-        array('source' => 'StatusNet', # source attribute for Twitter
-              'taguri' => $_server.',2009'), # base for tag URIs
-       'twitter' =>
-       array('enabled'         => true,
-              'consumer_key'    => null,
-             'consumer_secret' => null),
-        'memcached' =>
-        array('enabled' => false,
-              'server' => 'localhost',
-              'base' => null,
-              'port' => 11211),
-               'ping' =>
-        array('notify' => array()),
-        'inboxes' =>
-        array('enabled' => true), # on by default for new sites
-        'newuser' =>
-        array('default' => null,
-              'welcome' => null),
-        'snapshot' =>
-        array('run' => 'web',
-              'frequency' => 10000,
-              'reporturl' => 'http://status.net/stats/report'),
-        'attachments' =>
-        array('server' => null,
-              'dir' => INSTALLDIR . '/file/',
-              'path' => $_path . '/file/',
-              'supported' => array('image/png',
-                                   'image/jpeg',
-                                   'image/gif',
-                                   'image/svg+xml',
-                                   'audio/mpeg',
-                                   'audio/x-speex',
-                                   'application/ogg',
-                                   'application/pdf',
-                                   'application/vnd.oasis.opendocument.text',
-                                   'application/vnd.oasis.opendocument.text-template',
-                                   'application/vnd.oasis.opendocument.graphics',
-                                   'application/vnd.oasis.opendocument.graphics-template',
-                                   'application/vnd.oasis.opendocument.presentation',
-                                   'application/vnd.oasis.opendocument.presentation-template',
-                                   'application/vnd.oasis.opendocument.spreadsheet',
-                                   'application/vnd.oasis.opendocument.spreadsheet-template',
-                                   'application/vnd.oasis.opendocument.chart',
-                                   'application/vnd.oasis.opendocument.chart-template',
-                                   'application/vnd.oasis.opendocument.image',
-                                   'application/vnd.oasis.opendocument.image-template',
-                                   'application/vnd.oasis.opendocument.formula',
-                                   'application/vnd.oasis.opendocument.formula-template',
-                                   'application/vnd.oasis.opendocument.text-master',
-                                   'application/vnd.oasis.opendocument.text-web',
-                                   'application/x-zip',
-                                   'application/zip',
-                                   'text/plain',
-                                   'video/mpeg',
-                                   'video/mp4',
-                                   'video/quicktime',
-                                   'video/mpeg'),
-        'file_quota' => 5000000,
-        'user_quota' => 50000000,
-        'monthly_quota' => 15000000,
-        'uploads' => true,
-        'filecommand' => '/usr/bin/file',
-        ),
-        'group' =>
-        array('maxaliases' => 3),
-        'oohembed' => array('endpoint' => 'http://oohembed.com/oohembed/'),
-        'search' =>
-        array('type' => 'fulltext'),
-        'sessions' =>
-        array('handle' => false, // whether to handle sessions ourselves
-              'debug' => false), // debugging output for sessions
-        'design' =>
-        array('backgroundcolor' => null, // null -> 'use theme default'
-              'contentcolor' => null,
-              'sidebarcolor' => null,
-              'textcolor' => null,
-              'linkcolor' => null,
-              'backgroundimage' => null,
-              'disposition' => null),
-        );
+$config = $default;
+
+// default configuration, overwritten in config.php
 
 $config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');
 
-$config['db'] =
-  array('database' => 'YOU HAVE TO SET THIS IN config.php',
-        'schema_location' => INSTALLDIR . '/classes',
-        'class_location' => INSTALLDIR . '/classes',
-        'require_prefix' => 'classes/',
-        'class_prefix' => '',
-        'mirror' => null,
-        'utf8' => true,
-        'db_driver' => 'DB', # XXX: JanRain libs only work with DB
-        'quote_identifiers' => false,
-        'type' => 'mysql' );
+$config['db'] = $default['db'];
 
 // Backward compatibility
 
@@ -382,13 +194,25 @@ if ($_db_name != 'statusnet' && !array_key_exists('ini_'.$_db_name, $config['db'
     $config['db']['ini_'.$_db_name] = INSTALLDIR.'/classes/statusnet.ini';
 }
 
-// Ignore openidonly if OpenID is disabled
-
-if (!$config['openid']['enabled']) {
-    $config['site']['openidonly'] = false;
+function __autoload($cls)
+{
+    if (file_exists(INSTALLDIR.'/classes/' . $cls . '.php')) {
+        require_once(INSTALLDIR.'/classes/' . $cls . '.php');
+    } else if (file_exists(INSTALLDIR.'/lib/' . strtolower($cls) . '.php')) {
+        require_once(INSTALLDIR.'/lib/' . strtolower($cls) . '.php');
+    } else if (mb_substr($cls, -6) == 'Action' &&
+               file_exists(INSTALLDIR.'/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php')) {
+        require_once(INSTALLDIR.'/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php');
+    } else if ($cls == 'OAuthRequest') {
+        require_once('OAuth.php');
+    } else {
+        Event::handle('Autoload', array(&$cls));
+    }
 }
 
 // XXX: how many of these could be auto-loaded on use?
+// XXX: note that these files should not use config options
+// at compile time since DB config options are not yet loaded.
 
 require_once 'Validate.php';
 require_once 'markdown.php';
@@ -404,24 +228,20 @@ require_once INSTALLDIR.'/lib/twitter.php';
 require_once INSTALLDIR.'/lib/clientexception.php';
 require_once INSTALLDIR.'/lib/serverexception.php';
 
-// XXX: other formats here
+// Load settings from database; note we need autoload for this
 
-define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER);
+Config::loadSettings();
 
-function __autoload($class)
-{
-    if ($class == 'OAuthRequest') {
-        require_once('OAuth.php');
-    } else if (file_exists(INSTALLDIR.'/classes/' . $class . '.php')) {
-        require_once(INSTALLDIR.'/classes/' . $class . '.php');
-    } else if (file_exists(INSTALLDIR.'/lib/' . strtolower($class) . '.php')) {
-        require_once(INSTALLDIR.'/lib/' . strtolower($class) . '.php');
-    } else if (mb_substr($class, -6) == 'Action' &&
-               file_exists(INSTALLDIR.'/actions/' . strtolower(mb_substr($class, 0, -6)) . '.php')) {
-        require_once(INSTALLDIR.'/actions/' . strtolower(mb_substr($class, 0, -6)) . '.php');
-    }
+// XXX: if plugins should check the schema at runtime, do that here.
+
+if ($config['db']['schemacheck'] == 'runtime') {
+    Event::handle('CheckSchema');
 }
 
+// XXX: other formats here
+
+define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER);
+
 // Give plugins a chance to initialize in a fully-prepared environment
 
 Event::handle('InitializePlugin');
diff --git a/lib/curlclient.php b/lib/curlclient.php
new file mode 100644 (file)
index 0000000..36fc7d1
--- /dev/null
@@ -0,0 +1,179 @@
+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;
+    }
+}
diff --git a/lib/default.php b/lib/default.php
new file mode 100644 (file)
index 0000000..9f3d4b1
--- /dev/null
@@ -0,0 +1,232 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Default settings for core configuration
+ *
+ * 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  Config
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2008-9 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/
+ */
+
+$default =
+  array('site' =>
+        array('name' => 'Just another StatusNet microblog',
+              'server' => $_server,
+              'theme' => 'default',
+              'path' => $_path,
+              'logfile' => null,
+              'logo' => null,
+              'logdebug' => false,
+              'fancy' => false,
+              'locale_path' => INSTALLDIR.'/locale',
+              'language' => 'en_US',
+              'languages' => get_all_languages(),
+              'email' =>
+              array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : null,
+              'broughtby' => null,
+              'timezone' => 'UTC',
+              'broughtbyurl' => null,
+              'closed' => false,
+              'inviteonly' => false,
+              'private' => false,
+              'ssl' => 'never',
+              'sslserver' => null,
+              'shorturllength' => 30,
+              'dupelimit' => 60, # default for same person saying the same thing
+              'textlimit' => 140,
+              ),
+        'db' =>
+        array('database' => 'YOU HAVE TO SET THIS IN config.php',
+              'schema_location' => INSTALLDIR . '/classes',
+              'class_location' => INSTALLDIR . '/classes',
+              'require_prefix' => 'classes/',
+              'class_prefix' => '',
+              'mirror' => null,
+              'utf8' => true,
+              'db_driver' => 'DB', # XXX: JanRain libs only work with DB
+              'quote_identifiers' => false,
+              'type' => 'mysql',
+              'schemacheck' => 'runtime'), // 'runtime' or 'script'
+        'syslog' =>
+        array('appname' => 'statusnet', # for syslog
+              'priority' => 'debug', # XXX: currently ignored
+              'facility' => LOG_USER),
+        'queue' =>
+        array('enabled' => false,
+              'subsystem' => 'db', # default to database, or 'stomp'
+              'stomp_server' => null,
+              'queue_basename' => 'statusnet',
+              'stomp_username' => null,
+              'stomp_password' => null,
+              ),
+        'license' =>
+        array('url' => 'http://creativecommons.org/licenses/by/3.0/',
+              'title' => 'Creative Commons Attribution 3.0',
+              'image' => 'http://i.creativecommons.org/l/by/3.0/80x15.png'),
+        'mail' =>
+        array('backend' => 'mail',
+              'params' => null),
+        'nickname' =>
+        array('blacklist' => array(),
+              'featured' => array()),
+        'profile' =>
+        array('banned' => array(),
+              'biolimit' => null),
+        'avatar' =>
+        array('server' => null,
+              'dir' => INSTALLDIR . '/avatar/',
+              'path' => $_path . '/avatar/'),
+        'background' =>
+        array('server' => null,
+              'dir' => INSTALLDIR . '/background/',
+              'path' => $_path . '/background/'),
+        'public' =>
+        array('localonly' => true,
+              'blacklist' => array(),
+              'autosource' => array()),
+        'theme' =>
+        array('server' => null,
+              'dir' => null,
+              'path'=> null),
+        'throttle' =>
+        array('enabled' => false, // whether to throttle edits; false by default
+              'count' => 20, // number of allowed messages in timespan
+              'timespan' => 600), // timespan for throttling
+        'xmpp' =>
+        array('enabled' => false,
+              'server' => 'INVALID SERVER',
+              'port' => 5222,
+              'user' => 'update',
+              'encryption' => true,
+              'resource' => 'uniquename',
+              'password' => 'blahblahblah',
+              'host' => null, # only set if != server
+              'debug' => false, # print extra debug info
+              'public' => array()), # JIDs of users who want to receive the public stream
+        'invite' =>
+        array('enabled' => true),
+        'sphinx' =>
+        array('enabled' => false,
+              'server' => 'localhost',
+              'port' => 3312),
+        'tag' =>
+        array('dropoff' => 864000.0),
+        'popular' =>
+        array('dropoff' => 864000.0),
+        'daemon' =>
+        array('piddir' => '/var/run',
+              'user' => false,
+              'group' => false),
+        'emailpost' =>
+        array('enabled' => true),
+        'sms' =>
+        array('enabled' => true),
+        'twitterbridge' =>
+        array('enabled' => false),
+        'integration' =>
+        array('source' => 'StatusNet', # source attribute for Twitter
+              'taguri' => $_server.',2009'), # base for tag URIs
+       'twitter' =>
+       array('enabled'       => true,
+           'consumer_key'    => null,
+           'consumer_secret' => null),
+        'memcached' =>
+        array('enabled' => false,
+              'server' => 'localhost',
+              'base' => null,
+              'port' => 11211),
+               'ping' =>
+        array('notify' => array()),
+        'inboxes' =>
+        array('enabled' => true), # ignored after 0.9.x
+        'newuser' =>
+        array('default' => null,
+              'welcome' => null),
+        'snapshot' =>
+        array('run' => 'web',
+              'frequency' => 10000,
+              'reporturl' => 'http://status.net/stats/report'),
+        'attachments' =>
+        array('server' => null,
+              'dir' => INSTALLDIR . '/file/',
+              'path' => $_path . '/file/',
+              'supported' => array('image/png',
+                                   'image/jpeg',
+                                   'image/gif',
+                                   'image/svg+xml',
+                                   'audio/mpeg',
+                                   'audio/x-speex',
+                                   'application/ogg',
+                                   'application/pdf',
+                                   'application/vnd.oasis.opendocument.text',
+                                   'application/vnd.oasis.opendocument.text-template',
+                                   'application/vnd.oasis.opendocument.graphics',
+                                   'application/vnd.oasis.opendocument.graphics-template',
+                                   'application/vnd.oasis.opendocument.presentation',
+                                   'application/vnd.oasis.opendocument.presentation-template',
+                                   'application/vnd.oasis.opendocument.spreadsheet',
+                                   'application/vnd.oasis.opendocument.spreadsheet-template',
+                                   'application/vnd.oasis.opendocument.chart',
+                                   'application/vnd.oasis.opendocument.chart-template',
+                                   'application/vnd.oasis.opendocument.image',
+                                   'application/vnd.oasis.opendocument.image-template',
+                                   'application/vnd.oasis.opendocument.formula',
+                                   'application/vnd.oasis.opendocument.formula-template',
+                                   'application/vnd.oasis.opendocument.text-master',
+                                   'application/vnd.oasis.opendocument.text-web',
+                                   'application/x-zip',
+                                   'application/zip',
+                                   'text/plain',
+                                   'video/mpeg',
+                                   'video/mp4',
+                                   'video/quicktime',
+                                   'video/mpeg'),
+        'file_quota' => 5000000,
+        'user_quota' => 50000000,
+        'monthly_quota' => 15000000,
+        'uploads' => true,
+        'filecommand' => '/usr/bin/file',
+        ),
+        'group' =>
+        array('maxaliases' => 3,
+              'desclimit' => null),
+        'oohembed' => array('endpoint' => 'http://oohembed.com/oohembed/'),
+        'search' =>
+        array('type' => 'fulltext'),
+        'sessions' =>
+        array('handle' => false, // whether to handle sessions ourselves
+              'debug' => false), // debugging output for sessions
+        'design' =>
+        array('backgroundcolor' => null, // null -> 'use theme default'
+              'contentcolor' => null,
+              'sidebarcolor' => null,
+              'textcolor' => null,
+              'linkcolor' => null,
+              'backgroundimage' => null,
+              'disposition' => null),
+        'notice' =>
+        array('contentlimit' => null),
+        'message' =>
+        array('contentlimit' => null),
+        'http' =>
+        array('client' => 'curl'), // XXX: should this be the default?
+        );
diff --git a/lib/deleteaction.php b/lib/deleteaction.php
deleted file mode 100644 (file)
index f702820..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Base class for deleting things
- *
- * 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    Evan Prodromou <evan@status.net>
- * @author    Sarven Capadisli <csarven@status.net>
- * @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);
-}
-
-class DeleteAction extends Action
-{
-    var $user         = null;
-    var $notice       = null;
-    var $profile      = null;
-    var $user_profile = null;
-
-    function prepare($args)
-    {
-        parent::prepare($args);
-
-        $this->user   = common_current_user();
-        $notice_id    = $this->trimmed('notice');
-        $this->notice = Notice::staticGet($notice_id);
-
-        if (!$this->notice) {
-            common_user_error(_('No such notice.'));
-            exit;
-        }
-
-        $this->profile      = $this->notice->getProfile();
-        $this->user_profile = $this->user->getProfile();
-
-        return true;
-    }
-
-    function handle($args)
-    {
-        parent::handle($args);
-
-        if (!common_logged_in()) {
-            common_user_error(_('Not logged in.'));
-            exit;
-        } else if ($this->notice->profile_id != $this->user_profile->id) {
-            common_user_error(_('Can\'t delete this notice.'));
-            exit;
-        }
-    }
-
-}
index 5cbb9be5312bf2ff50907cbc06dd41b31cc5a342..3f3a8d3b094b44cde2f94385d60247e49257eef1 100644 (file)
@@ -35,7 +35,6 @@ if (!defined('STATUSNET') && !defined('LACONICA'))
 require_once INSTALLDIR.'/lib/facebookutil.php';
 require_once INSTALLDIR.'/lib/noticeform.php';
 
-
 class FacebookAction extends Action
 {
 
@@ -181,7 +180,6 @@ class FacebookAction extends Action
 
     }
 
-
     // Make this into a widget later
     function showLocalNav()
     {
@@ -241,7 +239,6 @@ class FacebookAction extends Action
         $this->endHTML();
     }
 
-
     function showInstructions()
     {
 
@@ -257,13 +254,8 @@ class FacebookAction extends Action
         $this->elementStart('dd');
         $this->elementStart('p');
         $this->text(sprintf($loginmsg_part1, common_config('site', 'name')));
-        if (!common_config('site', 'openidonly')) {
-            $this->element('a',
-                array('href' => common_local_url('register')), _('Register'));
-        } else {
-            $this->element('a',
-                array('href' => common_local_url('openidlogin')), _('Register'));
-        }
+        $this->element('a',
+            array('href' => common_local_url('register')), _('Register'));
         $this->text($loginmsg_part2);
     $this->elementEnd('p');
         $this->elementEnd('dd');
@@ -272,7 +264,6 @@ class FacebookAction extends Action
         $this->elementEnd('div');
     }
 
-
     function showLoginForm($msg = null)
     {
 
@@ -317,7 +308,6 @@ class FacebookAction extends Action
 
     }
 
-
     function updateProfileBox($notice)
     {
 
@@ -399,7 +389,6 @@ class FacebookAction extends Action
         $this->xw->openURI('php://output');
     }
 
-
     /**
      * Generate pagination links
      *
@@ -458,8 +447,9 @@ class FacebookAction extends Action
         } else {
             $content_shortened = common_shorten_links($content);
 
-            if (mb_strlen($content_shortened) > 140) {
-                $this->showPage(_('That\'s too long. Max notice size is 140 chars.'));
+            if (Notice::contentTooLong($content_shortened)) {
+                $this->showPage(sprintf(_('That\'s too long. Max notice size is %d chars.'),
+                                        Notice::maxContent()));
                 return;
             }
         }
@@ -478,11 +468,11 @@ class FacebookAction extends Action
 
         $replyto = $this->trimmed('inreplyto');
 
-        $notice = Notice::saveNew($user->id, $content,
-            'web', 1, ($replyto == 'false') ? null : $replyto);
-
-        if (is_string($notice)) {
-            $this->showPage($notice);
+        try {
+            $notice = Notice::saveNew($user->id, $content,
+                                      'web', 1, ($replyto == 'false') ? null : $replyto);
+        } catch (Exception $e) {
+            $this->showPage($e->getMessage());
             return;
         }
 
index c29576b64c590f723b24b195c930ebfc84659e9b..c991c5439c40cda417aa9d0c9bc675de028061d9 100644 (file)
@@ -109,7 +109,6 @@ function facebookBroadcastNotice($notice)
 
             $can_update  = $facebook->api_client->users_hasAppPermission('status_update',
                                                                          $fbuid);
-
             if (!empty($attachments) && $can_publish == 1) {
                 $fbattachment = format_attachments($attachments);
                 $facebook->api_client->stream_publish($status, $fbattachment,
@@ -180,7 +179,11 @@ function format_attachments($attachments)
 
     foreach($attachments as $attachment)
     {
-        $fbmedia = get_fbmedia_for_attachment($attachment);
+        if($enclosure = $attachment->getEnclosure()){
+            $fbmedia = get_fbmedia_for_attachment($enclosure);
+        }else{
+            $fbmedia = get_fbmedia_for_attachment($attachment);
+        }
         if($fbmedia){
             $fbattachment['media'][]=$fbmedia;
         }else{
index a649c2ee3b258cecdecc67eb6bd2dea69064fab4..433f6a13871ccec4de2ad2d01837e95412df59ed 100644 (file)
@@ -150,27 +150,33 @@ class GroupEditForm extends Form
         $this->out->elementStart('li');
         $this->out->hidden('groupid', $id);
         $this->out->input('nickname', _('Nickname'),
-                     ($this->out->arg('nickname')) ? $this->out->arg('nickname') : $nickname,
-                     _('1-64 lowercase letters or numbers, no punctuation or spaces'));
+                          ($this->out->arg('nickname')) ? $this->out->arg('nickname') : $nickname,
+                          _('1-64 lowercase letters or numbers, no punctuation or spaces'));
         $this->out->elementEnd('li');
         $this->out->elementStart('li');
         $this->out->input('fullname', _('Full name'),
-                     ($this->out->arg('fullname')) ? $this->out->arg('fullname') : $fullname);
+                          ($this->out->arg('fullname')) ? $this->out->arg('fullname') : $fullname);
         $this->out->elementEnd('li');
         $this->out->elementStart('li');
         $this->out->input('homepage', _('Homepage'),
-                     ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage,
-                     _('URL of the homepage or blog of the group or topic'));
+                          ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage,
+                          _('URL of the homepage or blog of the group or topic'));
         $this->out->elementEnd('li');
         $this->out->elementStart('li');
+        $desclimit = User_group::maxDescription();
+        if ($desclimit == 0) {
+            $descinstr = _('Describe the group or topic');
+        } else {
+            $descinstr = sprintf(_('Describe the group or topic in %d characters'), $desclimit);
+        }
         $this->out->textarea('description', _('Description'),
-                        ($this->out->arg('description')) ? $this->out->arg('description') : $description,
-                        _('Describe the group or topic in 140 chars'));
+                             ($this->out->arg('description')) ? $this->out->arg('description') : $description,
+                             $descinstr);
         $this->out->elementEnd('li');
         $this->out->elementStart('li');
         $this->out->input('location', _('Location'),
-                     ($this->out->arg('location')) ? $this->out->arg('location') : $location,
-                     _('Location for the group, if any, like "City, State (or Region), Country"'));
+                          ($this->out->arg('location')) ? $this->out->arg('location') : $location,
+                          _('Location for the group, if any, like "City, State (or Region), Country"'));
         $this->out->elementEnd('li');
         if (common_config('group', 'maxaliases') > 0) {
             $aliases = (empty($this->group)) ? array() : $this->group->getAliases();
index 2ff9380cc15bc16183e738e084b8ab6628a31cb1..ce83295fb33783f1ddeac61da5ce19a3a2fe69e1 100644 (file)
@@ -106,7 +106,7 @@ class HTMLOutputter extends XMLOutputter
             }
         }
 
-        header('Content-Type: '.$type.'; charset=UTF-8');
+        header('Content-Type: '.$type);
 
         $this->extraHeaders();
         if (preg_match("/.*\/.*xml/", $type)) {
diff --git a/lib/httpclient.php b/lib/httpclient.php
new file mode 100644 (file)
index 0000000..c8c8ae5
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Utility for doing HTTP-related things
+ *
+ * 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);
+}
+
+/**
+ * Useful structure for HTTP responses
+ *
+ * We make HTTP calls in several places, and we have several different
+ * ways of doing them. This class hides the specifics of what underlying
+ * library (curl or PHP-HTTP or whatever) that's used.
+ *
+ * @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 HTTPResponse
+{
+    public $code = null;
+    public $headers = null;
+    public $body = null;
+}
+
+/**
+ * Utility class for doing HTTP client stuff
+ *
+ * We make HTTP calls in several places, and we have several different
+ * ways of doing them. This class hides the specifics of what underlying
+ * library (curl or PHP-HTTP or whatever) that's used.
+ *
+ * @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 HTTPClient
+{
+    static $_client = null;
+
+    static function start()
+    {
+        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;
+    }
+
+    function head($url, $headers)
+    {
+        throw new Exception("HEAD method unimplemented");
+    }
+
+    function get($url, $headers)
+    {
+        throw new Exception("GET method unimplemented");
+    }
+
+    function post($url, $headers, $body)
+    {
+        throw new Exception("POST method unimplemented");
+    }
+
+    function put($url, $headers, $body)
+    {
+        throw new Exception("PUT method unimplemented");
+    }
+
+    function delete($url, $headers)
+    {
+        throw new Exception("DELETE method unimplemented");
+    }
+
+    function userAgent()
+    {
+        return "StatusNet/".STATUSNET_VERSION." (".STATUSNET_CODENAME.")";
+    }
+}
index f740e329a4e0e4b787cede75577c0f0124a07079..b545fbf26914e7fbc44d6e2c83468b6fa9630f0b 100644 (file)
@@ -69,30 +69,25 @@ class LoginGroupNav extends Widget
 
     function show()
     {
-        // action => array('prompt', 'title')
-        $menu = array();
+        $action_name = $this->action->trimmed('action');
+
+        $this->action->elementStart('ul', array('class' => 'nav'));
+
+        if (Event::handle('StartLoginGroupNav', array(&$this->action))) {
+
+            $this->action->menuItem(common_local_url('login'),
+                                    _('Login'),
+                                    _('Login with a username and password'),
+                                    $action_name === 'login');
 
-        if (!common_config('site','openidonly')) {
-            $menu['login'] = array(_('Login'),
-                             _('Login with a username and password'));
             if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
-                $menu['register'] = array(_('Register'),
-                                    _('Sign up for a new account'));
+                $this->action->menuItem(common_local_url('register'),
+                                        _('Register'),
+                                        _('Sign up for a new account'),
+                                        $action_name === 'register');
             }
-        }
-        if (common_config('openid', 'enabled')) {
-            $menu['openidlogin'] = array(_('OpenID'),
-                                   _('Login or register with OpenID'));
-        }
-
-        $action_name = $this->action->trimmed('action');
-        $this->action->elementStart('ul', array('class' => 'nav'));
 
-        foreach ($menu as $menuaction => $menudesc) {
-            $this->action->menuItem(common_local_url($menuaction),
-                                    $menudesc[0],
-                                    $menudesc[1],
-                                    $action_name === $menuaction);
+            Event::handle('EndLoginGroupNav', array(&$this->action));
         }
 
         $this->action->elementEnd('ul');
index 6431bfdcc8297c880c05f23fb3cf4a38d12109db..e25ebfa08f29a2ecaf7a47ae17ff66156c0a34ca 100644 (file)
@@ -140,12 +140,19 @@ class MessageForm extends Form
                                               'rows' => 4,
                                               'name' => 'content'),
                             ($this->content) ? $this->content : '');
-        $this->out->elementStart('dl', 'form_note');
-        $this->out->element('dt', null, _('Available characters'));
-        $this->out->element('dd', array('id' => 'notice_text-count'),
-                            '140');
-        $this->out->elementEnd('dl');
 
+        $contentLimit = Message::maxContent();
+
+        $this->out->element('script', array('type' => 'text/javascript'),
+                            'maxLength = ' . $contentLimit . ';');
+
+        if ($contentLimit > 0) {
+            $this->out->elementStart('dl', 'form_note');
+            $this->out->element('dt', null, _('Available characters'));
+            $this->out->element('dd', array('id' => 'notice_text-count'),
+                                $contentLimit);
+            $this->out->elementEnd('dl');
+        }
     }
 
     /**
index 350e37db8cc90f78c11843fef7adf80aefd880a9..9864d15eb084dfb18fe4df869dddf25ef8ad2cc9 100644 (file)
@@ -90,7 +90,7 @@ class NoticeForm extends Form
         $this->action  = $action;
         $this->content = $content;
         $this->inreplyto = $inreplyto;
-        
+
         if ($user) {
             $this->user = $user;
         } else {
@@ -124,7 +124,6 @@ class NoticeForm extends Form
         return common_local_url('newnotice');
     }
 
-
     /**
      * Legend of the Form
      *
@@ -135,7 +134,6 @@ class NoticeForm extends Form
         $this->out->element('legend', null, _('Send a notice'));
     }
 
-
     /**
      * Data elements
      *
@@ -144,31 +142,44 @@ class NoticeForm extends Form
 
     function formData()
     {
-        $this->out->element('label', array('for' => 'notice_data-text'),
-                            sprintf(_('What\'s up, %s?'), $this->user->nickname));
-        // XXX: vary by defined max size
-        $this->out->element('textarea', array('id' => 'notice_data-text',
-                                              'cols' => 35,
-                                              'rows' => 4,
-                                              'name' => 'status_textarea'),
-                            ($this->content) ? $this->content : '');
-        $this->out->elementStart('dl', 'form_note');
-        $this->out->element('dt', null, _('Available characters'));
-        $this->out->element('dd', array('id' => 'notice_text-count'),
-                            '140');
-        $this->out->elementEnd('dl');
-        if (common_config('attachments', 'uploads')) {
-            $this->out->element('label', array('for' => 'notice_data-attach'),_('Attach'));
-            $this->out->element('input', array('id' => 'notice_data-attach',
-                                               'type' => 'file',
-                                               'name' => 'attach',
-                                               'title' => _('Attach a file')));
-            $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota'));
-        }
-        if ($this->action) {
-            $this->out->hidden('notice_return-to', $this->action, 'returnto');
+        if (Event::handle('StartShowNoticeFormData', array($this))) {
+            $this->out->element('label', array('for' => 'notice_data-text'),
+                                sprintf(_('What\'s up, %s?'), $this->user->nickname));
+            // XXX: vary by defined max size
+            $this->out->element('textarea', array('id' => 'notice_data-text',
+                                                  'cols' => 35,
+                                                  'rows' => 4,
+                                                  'name' => 'status_textarea'),
+                                ($this->content) ? $this->content : '');
+
+            $contentLimit = Notice::maxContent();
+
+            $this->out->element('script', array('type' => 'text/javascript'),
+                                'maxLength = ' . $contentLimit . ';');
+
+            if ($contentLimit > 0) {
+                $this->out->elementStart('dl', 'form_note');
+                $this->out->element('dt', null, _('Available characters'));
+                $this->out->element('dd', array('id' => 'notice_text-count'),
+                                    $contentLimit);
+                $this->out->elementEnd('dl');
+            }
+
+            if (common_config('attachments', 'uploads')) {
+                $this->out->element('label', array('for' => 'notice_data-attach'),_('Attach'));
+                $this->out->element('input', array('id' => 'notice_data-attach',
+                                                   'type' => 'file',
+                                                   'name' => 'attach',
+                                                   'title' => _('Attach a file')));
+                $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota'));
+            }
+            if ($this->action) {
+                $this->out->hidden('notice_return-to', $this->action, 'returnto');
+            }
+            $this->out->hidden('notice_in-reply-to', $this->inreplyto, 'inreplyto');
+
+            Event::handle('StartShowNoticeFormData', array($this));
         }
-        $this->out->hidden('notice_in-reply-to', $this->inreplyto, 'inreplyto');
     }
 
     /**
index d4cd3ff6e5d21caf08c05e00633f1ded4d00d997..6c296f82a7bfb154db87c28686b3a3592236de74 100644 (file)
@@ -178,9 +178,12 @@ class NoticeListItem extends Widget
     function show()
     {
         $this->showStart();
-        $this->showNotice();
-        $this->showNoticeInfo();
-        $this->showNoticeOptions();
+        if (Event::handle('StartShowNoticeItem', array($this))) {
+            $this->showNotice();
+            $this->showNoticeInfo();
+            $this->showNoticeOptions();
+            Event::handle('EndShowNoticeItem', array($this));
+        }
         $this->showEnd();
     }
 
@@ -469,7 +472,10 @@ class NoticeListItem extends Widget
     function showDeleteLink()
     {
         $user = common_current_user();
-        if ($user && $this->notice->profile_id == $user->id) {
+
+        if (!empty($user) &&
+            ($this->notice->profile_id == $user->id || $user->hasRight(Right::deleteOthersNotice))) {
+
             $deleteurl = common_local_url('deletenotice',
                                           array('notice' => $this->notice->id));
             $this->out->element('a', array('href' => $deleteurl,
index 6db07b20f781c9e2cf901a6e631760b681e80f41..d617a7df7e6216f52d72d0beec8892487256f94b 100644 (file)
 
 if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
 
-require_once(INSTALLDIR.'/lib/omb.php');
+require_once 'libomb/datastore.php';
 
 class StatusNetOAuthDataStore extends OAuthDataStore
 {
 
     // We keep a record of who's contacted us
-
     function lookup_consumer($consumer_key)
     {
         $con = Consumer::staticGet('consumer_key', $consumer_key);
@@ -44,7 +43,9 @@ class StatusNetOAuthDataStore extends OAuthDataStore
     function lookup_token($consumer, $token_type, $token_key)
     {
         $t = new Token();
-        $t->consumer_key = $consumer->key;
+        if (!is_null($consumer)) {
+            $t->consumer_key = $consumer->key;
+        }
         $t->tok = $token_key;
         $t->type = ($token_type == 'access') ? 1 : 0;
         if ($t->find(true)) {
@@ -154,4 +155,345 @@ class StatusNetOAuthDataStore extends OAuthDataStore
     {
         return $this->new_access_token($consumer);
     }
+
+    /**
+     * Revoke specified OAuth token
+     *
+     * Revokes the authorization token specified by $token_key.
+     * Throws exceptions in case of error.
+     *
+     * @param string $token_key The token to be revoked
+     *
+     * @access public
+     **/
+    public function revoke_token($token_key) {
+        $rt = new Token();
+        $rt->tok = $token_key;
+        $rt->type = 0;
+        $rt->state = 0;
+        if (!$rt->find(true)) {
+            throw new Exception('Tried to revoke unknown token');
+        }
+        if (!$rt->delete()) {
+            throw new Exception('Failed to delete revoked token');
+        }
+    }
+
+    /**
+     * Authorize specified OAuth token
+     *
+     * Authorizes the authorization token specified by $token_key.
+     * Throws exceptions in case of error.
+     *
+     * @param string $token_key The token to be authorized
+     *
+     * @access public
+     **/
+    public function authorize_token($token_key) {
+        $rt = new Token();
+        $rt->tok = $token_key;
+        $rt->type = 0;
+        $rt->state = 0;
+        if (!$rt->find(true)) {
+            throw new Exception('Tried to authorize unknown token');
+        }
+        $orig_rt = clone($rt);
+        $rt->state = 1; # Authorized but not used
+        if (!$rt->update($orig_rt)) {
+            throw new Exception('Failed to authorize token');
+        }
+    }
+
+    /**
+     * Get profile by identifying URI
+     *
+     * Returns an OMB_Profile object representing the OMB profile identified by
+     * $identifier_uri.
+     * Returns null if there is no such OMB profile.
+     * Throws exceptions in case of other error.
+     *
+     * @param string $identifier_uri The OMB identifier URI specifying the
+     *                               requested profile
+     *
+     * @access public
+     *
+     * @return OMB_Profile The corresponding profile
+     **/
+    public function getProfile($identifier_uri) {
+        /* getProfile is only used for remote profiles by libomb.
+           TODO: Make it work with local ones anyway. */
+        $remote = Remote_profile::staticGet('uri', $identifier_uri);
+        if (!$remote) throw new Exception('No such remote profile');
+        $profile = Profile::staticGet('id', $remote->id);
+        if (!$profile) throw new Exception('No profile for remote user');
+
+        require_once INSTALLDIR.'/lib/omb.php';
+        return profile_to_omb_profile($identifier_uri, $profile);
+    }
+
+    /**
+     * Save passed profile
+     *
+     * Stores the OMB profile $profile. Overwrites an existing entry.
+     * Throws exceptions in case of error.
+     *
+     * @param OMB_Profile $profile   The OMB profile which should be saved
+     *
+     * @access public
+     **/
+    public function saveProfile($omb_profile) {
+        if (common_profile_url($omb_profile->getNickname()) ==
+                                                $omb_profile->getProfileURL()) {
+            throw new Exception('Not implemented');
+        } else {
+            $remote = Remote_profile::staticGet('uri', $omb_profile->getIdentifierURI());
+
+            if ($remote) {
+                $exists = true;
+                $profile = Profile::staticGet($remote->id);
+                $orig_remote = clone($remote);
+                $orig_profile = clone($profile);
+                # XXX: compare current postNotice and updateProfile URLs to the ones
+                # stored in the DB to avoid (possibly...) above attack
+            } else {
+                $exists = false;
+                $remote = new Remote_profile();
+                $remote->uri = $omb_profile->getIdentifierURI();
+                $profile = new Profile();
+            }
+
+            $profile->nickname = $omb_profile->getNickname();
+            $profile->profileurl = $omb_profile->getProfileURL();
+
+            $fullname = $omb_profile->getFullname();
+            $profile->fullname = is_null($fullname) ? '' : $fullname;
+            $homepage = $omb_profile->getHomepage();
+            $profile->homepage = is_null($homepage) ? '' : $homepage;
+            $bio = $omb_profile->getBio();
+            $profile->bio = is_null($bio) ? '' : $bio;
+            $location = $omb_profile->getLocation();
+            $profile->location = is_null($location) ? '' : $location;
+
+            if ($exists) {
+                $profile->update($orig_profile);
+            } else {
+                $profile->created = DB_DataObject_Cast::dateTime(); # current time
+                $id = $profile->insert();
+                if (!$id) {
+                    throw new Exception(_('Error inserting new profile'));
+                }
+                $remote->id = $id;
+            }
+
+            $avatar_url = $omb_profile->getAvatarURL();
+            if ($avatar_url) {
+                if (!$this->add_avatar($profile, $avatar_url)) {
+                    throw new Exception(_('Error inserting avatar'));
+                }
+            } else {
+                $avatar = $profile->getOriginalAvatar();
+                if($avatar) $avatar->delete();
+                $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
+                if($avatar) $avatar->delete();
+                $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+                if($avatar) $avatar->delete();
+                $avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
+                if($avatar) $avatar->delete();
+            }
+
+            if ($exists) {
+                if (!$remote->update($orig_remote)) {
+                    throw new Exception(_('Error updating remote profile'));
+                }
+            } else {
+                $remote->created = DB_DataObject_Cast::dateTime(); # current time
+                if (!$remote->insert()) {
+                    throw new Exception(_('Error inserting remote profile'));
+                }
+            }
+        }
+    }
+
+    function add_avatar($profile, $url)
+    {
+        $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
+        copy($url, $temp_filename);
+        $imagefile = new ImageFile($profile->id, $temp_filename);
+        $filename = Avatar::filename($profile->id,
+                                     image_type_to_extension($imagefile->type),
+                                     null,
+                                     common_timestamp());
+        rename($temp_filename, Avatar::path($filename));
+        return $profile->setOriginal($filename);
+    }
+
+    /**
+     * Save passed notice
+     *
+     * Stores the OMB notice $notice. The datastore may change the passed notice.
+     * This might by neccessary for URIs depending on a database key. Note that
+     * it is the user’s duty to present a mechanism for his OMB_Datastore to
+     * appropriately change his OMB_Notice.
+     * Throws exceptions in case of error.
+     *
+     * @param OMB_Notice $notice The OMB notice which should be saved
+     *
+     * @access public
+     **/
+    public function saveNotice(&$omb_notice) {
+        if (Notice::staticGet('uri', $omb_notice->getIdentifierURI())) {
+            throw new Exception(_('Duplicate notice'));
+        }
+        $author_uri = $omb_notice->getAuthor()->getIdentifierURI();
+        common_log(LOG_DEBUG, $author_uri, __FILE__);
+        $author = Remote_profile::staticGet('uri', $author_uri);
+        if (!$author) {
+            $author = User::staticGet('uri', $author_uri);
+        }
+        if (!$author) {
+            throw new Exception('No such user');
+        }
+
+        common_log(LOG_DEBUG, print_r($author, true), __FILE__);
+
+        $notice = Notice::saveNew($author->id,
+                                  $omb_notice->getContent(),
+                                  'omb',
+                                  false,
+                                  null,
+                                  $omb_notice->getIdentifierURI());
+
+        common_broadcast_notice($notice, true);
+    }
+
+    /**
+     * Get subscriptions of a given profile
+     *
+     * Returns an array containing subscription informations for the specified
+     * profile. Every array entry should in turn be an array with keys
+     *   'uri´: The identifier URI of the subscriber
+     *   'token´: The subscribe token
+     *   'secret´: The secret token
+     * Throws exceptions in case of error.
+     *
+     * @param string $subscribed_user_uri The OMB identifier URI specifying the
+     *                                    subscribed profile
+     *
+     * @access public
+     *
+     * @return mixed An array containing the subscriptions or 0 if no
+     *               subscription has been found.
+     **/
+    public function getSubscriptions($subscribed_user_uri) {
+        $sub = new Subscription();
+
+        $user = $this->_getAnyProfile($subscribed_user_uri);
+
+        $sub->subscribed = $user->id;
+
+        if (!$sub->find(true)) {
+            return 0;
+        }
+
+        /* Since we do not use OMB_Service_Provider’s action methods, there
+           is no need to actually return the subscriptions. */
+        return 1;
+    }
+
+    private function _getAnyProfile($uri)
+    {
+        $user = Remote_profile::staticGet('uri', $uri);
+        if (!$user) {
+            $user = User::staticGet('uri', $uri);
+        }
+        if (!$user) {
+            throw new Exception('No such user');
+        }
+        return $user;
+    }
+
+    /**
+     * Delete a subscription
+     *
+     * Deletes the subscription from $subscriber_uri to $subscribed_user_uri.
+     * Throws exceptions in case of error.
+     *
+     * @param string $subscriber_uri      The OMB identifier URI specifying the
+     *                                    subscribing profile
+     *
+     * @param string $subscribed_user_uri The OMB identifier URI specifying the
+     *                                    subscribed profile
+     *
+     * @access public
+     **/
+    public function deleteSubscription($subscriber_uri, $subscribed_user_uri)
+    {
+        $sub = new Subscription();
+
+        $subscribed = $this->_getAnyProfile($subscribed_user_uri);
+        $subscriber = $this->_getAnyProfile($subscriber_uri);
+
+        $sub->subscribed = $subscribed->id;
+        $sub->subscriber = $subscriber->id;
+
+        $sub->delete();
+    }
+
+    /**
+     * Save a subscription
+     *
+     * Saves the subscription from $subscriber_uri to $subscribed_user_uri.
+     * Throws exceptions in case of error.
+     *
+     * @param string     $subscriber_uri      The OMB identifier URI specifying
+     *                                        the subscribing profile
+     *
+     * @param string     $subscribed_user_uri The OMB identifier URI specifying
+     *                                        the subscribed profile
+     * @param OAuthToken $token               The access token
+     *
+     * @access public
+     **/
+    public function saveSubscription($subscriber_uri, $subscribed_user_uri,
+                                                                       $token)
+    {
+        $sub = new Subscription();
+
+        $subscribed = $this->_getAnyProfile($subscribed_user_uri);
+        $subscriber = $this->_getAnyProfile($subscriber_uri);
+
+        $sub->subscribed = $subscribed->id;
+        $sub->subscriber = $subscriber->id;
+
+        $sub_exists = $sub->find(true);
+
+        if ($sub_exists) {
+            $orig_sub = clone($sub);
+        } else {
+            $sub->created = DB_DataObject_Cast::dateTime();
+        }
+
+        $sub->token  = $token->key;
+        $sub->secret = $token->secret;
+
+        if ($sub_exists) {
+            $result = $sub->update($orig_sub);
+        } else {
+            $result = $sub->insert();
+        }
+
+        if (!$result) {
+            common_log_db_error($sub, ($sub_exists) ? 'UPDATE' : 'INSERT', __FILE__);
+            throw new Exception(_('Couldn\'t insert new subscription.'));
+            return;
+        }
+
+        /* Notify user, if necessary. */
+
+        if ($subscribed instanceof User) {
+            mail_subscribe_notify_profile($subscribed,
+                                          Profile::staticGet($subscriber->id));
+        }
+    }
 }
+?>
index 7dca760c6951da3c72e2e71132d7c6c133d12400..0566701ff16990fbc45b6e3003b6e4f67455c5f7 100644 (file)
 
 if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
 
-require_once('OAuth.php');
-require_once(INSTALLDIR.'/lib/oauthstore.php');
-
-require_once(INSTALLDIR.'/classes/Consumer.php');
-require_once(INSTALLDIR.'/classes/Nonce.php');
-require_once(INSTALLDIR.'/classes/Token.php');
-
-require_once('Auth/Yadis/Yadis.php');
-
-define('OAUTH_NAMESPACE', 'http://oauth.net/core/1.0/');
-define('OMB_NAMESPACE', 'http://openmicroblogging.org/protocol/0.1');
-define('OMB_VERSION_01', 'http://openmicroblogging.org/protocol/0.1');
-define('OAUTH_DISCOVERY', 'http://oauth.net/discovery/1.0');
-
-define('OMB_ENDPOINT_UPDATEPROFILE', OMB_NAMESPACE.'/updateProfile');
-define('OMB_ENDPOINT_POSTNOTICE', OMB_NAMESPACE.'/postNotice');
-define('OAUTH_ENDPOINT_REQUEST', OAUTH_NAMESPACE.'endpoint/request');
-define('OAUTH_ENDPOINT_AUTHORIZE', OAUTH_NAMESPACE.'endpoint/authorize');
-define('OAUTH_ENDPOINT_ACCESS', OAUTH_NAMESPACE.'endpoint/access');
-define('OAUTH_ENDPOINT_RESOURCE', OAUTH_NAMESPACE.'endpoint/resource');
-define('OAUTH_AUTH_HEADER', OAUTH_NAMESPACE.'parameters/auth-header');
-define('OAUTH_POST_BODY', OAUTH_NAMESPACE.'parameters/post-body');
-define('OAUTH_HMAC_SHA1', OAUTH_NAMESPACE.'signature/HMAC-SHA1');
+require_once INSTALLDIR.'/lib/oauthstore.php';
+require_once 'OAuth.php';
+require_once 'libomb/constants.php';
+require_once 'libomb/service_consumer.php';
+require_once 'libomb/notice.php';
+require_once 'libomb/profile.php';
+require_once 'Auth/Yadis/Yadis.php';
 
 function omb_oauth_consumer()
 {
     static $con = null;
-    if (!$con) {
+    if (is_null($con)) {
         $con = new OAuthConsumer(common_root_url(), '');
     }
     return $con;
@@ -55,7 +39,7 @@ function omb_oauth_consumer()
 function omb_oauth_server()
 {
     static $server = null;
-    if (!$server) {
+    if (is_null($server)) {
         $server = new OAuthServer(omb_oauth_datastore());
         $server->add_signature_method(omb_hmac_sha1());
     }
@@ -65,7 +49,7 @@ function omb_oauth_server()
 function omb_oauth_datastore()
 {
     static $store = null;
-    if (!$store) {
+    if (is_null($store)) {
         $store = new StatusNetOAuthDataStore();
     }
     return $store;
@@ -74,57 +58,18 @@ function omb_oauth_datastore()
 function omb_hmac_sha1()
 {
     static $hmac_method = null;
-    if (!$hmac_method) {
+    if (is_null($hmac_method)) {
         $hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
     }
     return $hmac_method;
 }
 
-function omb_get_services($xrd, $type)
+function omb_broadcast_notice($notice)
 {
-    return $xrd->services(array(omb_service_filter($type)));
-}
-
-function omb_service_filter($type)
-{
-    return create_function('$s',
-                           'return omb_match_service($s, \''.$type.'\');');
-}
-
-function omb_match_service($service, $type)
-{
-    return in_array($type, $service->getTypes());
-}
-
-function omb_service_uri($service)
-{
-    if (!$service) {
-        return null;
-    }
-    $uris = $service->getURIs();
-    if (!$uris) {
-        return null;
-    }
-    return $uris[0];
-}
-
-function omb_local_id($service)
-{
-    if (!$service) {
-        return null;
-    }
-    $els = $service->getElements('xrd:LocalID');
-    if (!$els) {
-        return null;
-    }
-    $el = $els[0];
-    return $service->parser->content($el);
-}
 
-function omb_broadcast_remote_subscribers($notice)
-{
+    $omb_notice = notice_to_omb_notice($notice);
 
-    # First, get remote users subscribed to this profile
+    /* Get remote users subscribed to this profile. */
     $rp = new Remote_profile();
 
     $rp->query('SELECT postnoticeurl, token, secret ' .
@@ -135,170 +80,148 @@ function omb_broadcast_remote_subscribers($notice)
     $posted = array();
 
     while ($rp->fetch()) {
-        if (!array_key_exists($rp->postnoticeurl, $posted)) {
-            common_log(LOG_DEBUG, 'Posting to ' . $rp->postnoticeurl);
-            if (omb_post_notice_keys($notice, $rp->postnoticeurl, $rp->token, $rp->secret)) {
-                common_log(LOG_DEBUG, 'Finished to ' . $rp->postnoticeurl);
-                $posted[$rp->postnoticeurl] = true;
-            } else {
-                common_log(LOG_DEBUG, 'Failed posting to ' . $rp->postnoticeurl);
-            }
+        if (isset($posted[$rp->postnoticeurl])) {
+            /* We already posted to this url. */
+            continue;
         }
-    }
-
-    $rp->free();
-    unset($rp);
+        common_debug('Posting to ' . $rp->postnoticeurl, __FILE__);
+
+        /* Post notice. */
+        $service = new Laconica_OMB_Service_Consumer(
+                     array(OMB_ENDPOINT_POSTNOTICE => $rp->postnoticeurl));
+        try {
+            $service->setToken($rp->token, $rp->secret);
+            $service->postNotice($omb_notice);
+        } catch (Exception $e) {
+            common_log(LOG_ERR, 'Failed posting to ' . $rp->postnoticeurl);
+            common_log(LOG_ERR, 'Error status '.$e);
+            continue;
+        }
+        $posted[$rp->postnoticeurl] = true;
 
-    return true;
-}
+        common_debug('Finished to ' . $rp->postnoticeurl, __FILE__);
+    }
 
-function omb_post_notice($notice, $remote_profile, $subscription)
-{
-    return omb_post_notice_keys($notice, $remote_profile->postnoticeurl, $subscription->token, $subscription->secret);
+    return;
 }
 
-function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret)
+function omb_broadcast_profile($profile)
 {
-    $user = User::staticGet('id', $notice->profile_id);
+    $user = User::staticGet('id', $profile->id);
 
     if (!$user) {
         return false;
     }
 
-    $con = omb_oauth_consumer();
+    $profile = $user->getProfile();
 
-    $token = new OAuthToken($tk, $secret);
-
-    $url = $postnoticeurl;
-    $parsed = parse_url($url);
-    $params = array();
-    parse_str($parsed['query'], $params);
-
-    $req = OAuthRequest::from_consumer_and_token($con, $token,
-                                                 'POST', $url, $params);
-
-    $req->set_parameter('omb_version', OMB_VERSION_01);
-    $req->set_parameter('omb_listenee', $user->uri);
-    $req->set_parameter('omb_notice', $notice->uri);
-    $req->set_parameter('omb_notice_content', $notice->content);
-    $req->set_parameter('omb_notice_url', common_local_url('shownotice',
-                                                           array('notice' =>
-                                                                 $notice->id)));
-    $req->set_parameter('omb_notice_license', common_config('license', 'url'));
+    $omb_profile = profile_to_omb_profile($user->uri, $profile, true);
 
-    $user->free();
-    unset($user);
+    /* Get remote users subscribed to this profile. */
+    $rp = new Remote_profile();
 
-    $req->sign_request(omb_hmac_sha1(), $con, $token);
+    $rp->query('SELECT updateprofileurl, token, secret ' .
+               'FROM subscription JOIN remote_profile ' .
+               'ON subscription.subscriber = remote_profile.id ' .
+               'WHERE subscription.subscribed = ' . $profile->id . ' ');
 
-    # We re-use this tool's fetcher, since it's pretty good
+    $posted = array();
 
-    $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+    while ($rp->fetch()) {
+        if (isset($posted[$rp->updateprofileurl])) {
+            /* We already posted to this url. */
+            continue;
+        }
+        common_debug('Posting to ' . $rp->updateprofileurl, __FILE__);
+
+        /* Update profile. */
+        $service = new StatusNet_OMB_Service_Consumer(
+                     array(OMB_ENDPOINT_UPDATEPROFILE => $rp->updateprofileurl));
+        try {
+            $service->setToken($rp->token, $rp->secret);
+            $service->updateProfile($omb_profile);
+        } catch (Exception $e) {
+            common_log(LOG_ERR, 'Failed posting to ' . $rp->updateprofileurl);
+            common_log(LOG_ERR, 'Error status '.$e);
+            continue;
+        }
+        $posted[$rp->updateprofileurl] = true;
 
-    if (!$fetcher) {
-        common_log(LOG_WARNING, 'Failed to initialize Yadis fetcher.', __FILE__);
-        return false;
+        common_debug('Finished to ' . $rp->updateprofileurl, __FILE__);
     }
 
-    $result = $fetcher->post($req->get_normalized_http_url(),
-                             $req->to_postdata(),
-                             array('User-Agent: StatusNet/' . STATUSNET_VERSION));
-
-    if ($result->status == 403) { # not authorized, don't send again
-        common_debug('403 result, deleting subscription', __FILE__);
-        # FIXME: figure out how to delete this
-        # $subscription->delete();
-        return false;
-    } else if ($result->status != 200) {
-        common_debug('Error status '.$result->status, __FILE__);
-        return false;
-    } else { # success!
-        parse_str($result->body, $return);
-        if ($return['omb_version'] == OMB_VERSION_01) {
-            return true;
-        } else {
-            return false;
-        }
-    }
+    return;
 }
 
-function omb_broadcast_profile($profile)
-{
-    # First, get remote users subscribed to this profile
-    # XXX: use a join here rather than looping through results
-    $sub = new Subscription();
-    $sub->subscribed = $profile->id;
-    if ($sub->find()) {
-        $updated = array();
-        while ($sub->fetch()) {
-            $rp = Remote_profile::staticGet('id', $sub->subscriber);
-            if ($rp) {
-                if (!array_key_exists($rp->updateprofileurl, $updated)) {
-                    if (omb_update_profile($profile, $rp, $sub)) {
-                        $updated[$rp->updateprofileurl] = true;
-                    }
-                }
-            }
-        }
+class StatusNet_OMB_Service_Consumer extends OMB_Service_Consumer {
+    public function __construct($urls)
+    {
+        $this->services       = $urls;
+        $this->datastore      = omb_oauth_datastore();
+        $this->oauth_consumer = omb_oauth_consumer();
+        $this->fetcher        = Auth_Yadis_Yadis::getHTTPFetcher();
     }
+
 }
 
-function omb_update_profile($profile, $remote_profile, $subscription)
+function profile_to_omb_profile($uri, $profile, $force = false)
 {
-    $user = User::staticGet($profile->id);
-    $con = omb_oauth_consumer();
-    $token = new OAuthToken($subscription->token, $subscription->secret);
-    $url = $remote_profile->updateprofileurl;
-    $parsed = parse_url($url);
-    $params = array();
-    parse_str($parsed['query'], $params);
-    $req = OAuthRequest::from_consumer_and_token($con, $token,
-                                                 "POST", $url, $params);
-    $req->set_parameter('omb_version', OMB_VERSION_01);
-    $req->set_parameter('omb_listenee', $user->uri);
-    $req->set_parameter('omb_listenee_profile', common_profile_url($profile->nickname));
-    $req->set_parameter('omb_listenee_nickname', $profile->nickname);
-
-    # We use blanks to force emptying any existing values in these optional fields
-
-    $req->set_parameter('omb_listenee_fullname',
-                        ($profile->fullname) ? $profile->fullname : '');
-    $req->set_parameter('omb_listenee_homepage',
-                        ($profile->homepage) ? $profile->homepage : '');
-    $req->set_parameter('omb_listenee_bio',
-                        ($profile->bio) ? $profile->bio : '');
-    $req->set_parameter('omb_listenee_location',
-                        ($profile->location) ? $profile->location : '');
+    $omb_profile = new OMB_Profile($uri);
+    $omb_profile->setNickname($profile->nickname);
+    $omb_profile->setLicenseURL(common_config('license', 'url'));
+    if (!is_null($profile->fullname)) {
+        $omb_profile->setFullname($profile->fullname);
+    } elseif ($force) {
+        $omb_profile->setFullname('');
+    }
+    if (!is_null($profile->homepage)) {
+        $omb_profile->setHomepage($profile->homepage);
+    } elseif ($force) {
+        $omb_profile->setHomepage('');
+    }
+    if (!is_null($profile->bio)) {
+        $omb_profile->setBio($profile->bio);
+    } elseif ($force) {
+        $omb_profile->setBio('');
+    }
+    if (!is_null($profile->location)) {
+        $omb_profile->setLocation($profile->location);
+    } elseif ($force) {
+        $omb_profile->setLocation('');
+    }
+    if (!is_null($profile->profileurl)) {
+        $omb_profile->setProfileURL($profile->profileurl);
+    } elseif ($force) {
+        $omb_profile->setProfileURL('');
+    }
 
     $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
-    $req->set_parameter('omb_listenee_avatar',
-                        ($avatar) ? $avatar->url : '');
+    if ($avatar) {
+        $omb_profile->setAvatarURL($avatar->url);
+    } elseif ($force) {
+        $omb_profile->setAvatarURL('');
+    }
+    return $omb_profile;
+}
 
-    $req->sign_request(omb_hmac_sha1(), $con, $token);
+function notice_to_omb_notice($notice)
+{
+    /* Create an OMB_Notice for $notice. */
+    $user = User::staticGet('id', $notice->profile_id);
 
-    # We re-use this tool's fetcher, since it's pretty good
+    if (!$user) {
+        return null;
+    }
 
-    $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+    $profile = $user->getProfile();
 
-    $result = $fetcher->post($req->get_normalized_http_url(),
-                             $req->to_postdata(),
-                             array('User-Agent: StatusNet/' . STATUSNET_VERSION));
+    $omb_notice = new OMB_Notice(profile_to_omb_profile($user->uri, $profile),
+                                 $notice->uri,
+                                 $notice->content);
+    $omb_notice->setURL(common_local_url('shownotice', array('notice' =>
+                                                                 $notice->id)));
+    $omb_notice->setLicenseURL(common_config('license', 'url'));
 
-    if (empty($result) || !$result) {
-        common_debug("Unable to contact " . $req->get_normalized_http_url());
-    } else if ($result->status == 403) { # not authorized, don't send again
-        common_debug('403 result, deleting subscription', __FILE__);
-        $subscription->delete();
-        return false;
-    } else if ($result->status != 200) {
-        common_debug('Error status '.$result->status, __FILE__);
-        return false;
-    } else { # success!
-        parse_str($result->body, $return);
-        if (isset($return['omb_version']) && $return['omb_version'] === OMB_VERSION_01) {
-            return true;
-        } else {
-            return false;
-        }
-    }
+    return $omb_notice;
 }
+?>
diff --git a/lib/openid.php b/lib/openid.php
deleted file mode 100644 (file)
index 7a2c46f..0000000
+++ /dev/null
@@ -1,280 +0,0 @@
-<?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.'/classes/User_openid.php');
-
-require_once('Auth/OpenID.php');
-require_once('Auth/OpenID/Consumer.php');
-require_once('Auth/OpenID/SReg.php');
-require_once('Auth/OpenID/MySQLStore.php');
-
-# About one year cookie expiry
-
-define('OPENID_COOKIE_EXPIRY', round(365.25 * 24 * 60 * 60));
-define('OPENID_COOKIE_KEY', 'lastusedopenid');
-
-function oid_store()
-{
-    static $store = null;
-    if (!$store) {
-        # Can't be called statically
-        $user = new User();
-        $conn = $user->getDatabaseConnection();
-        $store = new Auth_OpenID_MySQLStore($conn);
-    }
-    return $store;
-}
-
-function oid_consumer()
-{
-    $store = oid_store();
-    $consumer = new Auth_OpenID_Consumer($store);
-    return $consumer;
-}
-
-function oid_clear_last()
-{
-    oid_set_last('');
-}
-
-function oid_set_last($openid_url)
-{
-    common_set_cookie(OPENID_COOKIE_KEY,
-                     $openid_url,
-                     time() + OPENID_COOKIE_EXPIRY);
-}
-
-function oid_get_last()
-{
-    if (empty($_COOKIE[OPENID_COOKIE_KEY])) {
-        return null;
-    }
-    $openid_url = $_COOKIE[OPENID_COOKIE_KEY];
-    if ($openid_url && strlen($openid_url) > 0) {
-        return $openid_url;
-    } else {
-        return null;
-    }
-}
-
-function oid_link_user($id, $canonical, $display)
-{
-
-    $oid = new User_openid();
-    $oid->user_id = $id;
-    $oid->canonical = $canonical;
-    $oid->display = $display;
-    $oid->created = DB_DataObject_Cast::dateTime();
-
-    if (!$oid->insert()) {
-        $err = PEAR::getStaticProperty('DB_DataObject','lastError');
-        common_debug('DB error ' . $err->code . ': ' . $err->message, __FILE__);
-        return false;
-    }
-
-    return true;
-}
-
-function oid_get_user($openid_url)
-{
-    $user = null;
-    $oid = User_openid::staticGet('canonical', $openid_url);
-    if ($oid) {
-        $user = User::staticGet('id', $oid->user_id);
-    }
-    return $user;
-}
-
-function oid_check_immediate($openid_url, $backto=null)
-{
-    if (!$backto) {
-        $action = $_REQUEST['action'];
-        $args = common_copy_args($_GET);
-        unset($args['action']);
-        $backto = common_local_url($action, $args);
-    }
-    common_debug('going back to "' . $backto . '"', __FILE__);
-
-    common_ensure_session();
-
-    $_SESSION['openid_immediate_backto'] = $backto;
-    common_debug('passed-in variable is "' . $backto . '"', __FILE__);
-    common_debug('session variable is "' . $_SESSION['openid_immediate_backto'] . '"', __FILE__);
-
-    oid_authenticate($openid_url,
-                     'finishimmediate',
-                     true);
-}
-
-function oid_authenticate($openid_url, $returnto, $immediate=false)
-{
-
-    $consumer = oid_consumer();
-
-    if (!$consumer) {
-        common_server_error(_('Cannot instantiate OpenID consumer object.'));
-        return false;
-    }
-
-    common_ensure_session();
-
-    $auth_request = $consumer->begin($openid_url);
-
-    // Handle failure status return values.
-    if (!$auth_request) {
-        return _('Not a valid OpenID.');
-    } else if (Auth_OpenID::isFailure($auth_request)) {
-        return sprintf(_('OpenID failure: %s'), $auth_request->message);
-    }
-
-    $sreg_request = Auth_OpenID_SRegRequest::build(// Required
-                                                   array(),
-                                                   // Optional
-                                                   array('nickname',
-                                                         'email',
-                                                         'fullname',
-                                                         'language',
-                                                         'timezone',
-                                                         'postcode',
-                                                         'country'));
-
-    if ($sreg_request) {
-        $auth_request->addExtension($sreg_request);
-    }
-
-    $trust_root = common_root_url(true);
-    $process_url = common_local_url($returnto);
-
-    if ($auth_request->shouldSendRedirect()) {
-        $redirect_url = $auth_request->redirectURL($trust_root,
-                                                   $process_url,
-                                                   $immediate);
-        if (!$redirect_url) {
-        } else if (Auth_OpenID::isFailure($redirect_url)) {
-            return sprintf(_('Could not redirect to server: %s'), $redirect_url->message);
-        } else {
-            common_redirect($redirect_url, 303);
-        }
-    } else {
-        // Generate form markup and render it.
-        $form_id = 'openid_message';
-        $form_html = $auth_request->formMarkup($trust_root, $process_url,
-                                               $immediate, array('id' => $form_id));
-
-        # XXX: This is cheap, but things choke if we don't escape ampersands
-        # in the HTML attributes
-
-        $form_html = preg_replace('/&/', '&amp;', $form_html);
-
-        // Display an error if the form markup couldn't be generated;
-        // otherwise, render the HTML.
-        if (Auth_OpenID::isFailure($form_html)) {
-            common_server_error(sprintf(_('Could not create OpenID form: %s'), $form_html->message));
-        } else {
-            $action = new AutosubmitAction(); // see below
-            $action->form_html = $form_html;
-            $action->form_id = $form_id;
-            $action->prepare(array('action' => 'autosubmit'));
-            $action->handle(array('action' => 'autosubmit'));
-        }
-    }
-}
-
-# Half-assed attempt at a module-private function
-
-function _oid_print_instructions()
-{
-    common_element('div', 'instructions',
-                   _('This form should automatically submit itself. '.
-                      'If not, click the submit button to go to your '.
-                      'OpenID provider.'));
-}
-
-# update a user from sreg parameters
-
-function oid_update_user(&$user, &$sreg)
-{
-
-    $profile = $user->getProfile();
-
-    $orig_profile = clone($profile);
-
-    if ($sreg['fullname'] && strlen($sreg['fullname']) <= 255) {
-        $profile->fullname = $sreg['fullname'];
-    }
-
-    if ($sreg['country']) {
-        if ($sreg['postcode']) {
-            # XXX: use postcode to get city and region
-            # XXX: also, store postcode somewhere -- it's valuable!
-            $profile->location = $sreg['postcode'] . ', ' . $sreg['country'];
-        } else {
-            $profile->location = $sreg['country'];
-        }
-    }
-
-    # XXX save language if it's passed
-    # XXX save timezone if it's passed
-
-    if (!$profile->update($orig_profile)) {
-        common_server_error(_('Error saving the profile.'));
-        return false;
-    }
-
-    $orig_user = clone($user);
-
-    if ($sreg['email'] && Validate::email($sreg['email'], true)) {
-        $user->email = $sreg['email'];
-    }
-
-    if (!$user->update($orig_user)) {
-        common_server_error(_('Error saving the user.'));
-        return false;
-    }
-
-    return true;
-}
-
-class AutosubmitAction extends Action
-{
-    var $form_html = null;
-    var $form_id = null;
-
-    function handle($args)
-    {
-        parent::handle($args);
-        $this->showPage();
-    }
-
-    function title()
-    {
-        return _('OpenID Auto-Submit');
-    }
-
-    function showContent()
-    {
-        $this->raw($this->form_html);
-        $this->element('script', null,
-                       '$(document).ready(function() { ' .
-                       '    $(\'#'. $this->form_id .'\').submit(); '.
-                       '});');
-    }
-}
index 87d7be5a7597296bb2c2d777358f7365455beb5d..59bf3ba9d695914c6544dd76dade659e97bd2dad 100644 (file)
@@ -76,4 +76,18 @@ class Plugin
     {
         return true;
     }
+
+    /* 
+    * the name of the shortener
+    * shortenerInfo associative array with additional information. One possible element is 'freeService' which can be true or false
+    * shortener array, first element is the name of the class, second element is an array to be passed as constructor parameters to the class
+    */
+    function registerUrlShortener($name, $shortenerInfo, $shortener)
+    {
+        global $_shorteners;
+        if(!is_array($_shorteners)){
+            $_shorteners=array();
+        }
+        $_shorteners[$name]=array('info'=>$shortenerInfo, 'callInfo'=>$shortener);
+    }
 }
diff --git a/lib/right.php b/lib/right.php
new file mode 100644 (file)
index 0000000..4e0096d
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for user rights
+ *
+ * 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  Authorization
+ * @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 rights
+ *
+ * Mostly for holding the rights constants
+ *
+ * @category Authorization
+ * @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 Right
+{
+    const deleteOthersNotice = 'deleteothersnotice';
+}
+
index 5529e60acb82f8a59f71d079823dff6bba6086d7..b9a45d867f0b3dba00819dc7d1ee2f6b0bb8c0ea 100644 (file)
@@ -50,8 +50,7 @@ class Router
     var $m = null;
     static $inst = null;
     static $bare = array('requesttoken', 'accesstoken', 'userauthorization',
-                         'postnotice', 'updateprofile', 'finishremotesubscribe',
-                         'finishopenidlogin', 'finishaddopenid');
+                         'postnotice', 'updateprofile', 'finishremotesubscribe');
 
     static function get()
     {
@@ -76,7 +75,6 @@ class Router
 
         $m->connect('', array('action' => 'public'));
         $m->connect('rss', array('action' => 'publicrss'));
-        $m->connect('xrds', array('action' => 'publicxrds'));
         $m->connect('featuredrss', array('action' => 'featuredrss'));
         $m->connect('favoritedrss', array('action' => 'favoritedrss'));
         $m->connect('opensearch/people', array('action' => 'opensearch',
@@ -128,7 +126,6 @@ class Router
 
         // exceptional
 
-        $m->connect('main/openid', array('action' => 'openidlogin'));
         $m->connect('main/remote', array('action' => 'remotesubscribe'));
         $m->connect('main/remote?nickname=:nickname', array('action' => 'remotesubscribe'), array('nickname' => '[A-Za-z0-9_-]+'));
 
@@ -138,7 +135,7 @@ class Router
 
         // settings
 
-        foreach (array('profile', 'avatar', 'password', 'openid', 'im',
+        foreach (array('profile', 'avatar', 'password', 'im',
                        'email', 'sms', 'twitter', 'userdesign', 'other') as $s) {
             $m->connect('settings/'.$s, array('action' => $s.'settings'));
         }
@@ -244,6 +241,10 @@ class Router
                         array('nickname' => '[a-zA-Z0-9]+'));
         }
 
+        $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]+'));
@@ -269,22 +270,100 @@ class Router
 
         // statuses API
 
-        $m->connect('api/statuses/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'statuses'),
-                    array('method' => '(public_timeline|home_timeline|friends_timeline|user_timeline|update|replies|mentions|show|friends|followers|featured)(\.(atom|rss|xml|json))?'));
-
-        $m->connect('api/statuses/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'statuses'),
-                    array('method' => '(user_timeline|home_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)'));
+        $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)'));
+
+        $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/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/statuses/friends.:format',
+                     array('action' => 'ApiUserFriends',
+                           'format' => '(xml|json)'));
+
+        $m->connect('api/statuses/friends/:id.:format',
+                    array('action' => 'ApiUserFriends',
+                    'id' => '[a-zA-Z0-9]+',
+                    'format' => '(xml|json)'));
+
+        $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)'));
+
+        $m->connect('api/statuses/update.:format',
+                    array('action' => 'ApiStatusesUpdate',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/statuses/destroy.:format',
+                  array('action' => 'ApiStatusesDestroy',
+                        'format' => '(xml|json)'));
+
+        $m->connect('api/statuses/destroy/:id.:format',
+                  array('action' => 'ApiStatusesDestroy',
+                        'id' => '[0-9]+',
+                        'format' => '(xml|json)'));
 
         // users
 
-        $m->connect('api/users/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'users'),
-                    array('method' => 'show(\.(xml|json))?'));
+        $m->connect('api/users/show/:id.:format',
+                    array('action' => 'ApiUserShow',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json)'));
 
         $m->connect('api/users/:method',
                     array('action' => 'api',
@@ -293,93 +372,99 @@ class Router
 
         // direct messages
 
-        foreach (array('xml', 'json') as $e) {
-            $m->connect('api/direct_messages/new.'.$e,
-                        array('action' => 'api',
-                              'apiaction' => 'direct_messages',
-                              'method' => 'create.'.$e));
-        }
 
-        foreach (array('xml', 'json', 'rss', 'atom') as $e) {
-            $m->connect('api/direct_messages.'.$e,
-                        array('action' => 'api',
-                              'apiaction' => 'direct_messages',
-                              'method' => 'direct_messages.'.$e));
-        }
+        $m->connect('api/direct_messages.:format',
+                    array('action' => 'ApiDirectMessage',
+                          'format' => '(xml|json|rss|atom)'));
 
-        foreach (array('xml', 'json', 'rss', 'atom') as $e) {
-            $m->connect('api/direct_messages/sent.'.$e,
-                        array('action' => 'api',
-                              'apiaction' => 'direct_messages',
-                              'method' => 'sent.'.$e));
-        }
+        $m->connect('api/direct_messages/sent.:format',
+                    array('action' => 'ApiDirectMessage',
+                          'format' => '(xml|json|rss|atom)',
+                          'sent' => true));
 
-        $m->connect('api/direct_messages/destroy/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'direct_messages'));
+        $m->connect('api/direct_messages/new.:format',
+                     array('action' => 'ApiDirectMessageNew',
+                           'format' => '(xml|json)'));
 
         // friendships
 
-        $m->connect('api/friendships/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'friendships'),
-                    array('method' => '(create|destroy)'));
+        $m->connect('api/friendships/show.:format',
+                    array('action' => 'ApiFriendshipsShow',
+                          'format' => '(xml|json)'));
 
-        $m->connect('api/friendships/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'friendships'),
-                    array('method' => '(show|exists)(\.(xml|json))'));
+        $m->connect('api/friendships/exists.:format',
+                    array('action' => 'ApiFriendshipsExists',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/friendships/create.:format',
+                    array('action' => 'ApiFriendshipsCreate',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/friendships/destroy.:format',
+                     array('action' => 'ApiFriendshipsDestroy',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/friendships/create/:id.:format',
+                    array('action' => 'ApiFriendshipsCreate',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/friendships/destroy/:id.:format',
+                    array('action' => 'ApiFriendshipsDestroy',
+                    'id' => '[a-zA-Z0-9]+',
+                    'format' => '(xml|json)'));
 
         // Social graph
 
-        $m->connect('api/friends/ids/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'statuses',
-                          'method' => 'friendsIDs'));
-
-        foreach (array('xml', 'json') as $e) {
-            $m->connect('api/friends/ids.'.$e,
-                        array('action' => 'api',
-                              'apiaction' => 'statuses',
-                              'method' => 'friendsIDs.'.$e));
-        }
+        $m->connect('api/friends/ids/:id.:format',
+                    array('action' => 'apiFriends',
+                          'ids_only' => true));
 
-        $m->connect('api/followers/ids/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'statuses',
-                          'method' => 'followersIDs'));
-
-        foreach (array('xml', 'json') as $e) {
-            $m->connect('api/followers/ids.'.$e,
-                        array('action' => 'api',
-                              'apiaction' => 'statuses',
-                              'method' => 'followersIDs.'.$e));
-        }
+        $m->connect('api/followers/ids/:id.:format',
+                    array('action' => 'apiFollowers',
+                          'ids_only' => true));
+
+        $m->connect('api/friends/ids.:format',
+                    array('action' => 'apiFriends',
+                          'ids_only' => true));
+
+        $m->connect('api/followers/ids.:format',
+                     array('action' => 'apiFollowers',
+                          'ids_only' => true));
 
         // account
 
-        $m->connect('api/account/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'account'));
+        $m->connect('api/account/verify_credentials.:format',
+                    array('action' => 'ApiAccountVerifyCredentials'));
+
+        // special case where verify_credentials is called w/out a format
+
+        $m->connect('api/account/verify_credentials',
+                    array('action' => 'ApiAccountVerifyCredentials'));
+
+        $m->connect('api/account/rate_limit_status.:format',
+                    array('action' => 'ApiAccountRateLimitStatus'));
 
         // favorites
 
-        $m->connect('api/favorites/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'favorites',
-                          array('method' => '(create|destroy)')));
+        $m->connect('api/favorites.:format',
+                    array('action' => 'ApiTimelineFavorites',
+                    'format' => '(xml|json|rss|atom)'));
 
-        $m->connect('api/favorites/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'favorites',
-                          'method' => 'favorites'));
-
-        foreach (array('xml', 'json', 'rss', 'atom') as $e) {
-            $m->connect('api/favorites.'.$e,
-                        array('action' => 'api',
-                              'apiaction' => 'favorites',
-                              'method' => 'favorites.'.$e));
-        }
+        $m->connect('api/favorites/:id.:format',
+                    array('action' => 'ApiTimelineFavorites',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xmljson|rss|atom)'));
+
+        $m->connect('api/favorites/create/:id.:format',
+                    array('action' => 'ApiFavoriteCreate',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/favorites/destroy/:id.:format',
+                    array('action' => 'ApiFavoriteDestroy',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json)'));
 
         // notifications
 
@@ -389,71 +474,112 @@ class Router
 
         // blocks
 
-        $m->connect('api/blocks/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'blocks'));
+        $m->connect('api/blocks/create/:id.:format',
+                    array('action' => 'ApiBlockCreate',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json)'));
 
+        $m->connect('api/blocks/destroy/:id.:format',
+                    array('action' => 'ApiBlockDestroy',
+                          'id' => '[a-zA-Z0-9]+',
+                          'format' => '(xml|json)'));
         // help
 
-        $m->connect('api/help/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'help'));
+        $m->connect('api/help/test.:format',
+                    array('action' => 'ApiHelpTest',
+                          'format' => '(xml|json)'));
 
         // statusnet
 
-        $m->connect('api/statusnet/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'statusnet'));
+        $m->connect('api/statusnet/version.:format',
+                    array('action' => 'ApiStatusnetVersion',
+                          'format' => '(xml|json)'));
+
+        $m->connect('api/statusnet/config.:format',
+                    array('action' => 'ApiStatusnetConfig',
+                   'format' => '(xml|json)'));
 
         // For older methods, we provide "laconica" base action
 
-        $m->connect('api/laconica/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'statusnet'));
+        $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/list/:argument',
-                    array('action' => 'api',
-                          'method' => 'list_groups',
-                          'apiaction' => 'groups'));
-
-        foreach (array('xml', 'json', 'rss', 'atom') as $e) {
-            $m->connect('api/statusnet/groups/list.' . $e,
-                    array('action' => 'api',
-                          'method' => 'list_groups.' . $e,
-                          'apiaction' => 'groups'));
-        }
-
-        $m->connect('api/statusnet/groups/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'statuses'),
-                    array('method' => '(list_all|)(\.(atom|rss|xml|json))?'));
-
-        $m->connect('api/statuses/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'statuses'),
-                    array('method' => '(user_timeline|home_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)'));
-
-        $m->connect('api/statusnet/groups/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'groups'));
-
-        $m->connect('api/statusnet/groups/:method',
-                    array('action' => 'api',
-                          'apiaction' => 'groups'));
 
+        $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/:method/:argument',
-                    array('action' => 'api',
-                          'apiaction' => 'tags'));
-
-        $m->connect('api/statusnet/tags/:method',
-                    array('action' => 'api',
-                          'apiaction' => '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'));
@@ -463,7 +589,7 @@ class Router
         // user stuff
 
         foreach (array('subscriptions', 'subscribers',
-                       'nudge', 'xrds', 'all', 'foaf',
+                       'nudge', 'all', 'foaf', 'xrds',
                        'replies', 'inbox', 'outbox', 'microsummary') as $a) {
             $m->connect(':nickname/'.$a,
                         array('action' => $a),
index 60611e48d0f0a22a9fc87434a74e90773f532dbe..faf6bec7dec4618d8ebc4888c4e55353f8aff99a 100644 (file)
@@ -78,25 +78,12 @@ class Rss10Action extends Action
     function prepare($args)
     {
         parent::prepare($args);
+
         $this->limit = (int) $this->trimmed('limit');
+
         if ($this->limit == 0) {
             $this->limit = DEFAULT_RSS_LIMIT;
         }
-        return true;
-    }
-
-    /**
-     * Handle a request
-     *
-     * @param array $args Arguments from $_REQUEST
-     *
-     * @return void
-     */
-
-    function handle($args)
-    {
-        // Parent handling, including cache check
-        parent::handle($args);
 
         if (common_config('site', 'private')) {
             if (!isset($_SERVER['PHP_AUTH_USER'])) {
@@ -122,8 +109,21 @@ class Rss10Action extends Action
             }
         }
 
-        // Get the list of notices
-        $this->notices = $this->getNotices($this->limit);
+        return true;
+    }
+
+    /**
+     * Handle a request
+     *
+     * @param array $args Arguments from $_REQUEST
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        // Parent handling, including cache check
+        parent::handle($args);
         $this->showRss();
     }
 
@@ -140,7 +140,7 @@ class Rss10Action extends Action
     }
 
     /**
-     * Get the notices to output in this stream
+     * Get the notices to output in this stream.
      *
      * @return array an array of Notice objects sorted in reverse chron
      */
@@ -258,26 +258,27 @@ class Rss10Action extends Action
         $attachments = $notice->attachments();
         if($attachments){
             foreach($attachments as $attachment){
-                if ($attachment->isEnclosure()) {
+                $enclosure=$attachment->getEnclosure();
+                if ($enclosure) {
                     // DO NOT move xmlns declaration to root element. Making it
                     // the default namespace here improves compatibility with
                     // real-world feed readers.
                     $attribs = array(
-                        'rdf:resource' => $attachment->url,
-                        'url' => $attachment->url,
+                        'rdf:resource' => $enclosure->url,
+                        'url' => $enclosure->url,
                         'xmlns' => 'http://purl.oclc.org/net/rss_2.0/enc#'
                         );
-                    if ($attachment->title) {
-                        $attribs['dc:title'] = $attachment->title;
+                    if ($enclosure->title) {
+                        $attribs['dc:title'] = $enclosure->title;
                     }
-                    if ($attachment->modified) {
-                        $attribs['dc:date'] = common_date_w3dtf($attachment->modified);
+                    if ($enclosure->modified) {
+                        $attribs['dc:date'] = common_date_w3dtf($enclosure->modified);
                     }
-                    if ($attachment->size) {
-                        $attribs['length'] = $attachment->size;
+                    if ($enclosure->size) {
+                        $attribs['length'] = $enclosure->size;
                     }
-                    if ($attachment->mimetype) {
-                        $attribs['type'] = $attachment->mimetype;
+                    if ($enclosure->mimetype) {
+                        $attribs['type'] = $enclosure->mimetype;
                     }
                     $this->element('enclosure', $attribs);
                 }
diff --git a/lib/schema.php b/lib/schema.php
new file mode 100644 (file)
index 0000000..1e0c1f3
--- /dev/null
@@ -0,0 +1,680 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Database schema utilities
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Database
+ * @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);
+}
+
+/**
+ * Class representing the database schema
+ *
+ * A class representing the database schema. Can be used to
+ * manipulate the schema -- especially for plugins and upgrade
+ * utilities.
+ *
+ * @category Database
+ * @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 Schema
+{
+    static $_single = null;
+    protected $conn = null;
+
+    /**
+     * Constructor. Only run once for singleton object.
+     */
+
+    protected function __construct()
+    {
+        // XXX: there should be an easier way to do this.
+        $user = new User();
+
+        $this->conn = $user->getDatabaseConnection();
+
+        $user->free();
+
+        unset($user);
+    }
+
+    /**
+     * Main public entry point. Use this to get
+     * the singleton object.
+     *
+     * @return Schema the (single) Schema object
+     */
+
+    static function get()
+    {
+        if (empty(self::$_single)) {
+            self::$_single = new Schema();
+        }
+        return self::$_single;
+    }
+
+    /**
+     * Returns a TableDef object for the table
+     * in the schema with the given name.
+     *
+     * Throws an exception if the table is not found.
+     *
+     * @param string $name Name of the table to get
+     *
+     * @return TableDef tabledef for that table.
+     */
+
+    public function getTableDef($name)
+    {
+        $res =& $this->conn->query('DESCRIBE ' . $name);
+
+        if (PEAR::isError($res)) {
+            throw new Exception($res->getMessage());
+        }
+
+        $td = new TableDef();
+
+        $td->name    = $name;
+        $td->columns = array();
+
+        $row = array();
+
+        while ($res->fetchInto($row, DB_FETCHMODE_ASSOC)) {
+
+            $cd = new ColumnDef();
+
+            $cd->name = $row['Field'];
+
+            $packed = $row['Type'];
+
+            if (preg_match('/^(\w+)\((\d+)\)$/', $packed, $match)) {
+                $cd->type = $match[1];
+                $cd->size = $match[2];
+            } else {
+                $cd->type = $packed;
+            }
+
+            $cd->nullable = ($row['Null'] == 'YES') ? true : false;
+            $cd->key      = $row['Key'];
+            $cd->default  = $row['Default'];
+            $cd->extra    = $row['Extra'];
+
+            $td->columns[] = $cd;
+        }
+
+        return $td;
+    }
+
+    /**
+     * Gets a ColumnDef object for a single column.
+     *
+     * Throws an exception if the table is not found.
+     *
+     * @param string $table  name of the table
+     * @param string $column name of the column
+     *
+     * @return ColumnDef definition of the column or null
+     *                   if not found.
+     */
+
+    public function getColumnDef($table, $column)
+    {
+        $td = $this->getTableDef($table);
+
+        foreach ($td->columns as $cd) {
+            if ($cd->name == $column) {
+                return $cd;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Creates a table with the given names and columns.
+     *
+     * @param string $name    Name of the table
+     * @param array  $columns Array of ColumnDef objects
+     *                        for new table.
+     *
+     * @return boolean success flag
+     */
+
+    public function createTable($name, $columns)
+    {
+        $uniques = array();
+        $primary = array();
+        $indices = array();
+
+        $sql = "CREATE TABLE $name (\n";
+
+        for ($i = 0; $i < count($columns); $i++) {
+
+            $cd =& $columns[$i];
+
+            if ($i > 0) {
+                $sql .= ",\n";
+            }
+
+            $sql .= $this->_columnSql($cd);
+
+            switch ($cd->key) {
+            case 'UNI':
+                $uniques[] = $cd->name;
+                break;
+            case 'PRI':
+                $primary[] = $cd->name;
+                break;
+            case 'MUL':
+                $indices[] = $cd->name;
+                break;
+            }
+        }
+
+        if (count($primary) > 0) { // it really should be...
+            $sql .= ",\nconstraint primary key (" . implode(',', $primary) . ")";
+        }
+
+        foreach ($uniques as $u) {
+            $sql .= ",\nunique index {$name}_{$u}_idx ($u)";
+        }
+
+        foreach ($indices as $i) {
+            $sql .= ",\nindex {$name}_{$i}_idx ($i)";
+        }
+
+        $sql .= "); ";
+
+        $res =& $this->conn->query($sql);
+
+        if (PEAR::isError($res)) {
+            throw new Exception($res->getMessage());
+        }
+
+        return true;
+    }
+
+    /**
+     * Drops a table from the schema
+     *
+     * Throws an exception if the table is not found.
+     *
+     * @param string $name Name of the table to drop
+     *
+     * @return boolean success flag
+     */
+
+    public function dropTable($name)
+    {
+        $res =& $this->conn->query("DROP TABLE $name");
+
+        if (PEAR::isError($res)) {
+            throw new Exception($res->getMessage());
+        }
+
+        return true;
+    }
+
+    /**
+     * Adds an index to a table.
+     *
+     * If no name is provided, a name will be made up based
+     * on the table name and column names.
+     *
+     * Throws an exception on database error, esp. if the table
+     * does not exist.
+     *
+     * @param string $table       Name of the table
+     * @param array  $columnNames Name of columns to index
+     * @param string $name        (Optional) name of the index
+     *
+     * @return boolean success flag
+     */
+
+    public function createIndex($table, $columnNames, $name=null)
+    {
+        if (!is_array($columnNames)) {
+            $columnNames = array($columnNames);
+        }
+
+        if (empty($name)) {
+            $name = "$table_".implode("_", $columnNames)."_idx";
+        }
+
+        $res =& $this->conn->query("ALTER TABLE $table ".
+                                   "ADD INDEX $name (".
+                                   implode(",", $columnNames).")");
+
+        if (PEAR::isError($res)) {
+            throw new Exception($res->getMessage());
+        }
+
+        return true;
+    }
+
+    /**
+     * Drops a named index from a table.
+     *
+     * @param string $table name of the table the index is on.
+     * @param string $name  name of the index
+     *
+     * @return boolean success flag
+     */
+
+    public function dropIndex($table, $name)
+    {
+        $res =& $this->conn->query("ALTER TABLE $table DROP INDEX $name");
+
+        if (PEAR::isError($res)) {
+            throw new Exception($res->getMessage());
+        }
+
+        return true;
+    }
+
+    /**
+     * Adds a column to a table
+     *
+     * @param string    $table     name of the table
+     * @param ColumnDef $columndef Definition of the new
+     *                             column.
+     *
+     * @return boolean success flag
+     */
+
+    public function addColumn($table, $columndef)
+    {
+        $sql = "ALTER TABLE $table ADD COLUMN " . $this->_columnSql($columndef);
+
+        $res =& $this->conn->query($sql);
+
+        if (PEAR::isError($res)) {
+            throw new Exception($res->getMessage());
+        }
+
+        return true;
+    }
+
+    /**
+     * Modifies a column in the schema.
+     *
+     * The name must match an existing column and table.
+     *
+     * @param string    $table     name of the table
+     * @param ColumnDef $columndef new definition of the column.
+     *
+     * @return boolean success flag
+     */
+
+    public function modifyColumn($table, $columndef)
+    {
+        $sql = "ALTER TABLE $table MODIFY COLUMN " .
+          $this->_columnSql($columndef);
+
+        $res =& $this->conn->query($sql);
+
+        if (PEAR::isError($res)) {
+            throw new Exception($res->getMessage());
+        }
+
+        return true;
+    }
+
+    /**
+     * Drops a column from a table
+     *
+     * The name must match an existing column.
+     *
+     * @param string $table      name of the table
+     * @param string $columnName name of the column to drop
+     *
+     * @return boolean success flag
+     */
+
+    public function dropColumn($table, $columnName)
+    {
+        $sql = "ALTER TABLE $table DROP COLUMN $columnName";
+
+        $res =& $this->conn->query($sql);
+
+        if (PEAR::isError($res)) {
+            throw new Exception($res->getMessage());
+        }
+
+        return true;
+    }
+
+    /**
+     * Ensures that a table exists with the given
+     * name and the given column definitions.
+     *
+     * If the table does not yet exist, it will
+     * create the table. If it does exist, it will
+     * alter the table to match the column definitions.
+     *
+     * @param string $tableName name of the table
+     * @param array  $columns   array of ColumnDef
+     *                          objects for the table
+     *
+     * @return boolean success flag
+     */
+
+    public function ensureTable($tableName, $columns)
+    {
+        // XXX: DB engine portability -> toilet
+
+        try {
+            $td = $this->getTableDef($tableName);
+        } catch (Exception $e) {
+            if (preg_match('/no such table/', $e->getMessage())) {
+                return $this->createTable($tableName, $columns);
+            } else {
+                throw $e;
+            }
+        }
+
+        $cur = $this->_names($td->columns);
+        $new = $this->_names($columns);
+
+        $toadd  = array_diff($new, $cur);
+        $todrop = array_diff($cur, $new);
+        $same   = array_intersect($new, $cur);
+        $tomod  = array();
+
+        foreach ($same as $m) {
+            $curCol = $this->_byName($td->columns, $m);
+            $newCol = $this->_byName($columns, $m);
+
+            if (!$newCol->equals($curCol)) {
+                $tomod[] = $newCol->name;
+            }
+        }
+
+        if (count($toadd) + count($todrop) + count($tomod) == 0) {
+            // nothing to do
+            return true;
+        }
+
+        // For efficiency, we want this all in one
+        // query, instead of using our methods.
+
+        $phrase = array();
+
+        foreach ($toadd as $columnName) {
+            $cd = $this->_byName($columns, $columnName);
+
+            $phrase[] = 'ADD COLUMN ' . $this->_columnSql($cd);
+        }
+
+        foreach ($todrop as $columnName) {
+            $phrase[] = 'DROP COLUMN ' . $columnName;
+        }
+
+        foreach ($tomod as $columnName) {
+            $cd = $this->_byName($columns, $columnName);
+
+            $phrase[] = 'MODIFY COLUMN ' . $this->_columnSql($cd);
+        }
+
+        $sql = 'ALTER TABLE ' . $tableName . ' ' . implode(', ', $phrase);
+
+        $res =& $this->conn->query($sql);
+
+        if (PEAR::isError($res)) {
+            throw new Exception($res->getMessage());
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the array of names from an array of
+     * ColumnDef objects.
+     *
+     * @param array $cds array of ColumnDef objects
+     *
+     * @return array strings for name values
+     */
+
+    private function _names($cds)
+    {
+        $names = array();
+
+        foreach ($cds as $cd) {
+            $names[] = $cd->name;
+        }
+
+        return $names;
+    }
+
+    /**
+     * Get a ColumnDef from an array matching
+     * name.
+     *
+     * @param array  $cds  Array of ColumnDef objects
+     * @param string $name Name of the column
+     *
+     * @return ColumnDef matching item or null if no match.
+     */
+
+    private function _byName($cds, $name)
+    {
+        foreach ($cds as $cd) {
+            if ($cd->name == $name) {
+                return $cd;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the proper SQL for creating or
+     * altering a column.
+     *
+     * Appropriate for use in CREATE TABLE or
+     * ALTER TABLE statements.
+     *
+     * @param ColumnDef $cd column to create
+     *
+     * @return string correct SQL for that column
+     */
+
+    private function _columnSql($cd)
+    {
+        $sql = "{$cd->name} ";
+
+        if (!empty($cd->size)) {
+            $sql .= "{$cd->type}({$cd->size}) ";
+        } else {
+            $sql .= "{$cd->type} ";
+        }
+
+        if (!empty($cd->default)) {
+            $sql .= "default {$cd->default} ";
+        } else {
+            $sql .= ($cd->nullable) ? "null " : "not null ";
+        }
+
+        return $sql;
+    }
+}
+
+/**
+ * A class encapsulating the structure of a table.
+ *
+ * @category Database
+ * @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 TableDef
+{
+    /** name of the table */
+    public $name;
+    /** array of ColumnDef objects for the columns. */
+    public $columns;
+}
+
+/**
+ * A class encapsulating the structure of a column in a table.
+ *
+ * @category Database
+ * @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 ColumnDef
+{
+    /** name of the column. */
+    public $name;
+    /** type of column, e.g. 'int', 'varchar' */
+    public $type;
+    /** size of the column. */
+    public $size;
+    /** boolean flag; can it be null? */
+    public $nullable;
+    /**
+     * type of key: null = no key; 'PRI' => primary;
+     * 'UNI' => unique key; 'MUL' => multiple values.
+     */
+    public $key;
+    /** default value if any. */
+    public $default;
+    /** 'extra' stuff. Returned by MySQL, largely
+     * unused. */
+    public $extra;
+
+    /**
+     * Constructor.
+     *
+     * @param string  $name     name of the column
+     * @param string  $type     type of the column
+     * @param int     $size     size of the column
+     * @param boolean $nullable can this be null?
+     * @param string  $key      type of key
+     * @param value   $default  default value
+     * @param value   $extra    unused
+     */
+
+    function __construct($name=null, $type=null, $size=null,
+                         $nullable=true, $key=null, $default=null,
+                         $extra=null)
+    {
+        $this->name     = strtolower($name);
+        $this->type     = strtolower($type);
+        $this->size     = $size+0;
+        $this->nullable = $nullable;
+        $this->key      = $key;
+        $this->default  = $default;
+        $this->extra    = $extra;
+    }
+
+    /**
+     * Compares this columndef with another to see
+     * if they're functionally equivalent.
+     *
+     * @param ColumnDef $other column to compare
+     *
+     * @return boolean true if equivalent, otherwise false.
+     */
+
+    function equals($other)
+    {
+        return ($this->name == $other->name &&
+                $this->_typeMatch($other) &&
+                $this->_defaultMatch($other) &&
+                $this->_nullMatch($other) &&
+                $this->key == $other->key);
+    }
+
+    /**
+     * Does the type of this column match the
+     * type of the other column?
+     *
+     * Checks the type and size of a column. Tries
+     * to ignore differences between synonymous
+     * data types, like 'integer' and 'int'.
+     *
+     * @param ColumnDef $other other column to check
+     *
+     * @return boolean true if they're about equivalent
+     */
+
+    private function _typeMatch($other)
+    {
+        switch ($this->type) {
+        case 'integer':
+        case 'int':
+            return ($other->type == 'integer' ||
+                    $other->type == 'int');
+            break;
+        default:
+            return ($this->type == $other->type &&
+                    $this->size == $other->size);
+        }
+    }
+
+    /**
+     * Does the default behaviour of this column match
+     * the other?
+     *
+     * @param ColumnDef $other other column to check
+     *
+     * @return boolean true if defaults are effectively the same.
+     */
+
+    private function _defaultMatch($other)
+    {
+        return ((is_null($this->default) && is_null($other->default)) ||
+                ($this->default == $other->default));
+    }
+
+    /**
+     * Does the null behaviour of this column match
+     * the other?
+     *
+     * @param ColumnDef $other other column to check
+     *
+     * @return boolean true if these columns 'null' the same.
+     */
+
+    private function _nullMatch($other)
+    {
+        return ((!is_null($this->default) && !is_null($other->default) &&
+                 $this->default == $other->default) ||
+                ($this->nullable == $other->nullable));
+    }
+}
index a1f305f5b74e076cdda6256b8bb04a6e1d1cbdae..c3669868d4d3cb177401e1f1747b7f7c06cf6c0d 100644 (file)
@@ -77,9 +77,7 @@ class SettingsAction extends CurrentUserDesignAction
             // _all_ our settings are important
             common_set_returnto($this->selfUrl());
             $user = common_current_user();
-            if ($user->hasOpenID()) {
-                common_redirect(common_local_url('openidlogin'), 303);
-            } else {
+            if (Event::handle('RedirectToLogin', array($this, $user))) {
                 common_redirect(common_local_url('login'), 303);
             }
         } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
index 676c9b20a2522f37fafbcd0092a7c88dd1e1f778..afc3f55bab932614744ef43732485348f53c7eba 100644 (file)
@@ -23,7 +23,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
 
 define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
 
-function update_twitter_user($twitter_id, $screen_name)
+function updateTwitter_user($twitter_id, $screen_name)
 {
     $uri = 'http://twitter.com/' . $screen_name;
     $fuser = new Foreign_user();
@@ -115,7 +115,7 @@ function save_twitter_user($twitter_id, $screen_name)
         // Only update if Twitter screen name has changed
 
         if ($fuser->nickname != $screen_name) {
-            $result = update_twitter_user($twitter_id, $screen_name);
+            $result = updateTwitter_user($twitter_id, $screen_name);
 
             common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
                 "$fuser->id to $screen_name, was $fuser->nickname");
@@ -165,9 +165,10 @@ function broadcast_twitter($notice)
 }
 
 function broadcast_oauth($notice, $flink) {
-
     $user = $flink->getUser();
     $statustxt = format_status($notice);
+    // Convert !groups to #hashes
+    $statustxt = preg_replace('/(^|\s)!([A-Za-z0-9]{1,64})/', "\\1#\\2", $statustxt);
     $token = TwitterOAuthClient::unpackToken($flink->credentials);
     $client = new TwitterOAuthClient($token->key, $token->secret);
     $status = null;
@@ -222,6 +223,10 @@ function broadcast_basicauth($notice, $flink)
                           $user->nickname, $user->id);
         common_log(LOG_WARNING, $errmsg);
 
+            $errmsg = sprintf('No data returned by Twitter API when ' .
+                             'trying to send update for %1$s (user id %2$s).',
+                             $user->nickname, $user->id);
+            common_log(LOG_WARNING, $errmsg);
         return false;
     }
 
diff --git a/lib/twitterapi.php b/lib/twitterapi.php
deleted file mode 100644 (file)
index 3bac400..0000000
+++ /dev/null
@@ -1,1182 +0,0 @@
-<?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);
-}
-
-class TwitterapiAction extends Action
-{
-
-    var $auth_user;
-
-    /**
-     * Initialization.
-     *
-     * @param array $args Web and URL arguments
-     *
-     * @return boolean false if user doesn't exist
-     */
-
-    function prepare($args)
-    {
-        parent::prepare($args);
-        return true;
-    }
-
-    /**
-     * Handle a request
-     *
-     * @param array $args Arguments from $_REQUEST
-     *
-     * @return void
-     */
-
-    function handle($args)
-    {
-        parent::handle($args);
-    }
-
-    /**
-     * Overrides XMLOutputter::element to write booleans as strings (true|false).
-     * See that method's documentation for more info.
-     *
-     * @param string $tag     Element type or tagname
-     * @param array  $attrs   Array of element attributes, as
-     *                        key-value pairs
-     * @param string $content string content of the element
-     *
-     * @return void
-     */
-    function element($tag, $attrs=null, $content=null)
-    {
-        if (is_bool($content)) {
-            $content = ($content ? 'true' : 'false');
-        }
-
-        return parent::element($tag, $attrs, $content);
-    }
-
-    function twitter_user_array($profile, $get_notice=false)
-    {
-        $twitter_user = array();
-
-        $twitter_user['id'] = intval($profile->id);
-        $twitter_user['name'] = $profile->getBestName();
-        $twitter_user['screen_name'] = $profile->nickname;
-        $twitter_user['location'] = ($profile->location) ? $profile->location : null;
-        $twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
-
-        $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
-        $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() :
-            Avatar::defaultImage(AVATAR_STREAM_SIZE);
-
-        $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
-        $twitter_user['protected'] = false; # not supported by StatusNet yet
-        $twitter_user['followers_count'] = $profile->subscriberCount();
-
-        // To be supported soon...
-        $twitter_user['profile_background_color'] = '';
-        $twitter_user['profile_text_color'] = '';
-        $twitter_user['profile_link_color'] = '';
-        $twitter_user['profile_sidebar_fill_color'] = '';
-        $twitter_user['profile_sidebar_border_color'] = '';
-
-        $twitter_user['friends_count'] = $profile->subscriptionCount();
-
-        $twitter_user['created_at'] = $this->date_twitter($profile->created);
-
-        $twitter_user['favourites_count'] = $profile->faveCount(); // British spelling!
-
-        // Need to pull up the user for some of this
-        $user = User::staticGet($profile->id);
-
-        $timezone = 'UTC';
-
-        if ($user->timezone) {
-            $timezone = $user->timezone;
-        }
-
-        $t = new DateTime;
-        $t->setTimezone(new DateTimeZone($timezone));
-
-        $twitter_user['utc_offset'] = $t->format('Z');
-        $twitter_user['time_zone'] = $timezone;
-
-        // To be supported some day, perhaps
-        $twitter_user['profile_background_image_url'] = '';
-        $twitter_user['profile_background_tile'] = false;
-
-        $twitter_user['statuses_count'] = $profile->noticeCount();
-
-        // Is the requesting user following this user?
-        $twitter_user['following'] = false;
-        $twitter_user['notifications'] = false;
-
-        if (isset($apidata['user'])) {
-
-            $twitter_user['following'] = $apidata['user']->isSubscribed($profile);
-
-            // Notifications on?
-            $sub = Subscription::pkeyGet(array('subscriber' =>
-                $apidata['user']->id, 'subscribed' => $profile->id));
-
-            if ($sub) {
-                $twitter_user['notifications'] = ($sub->jabber || $sub->sms);
-            }
-        }
-
-        if ($get_notice) {
-            $notice = $profile->getCurrentNotice();
-            if ($notice) {
-                # don't get user!
-                $twitter_user['status'] = $this->twitter_status_array($notice, false);
-            }
-        }
-
-        return $twitter_user;
-    }
-
-    function twitter_status_array($notice, $include_user=true)
-    {
-        $profile = $notice->getProfile();
-
-        $twitter_status = array();
-        $twitter_status['text'] = $notice->content;
-        $twitter_status['truncated'] = false; # Not possible on StatusNet
-        $twitter_status['created_at'] = $this->date_twitter($notice->created);
-        $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ?
-            intval($notice->reply_to) : null;
-        $twitter_status['source'] = $this->source_link($notice->source);
-        $twitter_status['id'] = intval($notice->id);
-
-        $replier_profile = null;
-
-        if ($notice->reply_to) {
-            $reply = Notice::staticGet(intval($notice->reply_to));
-            if ($reply) {
-                $replier_profile = $reply->getProfile();
-            }
-        }
-
-        $twitter_status['in_reply_to_user_id'] =
-            ($replier_profile) ? intval($replier_profile->id) : null;
-        $twitter_status['in_reply_to_screen_name'] =
-            ($replier_profile) ? $replier_profile->nickname : null;
-
-        if (isset($this->auth_user)) {
-            $twitter_status['favorited'] = $this->auth_user->hasFave($notice);
-        } else {
-            $twitter_status['favorited'] = false;
-        }
-
-        // Enclosures
-        $attachments = $notice->attachments();
-
-        if (!empty($attachments)) {
-
-            $twitter_status['attachments'] = array();
-
-            foreach ($attachments as $attachment) {
-                if ($attachment->isEnclosure()) {
-                    $enclosure = array();
-                    $enclosure['url'] = $attachment->url;
-                    $enclosure['mimetype'] = $attachment->mimetype;
-                    $enclosure['size'] = $attachment->size;
-                    $twitter_status['attachments'][] = $enclosure;
-                }
-            }
-        }
-
-        if ($include_user) {
-            # Don't get notice (recursive!)
-            $twitter_user = $this->twitter_user_array($profile, false);
-            $twitter_status['user'] = $twitter_user;
-        }
-
-        return $twitter_status;
-    }
-
-    function twitter_group_array($group)
-    {
-        $twitter_group=array();
-        $twitter_group['id']=$group->id;
-        $twitter_group['url']=$group->permalink();
-        $twitter_group['nickname']=$group->nickname;
-        $twitter_group['fullname']=$group->fullname;
-        $twitter_group['homepage_url']=$group->homepage_url;
-        $twitter_group['original_logo']=$group->original_logo;
-        $twitter_group['homepage_logo']=$group->homepage_logo;
-        $twitter_group['stream_logo']=$group->stream_logo;
-        $twitter_group['mini_logo']=$group->mini_logo;
-        $twitter_group['homepage']=$group->homepage;
-        $twitter_group['description']=$group->description;
-        $twitter_group['location']=$group->location;
-        $twitter_group['created']=$this->date_twitter($group->created);
-        $twitter_group['modified']=$this->date_twitter($group->modified);
-        return $twitter_group;
-    }
-
-    function twitter_rss_group_array($group)
-    {
-        $entry = array();
-        $entry['content']=$group->description;
-        $entry['title']=$group->nickname;
-        $entry['link']=$group->permalink();
-        $entry['published']=common_date_iso8601($group->created);
-        $entry['updated']==common_date_iso8601($group->modified);
-        $taguribase = common_config('integration', 'groupuri');
-        $entry['id'] = "group:$groupuribase:$entry[link]";
-
-        $entry['description'] = $entry['content'];
-        $entry['pubDate'] = common_date_rfc2822($group->created);
-        $entry['guid'] = $entry['link'];
-
-        return $entry;
-    }
-
-    function twitter_rss_entry_array($notice)
-    {
-        $profile = $notice->getProfile();
-        $entry = array();
-
-        // We trim() to avoid extraneous whitespace in the output
-
-        $entry['content'] = common_xml_safe_str(trim($notice->rendered));
-        $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
-        $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id));
-        $entry['published'] = common_date_iso8601($notice->created);
-
-        $taguribase = common_config('integration', 'taguri');
-        $entry['id'] = "tag:$taguribase:$entry[link]";
-
-        $entry['updated'] = $entry['published'];
-        $entry['author'] = $profile->getBestName();
-
-        // Enclosures
-        $attachments = $notice->attachments();
-        $enclosures = array();
-
-        foreach ($attachments as $attachment) {
-            if ($attachment->isEnclosure()) {
-                 $enclosure = array();
-                 $enclosure['url'] = $attachment->url;
-                 $enclosure['mimetype'] = $attachment->mimetype;
-                 $enclosure['size'] = $attachment->size;
-                 $enclosures[] = $enclosure;
-            }
-        }
-
-        if (!empty($enclosures)) {
-            $entry['enclosures'] = $enclosures;
-        }
-
-/*
-        // Enclosure
-        $attachments = $notice->attachments();
-        if($attachments){
-            $entry['enclosures']=array();
-            foreach($attachments as $attachment){
-                if ($attachment->isEnclosure()) {
-                    $enclosure=array();
-                    $enclosure['url']=$attachment->url;
-                    $enclosure['mimetype']=$attachment->mimetype;
-                    $enclosure['size']=$attachment->size;
-                    $entry['enclosures'][]=$enclosure;
-                }
-            }
-        }
-*/
-
-        // Tags/Categories
-        $tag = new Notice_tag();
-        $tag->notice_id = $notice->id;
-        if ($tag->find()) {
-            $entry['tags']=array();
-            while ($tag->fetch()) {
-                $entry['tags'][]=$tag->tag;
-            }
-        }
-        $tag->free();
-
-        // RSS Item specific
-        $entry['description'] = $entry['content'];
-        $entry['pubDate'] = common_date_rfc2822($notice->created);
-        $entry['guid'] = $entry['link'];
-
-        return $entry;
-    }
-
-    function twitter_rss_dmsg_array($message)
-    {
-
-        $entry = array();
-
-        $entry['title'] = sprintf('Message from %s to %s',
-            $message->getFrom()->nickname, $message->getTo()->nickname);
-
-        $entry['content'] = common_xml_safe_str(trim($message->content));
-        $entry['link'] = common_local_url('showmessage', array('message' => $message->id));
-        $entry['published'] = common_date_iso8601($message->created);
-
-        $taguribase = common_config('integration', 'taguri');
-
-        $entry['id'] = "tag:$taguribase,:$entry[link]";
-        $entry['updated'] = $entry['published'];
-        $entry['author'] = $message->getFrom()->getBestName();
-
-        # RSS Item specific
-        $entry['description'] = $entry['content'];
-        $entry['pubDate'] = common_date_rfc2822($message->created);
-        $entry['guid'] = $entry['link'];
-
-        return $entry;
-    }
-
-    function twitter_dmsg_array($message)
-    {
-        $twitter_dm = array();
-
-        $from_profile = $message->getFrom();
-        $to_profile = $message->getTo();
-
-        $twitter_dm['id'] = $message->id;
-        $twitter_dm['sender_id'] = $message->from_profile;
-        $twitter_dm['text'] = trim($message->content);
-        $twitter_dm['recipient_id'] = $message->to_profile;
-        $twitter_dm['created_at'] = $this->date_twitter($message->created);
-        $twitter_dm['sender_screen_name'] = $from_profile->nickname;
-        $twitter_dm['recipient_screen_name'] = $to_profile->nickname;
-        $twitter_dm['sender'] = $this->twitter_user_array($from_profile, false);
-        $twitter_dm['recipient'] = $this->twitter_user_array($to_profile, false);
-
-        return $twitter_dm;
-    }
-
-    function twitter_relationship_array($source, $target)
-    {
-        $relationship = array();
-
-        $relationship['source'] =
-            $this->relationship_details_array($source, $target);
-        $relationship['target'] =
-            $this->relationship_details_array($target, $source);
-
-        return array('relationship' => $relationship);
-    }
-
-    function relationship_details_array($source, $target)
-    {
-        $details = array();
-
-        $details['screen_name'] = $source->nickname;
-        $details['followed_by'] = $target->isSubscribed($source);
-        $details['following'] = $source->isSubscribed($target);
-
-        $notifications = false;
-
-        if ($source->isSubscribed($target)) {
-
-            $sub = Subscription::pkeyGet(array('subscriber' =>
-                $source->id, 'subscribed' => $target->id));
-
-            if (!empty($sub)) {
-                $notifications = ($sub->jabber || $sub->sms);
-            }
-        }
-
-        $details['notifications_enabled'] = $notifications;
-        $details['blocking'] = $source->hasBlocked($target);
-        $details['id'] = $source->id;
-
-        return $details;
-    }
-
-    function show_twitter_xml_relationship($relationship)
-    {
-        $this->elementStart('relationship');
-
-        foreach($relationship as $element => $value) {
-            if ($element == 'source' || $element == 'target') {
-                $this->elementStart($element);
-                $this->show_xml_relationship_details($value);
-                $this->elementEnd($element);
-            }
-        }
-
-        $this->elementEnd('relationship');
-    }
-
-    function show_xml_relationship_details($details)
-    {
-        foreach($details as $element => $value) {
-            $this->element($element, null, $value);
-        }
-    }
-
-    function show_twitter_xml_status($twitter_status)
-    {
-        $this->elementStart('status');
-        foreach($twitter_status as $element => $value) {
-            switch ($element) {
-            case 'user':
-                $this->show_twitter_xml_user($twitter_status['user']);
-                break;
-            case 'text':
-                $this->element($element, null, common_xml_safe_str($value));
-                break;
-            case 'attachments':
-                $this->show_xml_attachments($twitter_status['attachments']);
-                break;
-            default:
-                $this->element($element, null, $value);
-            }
-        }
-        $this->elementEnd('status');
-    }
-
-    function show_twitter_xml_group($twitter_group)
-    {
-        $this->elementStart('group');
-        foreach($twitter_group as $element => $value) {
-            $this->element($element, null, $value);
-        }
-        $this->elementEnd('group');
-    }
-
-    function show_twitter_xml_user($twitter_user, $role='user')
-    {
-        $this->elementStart($role);
-        foreach($twitter_user as $element => $value) {
-            if ($element == 'status') {
-                $this->show_twitter_xml_status($twitter_user['status']);
-            } else {
-                $this->element($element, null, $value);
-            }
-        }
-        $this->elementEnd($role);
-    }
-
-    function show_xml_attachments($attachments) {
-        if (!empty($attachments)) {
-            $this->elementStart('attachments', array('type' => 'array'));
-            foreach ($attachments as $attachment) {
-                $attrs = array();
-                $attrs['url'] = $attachment['url'];
-                $attrs['mimetype'] = $attachment['mimetype'];
-                $attrs['size'] = $attachment['size'];
-                $this->element('enclosure', $attrs, '');
-            }
-            $this->elementEnd('attachments');
-        }
-    }
-
-    function show_twitter_rss_item($entry)
-    {
-        $this->elementStart('item');
-        $this->element('title', null, $entry['title']);
-        $this->element('description', null, $entry['description']);
-        $this->element('pubDate', null, $entry['pubDate']);
-        $this->element('guid', null, $entry['guid']);
-        $this->element('link', null, $entry['link']);
-
-        # RSS only supports 1 enclosure per item
-        if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){
-            $enclosure = $entry['enclosures'][0];
-            $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null);
-        }
-
-        if(array_key_exists('tags', $entry)){
-            foreach($entry['tags'] as $tag){
-                $this->element('category', null,$tag);
-            }
-        }
-
-        $this->elementEnd('item');
-    }
-
-    function show_json_objects($objects)
-    {
-        print(json_encode($objects));
-    }
-
-    function show_single_xml_status($notice)
-    {
-        $this->init_document('xml');
-        $twitter_status = $this->twitter_status_array($notice);
-        $this->show_twitter_xml_status($twitter_status);
-        $this->end_document('xml');
-    }
-
-    function show_single_json_status($notice)
-    {
-        $this->init_document('json');
-        $status = $this->twitter_status_array($notice);
-        $this->show_json_objects($status);
-        $this->end_document('json');
-    }
-
-    function show_single_xml_dmsg($message)
-    {
-        $this->init_document('xml');
-        $dmsg = $this->twitter_dmsg_array($message);
-        $this->show_twitter_xml_dmsg($dmsg);
-        $this->end_document('xml');
-    }
-
-    function show_single_json_dmsg($message)
-    {
-        $this->init_document('json');
-        $dmsg = $this->twitter_dmsg_array($message);
-        $this->show_json_objects($dmsg);
-        $this->end_document('json');
-    }
-
-    function show_twitter_xml_dmsg($twitter_dm)
-    {
-        $this->elementStart('direct_message');
-        foreach($twitter_dm as $element => $value) {
-            switch ($element) {
-            case 'sender':
-            case 'recipient':
-                $this->show_twitter_xml_user($value, $element);
-                break;
-            case 'text':
-                $this->element($element, null, common_xml_safe_str($value));
-                break;
-            default:
-                $this->element($element, null, $value);
-            }
-        }
-        $this->elementEnd('direct_message');
-    }
-
-    function show_xml_timeline($notice)
-    {
-
-        $this->init_document('xml');
-        $this->elementStart('statuses', array('type' => 'array'));
-
-        if (is_array($notice)) {
-            foreach ($notice as $n) {
-                $twitter_status = $this->twitter_status_array($n);
-                $this->show_twitter_xml_status($twitter_status);
-            }
-        } else {
-            while ($notice->fetch()) {
-                $twitter_status = $this->twitter_status_array($notice);
-                $this->show_twitter_xml_status($twitter_status);
-            }
-        }
-
-        $this->elementEnd('statuses');
-        $this->end_document('xml');
-    }
-
-    function show_rss_timeline($notice, $title, $link, $subtitle, $suplink=null)
-    {
-
-        $this->init_document('rss');
-
-        $this->elementStart('channel');
-        $this->element('title', null, $title);
-        $this->element('link', null, $link);
-        if (!is_null($suplink)) {
-            # For FriendFeed's SUP protocol
-            $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
-                                         'rel' => 'http://api.friendfeed.com/2008/03#sup',
-                                         'href' => $suplink,
-                                         'type' => 'application/json'));
-        }
-        $this->element('description', null, $subtitle);
-        $this->element('language', null, 'en-us');
-        $this->element('ttl', null, '40');
-
-        if (is_array($notice)) {
-            foreach ($notice as $n) {
-                $entry = $this->twitter_rss_entry_array($n);
-                $this->show_twitter_rss_item($entry);
-            }
-        } else {
-            while ($notice->fetch()) {
-                $entry = $this->twitter_rss_entry_array($notice);
-                $this->show_twitter_rss_item($entry);
-            }
-        }
-
-        $this->elementEnd('channel');
-        $this->end_twitter_rss();
-    }
-
-    function show_atom_timeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null)
-    {
-
-        $this->init_document('atom');
-
-        $this->element('title', null, $title);
-        $this->element('id', null, $id);
-        $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
-
-        if (!is_null($suplink)) {
-            # For FriendFeed's SUP protocol
-            $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
-                                         'href' => $suplink,
-                                         'type' => 'application/json'));
-        }
-
-        if (!is_null($selfuri)) {
-            $this->element('link', array('href' => $selfuri,
-                'rel' => 'self', 'type' => 'application/atom+xml'), null);
-        }
-
-        $this->element('updated', null, common_date_iso8601('now'));
-        $this->element('subtitle', null, $subtitle);
-
-        if (is_array($notice)) {
-            foreach ($notice as $n) {
-                $this->raw($n->asAtomEntry());
-            }
-        } else {
-            while ($notice->fetch()) {
-                $this->raw($notice->asAtomEntry());
-            }
-        }
-
-        $this->end_document('atom');
-
-    }
-
-    function show_rss_groups($group, $title, $link, $subtitle)
-    {
-
-        $this->init_document('rss');
-
-        $this->elementStart('channel');
-        $this->element('title', null, $title);
-        $this->element('link', null, $link);
-        $this->element('description', null, $subtitle);
-        $this->element('language', null, 'en-us');
-        $this->element('ttl', null, '40');
-
-        if (is_array($group)) {
-            foreach ($group as $g) {
-                $twitter_group = $this->twitter_rss_group_array($g);
-                $this->show_twitter_rss_item($twitter_group);
-            }
-        } else {
-            while ($group->fetch()) {
-                $twitter_group = $this->twitter_rss_group_array($group);
-                $this->show_twitter_rss_item($twitter_group);
-            }
-        }
-
-        $this->elementEnd('channel');
-        $this->end_twitter_rss();
-    }
-
-    function show_atom_groups($group, $title, $id, $link, $subtitle=null, $selfuri=null)
-    {
-
-        $this->init_document('atom');
-
-        $this->element('title', null, $title);
-        $this->element('id', null, $id);
-        $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
-
-        if (!is_null($selfuri)) {
-            $this->element('link', array('href' => $selfuri,
-                'rel' => 'self', 'type' => 'application/atom+xml'), null);
-        }
-
-        $this->element('updated', null, common_date_iso8601('now'));
-        $this->element('subtitle', null, $subtitle);
-
-        if (is_array($group)) {
-            foreach ($group as $g) {
-                $this->raw($g->asAtomEntry());
-            }
-        } else {
-            while ($group->fetch()) {
-                $this->raw($group->asAtomEntry());
-            }
-        }
-
-        $this->end_document('atom');
-
-    }
-
-    function show_json_timeline($notice)
-    {
-
-        $this->init_document('json');
-
-        $statuses = array();
-
-        if (is_array($notice)) {
-            foreach ($notice as $n) {
-                $twitter_status = $this->twitter_status_array($n);
-                array_push($statuses, $twitter_status);
-            }
-        } else {
-            while ($notice->fetch()) {
-                $twitter_status = $this->twitter_status_array($notice);
-                array_push($statuses, $twitter_status);
-            }
-        }
-
-        $this->show_json_objects($statuses);
-
-        $this->end_document('json');
-    }
-
-    function show_json_groups($group)
-    {
-
-        $this->init_document('json');
-
-        $groups = array();
-
-        if (is_array($group)) {
-            foreach ($group as $g) {
-                $twitter_group = $this->twitter_group_array($g);
-                array_push($groups, $twitter_group);
-            }
-        } else {
-            while ($group->fetch()) {
-                $twitter_group = $this->twitter_group_array($group);
-                array_push($groups, $twitter_group);
-            }
-        }
-
-        $this->show_json_objects($groups);
-
-        $this->end_document('json');
-    }
-
-    function show_xml_groups($group)
-    {
-
-        $this->init_document('xml');
-        $this->elementStart('groups', array('type' => 'array'));
-
-        if (is_array($group)) {
-            foreach ($group as $g) {
-                $twitter_group = $this->twitter_group_array($g);
-                $this->show_twitter_xml_group($twitter_group);
-            }
-        } else {
-            while ($group->fetch()) {
-                $twitter_group = $this->twitter_group_array($group);
-                $this->show_twitter_xml_group($twitter_group);
-            }
-        }
-
-        $this->elementEnd('groups');
-        $this->end_document('xml');
-    }
-
-    function show_twitter_xml_users($user)
-    {
-
-        $this->init_document('xml');
-        $this->elementStart('users', array('type' => 'array'));
-
-        if (is_array($user)) {
-            foreach ($group as $g) {
-                $twitter_user = $this->twitter_user_array($g);
-                $this->show_twitter_xml_user($twitter_user,'user');
-            }
-        } else {
-            while ($user->fetch()) {
-                $twitter_user = $this->twitter_user_array($user);
-                $this->show_twitter_xml_user($twitter_user);
-            }
-        }
-
-        $this->elementEnd('users');
-        $this->end_document('xml');
-    }
-
-    function show_json_users($user)
-    {
-
-        $this->init_document('json');
-
-        $users = array();
-
-        if (is_array($user)) {
-            foreach ($user as $u) {
-                $twitter_user = $this->twitter_user_array($u);
-                array_push($users, $twitter_user);
-            }
-        } else {
-            while ($user->fetch()) {
-                $twitter_user = $this->twitter_user_array($user);
-                array_push($users, $twitter_user);
-            }
-        }
-
-        $this->show_json_objects($users);
-
-        $this->end_document('json');
-    }
-
-    function show_single_json_group($group)
-    {
-        $this->init_document('json');
-        $twitter_group = $this->twitter_group_array($group);
-        $this->show_json_objects($twitter_group);
-        $this->end_document('json');
-    }
-
-    function show_single_xml_group($group)
-    {
-        $this->init_document('xml');
-        $twitter_group = $this->twitter_group_array($group);
-        $this->show_twitter_xml_group($twitter_group);
-        $this->end_document('xml');
-    }
-
-    // Anyone know what date format this is?
-    // Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach
-    function date_twitter($dt)
-    {
-        $t = strtotime($dt);
-        return date("D M d H:i:s O Y", $t);
-    }
-
-    // XXX: Candidate for a general utility method somewhere?
-    function count_subscriptions($profile)
-    {
-
-        $count = 0;
-        $sub = new Subscription();
-        $sub->subscribed = $profile->id;
-
-        $count = $sub->find();
-
-        if ($count > 0) {
-            return $count - 1;
-        } else {
-            return 0;
-        }
-    }
-
-    function init_document($type='xml')
-    {
-        switch ($type) {
-        case 'xml':
-            header('Content-Type: application/xml; charset=utf-8');
-            $this->startXML();
-            break;
-        case 'json':
-            header('Content-Type: application/json; charset=utf-8');
-
-            // Check for JSONP callback
-            $callback = $this->arg('callback');
-            if ($callback) {
-                print $callback . '(';
-            }
-            break;
-        case 'rss':
-            header("Content-Type: application/rss+xml; charset=utf-8");
-            $this->init_twitter_rss();
-            break;
-        case 'atom':
-            header('Content-Type: application/atom+xml; charset=utf-8');
-            $this->init_twitter_atom();
-            break;
-        default:
-            $this->clientError(_('Not a supported data format.'));
-            break;
-        }
-
-        return;
-    }
-
-    function end_document($type='xml')
-    {
-        switch ($type) {
-        case 'xml':
-            $this->endXML();
-            break;
-        case 'json':
-
-            // Check for JSONP callback
-            $callback = $this->arg('callback');
-            if ($callback) {
-                print ')';
-            }
-            break;
-        case 'rss':
-            $this->end_twitter_rss();
-            break;
-        case 'atom':
-            $this->end_twitter_rss();
-            break;
-        default:
-            $this->clientError(_('Not a supported data format.'));
-            break;
-        }
-        return;
-    }
-
-    function clientError($msg, $code = 400, $content_type = 'json')
-    {
-        $action = $this->trimmed('action');
-
-        common_debug("User error '$code' on '$action': $msg", __FILE__);
-
-        if (!array_key_exists($code, ClientErrorAction::$status)) {
-            $code = 400;
-        }
-
-        $status_string = ClientErrorAction::$status[$code];
-
-        header('HTTP/1.1 '.$code.' '.$status_string);
-
-        if ($content_type == 'xml') {
-            $this->init_document('xml');
-            $this->elementStart('hash');
-            $this->element('error', null, $msg);
-            $this->element('request', null, $_SERVER['REQUEST_URI']);
-            $this->elementEnd('hash');
-            $this->end_document('xml');
-        } else {
-            $this->init_document('json');
-            $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
-            print(json_encode($error_array));
-            $this->end_document('json');
-        }
-
-    }
-
-    function serverError($msg, $code = 500, $content_type = 'json')
-    {
-        $action = $this->trimmed('action');
-
-        common_debug("Server error '$code' on '$action': $msg", __FILE__);
-
-        if (!array_key_exists($code, ServerErrorAction::$status)) {
-            $code = 400;
-        }
-
-        $status_string = ServerErrorAction::$status[$code];
-
-        header('HTTP/1.1 '.$code.' '.$status_string);
-
-        if ($content_type == 'xml') {
-            $this->init_document('xml');
-            $this->elementStart('hash');
-            $this->element('error', null, $msg);
-            $this->element('request', null, $_SERVER['REQUEST_URI']);
-            $this->elementEnd('hash');
-            $this->end_document('xml');
-        } else {
-            $this->init_document('json');
-            $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
-            print(json_encode($error_array));
-            $this->end_document('json');
-        }
-    }
-
-    function init_twitter_rss()
-    {
-        $this->startXML();
-        $this->elementStart('rss', array('version' => '2.0'));
-    }
-
-    function end_twitter_rss()
-    {
-        $this->elementEnd('rss');
-        $this->endXML();
-    }
-
-    function init_twitter_atom()
-    {
-        $this->startXML();
-        // FIXME: don't hardcode the language here!
-        $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom',
-                                          'xml:lang' => 'en-US',
-                                          'xmlns:thr' => 'http://purl.org/syndication/thread/1.0'));
-    }
-
-    function end_twitter_atom()
-    {
-        $this->elementEnd('feed');
-        $this->endXML();
-    }
-
-    function show_profile($profile, $content_type='xml', $notice=null, $includeStatuses=true)
-    {
-        $profile_array = $this->twitter_user_array($profile, $includeStatuses);
-        switch ($content_type) {
-        case 'xml':
-            $this->show_twitter_xml_user($profile_array);
-            break;
-        case 'json':
-            $this->show_json_objects($profile_array);
-            break;
-        default:
-            $this->clientError(_('Not a supported data format.'));
-            return;
-        }
-        return;
-    }
-
-    function get_user($id, $apidata=null)
-    {
-        if (empty($id)) {
-
-            // Twitter supports these other ways of passing the user ID
-            if (is_numeric($this->arg('id'))) {
-                return User::staticGet($this->arg('id'));
-            } else if ($this->arg('id')) {
-                $nickname = common_canonical_nickname($this->arg('id'));
-                return User::staticGet('nickname', $nickname);
-            } else if ($this->arg('user_id')) {
-                // This is to ensure that a non-numeric user_id still
-                // overrides screen_name even if it doesn't get used
-                if (is_numeric($this->arg('user_id'))) {
-                    return User::staticGet('id', $this->arg('user_id'));
-                }
-            } else if ($this->arg('screen_name')) {
-                $nickname = common_canonical_nickname($this->arg('screen_name'));
-                return User::staticGet('nickname', $nickname);
-            } else {
-                // Fall back to trying the currently authenticated user
-                return $apidata['user'];
-            }
-
-        } else if (is_numeric($id)) {
-            return User::staticGet($id);
-        } else {
-            $nickname = common_canonical_nickname($id);
-            return User::staticGet('nickname', $nickname);
-        }
-    }
-
-    function get_group($id, $apidata=null)
-    {
-        if (empty($id)) {
-
-            if (is_numeric($this->arg('id'))) {
-                return User_group::staticGet($this->arg('id'));
-            } else if ($this->arg('id')) {
-                $nickname = common_canonical_nickname($this->arg('id'));
-                return User_group::staticGet('nickname', $nickname);
-            } else if ($this->arg('group_id')) {
-                // This is to ensure that a non-numeric user_id still
-                // overrides screen_name even if it doesn't get used
-                if (is_numeric($this->arg('group_id'))) {
-                    return User_group::staticGet('id', $this->arg('group_id'));
-                }
-            } else if ($this->arg('group_name')) {
-                $nickname = common_canonical_nickname($this->arg('group_name'));
-                return User_group::staticGet('nickname', $nickname);
-            }
-
-        } else if (is_numeric($id)) {
-            return User_group::staticGet($id);
-        } else {
-            $nickname = common_canonical_nickname($id);
-            return User_group::staticGet('nickname', $nickname);
-        }
-    }
-
-    function get_profile($id)
-    {
-        if (is_numeric($id)) {
-            return Profile::staticGet($id);
-        } else {
-            $user = User::staticGet('nickname', $id);
-            if ($user) {
-                return $user->getProfile();
-            } else {
-                return null;
-            }
-        }
-    }
-
-    function source_link($source)
-    {
-        $source_name = _($source);
-        switch ($source) {
-        case 'web':
-        case 'xmpp':
-        case 'mail':
-        case 'omb':
-        case 'api':
-            break;
-        default:
-            $ns = Notice_source::staticGet($source);
-            if ($ns) {
-                $source_name = '<a href="' . $ns->url . '">' . $ns->name . '</a>';
-            }
-            break;
-        }
-        return $source_name;
-    }
-
-    /**
-     * Returns query argument or default value if not found. Certain
-     * parameters used throughout the API are lightly scrubbed and
-     * bounds checked.  This overrides Action::arg().
-     *
-     * @param string $key requested argument
-     * @param string $def default value to return if $key is not provided
-     *
-     * @return var $var
-     */
-    function arg($key, $def=null)
-    {
-
-        // XXX: Do even more input validation/scrubbing?
-
-        if (array_key_exists($key, $this->args)) {
-            switch($key) {
-            case 'page':
-                $page = (int)$this->args['page'];
-                return ($page < 1) ? 1 : $page;
-            case 'count':
-                $count = (int)$this->args['count'];
-                if ($count < 1) {
-                    return 20;
-                } elseif ($count > 200) {
-                    return 200;
-                } else {
-                    return $count;
-                }
-            case 'since_id':
-                $since_id = (int)$this->args['since_id'];
-                return ($since_id < 1) ? 0 : $since_id;
-            case 'max_id':
-                $max_id = (int)$this->args['max_id'];
-                return ($max_id < 1) ? 0 : $max_id;
-            case 'since':
-                return strtotime($this->args['since']);
-            default:
-                return parent::arg($key, $def);
-            }
-        } else {
-            return $def;
-        }
-    }
-
-}
index fd331fbdc90966289abd9d5c342eb34c8280adf0..1040d72fb6229d1bbc8a78c969bfd464eb0119e2 100644 (file)
@@ -36,8 +36,14 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
  *
  * @category Integration
  * @package  StatusNet
- * @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
+ * @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/
  *
  */
index e37fa05f0a6214c140ab71dab264c9f200702496..bad2b74ca324101388eaa6feae1e787e65c72892 100644 (file)
@@ -118,7 +118,7 @@ class TwitterOAuthClient extends OAuthClient
     }
 
     /**
-     * Calls Twitter's /stutuses/update API method
+     * Calls Twitter's /statuses/update API method
      *
      * @param string $status                text of the status
      * @param int    $in_reply_to_status_id optional id of the status it's
@@ -137,7 +137,7 @@ class TwitterOAuthClient extends OAuthClient
     }
 
     /**
-     * Calls Twitter's /stutuses/friends_timeline API method
+     * Calls Twitter's /statuses/friends_timeline API method
      *
      * @param int $since_id show statuses after this id
      * @param int $max_id   show statuses before this id
@@ -167,7 +167,7 @@ class TwitterOAuthClient extends OAuthClient
     }
 
     /**
-     * Calls Twitter's /stutuses/friends API method
+     * Calls Twitter's /statuses/friends API method
      *
      * @param int $id          id of the user whom you wish to see friends of
      * @param int $user_id     numerical user id
@@ -197,7 +197,7 @@ class TwitterOAuthClient extends OAuthClient
     }
 
     /**
-     * Calls Twitter's /stutuses/friends/ids API method
+     * Calls Twitter's /statuses/friends/ids API method
      *
      * @param int $id          id of the user whom you wish to see friends of
      * @param int $user_id     numerical user id
index 3cdad0b54a7473a1478571f6dc8dd44ef1f4c34f..6cfe5bcbd31e9c7d518f6f8929a76be52583e6ab 100644 (file)
@@ -39,7 +39,7 @@ class UnQueueManager
          case 'omb':
             if ($this->_isLocal($notice)) {
                 require_once(INSTALLDIR.'/lib/omb.php');
-                omb_broadcast_remote_subscribers($notice);
+                omb_broadcast_notice($notice);
             }
             break;
          case 'public':
@@ -72,8 +72,13 @@ class UnQueueManager
             require_once(INSTALLDIR.'/lib/jabber.php');
             jabber_broadcast_notice($notice);
             break;
+         case 'plugin':
+            Event::handle('HandleQueuedNotice', array(&$notice));
+            break;
          default:
-            throw ServerException("UnQueueManager: Unknown queue: $type");
+            if (Event::handle('UnqueueHandleNotice', array(&$notice, $queue))) {
+                throw ServerException("UnQueueManager: Unknown queue: $queue");
+            }
         }
     }
 
index d679d717f2fa4c8a078cc087fc78d0df239148a8..e9ae2bd50e424a27d5585fc2696c126f8df432ef 100644 (file)
@@ -401,10 +401,10 @@ function common_render_content($text, $notice)
 {
     $r = common_render_text($text);
     $id = $notice->profile_id;
-    $r = preg_replace('/(^|\s+)@([A-Za-z0-9]{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r);
+    $r = preg_replace('/(^|[\s\.\,\:\;]+)@([A-Za-z0-9]{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r);
     $r = preg_replace('/^T ([A-Z0-9]{1,64}) /e', "'T '.common_at_link($id, '\\1').' '", $r);
-    $r = preg_replace('/(^|\s+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r);
-    $r = preg_replace('/(^|\s)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r);
+    $r = preg_replace('/(^|[\s\.\,\:\;]+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r);
+    $r = preg_replace('/(^|[\s\.\,\:\;]+)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r);
     return $r;
 }
 
@@ -503,7 +503,7 @@ function callback_helper($matches, $callback, $notice_id) {
     }while($original_url!=$url);
 
     if(empty($notice_id)){
-        $result = call_user_func_array($callback,$url);
+        $result = call_user_func_array($callback, array($url));
     }else{
         $result = call_user_func_array($callback, array(array($url,$notice_id)) );
     }
@@ -532,21 +532,22 @@ function common_linkify($url) {
 
    if(strpos($url, '@') !== false && strpos($url, ':') === false) {
        //url is an email address without the mailto: protocol
-       return XMLStringer::estring('a', array('href' => "mailto:$url", 'rel' => 'external'), $url);
-   }
+       $canon = "mailto:$url";
+       $longurl = "mailto:$url";
+   }else{
 
-    $canon = File_redirection::_canonUrl($url);
+        $canon = File_redirection::_canonUrl($url);
 
-    $longurl_data = File_redirection::where($url);
-    if (is_array($longurl_data)) {
-        $longurl = $longurl_data['url'];
-    } elseif (is_string($longurl_data)) {
-        $longurl = $longurl_data;
-    } else {
-        throw new ServerException("Can't linkify url '$url'");
+        $longurl_data = File_redirection::where($canon);
+        if (is_array($longurl_data)) {
+            $longurl = $longurl_data['url'];
+        } elseif (is_string($longurl_data)) {
+            $longurl = $longurl_data;
+        } else {
+            throw new ServerException("Can't linkify url '$url'");
+        }
     }
-
-    $attrs = array('href' => $canon, 'rel' => 'external');
+    $attrs = array('href' => $canon, 'title' => $longurl, 'rel' => 'external');
 
     $is_attachment = false;
     $attachment_id = null;
@@ -594,7 +595,8 @@ function common_linkify($url) {
 
 function common_shorten_links($text)
 {
-    if (mb_strlen($text) <= 140) return $text;
+    $maxLength = Notice::maxContent();
+    if ($maxLength == 0 || mb_strlen($text) <= $maxLength) return $text;
     return common_replace_urls_callback($text, array('File_redirection', 'makeShort'));
 }
 
@@ -739,14 +741,10 @@ function common_relative_profile($sender, $nickname, $dt=null)
 
 function common_local_url($action, $args=null, $params=null, $fragment=null)
 {
-    static $sensitive = array('login', 'register', 'passwordsettings',
-                              'twittersettings', 'finishopenidlogin',
-                              'finishaddopenid', 'api');
-
     $r = Router::get();
     $path = $r->build($action, $args, $params, $fragment);
 
-    $ssl = in_array($action, $sensitive);
+    $ssl = common_is_sensitive($action);
 
     if (common_config('site','fancy')) {
         $url = common_path(mb_substr($path, 1), $ssl);
@@ -760,6 +758,19 @@ function common_local_url($action, $args=null, $params=null, $fragment=null)
     return $url;
 }
 
+function common_is_sensitive($action)
+{
+    static $sensitive = array('login', 'register', 'passwordsettings',
+                              'twittersettings', 'api');
+    $ssl = null;
+
+    if (Event::handle('SensitiveAction', array($action, &$ssl))) {
+        $ssl = in_array($action, $sensitive);
+    }
+
+    return $ssl;
+}
+
 function common_path($relative, $ssl=false)
 {
     $pathpart = (common_config('site', 'path')) ? common_config('site', 'path')."/" : '';
@@ -898,7 +909,8 @@ function common_enqueue_notice($notice)
                                     'twitter',
                                     'facebook',
                                     'ping');
-    static $allTransports = array('sms');
+
+    static $allTransports = array('sms', 'plugin');
 
     $transports = $allTransports;
 
@@ -916,11 +928,16 @@ function common_enqueue_notice($notice)
         }
     }
 
-    $qm = QueueManager::get();
+    if (Event::handle('StartEnqueueNotice', array($notice, &$transports))) {
+
+        $qm = QueueManager::get();
+
+        foreach ($transports as $transport)
+        {
+            $qm->enqueue($notice, $transport);
+        }
 
-    foreach ($transports as $transport)
-    {
-        $qm->enqueue($notice, $transport);
+        Event::handle('EndEnqueueNotice', array($notice, $transports));
     }
 
     return true;
@@ -1158,7 +1175,7 @@ function common_negotiate_type($cprefs, $sprefs)
     }
 
     if ('text/html' === $besttype) {
-        return "text/html";
+        return "text/html; charset=utf-8";
     }
     return $besttype;
 }
@@ -1166,7 +1183,8 @@ function common_negotiate_type($cprefs, $sprefs)
 function common_config($main, $sub)
 {
     global $config;
-    return isset($config[$main][$sub]) ? $config[$main][$sub] : false;
+    return (array_key_exists($main, $config) &&
+            array_key_exists($sub, $config[$main])) ? $config[$main][$sub] : false;
 }
 
 function common_copy_args($from)
@@ -1373,57 +1391,19 @@ function common_shorten_url($long_url)
     } else {
         $svc = $user->urlshorteningservice;
     }
-
-    $curlh = curl_init();
-    curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 20); // # seconds to wait
-    curl_setopt($curlh, CURLOPT_USERAGENT, 'StatusNet');
-    curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
-
-    switch($svc) {
-     case 'ur1.ca':
-        require_once INSTALLDIR.'/lib/Shorturl_api.php';
-        $short_url_service = new LilUrl;
-        $short_url = $short_url_service->shorten($long_url);
-        break;
-
-     case '2tu.us':
-        $short_url_service = new TightUrl;
-        require_once INSTALLDIR.'/lib/Shorturl_api.php';
-        $short_url = $short_url_service->shorten($long_url);
-        break;
-
-     case 'ptiturl.com':
-        require_once INSTALLDIR.'/lib/Shorturl_api.php';
-        $short_url_service = new PtitUrl;
-        $short_url = $short_url_service->shorten($long_url);
-        break;
-
-     case 'bit.ly':
-        curl_setopt($curlh, CURLOPT_URL, 'http://bit.ly/api?method=shorten&long_url='.urlencode($long_url));
-        $short_url = current(json_decode(curl_exec($curlh))->results)->hashUrl;
-        break;
-
-     case 'is.gd':
-        curl_setopt($curlh, CURLOPT_URL, 'http://is.gd/api.php?longurl='.urlencode($long_url));
-        $short_url = curl_exec($curlh);
-        break;
-     case 'snipr.com':
-        curl_setopt($curlh, CURLOPT_URL, 'http://snipr.com/site/snip?r=simple&link='.urlencode($long_url));
-        $short_url = curl_exec($curlh);
-        break;
-     case 'metamark.net':
-        curl_setopt($curlh, CURLOPT_URL, 'http://metamark.net/api/rest/simple?long_url='.urlencode($long_url));
-        $short_url = curl_exec($curlh);
-        break;
-     case 'tinyurl.com':
-        curl_setopt($curlh, CURLOPT_URL, 'http://tinyurl.com/api-create.php?url='.urlencode($long_url));
-        $short_url = curl_exec($curlh);
-        break;
-     default:
-        $short_url = false;
+    global $_shorteners;
+    if (!isset($_shorteners[$svc])) {
+        //the user selected service doesn't exist, so default to ur1.ca
+        $svc = 'ur1.ca';
+    }
+    if (!isset($_shorteners[$svc])) {
+       // no shortener plugins installed.
+       return $long_url;
     }
 
-    curl_close($curlh);
+    $reflectionObj = new ReflectionClass($_shorteners[$svc]['callInfo'][0]);
+    $short_url_service = $reflectionObj->newInstanceArgs($_shorteners[$svc]['callInfo'][1]);
+    $short_url = $short_url_service->shorten($long_url);
 
     return $short_url;
 }
index ff74aade45b9301b1934afc45392dd207fc4151a..b31f69d81a56262d4080772fe8898ad4ea0b6b9c 100644 (file)
@@ -102,7 +102,7 @@ class FBConnectPlugin extends Plugin
 
     // Note: this script needs to appear in the <body>
 
-    function onStartShowHeader($action)
+    function onEndShowScripts($action)
     {
         if ($this->reqFbScripts($action)) {
 
diff --git a/plugins/LilUrl/LilUrlPlugin.php b/plugins/LilUrl/LilUrlPlugin.php
new file mode 100644 (file)
index 0000000..7665b6c
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin to push RSS/Atom updates to a PubSubHubBub hub
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Plugin
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @copyright 2009 Craig Andrews http://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/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once(INSTALLDIR.'/lib/Shorturl_api.php');
+
+class LilUrlPlugin extends Plugin
+{
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    function onInitializePlugin(){
+        $this->registerUrlShortener(
+            'ur1.ca',
+            array('freeService'=>true),
+            array('LilUrl',array('http://ur1.ca/'))
+        );
+    }
+}
+
+class LilUrl extends ShortUrlApi
+{
+    protected function shorten_imp($url) {
+        $data['longurl'] = $url;
+        $response = $this->http_post($data);
+        if (!$response) return $url;
+        $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'];
+        return $url;
+    }
+}
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
new file mode 100644 (file)
index 0000000..a933a11
--- /dev/null
@@ -0,0 +1,240 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Plugin
+ * @package   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 for OpenID authentication and identity
+ *
+ * This class enables consumer support for OpenID, the distributed authentication
+ * and identity system.
+ *
+ * @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/
+ * @link     http://openid.net/
+ */
+
+class OpenIDPlugin extends Plugin
+{
+    /**
+     * Initializer for the plugin.
+     */
+
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Add OpenID-related paths to the router table
+     *
+     * Hook for RouterInitialized event.
+     *
+     * @return boolean hook return
+     */
+
+    function onRouterInitialized(&$m)
+    {
+        $m->connect('main/openid', array('action' => 'openidlogin'));
+        $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'));
+
+        return true;
+    }
+
+    function onEndLoginGroupNav(&$action)
+    {
+        $action_name = $action->trimmed('action');
+
+        $action->menuItem(common_local_url('openidlogin'),
+                          _('OpenID'),
+                          _('Login or register with OpenID'),
+                          $action_name === 'openidlogin');
+
+        return true;
+    }
+
+    function onEndAccountSettingsNav(&$action)
+    {
+        $action_name = $action->trimmed('action');
+
+        $action->menuItem(common_local_url('openidsettings'),
+                          _('OpenID'),
+                          _('Add or remove OpenIDs'),
+                          $action_name === 'openidsettings');
+
+        return true;
+    }
+
+    function onAutoload($cls)
+    {
+        switch ($cls)
+        {
+         case 'OpenidloginAction':
+         case 'FinishopenidloginAction':
+         case 'FinishaddopenidAction':
+         case 'XrdsAction':
+         case 'PublicxrdsAction':
+         case 'OpenidsettingsAction':
+            require_once(INSTALLDIR.'/plugins/OpenID/' . strtolower(mb_substr($cls, 0, -6)) . '.php');
+            return false;
+         case 'User_openid':
+            require_once(INSTALLDIR.'/plugins/OpenID/User_openid.php');
+            return false;
+         default:
+            return true;
+        }
+    }
+
+    function onSensitiveAction($action, &$ssl)
+    {
+        switch ($action)
+        {
+         case 'finishopenidlogin':
+         case 'finishaddopenid':
+            $ssl = true;
+            return false;
+         default:
+            return true;
+        }
+    }
+
+    function onLoginAction($action, &$login)
+    {
+        switch ($action)
+        {
+         case 'openidlogin':
+         case 'finishopenidlogin':
+            $login = true;
+            return false;
+         default:
+            return true;
+        }
+    }
+
+    /**
+     * We include a <meta> element linking to the publicxrds page, for OpenID
+     * client-side authentication.
+     *
+     * @return void
+     */
+
+    function onEndHeadChildren($action)
+    {
+        // for client side of OpenID authentication
+        $action->element('meta', array('http-equiv' => 'X-XRDS-Location',
+                                       'content' => common_local_url('publicxrds')));
+    }
+
+    /**
+     * Redirect to OpenID login if they have an OpenID
+     *
+     * @return boolean whether to continue
+     */
+
+    function onRedirectToLogin($action, $user)
+    {
+        if (!empty($user) && User_openid::hasOpenID($user->id)) {
+            common_redirect(common_local_url('openidlogin'), 303);
+            return false;
+        }
+        return true;
+    }
+
+    function onEndShowPageNotice($action)
+    {
+        $name = $action->trimmed('action');
+
+        switch ($name)
+        {
+         case 'register':
+            $instr = '(Have an [OpenID](http://openid.net/)? ' .
+              'Try our [OpenID registration]'.
+              '(%%action.openidlogin%%)!)';
+            break;
+         case 'login':
+            $instr = '(Have an [OpenID](http://openid.net/)? ' .
+              'Try our [OpenID login]'.
+              '(%%action.openidlogin%%)!)';
+            break;
+         default:
+            return true;
+        }
+
+        $output = common_markup_to_html($instr);
+        $action->raw($output);
+        return true;
+    }
+
+    function onStartLoadDoc(&$title, &$output)
+    {
+        if ($title == 'openid')
+        {
+            $filename = INSTALLDIR.'/plugins/OpenID/doc-src/openid';
+
+            $c = file_get_contents($filename);
+            $output = common_markup_to_html($c);
+            return false; // success!
+        }
+
+        return true;
+    }
+
+    function onEndLoadDoc($title, &$output)
+    {
+        if ($title == 'help')
+        {
+            $menuitem = '* [OpenID](%%doc.openid%%) - what OpenID is and how to use it with this service';
+
+            $output .= common_markup_to_html($menuitem);
+        }
+
+        return true;
+    }
+
+    function onCheckSchema() {
+        $schema = Schema::get();
+        $schema->ensureTable('user_openid',
+                             array(new ColumnDef('canonical', 'varchar',
+                                                 '255', false, 'PRI'),
+                                   new ColumnDef('display', 'varchar',
+                                                 '255', false),
+                                   new ColumnDef('user_id', 'integer',
+                                                 null, false, 'MUL'),
+                                   new ColumnDef('created', 'datetime',
+                                                 null, false),
+                                   new ColumnDef('modified', 'timestamp')));
+        return true;
+    }
+}
diff --git a/plugins/OpenID/User_openid.php b/plugins/OpenID/User_openid.php
new file mode 100644 (file)
index 0000000..338e0f6
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Table Definition for user_openid
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class User_openid extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'user_openid';                     // table name
+    public $canonical;                       // varchar(255)  primary_key not_null
+    public $display;                         // varchar(255)  unique_key not_null
+    public $user_id;                         // int(4)   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',$k,$v); }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+
+    static function hasOpenID($user_id)
+    {
+        $oid = new User_openid();
+
+        $oid->user_id = $user_id;
+
+        $cnt = $oid->find();
+
+        return ($cnt > 0);
+    }
+}
diff --git a/plugins/OpenID/doc-src/openid b/plugins/OpenID/doc-src/openid
new file mode 100644 (file)
index 0000000..c741e36
--- /dev/null
@@ -0,0 +1,11 @@
+%%site.name%% supports the [OpenID](http://openid.net/) standard for single signon between Web sites. OpenID lets you log into many different Web sites without using a different password for each. (See [Wikipedia's OpenID article](http://en.wikipedia.org/wiki/OpenID) for more information.)
+
+If you already have an account on %%site.name%%, you can [login](%%action.login%%) with your username and password as usual.
+To use OpenID in the future, you can [add an OpenID to your account](%%action.openidsettings%%) after you have logged in normally.
+
+There are many [Public OpenID providers](http://wiki.openid.net/Public_OpenID_providers), and you may already have an OpenID-enabled account on another service.
+
+* On wikis: If you have an account on an OpenID-enabled wiki, like [Wikitravel](http://wikitravel.org/), [wikiHow](http://www.wikihow.com/), [Vinismo](http://vinismo.com/), [AboutUs](http://aboutus.org/) or [Keiki](http://kei.ki/), you can log in to %%site.name%% by entering the **full URL** of your user page on that other wiki in the box above. For example, *http://kei.ki/en/User:Evan*.
+* [Yahoo!](http://openid.yahoo.com/) : If you have an account with Yahoo!, you can log in to this site by entering your Yahoo!-provided OpenID in the box above. Yahoo! OpenID URLs have the form *https://me.yahoo.com/yourusername*.
+* [AOL](http://dev.aol.com/aol-and-63-million-openids) : If you have an account with [AOL](http://www.aol.com/), like an [AIM](http://www.aim.com/) account, you can log in to %%site.name%% by entering your AOL-provided OpenID in the box above. AOL OpenID URLs have the form *http://openid.aol.com/yourusername*. Your username should be all lowercase, no spaces.
+* [Blogger](http://bloggerindraft.blogspot.com/2008/01/new-feature-blogger-as-openid-provider.html), [Wordpress.com](http://faq.wordpress.com/2007/03/06/what-is-openid/), [LiveJournal](http://www.livejournal.com/openid/about.bml), [Vox](http://bradfitz.vox.com/library/post/openid-for-vox.html) : If you have a blog on any of these services, enter your blog URL in the box above. For example, *http://yourusername.blogspot.com/*, *http://yourusername.wordpress.com/*, *http://yourusername.livejournal.com/*, or *http://yourusername.vox.com/*.
diff --git a/plugins/OpenID/finishaddopenid.php b/plugins/OpenID/finishaddopenid.php
new file mode 100644 (file)
index 0000000..6e88920
--- /dev/null
@@ -0,0 +1,185 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Complete adding an 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    Evan Prodromou <evan@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.'/plugins/OpenID/openid.php';
+
+/**
+ * Complete adding an OpenID
+ *
+ * Handle the return from an OpenID verification
+ *
+ * @category Settings
+ * @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 FinishaddopenidAction extends Action
+{
+    var $msg = null;
+
+    /**
+     * Handle the redirect back from OpenID confirmation
+     *
+     * Check to see if the user's logged in, and then try
+     * to use the OpenID login system.
+     *
+     * @param array $args $_REQUEST arguments
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        if (!common_logged_in()) {
+            $this->clientError(_('Not logged in.'));
+        } else {
+            $this->tryLogin();
+        }
+    }
+
+    /**
+     * Try to log in using OpenID
+     *
+     * Check the OpenID for validity; potentially store it.
+     *
+     * @return void
+     */
+
+    function tryLogin()
+    {
+        $consumer =& oid_consumer();
+
+        $response = $consumer->complete(common_local_url('finishaddopenid'));
+
+        if ($response->status == Auth_OpenID_CANCEL) {
+            $this->message(_('OpenID authentication cancelled.'));
+            return;
+        } else if ($response->status == Auth_OpenID_FAILURE) {
+            // Authentication failed; display the error message.
+            $this->message(sprintf(_('OpenID authentication failed: %s'),
+                                   $response->message));
+        } else if ($response->status == Auth_OpenID_SUCCESS) {
+
+            $display   = $response->getDisplayIdentifier();
+            $canonical = ($response->endpoint && $response->endpoint->canonicalID) ?
+              $response->endpoint->canonicalID : $display;
+
+            $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
+
+            if ($sreg_resp) {
+                $sreg = $sreg_resp->contents();
+            }
+
+            $cur =& common_current_user();
+
+            $other = oid_get_user($canonical);
+
+            if ($other) {
+                if ($other->id == $cur->id) {
+                    $this->message(_('You already have this OpenID!'));
+                } else {
+                    $this->message(_('Someone else already has this OpenID.'));
+                }
+                return;
+            }
+
+            // start a transaction
+
+            $cur->query('BEGIN');
+
+            $result = oid_link_user($cur->id, $canonical, $display);
+
+            if (!$result) {
+                $this->message(_('Error connecting user.'));
+                return;
+            }
+            if ($sreg) {
+                if (!oid_update_user($cur, $sreg)) {
+                    $this->message(_('Error updating profile'));
+                    return;
+                }
+            }
+
+            // success!
+
+            $cur->query('COMMIT');
+
+            oid_set_last($display);
+
+            common_redirect(common_local_url('openidsettings'), 303);
+        }
+    }
+
+    /**
+     * Show a failure message
+     *
+     * Something went wrong. Save the message, and show the page.
+     *
+     * @param string $msg Error message to show
+     *
+     * @return void
+     */
+
+    function message($msg)
+    {
+        $this->message = $msg;
+        $this->showPage();
+    }
+
+    /**
+     * Title of the page
+     *
+     * @return string title
+     */
+
+    function title()
+    {
+        return _('OpenID Login');
+    }
+
+    /**
+     * Show error message
+     *
+     * @return void
+     */
+
+    function showPageNotice()
+    {
+        if ($this->message) {
+            $this->element('p', 'error', $this->message);
+        }
+    }
+}
diff --git a/plugins/OpenID/finishopenidlogin.php b/plugins/OpenID/finishopenidlogin.php
new file mode 100644 (file)
index 0000000..50a9c15
--- /dev/null
@@ -0,0 +1,495 @@
+<?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';
+
+class FinishopenidloginAction extends Action
+{
+    var $error = null;
+    var $username = null;
+    var $message = null;
+
+    function handle($args)
+    {
+        parent::handle($args);
+        if (common_is_real_login()) {
+            $this->clientError(_('Already logged in.'));
+        } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+            $token = $this->trimmed('token');
+            if (!$token || $token != common_session_token()) {
+                $this->showForm(_('There was a problem with your session token. Try again, please.'));
+                return;
+            }
+            if ($this->arg('create')) {
+                if (!$this->boolean('license')) {
+                    $this->showForm(_('You can\'t register if you don\'t agree to the license.'),
+                                    $this->trimmed('newname'));
+                    return;
+                }
+                $this->createNewUser();
+            } else if ($this->arg('connect')) {
+                $this->connectUser();
+            } else {
+                common_debug(print_r($this->args, true), __FILE__);
+                $this->showForm(_('Something weird happened.'),
+                                $this->trimmed('newname'));
+            }
+        } else {
+            $this->tryLogin();
+        }
+    }
+
+    function showPageNotice()
+    {
+        if ($this->error) {
+            $this->element('div', array('class' => 'error'), $this->error);
+        } else {
+            $this->element('div', 'instructions',
+                           sprintf(_('This is the first time you\'ve logged into %s so we must connect your OpenID to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name')));
+        }
+    }
+
+    function title()
+    {
+        return _('OpenID Account Setup');
+    }
+
+    function showForm($error=null, $username=null)
+    {
+        $this->error = $error;
+        $this->username = $username;
+
+        $this->showPage();
+    }
+
+    function showContent()
+    {
+        if (!empty($this->message_text)) {
+            $this->element('div', array('class' => 'error'), $this->message_text);
+            return;
+        }
+
+        $this->elementStart('form', array('method' => 'post',
+                                          'id' => 'account_connect',
+                                          'action' => common_local_url('finishopenidlogin')));
+        $this->hidden('token', common_session_token());
+        $this->element('h2', null,
+                       _('Create new account'));
+        $this->element('p', null,
+                       _('Create a new user with this nickname.'));
+        $this->input('newname', _('New nickname'),
+                     ($this->username) ? $this->username : '',
+                     _('1-64 lowercase letters or numbers, no punctuation or spaces'));
+        $this->elementStart('p');
+        $this->element('input', array('type' => 'checkbox',
+                                      'id' => 'license',
+                                      'name' => 'license',
+                                      'value' => 'true'));
+        $this->text(_('My text and files are available under '));
+        $this->element('a', array('href' => common_config('license', 'url')),
+                       common_config('license', 'title'));
+        $this->text(_(' except this private data: password, email address, IM address, phone number.'));
+        $this->elementEnd('p');
+        $this->submit('create', _('Create'));
+        $this->element('h2', null,
+                       _('Connect existing account'));
+        $this->element('p', null,
+                       _('If you already have an account, login with your username and password to connect it to your OpenID.'));
+        $this->input('nickname', _('Existing nickname'));
+        $this->password('password', _('Password'));
+        $this->submit('connect', _('Connect'));
+        $this->elementEnd('form');
+    }
+
+    function tryLogin()
+    {
+        $consumer = oid_consumer();
+
+        $response = $consumer->complete(common_local_url('finishopenidlogin'));
+
+        if ($response->status == Auth_OpenID_CANCEL) {
+            $this->message(_('OpenID authentication cancelled.'));
+            return;
+        } else if ($response->status == Auth_OpenID_FAILURE) {
+            // Authentication failed; display the error message.
+            $this->message(sprintf(_('OpenID authentication failed: %s'), $response->message));
+        } else if ($response->status == Auth_OpenID_SUCCESS) {
+            // This means the authentication succeeded; extract the
+            // identity URL and Simple Registration data (if it was
+            // returned).
+            $display = $response->getDisplayIdentifier();
+            $canonical = ($response->endpoint->canonicalID) ?
+              $response->endpoint->canonicalID : $response->getDisplayIdentifier();
+
+            $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
+
+            if ($sreg_resp) {
+                $sreg = $sreg_resp->contents();
+            }
+
+            $user = oid_get_user($canonical);
+
+            if ($user) {
+                oid_set_last($display);
+                # XXX: commented out at @edd's request until better
+                # control over how data flows from OpenID provider.
+                # oid_update_user($user, $sreg);
+                common_set_user($user);
+                common_real_login(true);
+                if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
+                    common_rememberme($user);
+                }
+                unset($_SESSION['openid_rememberme']);
+                $this->goHome($user->nickname);
+            } else {
+                $this->saveValues($display, $canonical, $sreg);
+                $this->showForm(null, $this->bestNewNickname($display, $sreg));
+            }
+        }
+    }
+
+    function message($msg)
+    {
+        $this->message_text = $msg;
+        $this->showPage();
+    }
+
+    function saveValues($display, $canonical, $sreg)
+    {
+        common_ensure_session();
+        $_SESSION['openid_display'] = $display;
+        $_SESSION['openid_canonical'] = $canonical;
+        $_SESSION['openid_sreg'] = $sreg;
+    }
+
+    function getSavedValues()
+    {
+        return array($_SESSION['openid_display'],
+                     $_SESSION['openid_canonical'],
+                     $_SESSION['openid_sreg']);
+    }
+
+    function createNewUser()
+    {
+        # FIXME: save invite code before redirect, and check here
+
+        if (common_config('site', 'closed')) {
+            $this->clientError(_('Registration not allowed.'));
+            return;
+        }
+
+        $invite = null;
+
+        if (common_config('site', 'inviteonly')) {
+            $code = $_SESSION['invitecode'];
+            if (empty($code)) {
+                $this->clientError(_('Registration not allowed.'));
+                return;
+            }
+
+            $invite = Invitation::staticGet($code);
+
+            if (empty($invite)) {
+                $this->clientError(_('Not a valid invitation code.'));
+                return;
+            }
+        }
+
+        $nickname = $this->trimmed('newname');
+
+        if (!Validate::string($nickname, array('min_length' => 1,
+                                               'max_length' => 64,
+                                               'format' => NICKNAME_FMT))) {
+            $this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
+            return;
+        }
+
+        if (!User::allowed_nickname($nickname)) {
+            $this->showForm(_('Nickname not allowed.'));
+            return;
+        }
+
+        if (User::staticGet('nickname', $nickname)) {
+            $this->showForm(_('Nickname already in use. Try another one.'));
+            return;
+        }
+
+        list($display, $canonical, $sreg) = $this->getSavedValues();
+
+        if (!$display || !$canonical) {
+            $this->serverError(_('Stored OpenID not found.'));
+            return;
+        }
+
+        # Possible race condition... let's be paranoid
+
+        $other = oid_get_user($canonical);
+
+        if ($other) {
+            $this->serverError(_('Creating new account for OpenID that already has a user.'));
+            return;
+        }
+
+        $location = '';
+        if (!empty($sreg['country'])) {
+            if ($sreg['postcode']) {
+                # XXX: use postcode to get city and region
+                # XXX: also, store postcode somewhere -- it's valuable!
+                $location = $sreg['postcode'] . ', ' . $sreg['country'];
+            } else {
+                $location = $sreg['country'];
+            }
+        }
+
+        if (!empty($sreg['fullname']) && mb_strlen($sreg['fullname']) <= 255) {
+            $fullname = $sreg['fullname'];
+        } else {
+            $fullname = '';
+        }
+
+        if (!empty($sreg['email']) && Validate::email($sreg['email'], true)) {
+            $email = $sreg['email'];
+        } else {
+            $email = '';
+        }
+
+        # XXX: add language
+        # XXX: add timezone
+
+        $args = array('nickname' => $nickname,
+                      'email' => $email,
+                      'fullname' => $fullname,
+                      'location' => $location);
+
+        if (!empty($invite)) {
+            $args['code'] = $invite->code;
+        }
+
+        $user = User::register($args);
+
+        $result = oid_link_user($user->id, $canonical, $display);
+
+        oid_set_last($display);
+        common_set_user($user);
+        common_real_login(true);
+        if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
+            common_rememberme($user);
+        }
+        unset($_SESSION['openid_rememberme']);
+        common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
+                        303);
+    }
+
+    function connectUser()
+    {
+        $nickname = $this->trimmed('nickname');
+        $password = $this->trimmed('password');
+
+        if (!common_check_user($nickname, $password)) {
+            $this->showForm(_('Invalid username or password.'));
+            return;
+        }
+
+        # They're legit!
+
+        $user = User::staticGet('nickname', $nickname);
+
+        list($display, $canonical, $sreg) = $this->getSavedValues();
+
+        if (!$display || !$canonical) {
+            $this->serverError(_('Stored OpenID not found.'));
+            return;
+        }
+
+        $result = oid_link_user($user->id, $canonical, $display);
+
+        if (!$result) {
+            $this->serverError(_('Error connecting user to OpenID.'));
+            return;
+        }
+
+        oid_update_user($user, $sreg);
+        oid_set_last($display);
+        common_set_user($user);
+        common_real_login(true);
+        if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
+            common_rememberme($user);
+        }
+        unset($_SESSION['openid_rememberme']);
+        $this->goHome($user->nickname);
+    }
+
+    function goHome($nickname)
+    {
+        $url = common_get_returnto();
+        if ($url) {
+            # We don't have to return to it again
+            common_set_returnto(null);
+        } else {
+            $url = common_local_url('all',
+                                    array('nickname' =>
+                                          $nickname));
+        }
+        common_redirect($url, 303);
+    }
+
+    function bestNewNickname($display, $sreg)
+    {
+
+        # Try the passed-in nickname
+
+        if (!empty($sreg['nickname'])) {
+            $nickname = $this->nicknamize($sreg['nickname']);
+            if ($this->isNewNickname($nickname)) {
+                return $nickname;
+            }
+        }
+
+        # Try the full name
+
+        if (!empty($sreg['fullname'])) {
+            $fullname = $this->nicknamize($sreg['fullname']);
+            if ($this->isNewNickname($fullname)) {
+                return $fullname;
+            }
+        }
+
+        # Try the URL
+
+        $from_url = $this->openidToNickname($display);
+
+        if ($from_url && $this->isNewNickname($from_url)) {
+            return $from_url;
+        }
+
+        # XXX: others?
+
+        return null;
+    }
+
+    function isNewNickname($str)
+    {
+        if (!Validate::string($str, array('min_length' => 1,
+                                          'max_length' => 64,
+                                          'format' => NICKNAME_FMT))) {
+            return false;
+        }
+        if (!User::allowed_nickname($str)) {
+            return false;
+        }
+        if (User::staticGet('nickname', $str)) {
+            return false;
+        }
+        return true;
+    }
+
+    function openidToNickname($openid)
+    {
+        if (Auth_Yadis_identifierScheme($openid) == 'XRI') {
+            return $this->xriToNickname($openid);
+        } else {
+            return $this->urlToNickname($openid);
+        }
+    }
+
+    # We try to use an OpenID URL as a legal StatusNet user name in this order
+    # 1. Plain hostname, like http://evanp.myopenid.com/
+    # 2. One element in path, like http://profile.typekey.com/EvanProdromou/
+    #    or http://getopenid.com/evanprodromou
+
+    function urlToNickname($openid)
+    {
+        static $bad = array('query', 'user', 'password', 'port', 'fragment');
+
+        $parts = parse_url($openid);
+
+        # If any of these parts exist, this won't work
+
+        foreach ($bad as $badpart) {
+            if (array_key_exists($badpart, $parts)) {
+                return null;
+            }
+        }
+
+        # We just have host and/or path
+
+        # If it's just a host...
+        if (array_key_exists('host', $parts) &&
+            (!array_key_exists('path', $parts) || strcmp($parts['path'], '/') == 0))
+        {
+            $hostparts = explode('.', $parts['host']);
+
+            # Try to catch common idiom of nickname.service.tld
+
+            if ((count($hostparts) > 2) &&
+                (strlen($hostparts[count($hostparts) - 2]) > 3) && # try to skip .co.uk, .com.au
+                (strcmp($hostparts[0], 'www') != 0))
+            {
+                return $this->nicknamize($hostparts[0]);
+            } else {
+                # Do the whole hostname
+                return $this->nicknamize($parts['host']);
+            }
+        } else {
+            if (array_key_exists('path', $parts)) {
+                # Strip starting, ending slashes
+                $path = preg_replace('@/$@', '', $parts['path']);
+                $path = preg_replace('@^/@', '', $path);
+                if (strpos($path, '/') === false) {
+                    return $this->nicknamize($path);
+                }
+            }
+        }
+
+        return null;
+    }
+
+    function xriToNickname($xri)
+    {
+        $base = $this->xriBase($xri);
+
+        if (!$base) {
+            return null;
+        } else {
+            # =evan.prodromou
+            # or @gratis*evan.prodromou
+            $parts = explode('*', substr($base, 1));
+            return $this->nicknamize(array_pop($parts));
+        }
+    }
+
+    function xriBase($xri)
+    {
+        if (substr($xri, 0, 6) == 'xri://') {
+            return substr($xri, 6);
+        } else {
+            return $xri;
+        }
+    }
+
+    # Given a string, try to make it work as a nickname
+
+    function nicknamize($str)
+    {
+        $str = preg_replace('/\W/', '', $str);
+        return strtolower($str);
+    }
+}
diff --git a/plugins/OpenID/openid.php b/plugins/OpenID/openid.php
new file mode 100644 (file)
index 0000000..0944117
--- /dev/null
@@ -0,0 +1,280 @@
+<?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/User_openid.php');
+
+require_once('Auth/OpenID.php');
+require_once('Auth/OpenID/Consumer.php');
+require_once('Auth/OpenID/SReg.php');
+require_once('Auth/OpenID/MySQLStore.php');
+
+# About one year cookie expiry
+
+define('OPENID_COOKIE_EXPIRY', round(365.25 * 24 * 60 * 60));
+define('OPENID_COOKIE_KEY', 'lastusedopenid');
+
+function oid_store()
+{
+    static $store = null;
+    if (!$store) {
+        # Can't be called statically
+        $user = new User();
+        $conn = $user->getDatabaseConnection();
+        $store = new Auth_OpenID_MySQLStore($conn);
+    }
+    return $store;
+}
+
+function oid_consumer()
+{
+    $store = oid_store();
+    $consumer = new Auth_OpenID_Consumer($store);
+    return $consumer;
+}
+
+function oid_clear_last()
+{
+    oid_set_last('');
+}
+
+function oid_set_last($openid_url)
+{
+    common_set_cookie(OPENID_COOKIE_KEY,
+                     $openid_url,
+                     time() + OPENID_COOKIE_EXPIRY);
+}
+
+function oid_get_last()
+{
+    if (empty($_COOKIE[OPENID_COOKIE_KEY])) {
+        return null;
+    }
+    $openid_url = $_COOKIE[OPENID_COOKIE_KEY];
+    if ($openid_url && strlen($openid_url) > 0) {
+        return $openid_url;
+    } else {
+        return null;
+    }
+}
+
+function oid_link_user($id, $canonical, $display)
+{
+
+    $oid = new User_openid();
+    $oid->user_id = $id;
+    $oid->canonical = $canonical;
+    $oid->display = $display;
+    $oid->created = DB_DataObject_Cast::dateTime();
+
+    if (!$oid->insert()) {
+        $err = PEAR::getStaticProperty('DB_DataObject','lastError');
+        common_debug('DB error ' . $err->code . ': ' . $err->message, __FILE__);
+        return false;
+    }
+
+    return true;
+}
+
+function oid_get_user($openid_url)
+{
+    $user = null;
+    $oid = User_openid::staticGet('canonical', $openid_url);
+    if ($oid) {
+        $user = User::staticGet('id', $oid->user_id);
+    }
+    return $user;
+}
+
+function oid_check_immediate($openid_url, $backto=null)
+{
+    if (!$backto) {
+        $action = $_REQUEST['action'];
+        $args = common_copy_args($_GET);
+        unset($args['action']);
+        $backto = common_local_url($action, $args);
+    }
+    common_debug('going back to "' . $backto . '"', __FILE__);
+
+    common_ensure_session();
+
+    $_SESSION['openid_immediate_backto'] = $backto;
+    common_debug('passed-in variable is "' . $backto . '"', __FILE__);
+    common_debug('session variable is "' . $_SESSION['openid_immediate_backto'] . '"', __FILE__);
+
+    oid_authenticate($openid_url,
+                     'finishimmediate',
+                     true);
+}
+
+function oid_authenticate($openid_url, $returnto, $immediate=false)
+{
+
+    $consumer = oid_consumer();
+
+    if (!$consumer) {
+        common_server_error(_('Cannot instantiate OpenID consumer object.'));
+        return false;
+    }
+
+    common_ensure_session();
+
+    $auth_request = $consumer->begin($openid_url);
+
+    // Handle failure status return values.
+    if (!$auth_request) {
+        return _('Not a valid OpenID.');
+    } else if (Auth_OpenID::isFailure($auth_request)) {
+        return sprintf(_('OpenID failure: %s'), $auth_request->message);
+    }
+
+    $sreg_request = Auth_OpenID_SRegRequest::build(// Required
+                                                   array(),
+                                                   // Optional
+                                                   array('nickname',
+                                                         'email',
+                                                         'fullname',
+                                                         'language',
+                                                         'timezone',
+                                                         'postcode',
+                                                         'country'));
+
+    if ($sreg_request) {
+        $auth_request->addExtension($sreg_request);
+    }
+
+    $trust_root = common_root_url(true);
+    $process_url = common_local_url($returnto);
+
+    if ($auth_request->shouldSendRedirect()) {
+        $redirect_url = $auth_request->redirectURL($trust_root,
+                                                   $process_url,
+                                                   $immediate);
+        if (!$redirect_url) {
+        } else if (Auth_OpenID::isFailure($redirect_url)) {
+            return sprintf(_('Could not redirect to server: %s'), $redirect_url->message);
+        } else {
+            common_redirect($redirect_url, 303);
+        }
+    } else {
+        // Generate form markup and render it.
+        $form_id = 'openid_message';
+        $form_html = $auth_request->formMarkup($trust_root, $process_url,
+                                               $immediate, array('id' => $form_id));
+
+        # XXX: This is cheap, but things choke if we don't escape ampersands
+        # in the HTML attributes
+
+        $form_html = preg_replace('/&/', '&amp;', $form_html);
+
+        // Display an error if the form markup couldn't be generated;
+        // otherwise, render the HTML.
+        if (Auth_OpenID::isFailure($form_html)) {
+            common_server_error(sprintf(_('Could not create OpenID form: %s'), $form_html->message));
+        } else {
+            $action = new AutosubmitAction(); // see below
+            $action->form_html = $form_html;
+            $action->form_id = $form_id;
+            $action->prepare(array('action' => 'autosubmit'));
+            $action->handle(array('action' => 'autosubmit'));
+        }
+    }
+}
+
+# Half-assed attempt at a module-private function
+
+function _oid_print_instructions()
+{
+    common_element('div', 'instructions',
+                   _('This form should automatically submit itself. '.
+                      'If not, click the submit button to go to your '.
+                      'OpenID provider.'));
+}
+
+# update a user from sreg parameters
+
+function oid_update_user(&$user, &$sreg)
+{
+
+    $profile = $user->getProfile();
+
+    $orig_profile = clone($profile);
+
+    if ($sreg['fullname'] && strlen($sreg['fullname']) <= 255) {
+        $profile->fullname = $sreg['fullname'];
+    }
+
+    if ($sreg['country']) {
+        if ($sreg['postcode']) {
+            # XXX: use postcode to get city and region
+            # XXX: also, store postcode somewhere -- it's valuable!
+            $profile->location = $sreg['postcode'] . ', ' . $sreg['country'];
+        } else {
+            $profile->location = $sreg['country'];
+        }
+    }
+
+    # XXX save language if it's passed
+    # XXX save timezone if it's passed
+
+    if (!$profile->update($orig_profile)) {
+        common_server_error(_('Error saving the profile.'));
+        return false;
+    }
+
+    $orig_user = clone($user);
+
+    if ($sreg['email'] && Validate::email($sreg['email'], true)) {
+        $user->email = $sreg['email'];
+    }
+
+    if (!$user->update($orig_user)) {
+        common_server_error(_('Error saving the user.'));
+        return false;
+    }
+
+    return true;
+}
+
+class AutosubmitAction extends Action
+{
+    var $form_html = null;
+    var $form_id = null;
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showPage();
+    }
+
+    function title()
+    {
+        return _('OpenID Auto-Submit');
+    }
+
+    function showContent()
+    {
+        $this->raw($this->form_html);
+        $this->element('script', null,
+                       '$(document).ready(function() { ' .
+                       '    $(\'#'. $this->form_id .'\').submit(); '.
+                       '});');
+    }
+}
diff --git a/plugins/OpenID/openidlogin.php b/plugins/OpenID/openidlogin.php
new file mode 100644 (file)
index 0000000..29e8923
--- /dev/null
@@ -0,0 +1,137 @@
+<?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';
+
+class OpenidloginAction extends Action
+{
+    function handle($args)
+    {
+        parent::handle($args);
+        if (common_is_real_login()) {
+            $this->clientError(_('Already logged in.'));
+        } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+            $openid_url = $this->trimmed('openid_url');
+
+            # CSRF protection
+            $token = $this->trimmed('token');
+            if (!$token || $token != common_session_token()) {
+                $this->showForm(_('There was a problem with your session token. Try again, please.'), $openid_url);
+                return;
+            }
+
+            $rememberme = $this->boolean('rememberme');
+
+            common_ensure_session();
+
+            $_SESSION['openid_rememberme'] = $rememberme;
+
+            $result = oid_authenticate($openid_url,
+                                       'finishopenidlogin');
+
+            if (is_string($result)) { # error message
+                unset($_SESSION['openid_rememberme']);
+                $this->showForm($result, $openid_url);
+            }
+        } else {
+            $openid_url = oid_get_last();
+            $this->showForm(null, $openid_url);
+        }
+    }
+
+    function getInstructions()
+    {
+        if (common_logged_in() && !common_is_real_login() &&
+            common_get_returnto()) {
+            // rememberme logins have to reauthenticate before
+            // changing any profile settings (cookie-stealing protection)
+            return _('For security reasons, please re-login with your ' .
+                     '[OpenID](%%doc.openid%%) ' .
+                     'before changing your settings.');
+        } else {
+            return _('Login with an [OpenID](%%doc.openid%%) account.');
+        }
+    }
+
+    function showPageNotice()
+    {
+        if ($this->error) {
+            $this->element('div', array('class' => 'error'), $this->error);
+        } else {
+            $instr = $this->getInstructions();
+            $output = common_markup_to_html($instr);
+            $this->elementStart('div', 'instructions');
+            $this->raw($output);
+            $this->elementEnd('div');
+        }
+    }
+
+    function showScripts()
+    {
+        parent::showScripts();
+        $this->autofocus('openid_url');
+    }
+
+    function title()
+    {
+        return _('OpenID Login');
+    }
+
+    function showForm($error=null, $openid_url)
+    {
+        $this->error = $error;
+        $this->openid_url = $openid_url;
+        $this->showPage();
+    }
+
+    function showContent() {
+        $formaction = common_local_url('openidlogin');
+        $this->elementStart('form', array('method' => 'post',
+                                           'id' => 'form_openid_login',
+                                           'class' => 'form_settings',
+                                           'action' => $formaction));
+        $this->elementStart('fieldset');
+        $this->element('legend', null, _('OpenID login'));
+        $this->hidden('token', common_session_token());
+
+        $this->elementStart('ul', 'form_data');
+        $this->elementStart('li');
+        $this->input('openid_url', _('OpenID URL'),
+                     $this->openid_url,
+                     _('Your OpenID URL'));
+        $this->elementEnd('li');
+        $this->elementStart('li', array('id' => 'settings_rememberme'));
+        $this->checkbox('rememberme', _('Remember me'), false,
+                        _('Automatically login in the future; ' .
+                           'not for shared computers!'));
+        $this->elementEnd('li');
+        $this->elementEnd('ul');
+        $this->submit('submit', _('Login'));
+        $this->elementEnd('fieldset');
+        $this->elementEnd('form');
+    }
+
+    function showLocalNav()
+    {
+        $nav = new LoginGroupNav($this);
+        $nav->show();
+    }
+}
diff --git a/plugins/OpenID/openidsettings.php b/plugins/OpenID/openidsettings.php
new file mode 100644 (file)
index 0000000..3ad46f5
--- /dev/null
@@ -0,0 +1,240 @@
+<?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    Evan Prodromou <evan@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.'/lib/accountsettingsaction.php';
+require_once INSTALLDIR.'/plugins/OpenID/openid.php';
+
+/**
+ * Settings for OpenID
+ *
+ * Lets users add, edit and delete OpenIDs from their account
+ *
+ * @category Settings
+ * @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 OpenidsettingsAction extends AccountSettingsAction
+{
+    /**
+     * Title of the page
+     *
+     * @return string Page title
+     */
+
+    function title()
+    {
+        return _('OpenID settings');
+    }
+
+    /**
+     * Instructions for use
+     *
+     * @return string Instructions for use
+     */
+
+    function getInstructions()
+    {
+        return _('[OpenID](%%doc.openid%%) lets you log into many sites' .
+                 ' with the same user account.'.
+                 ' Manage your associated OpenIDs from here.');
+    }
+
+    function showScripts()
+    {
+        parent::showScripts();
+        $this->autofocus('openid_url');
+    }
+
+    /**
+     * Show the form for OpenID management
+     *
+     * We have one form with a few different submit buttons to do different things.
+     *
+     * @return void
+     */
+
+    function showContent()
+    {
+        $user = common_current_user();
+
+        $this->elementStart('form', array('method' => 'post',
+                                          'id' => 'form_settings_openid_add',
+                                          'class' => 'form_settings',
+                                          'action' =>
+                                          common_local_url('openidsettings')));
+        $this->elementStart('fieldset', array('id' => 'settings_openid_add'));
+        $this->element('legend', null, _('Add OpenID'));
+        $this->hidden('token', common_session_token());
+        $this->element('p', 'form_guide',
+                       _('If you want to add an OpenID to your account, ' .
+                         'enter it in the box below and click "Add".'));
+        $this->elementStart('ul', 'form_data');
+        $this->elementStart('li');
+        $this->element('label', array('for' => 'openid_url'),
+                       _('OpenID URL'));
+        $this->element('input', array('name' => 'openid_url',
+                                      'type' => 'text',
+                                      'id' => 'openid_url'));
+        $this->elementEnd('li');
+        $this->elementEnd('ul');
+        $this->element('input', array('type' => 'submit',
+                                      'id' => 'settings_openid_add_action-submit',
+                                      'name' => 'add',
+                                      'class' => 'submit',
+                                      'value' => _('Add')));
+        $this->elementEnd('fieldset');
+        $this->elementEnd('form');
+
+        $oid = new User_openid();
+
+        $oid->user_id = $user->id;
+
+        $cnt = $oid->find();
+
+        if ($cnt > 0) {
+
+            $this->element('h2', null, _('Remove OpenID'));
+
+            if ($cnt == 1 && !$user->password) {
+
+                $this->element('p', 'form_guide',
+                               _('Removing your only OpenID '.
+                                 'would make it impossible to log in! ' .
+                                 'If you need to remove it, '.
+                                 'add another OpenID first.'));
+
+                if ($oid->fetch()) {
+                    $this->elementStart('p');
+                    $this->element('a', array('href' => $oid->canonical),
+                                   $oid->display);
+                    $this->elementEnd('p');
+                }
+
+            } else {
+
+                $this->element('p', 'form_guide',
+                               _('You can remove an OpenID from your account '.
+                                 'by clicking the button marked "Remove".'));
+                $idx = 0;
+
+                while ($oid->fetch()) {
+                    $this->elementStart('form',
+                                        array('method' => 'POST',
+                                              'id' => 'form_settings_openid_delete' . $idx,
+                                              'class' => 'form_settings',
+                                              'action' =>
+                                              common_local_url('openidsettings')));
+                    $this->elementStart('fieldset');
+                    $this->hidden('token', common_session_token());
+                    $this->element('a', array('href' => $oid->canonical),
+                                   $oid->display);
+                    $this->element('input', array('type' => 'hidden',
+                                                  'id' => 'openid_url'.$idx,
+                                                  'name' => 'openid_url',
+                                                  'value' => $oid->canonical));
+                    $this->element('input', array('type' => 'submit',
+                                                  'id' => 'remove'.$idx,
+                                                  'name' => 'remove',
+                                                  'class' => 'submit remove',
+                                                  'value' => _('Remove')));
+                    $this->elementEnd('fieldset');
+                    $this->elementEnd('form');
+                    $idx++;
+                }
+            }
+        }
+    }
+
+    /**
+     * Handle a POST request
+     *
+     * Muxes to different sub-functions based on which button was pushed
+     *
+     * @return void
+     */
+
+    function handlePost()
+    {
+        // CSRF protection
+        $token = $this->trimmed('token');
+        if (!$token || $token != common_session_token()) {
+            $this->showForm(_('There was a problem with your session token. '.
+                              'Try again, please.'));
+            return;
+        }
+
+        if ($this->arg('add')) {
+            $result = oid_authenticate($this->trimmed('openid_url'),
+                                       'finishaddopenid');
+            if (is_string($result)) { // error message
+                $this->showForm($result);
+            }
+        } else if ($this->arg('remove')) {
+            $this->removeOpenid();
+        } else {
+            $this->showForm(_('Something weird happened.'));
+        }
+    }
+
+    /**
+     * Handles a request to remove an OpenID from the user's account
+     *
+     * Validates input and, if everything is OK, deletes the OpenID.
+     * Reloads the form with a success or error notification.
+     *
+     * @return void
+     */
+
+    function removeOpenid()
+    {
+        $openid_url = $this->trimmed('openid_url');
+
+        $oid = User_openid::staticGet('canonical', $openid_url);
+
+        if (!$oid) {
+            $this->showForm(_('No such OpenID.'));
+            return;
+        }
+        $cur = common_current_user();
+        if (!$cur || $oid->user_id != $cur->id) {
+            $this->showForm(_('That OpenID does not belong to you.'));
+            return;
+        }
+        $oid->delete();
+        $this->showForm(_('OpenID removed.'), true);
+        return;
+    }
+}
diff --git a/plugins/OpenID/publicxrds.php b/plugins/OpenID/publicxrds.php
new file mode 100644 (file)
index 0000000..1b2b359
--- /dev/null
@@ -0,0 +1,122 @@
+<?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');
+    }
+}
+
diff --git a/plugins/Orbited/OrbitedPlugin.php b/plugins/Orbited/OrbitedPlugin.php
new file mode 100644 (file)
index 0000000..ba87b26
--- /dev/null
@@ -0,0 +1,154 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Plugin to do "real time" updates using Orbited + STOMP
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Plugin
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/plugins/Realtime/RealtimePlugin.php';
+
+/**
+ * Plugin to do realtime updates using Orbited + STOMP
+ *
+ * This plugin pushes data to a STOMP server which is then served to the
+ * browser by the Orbited server.
+ *
+ * @category Plugin
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ */
+
+class OrbitedPlugin extends RealtimePlugin
+{
+    public $webserver   = null;
+    public $webport     = null;
+    public $channelbase = null;
+    public $stompserver = null;
+    public $stompport   = null;
+    public $username    = null;
+    public $password    = null;
+    public $webuser     = null;
+    public $webpass     = null;
+
+    protected $con      = null;
+
+    function onStartShowHeadElements($action)
+    {
+        // See http://orbited.org/wiki/Deployment#Cross-SubdomainDeployment
+        $action->element('script', null, ' document.domain = document.domain; ');
+    }
+
+    function _getScripts()
+    {
+        $scripts = parent::_getScripts();
+
+        $port = (is_null($this->webport)) ? 8000 : $this->webport;
+
+        $server = (is_null($this->webserver)) ? common_config('site', 'server') : $this->webserver;
+
+        $root = 'http://'.$server.(($port == 80) ? '':':'.$port);
+
+        $scripts[] = $root.'/static/Orbited.js';
+        $scripts[] = common_path('plugins/Orbited/orbitedextra.js');
+        $scripts[] = $root.'/static/protocols/stomp/stomp.js';
+        $scripts[] = common_path('plugins/Orbited/orbitedupdater.js');
+
+        return $scripts;
+    }
+
+    function _updateInitialize($timeline, $user_id)
+    {
+        $script = parent::_updateInitialize($timeline, $user_id);
+
+        $server = $this->_getStompServer();
+        $port   = $this->_getStompPort();
+
+        return $script." OrbitedUpdater.init(\"$server\", $port, ".
+          "\"{$timeline}\", \"{$this->webuser}\", \"{$this->webpass}\");";
+    }
+
+    function _connect()
+    {
+        require_once(INSTALLDIR.'/extlib/Stomp.php');
+
+        $url = $this->_getStompUrl();
+
+        $this->con = new Stomp($url);
+
+        if ($this->con->connect($this->username, $this->password)) {
+            $this->log(LOG_INFO, "Connected.");
+        } else {
+            $this->log(LOG_ERR, 'Failed to connect to queue server');
+            throw new ServerException('Failed to connect to queue server');
+        }
+    }
+
+    function _publish($channel, $message)
+    {
+        $result = $this->con->send($channel,
+                                   json_encode($message));
+
+        return $result;
+        // TODO: parse and deal with result
+    }
+
+    function _disconnect()
+    {
+        $this->con->disconnect();
+    }
+
+    function _pathToChannel($path)
+    {
+        if (!empty($this->channelbase)) {
+            array_unshift($path, $this->channelbase);
+        }
+        return '/' . implode('/', $path);
+    }
+
+    function _getStompServer()
+    {
+        return (!is_null($this->stompserver)) ? $this->stompserver :
+        (!is_null($this->webserver)) ? $this->webserver :
+        common_config('site', 'server');
+    }
+
+    function _getStompPort()
+    {
+        return (!is_null($this->stompport)) ? $this->stompport : 61613;
+    }
+
+    function _getStompUrl()
+    {
+        $server = $this->_getStompServer();
+        $port   = $this->_getStompPort();
+        return "tcp://$server:$port/";
+    }
+}
diff --git a/plugins/Orbited/orbitedextra.js b/plugins/Orbited/orbitedextra.js
new file mode 100644 (file)
index 0000000..47e5c0c
--- /dev/null
@@ -0,0 +1,2 @@
+TCPSocket = Orbited.TCPSocket;
+
diff --git a/plugins/Orbited/orbitedupdater.js b/plugins/Orbited/orbitedupdater.js
new file mode 100644 (file)
index 0000000..8c5ab3b
--- /dev/null
@@ -0,0 +1,24 @@
+// Update the local timeline from a Orbited server
+
+var OrbitedUpdater = function()
+{
+     return {
+
+          init: function(server, port, timeline, username, password)
+          {
+               // set up stomp client.
+               stomp = new STOMPClient();
+
+               stomp.onmessageframe = function(frame) {
+                    RealtimeUpdate.receive(JSON.parse(frame.body));
+               };
+
+               stomp.onconnectedframe = function() {
+                    stomp.subscribe(timeline);
+               }
+
+               stomp.connect(server, port, username, password);
+          }
+     }
+}();
+
diff --git a/plugins/PtitUrl/PtitUrlPlugin.php b/plugins/PtitUrl/PtitUrlPlugin.php
new file mode 100644 (file)
index 0000000..f00d3e2
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin to push RSS/Atom updates to a PubSubHubBub hub
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Plugin
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @copyright 2009 Craig Andrews http://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/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+class PtitUrlPlugin extends Plugin
+{
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    function onInitializePlugin(){
+        $this->registerUrlShortener(
+            'ptiturl.com',
+            array(),
+            array('PtitUrl',array('http://ptiturl.com/?creer=oui&action=Reduire&url='))
+        );
+    }
+}
+
+class PtitUrl extends ShortUrlApi
+{
+    protected function shorten_imp($url) {
+        $response = $this->http_get($url);
+        if (!$response) return $url;
+        $response = $this->tidy($response);
+        $y = @simplexml_load_string($response);
+        if (!isset($y->body)) return $url;
+        $xml = $y->body->center->table->tr->td->pre->a->attributes();
+        if (isset($xml['href'])) return $xml['href'];
+        return $url;
+    }
+}
diff --git a/plugins/PubSubHubBub/PubSubHubBubPlugin.php b/plugins/PubSubHubBub/PubSubHubBubPlugin.php
new file mode 100644 (file)
index 0000000..e1e82e3
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin to push RSS/Atom updates to a PubSubHubBub hub
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Plugin
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @copyright 2009 Craig Andrews http://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/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+define('DEFAULT_HUB','http://pubsubhubbub.appspot.com');
+
+require_once(INSTALLDIR.'/plugins/PubSubHubBub/publisher.php');
+
+class PubSubHubBubPlugin extends Plugin
+{
+    private $hub;
+
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    function onInitializePlugin(){
+        $this->hub = common_config('PubSubHubBub', 'hub');
+        if(empty($this->hub)){
+            $this->hub = DEFAULT_HUB;
+        }
+    }
+
+    function onStartApiAtom($action){
+        $action->element('link',array('rel'=>'hub','href'=>$this->hub),null);
+    }
+
+    function onStartApiRss($action){
+        $action->element('atom:link',array('rel'=>'hub','href'=>$this->hub),null);
+    }
+
+    function onHandleQueuedNotice($notice){
+        $publisher = new Publisher($this->hub);
+
+        $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'));
+
+        //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'));
+
+        //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'));
+            }
+        }
+
+        //group feeds
+        $group_inbox = new Group_inbox();
+        $group_inbox->notice_id = $notice->id;
+        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'));
+            }
+        }
+
+        //feed of each user that subscribes to the notice's author
+        $notice_inbox = new Notice_inbox();
+        $notice_inbox->notice_id = $notice->id;
+        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'));
+            }
+        }
+
+        /* 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'));
+        }*/
+
+        foreach(array_unique($feeds) as $feed){
+            if(! $publisher->publish_update($feed)){
+                common_log_line(LOG_WARNING,$feed.' was not published to hub at '.$this->hub.':'.$publisher->last_response());
+            }
+        }
+    }
+}
diff --git a/plugins/PubSubHubBub/publisher.php b/plugins/PubSubHubBub/publisher.php
new file mode 100644 (file)
index 0000000..f176a9b
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+
+// a PHP client library for pubsubhubbub
+// as defined at http://code.google.com/p/pubsubhubbub/
+// written by Josh Fraser | joshfraser.com | josh@eventvue.com
+// Released under Apache License 2.0
+
+class Publisher {
+    
+    protected $hub_url;
+    protected $last_response;
+    
+    // create a new Publisher
+    public function __construct($hub_url) {
+        
+        if (!isset($hub_url))
+            throw new Exception('Please specify a hub url');
+        
+        if (!preg_match("|^https?://|i",$hub_url)) 
+            throw new Exception('The specified hub url does not appear to be valid: '.$hub_url);
+            
+        $this->hub_url = $hub_url;
+    }
+
+    // accepts either a single url or an array of urls
+    public function publish_update($topic_urls, $http_function = false) {
+        if (!isset($topic_urls))
+            throw new Exception('Please specify a topic url');
+        
+        // check that we're working with an array
+        if (!is_array($topic_urls)) {
+            $topic_urls = array($topic_urls);
+        }
+        
+        // set the mode to publish
+        $post_string = "hub.mode=publish";
+        // loop through each topic url 
+        foreach ($topic_urls as $topic_url) {
+
+            // lightweight check that we're actually working w/ a valid url
+            if (!preg_match("|^https?://|i",$topic_url)) 
+                throw new Exception('The specified topic url does not appear to be valid: '.$topic_url);
+            
+            // append the topic url parameters
+            $post_string .= "&hub.url=".urlencode($topic_url);
+        }
+        
+        // make the http post request and return true/false
+        // easy to over-write to use your own http function
+        if ($http_function)
+            return $http_function($this->hub_url,$post_string);
+        else
+            return $this->http_post($this->hub_url,$post_string);
+    }
+
+    // returns any error message from the latest request
+    public function last_response() {
+        return $this->last_response;
+    }
+    
+    // default http function that uses curl to post to the hub endpoint
+    private function http_post($url, $post_string) {
+        
+        // add any additional curl options here
+        $options = array(CURLOPT_URL => $url,
+                         CURLOPT_POST => true,
+                         CURLOPT_POSTFIELDS => $post_string,
+                         CURLOPT_USERAGENT => "PubSubHubbub-Publisher-PHP/1.0");
+
+       $ch = curl_init();
+       curl_setopt_array($ch, $options);
+
+        $response = curl_exec($ch);
+        $this->last_response = $response;
+        $info = curl_getinfo($ch);
+
+        curl_close($ch);
+        
+        // all good
+        if ($info['http_code'] == 204) 
+            return true;
+        return false;  
+    }
+}
+
+?>
\ No newline at end of file
index 0f0d0f9f42309b134298220d3fc1d148e3ae2ed7..0c7c1240c3f7bb24031e7db82cdc33c7acbb8c04 100644 (file)
@@ -230,6 +230,7 @@ class RealtimePlugin extends Plugin
         }
 
         $action->showContentBlock();
+        $action->showScripts();
         $action->elementEnd('body');
         return false; // No default processing
     }
@@ -239,13 +240,13 @@ class RealtimePlugin extends Plugin
         // FIXME: this code should be abstracted to a neutral third
         // party, like Notice::asJson(). I'm not sure of the ethics
         // of refactoring from within a plugin, so I'm just abusing
-        // the TwitterApiAction method. Don't do this unless you're me!
+        // the ApiAction method. Don't do this unless you're me!
 
-        require_once(INSTALLDIR.'/lib/twitterapi.php');
+        require_once(INSTALLDIR.'/lib/api.php');
 
-        $act = new TwitterApiAction('/dev/null');
+        $act = new ApiAction('/dev/null');
 
-        $arr = $act->twitter_status_array($notice, true);
+        $arr = $act->twitterStatusArray($notice, true);
         $arr['url'] = $notice->bestUrl();
         $arr['html'] = htmlspecialchars($notice->rendered);
         $arr['source'] = htmlspecialchars($arr['source']);
diff --git a/plugins/Realtime/jquery.getUrlParam.js b/plugins/Realtime/jquery.getUrlParam.js
deleted file mode 100644 (file)
index e8f73eb..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/* Copyright (c) 2006-2007 Mathias Bank (http://www.mathias-bank.de)
- * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 
- * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
- * 
- * Version 2.1
- * 
- * Thanks to 
- * Hinnerk Ruemenapf - http://hinnerk.ruemenapf.de/ for bug reporting and fixing.
- * Tom Leonard for some improvements
- * 
- */
-jQuery.fn.extend({
-/**
-* Returns get parameters.
-*
-* If the desired param does not exist, null will be returned
-*
-* To get the document params:
-* @example value = $(document).getUrlParam("paramName");
-* 
-* To get the params of a html-attribut (uses src attribute)
-* @example value = $('#imgLink').getUrlParam("paramName");
-*/ 
- getUrlParam: function(strParamName){
-         strParamName = escape(unescape(strParamName));
-         
-         var returnVal = new Array();
-         var qString = null;
-         
-         if ($(this).attr("nodeName")=="#document") {
-               //document-handler
-               
-               if (window.location.search.search(strParamName) > -1 ){
-                       
-                       qString = window.location.search.substr(1,window.location.search.length).split("&");
-               }
-                       
-         } else if ($(this).attr("src")!="undefined") {
-               
-               var strHref = $(this).attr("src")
-               if ( strHref.indexOf("?") > -1 ){
-               var strQueryString = strHref.substr(strHref.indexOf("?")+1);
-                       qString = strQueryString.split("&");
-               }
-         } else if ($(this).attr("href")!="undefined") {
-               
-               var strHref = $(this).attr("href")
-               if ( strHref.indexOf("?") > -1 ){
-               var strQueryString = strHref.substr(strHref.indexOf("?")+1);
-                       qString = strQueryString.split("&");
-               }
-         } else {
-               return null;
-         }
-               
-         
-         if (qString==null) return null;
-         
-         
-         for (var i=0;i<qString.length; i++){
-                       if (escape(unescape(qString[i].split("=")[0])) == strParamName){
-                               returnVal.push(qString[i].split("=")[1]);
-                       }
-                       
-         }
-         
-         
-         if (returnVal.length==0) return null;
-         else if (returnVal.length==1) return returnVal[0];
-         else return returnVal;
-       }
-});
\ No newline at end of file
index 4cd68a816b2bde0fc2d2a9cbeb424accd747a4fd..9371326fe600a2ac33cc9dc444b2fe143704804e 100644 (file)
@@ -7,6 +7,7 @@ RealtimeUpdate = {
      _replyurl: '',
      _favorurl: '',
      _deleteurl: '',
+     _updatecounter: 0,
 
      init: function(userid, replyurl, favorurl, deleteurl)
      {
@@ -15,6 +16,8 @@ RealtimeUpdate = {
         RealtimeUpdate._favorurl = favorurl;
         RealtimeUpdate._deleteurl = deleteurl;
 
+        DT = document.title;
+
         $(window).blur(function() {
           $('#notices_primary .notice').css({
             'border-top-color':$('#notices_primary .notice:last').css('border-top-color'),
@@ -25,24 +28,33 @@ RealtimeUpdate = {
             'border-top-color':'#AAAAAA',
             'border-top-style':'solid'
           });
+
+          RealtimeUpdate._updatecounter = 0;
+          document.title = DT;
+
+          return false;
         });
      },
 
      receive: function(data)
      {
-          id = data.id;
-
-          // Don't add it if it already exists
-          //
-          if ($("#notice-"+id).length > 0) {
-               return;
-          }
-
-          var noticeItem = RealtimeUpdate.makeNoticeItem(data);
-          $("#notices_primary .notices").prepend(noticeItem);
-          $("#notices_primary .notice:first").css({display:"none"});
-          $("#notices_primary .notice:first").fadeIn(1000);
-          NoticeReply();
+          setTimeout(function() {
+              id = data.id;
+
+              // Don't add it if it already exists
+              if ($("#notice-"+id).length > 0) {
+                   return;
+              }
+
+              var noticeItem = RealtimeUpdate.makeNoticeItem(data);
+              $("#notices_primary .notices").prepend(noticeItem);
+              $("#notices_primary .notice:first").css({display:"none"});
+              $("#notices_primary .notice:first").fadeIn(1000);
+              NoticeReply();
+
+              RealtimeUpdate._updatecounter += 1;
+              document.title = '('+RealtimeUpdate._updatecounter+') ' + DT;
+          }, 500);
      },
 
      makeNoticeItem: function(data)
@@ -125,14 +137,17 @@ RealtimeUpdate = {
 
      addPopup: function(url, timeline, iconurl)
      {
-         $('#content').prepend('<button id="realtime_timeline" title="Pop up in a window">Pop up</button>');
+         $('#notices_primary').css({'position':'relative'});
+         $('#notices_primary').prepend('<button id="realtime_timeline" title="Pop up in a window">Pop up</button>');
 
          $('#realtime_timeline').css({
-             'margin':'0 0 18px 0',
+             'margin':'0 0 11px 0',
              'background':'transparent url('+ iconurl + ') no-repeat 0% 30%',
              'padding':'0 0 0 20px',
              'display':'block',
-             'float':'right',
+             'position':'absolute',
+             'top':'-20px',
+             'right':'0',
              'border':'none',
              'cursor':'pointer',
              'color':$("a").css("color"),
diff --git a/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php b/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php
new file mode 100644 (file)
index 0000000..4806538
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin that requires the user to have a validated email address before they can post 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  Plugin
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @copyright 2009 Craig Andrews http://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/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+class RequireValidatedEmailPlugin extends Plugin
+{
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    function onStartNoticeSave($notice)
+    {
+        $user = User::staticGet('id', $notice->profile_id);
+        if (!empty($user)) { // it's a remote notice
+            if (empty($user->email)) {
+                throw new ClientException(_("You must validate your email address before posting."));
+            }
+        }
+        return true;
+    }
+}
+
diff --git a/plugins/SimpleUrl/SimpleUrlPlugin.php b/plugins/SimpleUrl/SimpleUrlPlugin.php
new file mode 100644 (file)
index 0000000..82d7720
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin to push RSS/Atom updates to a PubSubHubBub hub
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Plugin
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @copyright 2009 Craig Andrews http://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/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+class SimpleUrlPlugin extends Plugin
+{
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    function onInitializePlugin(){
+        $this->registerUrlShortener(
+            'is.gd',
+            array(),
+            array('SimpleUrl',array('http://is.gd/api.php?longurl='))
+        );
+        $this->registerUrlShortener(
+            'snipr.com',
+            array(),
+            array('SimpleUrl',array('http://snipr.com/site/snip?r=simple&link='))
+        );
+        $this->registerUrlShortener(
+            'metamark.net',
+            array(),
+            array('SimpleUrl',array('http://metamark.net/api/rest/simple?long_url='))
+        );
+        $this->registerUrlShortener(
+            'tinyurl.com',
+            array(),
+            array('SimpleUrl',array('http://tinyurl.com/api-create.php?url='))
+        );
+    }
+}
+
+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;
+    }
+}
diff --git a/plugins/TightUrl/TightUrlPlugin.php b/plugins/TightUrl/TightUrlPlugin.php
new file mode 100644 (file)
index 0000000..48efb35
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin to push RSS/Atom updates to a PubSubHubBub hub
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Plugin
+ * @package   StatusNet
+ * @author    Craig Andrews <candrews@integralblue.com>
+ * @copyright 2009 Craig Andrews http://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/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+class TightUrlPlugin extends Plugin
+{
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    function onInitializePlugin(){
+        $this->registerUrlShortener(
+            '2tu.us',
+            array('freeService'=>true),
+            array('TightUrl',array('http://2tu.us/?save=y&url='))
+        );
+    }
+}
+
+class TightUrl extends ShortUrlApi
+{
+    protected function shorten_imp($url) {
+        $response = $this->http_get($url);
+        if (!$response) return $url;
+        $response = $this->tidy($response);
+        $y = @simplexml_load_string($response);
+        if (!isset($y->body)) return $url;
+        $xml = $y->body->p[0]->code[0]->a->attributes();
+        if (isset($xml['href'])) return $xml['href'];
+        return $url;
+    }
+}
diff --git a/scripts/checkschema.php b/scripts/checkschema.php
new file mode 100644 (file)
index 0000000..bf52abe
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a 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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$helptext = <<<END_OF_CHECKSCHEMA_HELP
+Gives plugins a chance to update the database schema.
+
+END_OF_CHECKSCHEMA_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+Event::handle('CheckSchema');
index 71ed3bf7227ff2feb5ce4427c183c07be44a03f4..1266a9700b674ca58f76551b5417eee55cac5667 100644 (file)
@@ -101,7 +101,7 @@ function newSub($i)
 
     $to = User::staticGet('nickname', $tunic);
 
-    if (empty($from)) {
+    if (empty($to)) {
         throw new Exception("Can't find user '$tunic'.");
     }
 
index 8f48e8e6f04019a105771e6a2232345af4338d36..6dd01971296326e725f71ca5ae749011fd11d0d5 100755 (executable)
@@ -35,20 +35,36 @@ ENDOFHELP;
 
 require_once INSTALLDIR.'/scripts/commandline.inc';
 
+$daemons = array();
+
+$daemons[] = INSTALLDIR.'/scripts/pluginqueuehandler.php';
+$daemons[] = INSTALLDIR.'/scripts/ombqueuehandler.php';
+$daemons[] = INSTALLDIR.'/scripts/facebookqueuehandler.php';
+$daemons[] = INSTALLDIR.'/scripts/pingqueuehandler.php';
+
 if(common_config('xmpp','enabled')) {
-    echo "xmppdaemon.php jabberqueuehandler.php publicqueuehandler.php ";
-    echo "xmppconfirmhandler.php ";
+    $daemons[] = INSTALLDIR.'/scripts/xmppdaemon.php';
+    $daemons[] = INSTALLDIR.'/scripts/jabberqueuehandler.php';
+    $daemons[] = INSTALLDIR.'/scripts/publicqueuehandler.php';
+    $daemons[] = INSTALLDIR.'/scripts/xmppconfirmhandler.php';
 }
+
 if(common_config('twitterbridge','enabled')) {
-    echo "twitterstatusfetcher.php ";
+    $daemons[] = INSTALLDIR.'/scripts/twitterstatusfetcher.php';
 }
-echo "ombqueuehandler.php ";
+
 if (common_config('twitter', 'enabled')) {
-    echo "twitterqueuehandler.php ";
-    echo "synctwitterfriends.php ";
+    $daemons[] = INSTALLDIR.'/scripts/twitterqueuehandler.php';
+    $daemons[] = INSTALLDIR.'/scripts/synctwitterfriends.php';
 }
-echo "facebookqueuehandler.php ";
-echo "pingqueuehandler.php ";
+
 if (common_config('sms', 'enabled')) {
-    echo "smsqueuehandler.php ";
+    $daemons[] = INSTALLDIR.'/scripts/smsqueuehandler.php';
+}
+
+if (Event::handle('GetValidDaemons', array(&$daemons))) {
+    foreach ($daemons as $daemon) {
+        print $daemon . ' ';
+    }
+    print "\n";
 }
index 11911dcbdc6cbaba57d02438bc494a9178302715..586bef624ea154d2a53d60c7a71d7313cd18bf83 100755 (executable)
@@ -66,9 +66,10 @@ class MailerDaemon
         }
         $msg = $this->cleanup_msg($msg);
         $msg = common_shorten_links($msg);
-        if (mb_strlen($msg) > 140) {
-            $this->error($from,_('That\'s too long. '.
-                'Max notice size is 140 chars.'));
+        if (Notice::contentTooLong($msg)) {
+            $this->error($from, sprintf(_('That\'s too long. '.
+                                          'Max notice size is %d chars.'),
+                                        Notice::maxContent()));
         }
         $fileRecords = array();
         foreach($attachments as $attachment){
@@ -78,9 +79,9 @@ class MailerDaemon
                 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.'));
             }
@@ -96,9 +97,10 @@ class MailerDaemon
             $short_fileurl = common_shorten_url($fileurl);
             $msg .= ' ' . $short_fileurl;
 
-            if (mb_strlen($msg) > 140) {
+            if (Notice::contentTooLong($msg)) {
                 $this->deleteFile($filename);
-                $this->error($from,_('Max notice size is 140 chars, including attachment URL.'));
+                $this->error($from, sprintf(_('Max notice size is %d chars, including attachment URL.'),
+                                            Notice::maxContent()));
             }
 
             // Also, not sure this is necessary -- Zach
@@ -123,7 +125,7 @@ class MailerDaemon
         $stream  = stream_get_meta_data($attachment);
         if (copy($stream['uri'], $filepath) && chmod($filepath,0664)) {
             return $filename;
-        } else {   
+        } else {
             $this->error(null,_('File could not be moved to destination directory.' . $stream['uri'] . ' ' . $filepath));
         }
     }
@@ -152,7 +154,7 @@ class MailerDaemon
     }
 
     function maybeAddRedir($file_id, $url)
-    {   
+    {
         $file_redir = File_redirection::staticGet('url', $url);
 
         if (empty($file_redir)) {
@@ -258,10 +260,11 @@ class MailerDaemon
 
     function add_notice($user, $msg, $fileRecords)
     {
-        $notice = Notice::saveNew($user->id, $msg, 'mail');
-        if (is_string($notice)) {
-            $this->log(LOG_ERR, $notice);
-            return $notice;
+        try {
+            $notice = Notice::saveNew($user->id, $msg, 'mail');
+        } catch (Exception $e) {
+            $this->log(LOG_ERR, $e->getMessage());
+            return $e->getMessage();
         }
         foreach($fileRecords as $fileRecord){
             $this->attachFile($notice, $fileRecord);
@@ -273,7 +276,7 @@ class MailerDaemon
     }
 
     function attachFile($notice, $filerec)
-    {   
+    {
         File_to_post::processNew($filerec->id, $notice->id);
 
         $this->maybeAddRedir($filerec->id,
index 8e685f1c8ec33bbfb72495aa7924720117cc26a0..be33b9821801f63b2e2a12b2da9fdcc5a2b9fe1f 100755 (executable)
@@ -57,7 +57,7 @@ class OmbQueueHandler extends QueueHandler
             $this->log(LOG_DEBUG, 'Ignoring remote notice ' . $notice->id);
             return true;
         } else {
-            return omb_broadcast_remote_subscribers($notice);
+            return omb_broadcast_notice($notice);
         }
     }
 
diff --git a/scripts/pluginqueuehandler.php b/scripts/pluginqueuehandler.php
new file mode 100755 (executable)
index 0000000..ae807db
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/env php
+<?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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_OMB_HELP
+Daemon script for letting plugins handle stuff at queue time
+
+    -i --id           Identity (default none)
+
+END_OF_OMB_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+require_once INSTALLDIR . '/lib/queuehandler.php';
+
+class PluginQueueHandler extends QueueHandler
+{
+
+    function transport()
+    {
+        return 'plugin';
+    }
+
+    function handle_notice($notice)
+    {
+        Event::handle('HandleQueuedNotice', array(&$notice));
+        return true;
+    }
+}
+
+if (have_option('i', 'id')) {
+    $id = get_option_value('i', 'id');
+} else {
+    $id = null;
+}
+
+$handler = new PluginQueueHandler($id);
+$handler->runOnce();
diff --git a/scripts/showtable.php b/scripts/showtable.php
new file mode 100644 (file)
index 0000000..eb18a98
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a 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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$helptext = <<<END_OF_SHOWTABLE_HELP
+showtable.php <tablename>
+Shows the structure of a table
+
+END_OF_SHOWTABLE_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+if (count($args) != 1) {
+    show_help();
+}
+
+$name = $args[0];
+
+$schema = Schema::get();
+
+$td = $schema->getTableDef($name);
+
+print_r($td);
index 298162673c5a80b822e31825b6d39fdcaee2bf81..5fb75414d33322292682c09b91b83c71205fb11d 100755 (executable)
@@ -40,7 +40,7 @@ DAEMONS=`php $DIR/getvaliddaemons.php $ARGSG`
 for f in $DAEMONS; do
 
          printf "Starting $f...";
-        php $DIR/$f $ARGSD
+        php $f $ARGSD
         printf "DONE.\n"
 
 done
index 9e621e725a3fae3697c22cce4e8e8373607e65df..b2efc07c38c644092b4a1549761efb01af99c330 100755 (executable)
@@ -316,17 +316,22 @@ class XMPPDaemon extends Daemon
     {
         $body = trim($pl['body']);
         $content_shortened = common_shorten_links($body);
-        if (mb_strlen($content_shortened) > 140) {
+        if (Notice::contentTooLong($content_shortened)) {
           $from = jabber_normalize_jid($pl['from']);
-          $this->from_site($from, "Message too long - maximum is 140 characters, you sent ".mb_strlen($content_shortened));
+          $this->from_site($from, sprintf(_("Message too long - maximum is %d characters, you sent %d"),
+                                          Notice::maxContent(),
+                                          mb_strlen($content_shortened)));
           return;
         }
-        $notice = Notice::saveNew($user->id, $content_shortened, 'xmpp');
-        if (is_string($notice)) {
-            $this->log(LOG_ERR, $notice);
-            $this->from_site($user->jabber, $notice);
+
+        try {
+            $notice = Notice::saveNew($user->id, $content_shortened, 'xmpp');
+        } catch (Exception $e) {
+            $this->log(LOG_ERR, $e->getMessage());
+            $this->from_site($user->jabber, $e->getMessage());
             return;
         }
+
         common_broadcast_notice($notice);
         $this->log(LOG_INFO,
                    'Added notice ' . $notice->id . ' from user ' . $user->nickname);
index aeac4a5e3f6c932ac0e26a24b2c5610ed3999de1..483d7135e1592b2e9656d037b12fa130b667f695 100644 (file)
@@ -7,6 +7,7 @@ if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
 
 define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
 define('STATUSNET', true);
+define('LACONICA', true);
 
 require_once INSTALLDIR . '/lib/common.php';
 
index 1c3f7cd96f073d64a8eede2527f61af14ef5aaef..45203bf6e3986f73732f35085d74dbabf58a1457 100644 (file)
@@ -7,6 +7,7 @@ if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
 
 define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
 define('STATUSNET', true);
+define('LACONICA', true);
 
 require_once INSTALLDIR . '/lib/common.php';
 
@@ -28,69 +29,71 @@ class URLDetectionTest extends PHPUnit_Framework_TestCase
                      array('not a link :: no way',
                            'not a link :: no way'),
                      array('link http://www.somesite.com/xyz/35637563@N00/52803365/ link',
-                           'link <a href="http://www.somesite.com/xyz/35637563@N00/52803365/" rel="external">http://www.somesite.com/xyz/35637563@N00/52803365/</a> link'),
+                           'link <a href="http://www.somesite.com/xyz/35637563@N00/52803365/" title="http://www.somesite.com/xyz/35637563@N00/52803365/" rel="external">http://www.somesite.com/xyz/35637563@N00/52803365/</a> link'),
                      array('http://127.0.0.1',
-                           '<a href="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'),
+                           '<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'),
                      array('127.0.0.1',
-                           '<a href="http://127.0.0.1/" rel="external">127.0.0.1</a>'),
+                           '<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="external">127.0.0.1</a>'),
                      array('127.0.0.1:99',
-                           '<a href="http://127.0.0.1:99/" rel="external">127.0.0.1:99</a>'),
+                           '<a href="http://127.0.0.1:99/" title="http://127.0.0.1:99/" rel="external">127.0.0.1:99</a>'),
                      array('127.0.0.1/Name:test.php',
-                           '<a href="http://127.0.0.1/Name:test.php" rel="external">127.0.0.1/Name:test.php</a>'),
+                           '<a href="http://127.0.0.1/Name:test.php" title="http://127.0.0.1/Name:test.php" rel="external">127.0.0.1/Name:test.php</a>'),
                      array('127.0.0.1/~test',
-                           '<a href="http://127.0.0.1/~test" rel="external">127.0.0.1/~test</a>'),
+                           '<a href="http://127.0.0.1/~test" title="http://127.0.0.1/~test" rel="external">127.0.0.1/~test</a>'),
                      array('127.0.0.1/+test',
-                           '<a href="http://127.0.0.1/+test" rel="external">127.0.0.1/+test</a>'),
+                           '<a href="http://127.0.0.1/+test" title="http://127.0.0.1/+test" rel="external">127.0.0.1/+test</a>'),
                      array('127.0.0.1/$test',
-                           '<a href="http://127.0.0.1/$test" rel="external">127.0.0.1/$test</a>'),
+                           '<a href="http://127.0.0.1/$test" title="http://127.0.0.1/$test" rel="external">127.0.0.1/$test</a>'),
                      array('127.0.0.1/\'test',
-                           '<a href="http://127.0.0.1/\'test" rel="external">127.0.0.1/\'test</a>'),
+                           '<a href="http://127.0.0.1/\'test" title="http://127.0.0.1/\'test" rel="external">127.0.0.1/\'test</a>'),
                      array('127.0.0.1/"test',
-                           '<a href="http://127.0.0.1/&quot;test" rel="external">127.0.0.1/&quot;test</a>'),
+                           '<a href="http://127.0.0.1/&quot;test" title="http://127.0.0.1/&quot;test" rel="external">127.0.0.1/&quot;test</a>'),
                      array('127.0.0.1/-test',
-                           '<a href="http://127.0.0.1/-test" rel="external">127.0.0.1/-test</a>'),
+                           '<a href="http://127.0.0.1/-test" title="http://127.0.0.1/-test" rel="external">127.0.0.1/-test</a>'),
                      array('127.0.0.1/_test',
-                           '<a href="http://127.0.0.1/_test" rel="external">127.0.0.1/_test</a>'),
+                           '<a href="http://127.0.0.1/_test" title="http://127.0.0.1/_test" rel="external">127.0.0.1/_test</a>'),
                      array('127.0.0.1/!test',
-                           '<a href="http://127.0.0.1/!test" rel="external">127.0.0.1/!test</a>'),
+                           '<a href="http://127.0.0.1/!test" title="http://127.0.0.1/!test" rel="external">127.0.0.1/!test</a>'),
                      array('127.0.0.1/*test',
-                           '<a href="http://127.0.0.1/*test" rel="external">127.0.0.1/*test</a>'),
+                           '<a href="http://127.0.0.1/*test" title="http://127.0.0.1/*test" rel="external">127.0.0.1/*test</a>'),
                      array('127.0.0.1/test%20stuff',
-                           '<a href="http://127.0.0.1/test%20stuff" rel="external">127.0.0.1/test%20stuff</a>'),
+                           '<a href="http://127.0.0.1/test%20stuff" title="http://127.0.0.1/test%20stuff" rel="external">127.0.0.1/test%20stuff</a>'),
                      array('http://[::1]:99/test.php',
-                           '<a href="http://[::1]:99/test.php" rel="external">http://[::1]:99/test.php</a>'),
+                           '<a href="http://[::1]:99/test.php" title="http://[::1]:99/test.php" rel="external">http://[::1]:99/test.php</a>'),
                      array('http://::1/test.php',
-                           '<a href="http://::1/test.php" rel="external">http://::1/test.php</a>'),
+                           '<a href="http://::1/test.php" title="http://::1/test.php" rel="external">http://::1/test.php</a>'),
                      array('http://::1',
-                           '<a href="http://::1/" rel="external">http://::1</a>'),
+                           '<a href="http://::1/" title="http://::1/" rel="external">http://::1</a>'),
                      array('2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php',
-                           '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php</a>'),
+                           '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php" title="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php</a>'),
                      array('[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php',
-                           '<a href="http://[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php" rel="external">[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php</a>'),
+                           '<a href="http://[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php" title="http://[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php" rel="external">[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php</a>'),
                      array('2001:4978:1b5:0:21d:e0ff:fe66:59ab',
-                           '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab</a>'),
+                           '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/" title="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab</a>'),
                      array('http://127.0.0.1',
-                           '<a href="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'),
+                           '<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'),
                      array('example.com',
-                           '<a href="http://example.com/" rel="external">example.com</a>'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'),
                      array('example.com',
-                           '<a href="http://example.com/" rel="external">example.com</a>'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'),
                      array('http://example.com',
-                           '<a href="http://example.com/" rel="external">http://example.com</a>'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>'),
                      array('http://example.com.',
-                           '<a href="http://example.com/" rel="external">http://example.com</a>.'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>.'),
                      array('/var/lib/example.so',
                            '/var/lib/example.so'),
                      array('example',
                            'example'),
                      array('user@example.com',
-                           '<a href="mailto:user@example.com" rel="external">user@example.com</a>'),
+                           '<a href="mailto:user@example.com" title="mailto:user@example.com" rel="external">user@example.com</a>'),
                      array('user_name+other@example.com',
-                           '<a href="mailto:user_name+other@example.com" rel="external">user_name+other@example.com</a>'),
+                           '<a href="mailto:user_name+other@example.com" title="mailto:user_name+other@example.com" rel="external">user_name+other@example.com</a>'),
                      array('mailto:user@example.com',
-                           '<a href="mailto:user@example.com" rel="external">mailto:user@example.com</a>'),
+                           '<a href="mailto:user@example.com" title="mailto:user@example.com" rel="external">mailto:user@example.com</a>'),
                      array('mailto:user@example.com?subject=test',
-                           '<a href="mailto:user@example.com?subject=test" rel="external">mailto:user@example.com?subject=test</a>'),
+                           '<a href="mailto:user@example.com?subject=test" title="mailto:user@example.com?subject=test" rel="external">mailto:user@example.com?subject=test</a>'),
+                     array('xmpp:user@example.com',
+                           '<a href="xmpp:user@example.com" title="xmpp:user@example.com" rel="external">xmpp:user@example.com</a>'),
                      array('#example',
                            '#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('example'))) . '" rel="tag">example</a></span>'),
                      array('#example.com',
@@ -98,165 +101,165 @@ class URLDetectionTest extends PHPUnit_Framework_TestCase
                      array('#.net',
                            '#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('.net'))) . '" rel="tag">.net</a></span>'),
                      array('http://example',
-                           '<a href="http://example/" rel="external">http://example</a>'),
+                           '<a href="http://example/" title="http://example/" rel="external">http://example</a>'),
                      array('http://3xampl3',
-                           '<a href="http://3xampl3/" rel="external">http://3xampl3</a>'),
+                           '<a href="http://3xampl3/" title="http://3xampl3/" rel="external">http://3xampl3</a>'),
                      array('http://example/',
-                           '<a href="http://example/" rel="external">http://example/</a>'),
+                           '<a href="http://example/" title="http://example/" rel="external">http://example/</a>'),
                      array('http://example/path',
-                           '<a href="http://example/path" rel="external">http://example/path</a>'),
+                           '<a href="http://example/path" title="http://example/path" rel="external">http://example/path</a>'),
                      array('http://example.com',
-                           '<a href="http://example.com/" rel="external">http://example.com</a>'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>'),
                      array('https://example.com',
-                           '<a href="https://example.com/" rel="external">https://example.com</a>'),
+                           '<a href="https://example.com/" title="https://example.com/" rel="external">https://example.com</a>'),
                      array('ftp://example.com',
-                           '<a href="ftp://example.com/" rel="external">ftp://example.com</a>'),
+                           '<a href="ftp://example.com/" title="ftp://example.com/" rel="external">ftp://example.com</a>'),
                      array('ftps://example.com',
-                           '<a href="ftps://example.com/" rel="external">ftps://example.com</a>'),
+                           '<a href="ftps://example.com/" title="ftps://example.com/" rel="external">ftps://example.com</a>'),
                      array('http://user@example.com',
-                           '<a href="http://user@example.com/" rel="external">http://user@example.com</a>'),
+                           '<a href="http://user@example.com/" title="http://user@example.com/" rel="external">http://user@example.com</a>'),
                      array('http://user:pass@example.com',
-                           '<a href="http://user:pass@example.com/" rel="external">http://user:pass@example.com</a>'),
+                           '<a href="http://user:pass@example.com/" title="http://user:pass@example.com/" rel="external">http://user:pass@example.com</a>'),
                      array('http://example.com:8080',
-                           '<a href="http://example.com:8080/" rel="external">http://example.com:8080</a>'),
+                           '<a href="http://example.com:8080/" title="http://example.com:8080/" rel="external">http://example.com:8080</a>'),
                      array('http://example.com:8080/test.php',
-                           '<a href="http://example.com:8080/test.php" rel="external">http://example.com:8080/test.php</a>'),
+                           '<a href="http://example.com:8080/test.php" title="http://example.com:8080/test.php" rel="external">http://example.com:8080/test.php</a>'),
                      array('example.com:8080/test.php',
-                           '<a href="http://example.com:8080/test.php" rel="external">example.com:8080/test.php</a>'),
+                           '<a href="http://example.com:8080/test.php" title="http://example.com:8080/test.php" rel="external">example.com:8080/test.php</a>'),
                      array('http://www.example.com',
-                           '<a href="http://www.example.com/" rel="external">http://www.example.com</a>'),
+                           '<a href="http://www.example.com/" title="http://www.example.com/" rel="external">http://www.example.com</a>'),
                      array('http://example.com/',
-                           '<a href="http://example.com/" rel="external">http://example.com/</a>'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com/</a>'),
                      array('http://example.com/path',
-                           '<a href="http://example.com/path" rel="external">http://example.com/path</a>'),
+                           '<a href="http://example.com/path" title="http://example.com/path" rel="external">http://example.com/path</a>'),
                      array('http://example.com/path.html',
-                           '<a href="http://example.com/path.html" rel="external">http://example.com/path.html</a>'),
+                           '<a href="http://example.com/path.html" title="http://example.com/path.html" rel="external">http://example.com/path.html</a>'),
                      array('http://example.com/path.html#fragment',
-                           '<a href="http://example.com/path.html#fragment" rel="external">http://example.com/path.html#fragment</a>'),
+                           '<a href="http://example.com/path.html#fragment" title="http://example.com/path.html#fragment" rel="external">http://example.com/path.html#fragment</a>'),
                      array('http://example.com/path.php?foo=bar&bar=foo',
-                           '<a href="http://example.com/path.php?foo=bar&amp;bar=foo" rel="external">http://example.com/path.php?foo=bar&amp;bar=foo</a>'),
+                           '<a href="http://example.com/path.php?foo=bar&amp;bar=foo" title="http://example.com/path.php?foo=bar&amp;bar=foo" rel="external">http://example.com/path.php?foo=bar&amp;bar=foo</a>'),
                      array('http://example.com.',
-                           '<a href="http://example.com/" rel="external">http://example.com</a>.'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>.'),
                      array('http://müllärör.de',
-                           '<a href="http://m&#xFC;ll&#xE4;r&#xF6;r.de/" rel="external">http://müllärör.de</a>'),
+                           '<a href="http://m&#xFC;ll&#xE4;r&#xF6;r.de/" title="http://m&#xFC;ll&#xE4;r&#xF6;r.de/" rel="external">http://müllärör.de</a>'),
                      array('http://ﺱﺲﺷ.com',
-                           '<a href="http://&#xFEB1;&#xFEB2;&#xFEB7;.com/" rel="external">http://ﺱﺲﺷ.com</a>'),
+                           '<a href="http://&#xFEB1;&#xFEB2;&#xFEB7;.com/" title="http://&#xFEB1;&#xFEB2;&#xFEB7;.com/" rel="external">http://ﺱﺲﺷ.com</a>'),
                      array('http://сделаткартинки.com',
-                           '<a href="http://&#x441;&#x434;&#x435;&#x43B;&#x430;&#x442;&#x43A;&#x430;&#x440;&#x442;&#x438;&#x43D;&#x43A;&#x438;.com/" rel="external">http://сделаткартинки.com</a>'),
+                           '<a href="http://&#x441;&#x434;&#x435;&#x43B;&#x430;&#x442;&#x43A;&#x430;&#x440;&#x442;&#x438;&#x43D;&#x43A;&#x438;.com/" title="http://&#x441;&#x434;&#x435;&#x43B;&#x430;&#x442;&#x43A;&#x430;&#x440;&#x442;&#x438;&#x43D;&#x43A;&#x438;.com/" rel="external">http://сделаткартинки.com</a>'),
                      array('http://tūdaliņ.lv',
-                           '<a href="http://t&#x16B;dali&#x146;.lv/" rel="external">http://tūdaliņ.lv</a>'),
+                           '<a href="http://t&#x16B;dali&#x146;.lv/" title="http://t&#x16B;dali&#x146;.lv/" rel="external">http://tūdaliņ.lv</a>'),
                      array('http://brændendekærlighed.com',
-                           '<a href="http://br&#xE6;ndendek&#xE6;rlighed.com/" rel="external">http://brændendekærlighed.com</a>'),
+                           '<a href="http://br&#xE6;ndendek&#xE6;rlighed.com/" title="http://br&#xE6;ndendek&#xE6;rlighed.com/" rel="external">http://brændendekærlighed.com</a>'),
                      array('http://あーるいん.com',
-                           '<a href="http://&#x3042;&#x30FC;&#x308B;&#x3044;&#x3093;.com/" rel="external">http://あーるいん.com</a>'),
+                           '<a href="http://&#x3042;&#x30FC;&#x308B;&#x3044;&#x3093;.com/" title="http://&#x3042;&#x30FC;&#x308B;&#x3044;&#x3093;.com/" rel="external">http://あーるいん.com</a>'),
                      array('http://예비교사.com',
-                           '<a href="http://&#xC608;&#xBE44;&#xAD50;&#xC0AC;.com/" rel="external">http://예비교사.com</a>'),
+                           '<a href="http://&#xC608;&#xBE44;&#xAD50;&#xC0AC;.com/" title="http://&#xC608;&#xBE44;&#xAD50;&#xC0AC;.com/" rel="external">http://예비교사.com</a>'),
                      array('http://example.com.',
-                           '<a href="http://example.com/" rel="external">http://example.com</a>.'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>.'),
                      array('http://example.com?',
-                           '<a href="http://example.com/" rel="external">http://example.com</a>?'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>?'),
                      array('http://example.com!',
-                           '<a href="http://example.com/" rel="external">http://example.com</a>!'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>!'),
                      array('http://example.com,',
-                           '<a href="http://example.com/" rel="external">http://example.com</a>,'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>,'),
                      array('http://example.com;',
-                           '<a href="http://example.com/" rel="external">http://example.com</a>;'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>;'),
                      array('http://example.com:',
-                           '<a href="http://example.com/" rel="external">http://example.com</a>:'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>:'),
                      array('\'http://example.com\'',
-                           '\'<a href="http://example.com/" rel="external">http://example.com</a>\''),
+                           '\'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>\''),
                      array('"http://example.com"',
-                           '&quot;<a href="http://example.com/" rel="external">http://example.com</a>&quot;'),
+                           '&quot;<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>&quot;'),
                      array('http://example.com',
-                           '<a href="http://example.com/" rel="external">http://example.com</a>'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>'),
                      array('(http://example.com)',
-                           '(<a href="http://example.com/" rel="external">http://example.com</a>)'),
+                           '(<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>)'),
                      array('[http://example.com]',
-                           '[<a href="http://example.com/" rel="external">http://example.com</a>]'),
+                           '[<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>]'),
                      array('<http://example.com>',
-                           '&lt;<a href="http://example.com/" rel="external">http://example.com</a>&gt;'),
+                           '&lt;<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>&gt;'),
                      array('http://example.com/path/(foo)/bar',
-                           '<a href="http://example.com/path/(foo)/bar" rel="external">http://example.com/path/(foo)/bar</a>'),
+                           '<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" rel="external">http://example.com/path/[foo]/bar</a>'),
+                           '<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)" rel="external">http://example.com/path/foo/(bar)</a>'),
+                           '<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]" rel="external">http://example.com/path/foo/[bar]</a>'),
+                           '<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('Hey, check out my cool site http://example.com okay?',
-                           'Hey, check out my cool site <a href="http://example.com/" rel="external">http://example.com</a> 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))?',
-                           'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>)?'),
+                           'What about parens (e.g. <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('What about parens (e.g. http://example.com/path/foo/(bar)?',
-                           'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>?'),
+                           'What about parens (e.g. <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('What about parens (e.g. http://example.com/path/foo/(bar).)?',
-                           'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>.)?'),
+                           'What about parens (e.g. <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 commas
                      array('What about parens (e.g. http://example.com/path/(foo,bar)?',
-                           'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" rel="external">http://example.com/path/(foo,bar)</a>?'),
+                           'What about parens (e.g. <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('Unbalanced too (e.g. http://example.com/path/((((foo)/bar)?',
-                           'Unbalanced too (e.g. <a href="http://example.com/path/((((foo)/bar)" rel="external">http://example.com/path/((((foo)/bar)</a>?'),
+                           'Unbalanced too (e.g. <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('Unbalanced too (e.g. http://example.com/path/(foo))))/bar)?',
-                           'Unbalanced too (e.g. <a href="http://example.com/path/(foo))))/bar" rel="external">http://example.com/path/(foo))))/bar</a>)?'),
+                           'Unbalanced too (e.g. <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('Unbalanced too (e.g. http://example.com/path/foo/((((bar)?',
-                           'Unbalanced too (e.g. <a href="http://example.com/path/foo/((((bar)" rel="external">http://example.com/path/foo/((((bar)</a>?'),
+                           'Unbalanced too (e.g. <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('Unbalanced too (e.g. http://example.com/path/foo/(bar))))?',
-                           'Unbalanced too (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>)))?'),
+                           'Unbalanced too (e.g. <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('example.com',
-                           '<a href="http://example.com/" rel="external">example.com</a>'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'),
                      array('example.org',
-                           '<a href="http://example.org/" rel="external">example.org</a>'),
+                           '<a href="http://example.org/" title="http://example.org/" rel="external">example.org</a>'),
                      array('example.co.uk',
-                           '<a href="http://example.co.uk/" rel="external">example.co.uk</a>'),
+                           '<a href="http://example.co.uk/" title="http://example.co.uk/" rel="external">example.co.uk</a>'),
                      array('www.example.co.uk',
-                           '<a href="http://www.example.co.uk/" rel="external">www.example.co.uk</a>'),
+                           '<a href="http://www.example.co.uk/" title="http://www.example.co.uk/" rel="external">www.example.co.uk</a>'),
                      array('farm1.images.example.co.uk',
-                           '<a href="http://farm1.images.example.co.uk/" rel="external">farm1.images.example.co.uk</a>'),
+                           '<a href="http://farm1.images.example.co.uk/" title="http://farm1.images.example.co.uk/" rel="external">farm1.images.example.co.uk</a>'),
                      array('example.museum',
-                           '<a href="http://example.museum/" rel="external">example.museum</a>'),
+                           '<a href="http://example.museum/" title="http://example.museum/" rel="external">example.museum</a>'),
                      array('example.travel',
-                           '<a href="http://example.travel/" rel="external">example.travel</a>'),
+                           '<a href="http://example.travel/" title="http://example.travel/" rel="external">example.travel</a>'),
                      array('example.com.',
-                           '<a href="http://example.com/" rel="external">example.com</a>.'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.'),
                      array('example.com?',
-                           '<a href="http://example.com/" rel="external">example.com</a>?'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>?'),
                      array('example.com!',
-                           '<a href="http://example.com/" rel="external">example.com</a>!'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>!'),
                      array('example.com,',
-                           '<a href="http://example.com/" rel="external">example.com</a>,'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>,'),
                      array('example.com;',
-                           '<a href="http://example.com/" rel="external">example.com</a>;'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>;'),
                      array('example.com:',
-                           '<a href="http://example.com/" rel="external">example.com</a>:'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>:'),
                      array('\'example.com\'',
-                           '\'<a href="http://example.com/" rel="external">example.com</a>\''),
+                           '\'<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>\''),
                      array('"example.com"',
-                           '&quot;<a href="http://example.com/" rel="external">example.com</a>&quot;'),
+                           '&quot;<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>&quot;'),
                      array('example.com',
-                           '<a href="http://example.com/" rel="external">example.com</a>'),
+                           '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'),
                      array('(example.com)',
-                           '(<a href="http://example.com/" rel="external">example.com</a>)'),
+                           '(<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>)'),
                      array('[example.com]',
-                           '[<a href="http://example.com/" rel="external">example.com</a>]'),
+                           '[<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>]'),
                      array('<example.com>',
-                           '&lt;<a href="http://example.com/" rel="external">example.com</a>&gt;'),
+                           '&lt;<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>&gt;'),
                      array('Hey, check out my cool site example.com okay?',
-                           'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a> okay?'),
+                           'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a> okay?'),
                      array('Hey, check out my cool site example.com.I made it.',
-                           'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a>.I made it.'),
+                           'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.I made it.'),
                      array('Hey, check out my cool site example.com.Funny thing...',
-                           'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a>.Funny thing...'),
+                           'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.Funny thing...'),
                      array('Hey, check out my cool site example.com.You will love it.',
-                           'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a>.You will love it.'),
+                           'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.You will love it.'),
                      array('What about parens (e.g. example.com/path/foo/(bar))?',
-                           'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>)?'),
+                           'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>)?'),
                      array('What about parens (e.g. example.com/path/foo/(bar)?',
-                           'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>?'),
+                           'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>?'),
                      array('What about parens (e.g. example.com/path/foo/(bar).)?',
-                           'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>.)?'),
+                           'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>.)?'),
                      array('What about parens (e.g. example.com/path/(foo,bar)?',
-                           'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" rel="external">example.com/path/(foo,bar)</a>?'),
+                           'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" title="http://example.com/path/(foo,bar)" rel="external">example.com/path/(foo,bar)</a>?'),
                      array('file.ext',
                            'file.ext'),
                      array('file.html',
diff --git a/tests/UserRightsTest.php b/tests/UserRightsTest.php
new file mode 100644 (file)
index 0000000..6544ee5
--- /dev/null
@@ -0,0 +1,59 @@
+<?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';
+
+class UserRightsTest extends PHPUnit_Framework_TestCase
+{
+    protected $user = null;
+
+    function setUp()
+    {
+        $this->user = User::register(array('nickname' => 'userrightstestuser'));
+    }
+
+    function tearDown()
+    {
+        $profile = $this->user->getProfile();
+        $this->user->delete();
+        $profile->delete();
+    }
+
+    function testInvalidRole()
+    {
+        $this->assertFalse($this->user->hasRole('invalidrole'));
+    }
+
+    function standardRoles()
+    {
+        return array('admin', 'moderator');
+    }
+
+    /**
+     * @dataProvider standardRoles
+     *
+     */
+
+    function testUngrantedRole($role)
+    {
+        $this->assertFalse($this->user->hasRole($role));
+    }
+
+    /**
+     * @dataProvider standardRoles
+     *
+     */
+
+    function testGrantedRole($role)
+    {
+        $this->user->grantRole($role);
+        $this->assertFalse($this->user->hasRole($role));
+    }
+}
\ No newline at end of file
index 7706fba4845b58ab59daeec51e070eb58d296d7f..d9dca98156c85d6e11fcc58ebe512d9e79b0daa9 100644 (file)
@@ -251,7 +251,7 @@ margin-right:18px;
 margin-bottom:11px;
 margin-left:18px;
 }
-#site_nav_global_primary ul li {
+#site_nav_global_primary li {
 display:inline;
 margin-left:11px;
 }
@@ -468,16 +468,15 @@ margin-bottom:7px;
 #form_notice #notice_data-attach {
 position:absolute;
 top:25px;
+right:10.5%;
 cursor:pointer;
 }
 #form_notice label[for=notice_data-attach] {
 text-indent:-9999px;
-left:86%;
 width:16px;
 height:16px;
 }
 #form_notice #notice_data-attach {
-left:40.6%;
 padding:0;
 height:16px;
 }
diff --git a/theme/base/images/icons/icons-01.png b/theme/base/images/icons/icons-01.png
new file mode 100644 (file)
index 0000000..c4e3713
Binary files /dev/null and b/theme/base/images/icons/icons-01.png differ
index 86369cb9935d64f82f9757f1bdf81a398e5e8742..6833373b456a2920542406078562b95d88ee61a0 100644 (file)
@@ -18,7 +18,7 @@ font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
 font-size:1em;
 }
 address {
-margin-right:7.2%;
+margin-right:5.8%;
 }
 
 input, textarea, select, option {
@@ -88,7 +88,7 @@ color:#333333;
 color:#000000;
 }
 #form_notice label[for=notice_data-attach] {
-background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -328px;
 }
 #form_notice #notice_data-attach {
 opacity:0;
@@ -150,16 +150,18 @@ background-color:#9BB43E;
 
 #export_data li a {
 background-repeat:no-repeat;
-background-position:0 45%;
 }
 #export_data li a.rss {
-background-image:url(../../base/images/icons/icon_rss.png);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position:0 -130px;
 }
 #export_data li a.atom {
-background-image:url(../../base/images/icons/icon_atom.png);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position:0 -64px;
 }
 #export_data li a.foaf {
-background-image:url(../../base/images/icons/icon_foaf.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position:0 1px;
 }
 
 .entity_edit a,
@@ -171,7 +173,6 @@ background-image:url(../../base/images/icons/icon_foaf.gif);
 .form_group_unblock input.submit,
 .entity_nudge p,
 .form_make_admin input.submit {
-background-position: 0 40%;
 background-repeat: no-repeat;
 background-color:transparent;
 }
@@ -189,43 +190,48 @@ background-color:#87B4C8;
 }
 
 .entity_edit a {
-background-image:url(../../base/images/icons/twotone/green/edit.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position: 0 -718px;
 }
 .entity_send-a-message a {
-background-image:url(../../base/images/icons/twotone/green/quote.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position: 0 -849px;
 }
 .entity_nudge p,
 .form_user_nudge input.submit {
-background-image:url(../../base/images/icons/twotone/green/mail.gif);
+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/twotone/green/shield.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position: 0 -918px;
 }
 .form_make_admin input.submit {
-background-image:url(../../base/images/icons/twotone/green/admin.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position: 0 -983px;
 }
 
 /* NOTICES */
 .notice .attachment {
-background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -394px;
 }
 #attachments .attachment {
 background:none;
 }
 .notice-options .notice_reply {
-background:transparent url(../../base/images/icons/twotone/green/reply.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -589px;
 }
 .notice-options form.form_favor input.submit {
-background:transparent url(../../base/images/icons/twotone/green/favourite.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -457px;
 }
 .notice-options form.form_disfavor input.submit {
-background:transparent url(../../base/images/icons/twotone/green/disfavourite.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -523px;
 }
 .notice-options .notice_delete {
-background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -655px;
 }
 
 .notices div.entry-content,
@@ -262,7 +268,7 @@ background-color:rgba(200, 200, 200, 0.300);
 /*END: NOTICES */
 
 #new_group a {
-background:transparent url(../../base/images/icons/twotone/green/news.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -1054px;
 }
 
 .pagination .nav_prev a,
@@ -271,10 +277,10 @@ background-repeat:no-repeat;
 border-color:#C8D1D5;
 }
 .pagination .nav_prev a {
-background-image:url(../../base/images/icons/twotone/green/arrow-left.gif);
-background-position:10% 45%;
+background-image:url(../../base/images/icons/icons-01.png);
+background-position:10% -187px;
 }
 .pagination .nav_next a {
-background-image:url(../../base/images/icons/twotone/green/arrow-right.gif);
-background-position:90% 45%;
+background-image:url(../../base/images/icons/icons-01.png);
+background-position:105% -252px;
 }
index 9fc97180d4e997cccbac55d70b5237c1bdec1f7f..6339c9314e4b6f77b816fb2d03aa7c3f0ec002e2 100644 (file)
@@ -88,7 +88,7 @@ color:#333333;
 color:#000000;
 }
 #form_notice label[for=notice_data-attach] {
-background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -328px;
 }
 #form_notice #notice_data-attach {
 opacity:0;
@@ -150,16 +150,18 @@ background-color:#9BB43E;
 
 #export_data li a {
 background-repeat:no-repeat;
-background-position:0 45%;
 }
 #export_data li a.rss {
-background-image:url(../../base/images/icons/icon_rss.png);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position:0 -130px;
 }
 #export_data li a.atom {
-background-image:url(../../base/images/icons/icon_atom.png);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position:0 -64px;
 }
 #export_data li a.foaf {
-background-image:url(../../base/images/icons/icon_foaf.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position:0 1px;
 }
 
 .entity_edit a,
@@ -171,7 +173,6 @@ background-image:url(../../base/images/icons/icon_foaf.gif);
 .form_group_unblock input.submit,
 .entity_nudge p,
 .form_make_admin input.submit {
-background-position: 0 40%;
 background-repeat: no-repeat;
 background-color:transparent;
 }
@@ -189,43 +190,48 @@ background-color:#87B4C8;
 }
 
 .entity_edit a {
-background-image:url(../../base/images/icons/twotone/green/edit.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position: 0 -718px;
 }
 .entity_send-a-message a {
-background-image:url(../../base/images/icons/twotone/green/quote.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position: 0 -849px;
 }
 .entity_nudge p,
 .form_user_nudge input.submit {
-background-image:url(../../base/images/icons/twotone/green/mail.gif);
+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/twotone/green/shield.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position: 0 -918px;
 }
 .form_make_admin input.submit {
-background-image:url(../../base/images/icons/twotone/green/admin.gif);
+background-image:url(../../base/images/icons/icons-01.png);
+background-position: 0 -983px;
 }
 
 /* NOTICES */
 .notice .attachment {
-background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -394px;
 }
 #attachments .attachment {
 background:none;
 }
 .notice-options .notice_reply {
-background:transparent url(../../base/images/icons/twotone/green/reply.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -589px;
 }
 .notice-options form.form_favor input.submit {
-background:transparent url(../../base/images/icons/twotone/green/favourite.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -457px;
 }
 .notice-options form.form_disfavor input.submit {
-background:transparent url(../../base/images/icons/twotone/green/disfavourite.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -523px;
 }
 .notice-options .notice_delete {
-background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -655px;
 }
 
 .notices div.entry-content,
@@ -262,7 +268,7 @@ background-color:rgba(200, 200, 200, 0.300);
 /*END: NOTICES */
 
 #new_group a {
-background:transparent url(../../base/images/icons/twotone/green/news.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -1054px;
 }
 
 .pagination .nav_prev a,
@@ -271,10 +277,10 @@ background-repeat:no-repeat;
 border-color:#CEE1E9;
 }
 .pagination .nav_prev a {
-background-image:url(../../base/images/icons/twotone/green/arrow-left.gif);
-background-position:10% 45%;
+background-image:url(../../base/images/icons/icons-01.png);
+background-position:10% -187px;
 }
 .pagination .nav_next a {
-background-image:url(../../base/images/icons/twotone/green/arrow-right.gif);
-background-position:90% 45%;
+background-image:url(../../base/images/icons/icons-01.png);
+background-position:105% -252px;
 }
index 97cabc30a534a505d2b1ea92c6407aedd087493a..044c32ff16b0746742e902369da22a488cae8eaa 100644 (file)
@@ -7,8 +7,14 @@ color:#FFFFFF;
 background-color:#D9DADB;
 }
 #form_notice .form_note + label {
-background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -328px;
 }
 #form_notice #notice_data-attach {
 filter: alpha(opacity=0);
 }
+.notice-options form.form_favor input.submit {
+background-position:0 -460px;
+}
+.notice-options form.form_disfavor input.submit {
+background-position:0 -526px;
+}
diff --git a/theme/iphone/bg-body.gif b/theme/iphone/bg-body.gif
deleted file mode 100644 (file)
index d87e2e8..0000000
Binary files a/theme/iphone/bg-body.gif and /dev/null differ
diff --git a/theme/iphone/bg-header.gif b/theme/iphone/bg-header.gif
deleted file mode 100644 (file)
index 5154b2e..0000000
Binary files a/theme/iphone/bg-header.gif and /dev/null differ
diff --git a/theme/iphone/default-avatar-mini.png b/theme/iphone/default-avatar-mini.png
deleted file mode 100644 (file)
index 38b8692..0000000
Binary files a/theme/iphone/default-avatar-mini.png and /dev/null differ
diff --git a/theme/iphone/default-avatar-profile.png b/theme/iphone/default-avatar-profile.png
deleted file mode 100644 (file)
index f8357d4..0000000
Binary files a/theme/iphone/default-avatar-profile.png and /dev/null differ
diff --git a/theme/iphone/default-avatar-stream.png b/theme/iphone/default-avatar-stream.png
deleted file mode 100644 (file)
index 6b63baa..0000000
Binary files a/theme/iphone/default-avatar-stream.png and /dev/null differ
diff --git a/theme/iphone/display.css b/theme/iphone/display.css
deleted file mode 100644 (file)
index 1838a8e..0000000
+++ /dev/null
@@ -1,698 +0,0 @@
-/* CSS Document */
-/* Design & CSS by Marie-Claude Doyon http://www.marieclaudedoyon.com */
-/* Simplified for mobile by Ken Sheppardson http://identi.ca/kshep    */
-
-@import url(../../base/css/display.css);
-
-html {}
-body {
-  width: 100%;
-  padding: 0;
-  margin: 0;
-  font-family: Verdana, Arial, Helvetica, sans-serif;
-  font-size: 24px;
-  min-height: 100%;
-  height: 100%;
-  color: #193441;
-}
-
-a {
-  color: #C15D42;
-  text-decoration: none;
-}
-a:hover {
-  text-decoration: underline;
-}
-img, img a {
-  border: 0;
-}
-h1 {
-  font-size: 1.2em;
-}
-
-#wrap {
-  margin: 0;
-}
-
-#header {
-  width: 100%;
-  float: left;
-  background-color: #193441;
-  margin: 0 0 20px 0;
-  padding: 0;
-}
-#logo {
-  float: left;
-  margin: 10px 0px 0px 10px;
-}
-p#branding {
-  margin: 0;
-  padding: 6px 0 3px 0;
-  color: #fbf2d7;
-  font-size: 2em;
-  font-weight: bold;
-  line-height: 2.5em;
-}
-p#branding a {
-  color: #dab134;
-}
-
-#header h1.pagetitle {
-  display: none;
-  margin: 0;
-  padding: 0;
-  font-size: 1.2em;
-  line-height: 2em;
-  color: #d8e2d7;
-}
-
-#header h2.sitename {
-  display: none;
-  margin: 0;
-  padding: 0;
-  color: #FCFFF5;
-}
-
-/* ===== Begin Navigation Styling ===== */
-
-/* ----- Navigation ------ */
-#nav {
-  float: right;
-  margin: 0;
-  padding: 0;
-  list-style-type: none;
-  font-size: 1.2em;
-}
-#nav li {
-  display: block;
-  float: left;
-}
-#nav li a {
-  display: block;
-  padding: 9px 15px 12px 0px;
-  color: #91AA9D;
-}
-#nav li a:hover {
-  text-decoration: underline;
-}
-
-/* ----- Tabs ----- */
-#nav_views {
-  clear: both;
-  float: left;
-  margin: 10px 0px 0px 5px;
-  padding: 0;
-  bottom: 0;
-  list-style-type: none;
-  font-size: 1.1em;
-  font-weight: bold;
-}
-#nav_views li {
-  display: block;
-  float: left;
-  line-height: 1.3em;
-}
-#nav_views li a {
-  display: block;
-  margin: 0;
-  padding: 4px 12px 3px 12px;
-  color: #FCFFF5;
-  background-color: #91AA9D;
-  border-right: 1px solid #6A8787;
-}
-#nav_views li a:hover {
-  text-decoration: none;
-}
-#nav_views li.current a, #nav_views li.current a:hover {
-  color: #3F606F;
-  background-color: #FCFFF5;
-  border-right: 1px solid #6A8787;
-}
-#nav_views li.current a:hover {
-  color: #193441;
-}
-#nav_views li a:hover {
-  color: #FCFFF5;
-  background-color: #3F606F;
-  border-right: 1px solid #6A8787;
-}
-
-/* ----- Nav Footer ----- */
-#nav_sub {
-  clear: both;
-  margin: 18px 10px 0 10px;
-  padding: 0;
-  list-style-type: none;
-  font-size: 1.1em;
-  font-weight: bold;
-  line-height: 2em;
-  border-top: 1px solid #D8E2D7;
-}
-#nav_sub li {
-  display: block;
-  float: left;
-}
-#nav_sub li a {
-  padding: 6px 24px 6px 0;
-}
-#nav_sub li a:hover {
-  text-decoration: underline;
-}
-/* ===== End Navigation Styling ===== */
-
-#content {
-  clear: left;
-  margin: 10px;
-  font-family: Georgia, "Times New Roman", Times, serif;
-  font-size: 1em;
-  line-height: 1.1em;
-}
-#content h2 {
-  font-family: Verdana, Arial, Helvetica, sans-serif;
-  font-size: 1.1em;
-}
-#content label {
-  font-family: Verdana, Arial, Helvetica, sans-serif;
-  font-size: 1.1em;
-}
-
-.instructions {
-  clear: both;
-  float: left;
-  margin: 5px 5px 10px 5px;
-}
-.instructions p, .success, .error {
-  font-weight: normal;
-  margin: 0;
-  padding: 10px;
-  font-family: Georgia, "Times New Roman", Times, serif;
-  font-size: 1.1em;
-  line-height: 1.2em;
-  border: 1px solid #91AA9D;
-  color: #FCFFF5;
-}
-.instructions a, .success a, .error a {
-  color: #d8e2d7;
-  text-decoration: underline;
-}
-.instructions a:hover, .success a:hover, .error a:hover {
-  color: #FCFFF5;
-}
-.success {
-  clear: both;
-  float: left;
-  margin: 5px 5px 10px 5px;
-  background-color: #48705b;
-}
-.error {
-  clear: both;
-  float: left;
-  margin: 5px 5px 10px 5px;
-  background-color: #ce3728;
-}
-
-/* ----- Stream -----*/
-
-#notices {
-  clear: both;
-  margin: 0 auto;
-  padding: 0;
-  list-style-type: none;
-  border-top: 1px solid #D8E2D7;
-}
-#notices a:hover {
-  text-decoration: underline;
-}
-.notice_single {
-  clear: both;
-  display: block;
-  margin: 0;
-  padding: 5px 5px 5px 0;
-  min-height: 48px;
-  font-family: Georgia, "Times New Roman", Times, serif;
-  font-size: 1em;
-  line-height: 1.4em;
-  border-bottom: 1px solid #D8E2D7;
-}
-.notice_single:hover {
-  background-color: #F3F8EA;
-}
-.notice_single p {
-  display: inline;
-  margin: 0;
-  padding: 0;
-}
-#notice_delete_form #confirmation_text {
-  display: block;
-  font-size: 1.1em;
-  font-weight: bold;
-}
-input#submit_yes, input#submit_no {
-  margin: 18px 10px 0px 0px;
-  padding: 4px;
-  font-weight: bold;
-  color: #FCFFF5;
-  background-color: #C15D42;
-  cursor: pointer;
-  border: 0;
-  width: 40px;
-}
-input#submit_yes:hover, input#submit_no:hover {
-  background-color: #904632;
-}
-.avatar.stream {
-  float: left;
-  margin: 0 10px 0 0;
-}
-p.time {
-  display: block;
-  font-family: Verdana, Arial, Helvetica, sans-serif;
-  font-size: 0.9em;
-  line-height: 2em;
-}
-p.time a {
-  color: #91AA9D;
-}
-
-/* ----- Profile -----*/
-#profile {
-  clear: both;
-  float: left;
-  padding: 10px 0 0 0;
-  border-top: 1px solid #D8E2D7;
-  font-family: Georgia, "Times New Roman", Times, serif;
-}
-#profile h1 {
-  clear: both;
-  float: left;
-  margin: 0;
-  padding: 0;
-  font-family: Verdana, Arial, Helvetica, sans-serif;
-  font-size: 1.2em;
-}
-#profile h2 {
-  clear: both;
-  float: left;
-  margin: 0;
-  padding: 1em 0 0.2em 0;
-  font-family: Verdana, Arial, Helvetica, sans-serif;
-  font-size: 1.1em;
-  text-transform: uppercase;
-  color: #91AA9D;
-}
-#profile p {
-  clear: both;
-  float: left;
-  margin: 0 10px 0 0;
-  font-size: 1em;
-  line-height: 1.4em;
-}
-#profile p.location {
-  margin: 0 10px 12px 0;
-  font-style: italic;
-}
-#profile p.notice_current {
-  font-size: 1.2em;
-  line-height: 1.3em;
-}
-#profile_avatar {
-  float: left;
-  margin-right: 4px;
-}
-#profile_avatar img {
-  margin-bottom: 5px;
-}
-.avatar.profile {
-  clear: left;
-  margin: 0 10px 5px 0;
-}
-.avatar.original {
-  float: left;
-  margin: 0 10px 18px 0;
-}
-a.nickname {
-  font-family: Verdana, Arial, Helvetica, sans-serif;
-  font-weight: bold;
-  font-size: 1.1em;
-  padding-right: 3px;
-}
-#profile_information {
-  float: left;
-}
-
-.statistics {
-  clear: both;
-  float: left;
-}
-.statistics h2 {
-  clear: both;
-  float: left;
-  margin: 12px 0 3px 0;
-}
-dl.statistics {
-  margin: 0;
-}
-.statistics dt {
-  clear: left;
-  float: left;
-  width: 200px;
-}
-.statistics dd {
-  float: left;
-}
-.statistics dt:after {
-  content: ":";
-}
-#subscriptions {
-  clear: both;
-  float: left;
-  margin: 18px 0 30px 0;
-}
-#subscriptions_avatars {
-  clear: both;
-  float: left;
-  margin: 6px 0 0 0;
-  padding: 0;
-  list-style-type: none;
-}
-#subscriptions_avatars li .avatar.mini {
-  float: left;
-  margin: 0 3px 3px 0;
-  padding: 0;
-  line-height: 0;
-}
-#subscriptions_viewall {
-  clear: left;
-}
-/* ----- End Profile -----*/
-
-/* ----- Begin Subscriptions & Subscribers -----*/
-
-ul.subscriptions, ul.subscribers {
-  float: none;
-  margin: 0;
-  padding: 0;
-  list-style-type: none;
-  overflow: auto;
-}
-ul.subscriptions li, ul.subscribers li {
-  display: block;
-  float: left;
-  padding: 0;
-}
-/* ----- End Subscriptions & Subscribers -----*/
-
-#pagination {
-  margin: 18px auto;
-}
-#nav_pagination {
-  margin: 0 0 36px 0;
-  padding: 0;
-  float: right;
-  list-style-type: none;
-  font-size: 12px;
-  font-weight: bold;
-}
-#nav_pagination li {
-  display: block;
-  float: left;
-  background-color: #91AA9D;
-}
-#nav_pagination li.before {
-  margin-right: 1px;
-}
-#nav_pagination li a {
-  padding: 6px 15px;
-  line-height: 2em;
-  background-color: #91AA9D;
-  color: #FCFFF5;
-}
-#nav_pagination li a:hover {
-  background-color: #3F606F;
-  color: #FCFFF5;
-  text-decoration: none;
-}
-
-#footer {
-  clear: both;
-  margin: 10px;
-  border-top: 1px solid #D8E2D7;
-}
-#footer p {
-  font-size: 0.8em;
-  margin-top: 1em;
-  line-height: 1.2em;
-}
-#cc {
-  float: left;
-  margin: 3px 10px 0 0;
-}
-
-/* ===== Begin Forms Styling ===== */
-
-/* ----- Forms General Style ----- */
-form {
-  margin: 0 auto;
-  padding: 0;
-}
-form {
-  font-family: Verdana, Arial, Helvetica, sans-serif;
-  font-size: 1em;
-}
-form label {
-  display: block;
-  font-size: 1em;
-  font-weight: bold;
-  line-height: 1.5em;
-}
-form input {
-  border: 1px solid #D8E2D7;
-  width: 264px;
-}
-input#submit, input.submit  {
-  display: block;
-  margin: 18px 0;
-  padding: 4px;
-  font-weight: bold;
-  color: #FCFFF5;
-  background-color: #C15D42;
-  cursor: pointer;
-  border: 0;
-  width: auto;
-}
-input#submit:hover, input.submit:hover {
-  background-color: #904632;
-}
-input.checkbox {
-  width: auto;
-  border: 0;
-}
-textarea, input {
-  font-family: Verdana, Arial, Helvetica, sans-serif;
-  font-size: 1em;
-  color: #193441;
-  padding: 3px;
-}
-textarea:focus, input:focus {
-  background-color: #f0f6eb;
-}
-textarea {
-  width: 270px;
-  border: 1px solid #D8E2D7;
-}
-.input_instructions {
-  margin-top: 3px;
-  display: block;
-  font-size: 1em;
-  line-height: 1.2em;
-  color: #91aa9d;
-  font-family: Verdana, Arial, Helvetica, sans-serif;
-}
-
-/* ----- Status Form ----- */
-#status_form {
-  width: 100%;
-  margin: 0px 0px 10px 5px;
-}
-#status_form p {
-  margin: 0;
-  padding: 0;
-}
-#status_label {
-  display: none;
-  clear: both;
-  margin: 0;
-  padding: 0 0 3px 0;
-  font-size: 1.5em;
-  font-weight: bold;
-  line-height: 2em;
-  color: #91AA9D;
-}
-#status_textarea {
-  display: block;
-  float: left;
-  width: 70%;
-  height: 3em;
-  margin: 0 0 10px 0;
-  padding: 0;
-  font-family: Verdana, Arial, Helvetica, sans-serif;
-  font-size: 1.1em;
-  color: #193441;
-  border: 0;
-}
-#status_submit {
-  display: block;
-  float: left;
-  margin: 0 0 0 4px;
-  padding: 1em 10px 1em 10px;
-  line-height: 1em;
-  width: 10%;
-  background-color: #C15D42;
-  font-family: Verdana, Arial, Helvetica, sans-serif;
-  font-weight: bold;
-  font-size: 1em;
-  color: #FCFFF5;
-  cursor: pointer;
-  border: 0;
-}
-#status_submit:hover {
-  background-color: #904632;
-}
-#counter {
-  padding: 1em .5em 1em 5px;
-  color: #fff;
-  clear: both;
-  float: left;
-  font-weight: bold;
-  text-align: right;
-}
-/* ----- Subscribe Form ----- */
-#subscribe .submit, #unsubscribe .submit, #remotesubscribe .button, #remotesubscribe {
-  clear: left;
-  margin: 0;
-  width: 96px;
-  height: 27px;
-  font-family: verdana, arial, helvetica, sans-serif;
-  font-weight: bold;
-  font-size: 12px;
-  text-transform: uppercase;
-  background-color: #c15d42;
-  color: #fcfff5;
-  border: 0;
-}
-#remotesubscribe {
-  width: 96px;
-  height: 22px;
-  padding: 5px 0 0 0;
-  text-align: center;
-}
-#subscribe .button:hover, #unsubscribe .button:hover {
-  background-color: #904632;
-  cursor: pointer;
-}
-
-a#remotesubscribe {
-  display: block;
-}
-
-/* ----- Login Form -----*/
-input#license {
-  width: auto;
-  border: 0;
-}
-/* ----- Avatar Form -----*/
-form {
-  clear: left;
-}
-
-/* ----- OpenID Form -----*/
-
-input#openid_url {
-  background: url(login-bg.gif) no-repeat;
-  background-color: #fff;
-  background-position: 4px 50%;
-  color: #000;
-  padding-left: 24px;
-}
-
-/* People lists (search results, maybe subscribers) */
-
-#profiles {
-  clear: both;
-  margin: 0 auto;
-  padding: 0;
-  list-style-type: none;
-  border-top: 1px solid #D8E2D7;
-}
-#profiles a:hover {
-  text-decoration: underline;
-}
-
-.profile_single {
-  clear: both;
-  display: block;
-  margin: 0;
-  padding: 5px 5px 5px 0;
-  min-height: 48px;
-  font-family: Georgia, "Times New Roman", Times, serif;
-  font-size: 1.2em;
-  line-height: 1.4em;
-  border-bottom: 1px solid #D8E2D7;
-}
-.profile_single:hover {
-  background-color: #F3F8EA;
-}
-
-/* ----- IM Settings Form -----*/
-
-#imsettings p {
-  margin: 0;
-  padding: 0;
-  line-height: 1.3em;
-}
-
-/* ===== End Forms Styling ===== */
-
-/* ===== Tag Cloud Styling ===== */
-
-p.tagcloud {
-text-align: center;
-}
-
-p.tagcloud a {
-line-height:1em;
-vertical-align:middle;
-}
-
-p.tagcloud a.largest {
-font-size: 4em;
-}
-p.tagcloud a.verylarge {
-font-size: 3em;
-}
-
-p.tagcloud a.large {
-font-size: 2em;
-}
-
-p.tagcloud a.medium {
-font-size: 1.5em;
-}
-
-p.tagcloud a.small {
-font-size: 1em;
-}
-
-p.tagcloud a.verysmall {
-font-size: 80%;
-}
-
-p.tagcloud a.smallest {
-font-size: 60%;
-}
-
-a.replybutton {
-  border: 1px solid #D8E2D7;
-  padding: 0px 10px 0px 10px;
-  line-height: 0.8em;
-}
diff --git a/theme/iphone/display.css.dist b/theme/iphone/display.css.dist
deleted file mode 100644 (file)
index 395da2e..0000000
+++ /dev/null
@@ -1,686 +0,0 @@
-/* CSS Document */
-/* Design & CSS by Marie-Claude Doyon http://www.marieclaudedoyon.com */
-
-html {
-       background: url(bg-body.gif) repeat-y top center #d8e2d7;
-       }
-body {
-       position: absolute;
-       width: 100%;
-       margin: 0;
-       padding: 0;
-       font-family: Verdana, Arial, Helvetica, sans-serif;
-       font-size: 10px;
-       line-height: 12px;
-       min-height: 100%;
-       height: 100%;
-       color: #193441;
-       }
-a {
-       color: #C15D42;
-       text-decoration: none;
-       }
-a:hover {
-       text-decoration: underline;
-       }
-img, img a {
-       border: 0;
-       }
-h1 {
-       font-size: 14px;
-       }
-
-
-#wrap {
-       margin: 0 auto;
-       padding: 0 20px;
-       width: 760px;
-       background: url(bg-header.gif) repeat-x #FCFFF5;
-       }
-#header {
-       position: relative;
-       margin: 0 auto;
-       width: 540px;
-       height: 216px;
-       }
-#logo {
-       margin-top: 9px;
-       }
-p#branding {
-       margin: 0;
-       padding: 6px 0 3px 0;
-       color: #fbf2d7;
-       font-size: 21px;
-       font-weight: bold;
-       line-height: 27px;
-       }
-p#branding a {
-       color: #dab134;
-       }
-
-#header h1.pagetitle {
-       margin: 0;
-       padding: 0;
-       font-size: 15px;
-       line-height: 24px;
-       color: #d8e2d7;
-}
-
-#header h2.sitename {
-       display: none;
-       margin: 0;
-       padding: 0;
-       color: #FCFFF5;
-}
-
-/* ===== Begin Navigation Styling ===== */
-
-/* ----- Navigation ------ */
-#nav {
-       float: right;
-       margin: 0;
-       padding: 0;
-       list-style-type: none;
-       font-size: 12px;
-       }
-#nav li {
-       display: block;
-       float: left;
-       }
-#nav li a {
-       display: block;
-       padding: 9px 9px 12px 9px;
-       color: #91AA9D;
-       }
-#nav li a:hover {
-       text-decoration: underline;
-       }
-
-/* ----- Tabs ----- */
-#nav_views {
-       margin: 0 auto;
-       padding: 0;
-       position: absolute;
-       bottom: 0;
-       list-style-type: none;
-       font-size: 14px;
-       font-weight: bold;
-       width: 540px;
-       /*height: 30px;*/
-       }
-#nav_views li {
-       display: block;
-       float: left;
-       line-height: 21px;
-       }
-#nav_views li a {
-       display: block;
-       margin: 0;
-       padding: 4px 12px 3px 12px;
-       color: #FCFFF5;
-       background-color: #91AA9D;
-       border-right: 1px solid #6A8787;
-       }
-#nav_views li a:hover {
-       text-decoration: none;
-       }
-#nav_views li.current a, #nav_views li.current a:hover {
-       color: #3F606F;
-       background-color: #FCFFF5;
-       border-right: 1px solid #6A8787;
-       }
-#nav_views li.current a:hover {
-       color: #193441;
-       }
-#nav_views li a:hover {
-       color: #FCFFF5;
-       background-color: #3F606F;
-       border-right: 1px solid #6A8787;
-       }
-
-/* ----- Nav Footer ----- */
-#nav_sub {
-       clear: both;
-       margin: 18px auto 0 auto;
-       padding: 0;
-       list-style-type: none;
-       font-size: 11px;
-       font-weight: bold;
-       line-height: 21px;
-       border-top: 1px solid #D8E2D7;
-       width: 540px;
-       }
-#nav_sub li {
-       display: block;
-       float: left;
-       }
-#nav_sub li a {
-       padding: 6px 24px 6px 0;
-       }
-#nav_sub li a:hover {
-       text-decoration: underline;
-       }
-/* ===== End Navigation Styling ===== */
-
-#content {
-       clear: left;
-       margin: 40px 0 45px 0;
-       padding: 0 110px;
-       font-family: Georgia, "Times New Roman", Times, serif;
-       font-size: 14px;
-       line-height: 18px;
-       }
-#content h2 {
-       font-family: Verdana, Arial, Helvetica, sans-serif;
-       font-size: 15px;
-       }
-#content label {
-       font-family: Verdana, Arial, Helvetica, sans-serif;
-       font-size: 12px;
-       }
-.instructions p, .success, .error {
-       font-weight: normal;
-       margin: 36px 0 0 0;
-       padding: 10px;
-       font-family: Georgia, "Times New Roman", Times, serif;
-       font-size: 13px;
-       line-height: 15px;
-       border: 1px solid #91AA9D;
-       color: #FCFFF5;
-       }
-.instructions a, .success a, .error a {
-       color: #d8e2d7;
-       text-decoration: underline;
-       }
-.instructions a:hover, .success a:hover, .error a:hover {
-       color: #FCFFF5;
-       }               
-.success {
-       background-color: #48705b;
-       }
-.error {
-       background-color: #ce3728;
-       }
-
-
-/* ----- Stream -----*/
-
-#notices {
-       clear: both;
-       margin: 0 auto;
-       padding: 0;
-       list-style-type: none;
-       width: 540px;
-       border-top: 1px solid #D8E2D7;
-       }
-#notices a:hover {
-       text-decoration: underline;
-       }
-.notice_single {
-       clear: both;
-       display: block;
-       margin: 0;
-       padding: 5px 5px 5px 0;
-       min-height: 48px;
-       font-family: Georgia, "Times New Roman", Times, serif;
-       font-size: 13px;
-       line-height: 16px;
-       border-bottom: 1px solid #D8E2D7;
-       }
-.notice_single:hover {
-       background-color: #F3F8EA;
-       }
-.notice_single p {
-       display: inline;
-       margin: 0;
-       padding: 0;
-       }
-#notice_delete_form #confirmation_text {
-        display: block;
-       font-size: 14px;
-       font-weight: bold;
-       }
-input#submit_yes, input#submit_no {
-       margin: 18px 10px 0px 0px;
-       padding: 4px;
-       font-weight: bold;
-       color: #FCFFF5;
-       background-color: #C15D42;
-       cursor: pointer;
-       border: 0;
-       width: 40px;
-       }
-input#submit_yes:hover, input#submit_no:hover {
-       background-color: #904632;
-       }
-.avatar.stream {
-       float: left;
-       margin: 0 10px 0 0;
-       }
-p.time {
-       display: block;
-       font-family: Verdana, Arial, Helvetica, sans-serif;
-       font-size: 10px;
-       line-height: 15px;
-       }
-p.time a {
-       color: #91AA9D;
-       }
-       
-/* ----- Profile -----*/
-#profile {
-       clear: left;
-       margin: 0 -110px;
-       padding: 10px 0 0 0;
-       min-height: 170px;
-       border-top: 1px solid #D8E2D7;
-       font-family: Georgia, "Times New Roman", Times, serif;
-       }
-#profile h1 {
-       margin: 0;
-       padding: 0;
-       font-family: Verdana, Arial, Helvetica, sans-serif;
-       font-size: 14px;
-       }
-#profile h2 {
-       margin: 0;
-       padding: 0;
-       font-family: Verdana, Arial, Helvetica, sans-serif;
-       font-size: 11px;
-       text-transform: uppercase;
-       color: #91AA9D;
-       }
-#profile p {
-       margin: 0 10px 0 0;
-       font-size: 12px;
-       line-height: 14px;
-       }
-#profile p.location {
-       margin: 0 10px 12px 0;
-       font-style: italic;
-       }
-#profile p.notice_current {
-       font-size: 18px;
-       line-height: 21px;
-       }
-#profile_avatar {
-       float: left;
-       margin-right: 4px;
-       }
-#profile_avatar img {
-       margin-bottom: 5px;
-       }
-.avatar.profile {
-       clear: left;
-       margin: 0 10px 5px 0;
-       }
-.avatar.original {
-       float: left;
-       margin: 0 10px 18px 0;
-       }
-a.nickname {
-       font-family: Verdana, Arial, Helvetica, sans-serif;
-       font-weight: bold;
-       font-size: 12px;
-       padding-right: 3px;
-       }
-#profile_information {
-       float: left;
-       position: relative;
-       width: 270px;
-       height: 200px;
-       }
-.statistics {
-       margin-top: 18px;
-       }
-.statistics h2 {
-       margin: 12px 0 3px 0;
-       }
-dl.statistics {
-       margin: 0;
-       font-size: 12px;
-       line-height: 14px;
-       }
-.statistics dt {
-       float: left;
-       width: 96px;
-}      
-.statistics dd {
-       margin-left: 100px;
-}
-.statistics dt:after {
-       content: ":";
-       }
-#subscriptions {
-       float: left;
-       margin: 18px 0 30px 0;
-       }
-#subscriptions_avatars {
-       float: left;
-       margin: 6px 0 0 0;
-       padding: 0;
-       list-style-type: none;
-       width: 270px;
-       }
-#subscriptions_avatars li .avatar.mini {
-       float: left;
-       margin: 0 3px 3px 0;
-       padding: 0;
-       line-height: 0;
-       /* border: 1px solid #f00; */
-       }
-#subscriptions_viewall {
-       clear: left;
-       }
-/* ----- End Profile -----*/
-
-/* ----- Begin Subscriptions & Subscribers -----*/
-
-ul.subscriptions, ul.subscribers {
-       float: none;
-       margin: 0;
-       padding: 0;
-       list-style-type: none;
-       overflow: auto;
-       }
-ul.subscriptions li, ul.subscribers li {
-       display: block;
-       float: left;
-       padding: 0;
-       }
-/* ----- End Subscriptions & Subscribers -----*/
-
-
-
-#pagination {
-       margin: 18px auto;
-       width: 540px;
-       }
-#nav_pagination {
-       margin: 0 0 36px 0;
-       padding: 0;
-       float: right;
-       list-style-type: none;
-       font-size: 12px;
-       font-weight: bold;
-       }
-#nav_pagination li {
-       display: block;
-       float: left;
-       background-color: #91AA9D;
-       }
-#nav_pagination li.before {
-       margin-right: 1px;
-       }
-#nav_pagination li a {
-       padding: 6px 15px;
-       line-height: 21px;
-       background-color: #91AA9D;
-       color: #FCFFF5;
-       }
-#nav_pagination li a:hover {
-       background-color: #3F606F;
-       color: #FCFFF5;
-       text-decoration: none;
-       }
-
-#footer {
-       clear: both;
-       margin: 0 auto;
-       padding: 0 0 36px 0;
-       width: 540px;
-       border-top: 1px solid #D8E2D7;
-       }
-#footer p {
-       margin-top: 9px;
-       line-height: 12px;
-       }
-#cc {
-       float: left;
-       margin: 3px 10px 0 0;
-       }
-
-/* ===== Begin Forms Styling ===== */
-
-/* ----- Forms General Style ----- */
-form {
-       margin: 0 auto;
-       padding: 0;
-       }
-form {
-       font-family: Verdana, Arial, Helvetica, sans-serif;
-       font-size: 12px;
-       }
-form label {
-       display: block;
-       font-size: 12px;
-       font-weight: bold;
-       line-height: 18px;
-       }
-form input {
-       border: 1px solid #D8E2D7;
-       width: 264px;
-       }
-input#submit, input.submit  {
-       display: block;
-       margin: 18px 0;
-       padding: 4px;
-       font-weight: bold;
-       color: #FCFFF5;
-       background-color: #C15D42;
-       cursor: pointer;
-       border: 0;
-       width: auto;
-       }
-input#submit:hover, input.submit:hover {
-       background-color: #904632;
-       }
-input.checkbox {
-       /*width: 14px;
-       height: 14px;*/
-       width: auto;
-       border: 0;
-       }       
-textarea, input {
-       font-family: Verdana, Arial, Helvetica, sans-serif;
-       font-size: 12px;
-       color: #193441;
-       padding: 3px;
-       }
-textarea:focus, input:focus {
-       background-color: #f0f6eb;
-       }
-textarea {
-       width: 270px;
-       border: 1px solid #D8E2D7;
-       }
-.input_instructions {
-       margin-top: 3px;
-       display: block;
-       font-size: 11px;
-       line-height: 15px;
-       color: #91aa9d;
-       font-family: Verdana, Arial, Helvetica, sans-serif;
-       }
-
-/* ----- Status Form ----- */
-#status_form {
-       height: 96px;
-       /*background-color: #F00;*/
-       }
-#status_form p {
-       margin: 36px 0 0 0;
-       padding: 0;
-       }
-#status_label {
-       display: block;
-       clear: both;
-       margin: 0;
-       padding: 0 0 3px 0;
-       font-size: 18px;
-       font-weight: bold;
-       line-height: 24px;
-       color: #91AA9D;
-       }
-#status_textarea {
-       display: block;
-       float: left;
-       width: 463px;
-       height: 35px;
-       padding: 5px;
-       font-family: Verdana, Arial, Helvetica, sans-serif;
-       font-size: 12px;
-       color: #193441;
-       border: 0;
-       }
-#status_submit {
-       display: block;
-       float: left;
-       margin: 1px 0 0 4px;
-       width: 63px;
-       height: 45px;
-       background-color: #C15D42;
-       font-family: Verdana, Arial, Helvetica, sans-serif;
-       font-weight: bold;
-       font-size: 14px;
-       color: #FCFFF5;
-       cursor: pointer;
-       border: 0;
-       }
-#status_submit:hover {
-       background-color: #904632;
-       }
-#counter {
-       position: absolute;
-       top: 140px;
-       left: -64px;
-       width: 50px;
-       font-weight: bold;
-       text-align: right;
-}
-/* ----- Subscribe Form ----- */
-#subscribe .submit, #unsubscribe .submit, #remotesubscribe .button, #remotesubscribe {
-       clear: left;
-       margin: 0;
-       width: 96px;
-       height: 27px;
-       font-family: verdana, arial, helvetica, sans-serif;
-       font-weight: bold;
-       font-size: 10px;
-       text-transform: uppercase;
-       background-color: #c15d42;
-       color: #fcfff5;
-       border: 0;
-       }
-#remotesubscribe {
-       width: 96px;
-       height: 22px;
-       padding: 5px 0 0 0;
-       text-align: center;
-       }
-#subscribe .button:hover, #unsubscribe .button:hover {
-       background-color: #904632;
-       cursor: pointer;
-       }
-
-a#remotesubscribe {
-       display: block;
-}
-
-/* ----- Login Form -----*/
-input#license {
-       width: auto;
-       border: 0;
-       }
-/* ----- Avatar Form -----*/
-form {
-       clear: left;
-}
-
-/* ----- OpenID Form -----*/
-
-input#openid_url {
-   background: url(login-bg.gif) no-repeat;
-   background-color: #fff;
-   background-position: 4px 50%;
-   color: #000;
-   padding-left: 24px;
-}
-
-/* People lists (search results, maybe subscribers) */
-
-#profiles {
-       clear: both;
-       margin: 0 auto;
-       padding: 0;
-       list-style-type: none;
-       width: 540px;
-       border-top: 1px solid #D8E2D7;
-       /*border: 1px solid #F00;*/
-       }
-#profiles a:hover {
-       text-decoration: underline;
-       }
-       
-.profile_single {
-       clear: both;
-       display: block;
-       margin: 0;
-       padding: 5px 5px 5px 0;
-       min-height: 48px;
-       font-family: Georgia, "Times New Roman", Times, serif;
-       font-size: 13px;
-       line-height: 16px;
-       border-bottom: 1px solid #D8E2D7;
-       }
-.profile_single:hover {
-       background-color: #F3F8EA;
-       }
-
-/* ----- IM Settings Form -----*/
-
-#imsettings p {
-       margin: 0;
-       padding: 0;
-       line-height: 15px;
-}
-
-/* ===== End Forms Styling ===== */
-
-/* ===== Tag Cloud Styling ===== */
-
-p.tagcloud {
-text-align: center;
-}
-
-p.tagcloud a {
-line-height:100%;
-vertical-align:middle;
-}
-
-p.tagcloud a.largest {
-font-size: 400%;
-}
-p.tagcloud a.verylarge {
-font-size: 300%;
-}
-
-p.tagcloud a.large {
-font-size: 200%;
-}
-
-p.tagcloud a.medium {
-font-size: 150%;
-}
-
-p.tagcloud a.small {
-font-size: 100%;
-}
-
-p.tagcloud a.verysmall {
-font-size: 80%;
-}
-
-p.tagcloud a.smallest {
-font-size: 60%;
-}
diff --git a/theme/iphone/ie6.css.dist b/theme/iphone/ie6.css.dist
deleted file mode 100644 (file)
index 97d9fee..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-@charset "UTF-8";
-/* CSS Document */
-body {
-       text-align: center;
-}
-input {
-       height: 24px;
-}
-#wrap {
-       margin: 0 auto;
-       padding: 0 20px;
-       width: 800px;
-       text-align: left;
-       background: url(bg-header.gif) repeat-x #FCFFF5;
-       }
-#header {
-       position: relative;
-       margin-left: 108px;
-       }       
-#nav_views {
-       margin: 0;
-       }
-#nav_views li {
-       line-height: 19px;
-       }
-.statistics dd {
-       margin-top: -15px;
-       clear: both;
-       }       
-#notices {
-       margin: 0;
-       }                       
-.notice_single {
-       height: 48px;
-       }
-#profile p.notice_current {
-       height: 96px;
-       }       
-       
-#subscriptions_avatars li {
-       float: left;
-       margin: 0;
-       padding: 0;
-       }
-img.avatar.original, img.avatar.profile {
-       clear: none;
-       float: left;
-}              
-#status_textarea {
-       height: 46px;
-       }
-                       
-#nav_pagination li a {
-       padding: 6px 15px;
-       line-height: 27px;
-       }
-#nav_sub {
-       position: relative;
-       margin-left: 108px;
-       }       
-#footer {
-       margin-left: 108px;
-}
diff --git a/theme/iphone/ie7.css.dist b/theme/iphone/ie7.css.dist
deleted file mode 100644 (file)
index bbf52d5..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-@charset "UTF-8";
-/* CSS Document */
-
-#statistics dd {
-       clear: both;
-       }               
-       
-       
-#subscriptions_avatars li {
-       float: left;
-       }
-img.avatar.original, img.avatar.profile {
-       clear: none;
-       float: left;
-}
-                       
-#nav_pagination li a {
-       padding: 6px 15px;
-       line-height: 27px;
-       }
\ No newline at end of file
diff --git a/theme/iphone/login-bg.gif b/theme/iphone/login-bg.gif
deleted file mode 100644 (file)
index e2d8377..0000000
Binary files a/theme/iphone/login-bg.gif and /dev/null differ
diff --git a/theme/iphone/logo.png b/theme/iphone/logo.png
deleted file mode 100644 (file)
index 3b27181..0000000
Binary files a/theme/iphone/logo.png and /dev/null differ
index 151b1fb71cdc8111cf7c1a03646b08d65015bc57..d030f2db4dc1f0e31adab283c54105b3bf892f64 100644 (file)
@@ -1,7 +1,7 @@
 /** Howto: create a statusnet theme
  *
  * @package   StatusNet
- * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@status.net>
  * @copyright 2009 Control Yourself, Inc.
  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link      http://laconi.ca/