]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge commit 'refs/merge-requests/230' of git://gitorious.org/statusnet/mainline...
authorMikael Nordfeldth <mmn@hethane.se>
Mon, 12 Aug 2013 10:37:46 +0000 (12:37 +0200)
committerMikael Nordfeldth <mmn@hethane.se>
Mon, 12 Aug 2013 10:37:46 +0000 (12:37 +0200)
156 files changed:
EVENTS.txt
INSTALL
README
README.SOCIAL [new file with mode: 0644]
TODO.SOCIAL [new file with mode: 0644]
UPGRADE
actions/apisearchjson.php
actions/groupmembers.php
classes/Group_member.php
classes/Local_group.php
classes/Message.php
classes/Notice.php
classes/Profile.php
classes/Profile_tag.php
classes/Subscription.php
classes/User.php
classes/User_group.php
dc/better.png [new file with mode: 0644]
dc/dc.png [new file with mode: 0644]
dc/dclogo.png [new file with mode: 0644]
dc/done.html [new file with mode: 0644]
dc/getinvolved.png [new file with mode: 0644]
dc/idea.png [new file with mode: 0644]
dc/index.html [new file with mode: 0644]
dc/list.html [new file with mode: 0644]
dc/offer.png [new file with mode: 0644]
dc/privacy.png [new file with mode: 0644]
dc/signup.html [new file with mode: 0644]
dc/signup.png [new file with mode: 0644]
dc/signup2.html [new file with mode: 0644]
dc/youandus.png [new file with mode: 0644]
doc-src/about
doc-src/faq
extlib/DB/DataObject.php [changed mode: 0644->0755]
extlib/DB/DataObject/Cast.php [changed mode: 0644->0755]
extlib/DB/DataObject/Error.php [changed mode: 0644->0755]
extlib/DB/DataObject/Generator.php [changed mode: 0644->0755]
extlib/DB/DataObject/Links.php [new file with mode: 0644]
extlib/DB/DataObject/createTables.php [changed mode: 0644->0755]
lib/accountprofileblock.php
lib/action.php
lib/activity.php
lib/activityobject.php
lib/defaultprofileblock.php
lib/filenoticestream.php
lib/framework.php
lib/groupprofileblock.php
lib/jsonsearchresultslist.php
lib/profileblock.php
lib/siteprofile.php
lib/useractivitystream.php
locale/ca/LC_MESSAGES/statusnet.po
plugins/ActivitySpam/scripts/silencespammer.php [new file with mode: 0644]
plugins/FacebookBridge/FacebookBridgePlugin.php
plugins/FacebookBridge/images/f_logo.png [new file with mode: 0644]
plugins/FacebookBridge/locale/it/LC_MESSAGES/FacebookBridge.po
plugins/GNUsocialDukiDukiSnail/GNUsocialDukiDukiSnail.php [new file with mode: 0644]
plugins/GNUsocialDukiDukiSnail/README [new file with mode: 0644]
plugins/GNUsocialLayout/GNUsocialLayout.php [new file with mode: 0644]
plugins/GNUsocialOffTheRecordMessaging/GNUsocialOffTheRecordMessaging.php [new file with mode: 0644]
plugins/GNUsocialPhoto/GNUsocialPhotoPlugin.php [new file with mode: 0644]
plugins/GNUsocialPhoto/Photo.php [new file with mode: 0644]
plugins/GNUsocialPhoto/newphoto.php [new file with mode: 0644]
plugins/GNUsocialPhoto/newphotoform.php [new file with mode: 0644]
plugins/GNUsocialPhoto/showphoto.php [new file with mode: 0644]
plugins/GNUsocialPhotos/GNUsocialPhotosPlugin.php [new file with mode: 0644]
plugins/GNUsocialPhotos/actions/editphoto.php [new file with mode: 0644]
plugins/GNUsocialPhotos/actions/photo.php [new file with mode: 0644]
plugins/GNUsocialPhotos/actions/photos.php [new file with mode: 0644]
plugins/GNUsocialPhotos/actions/photoupload.php [new file with mode: 0644]
plugins/GNUsocialPhotos/classes/gnusocialphoto.php [new file with mode: 0644]
plugins/GNUsocialPhotos/classes/gnusocialphotoalbum.php [new file with mode: 0644]
plugins/GNUsocialPhotos/lib/photolib.php [new file with mode: 0644]
plugins/GNUsocialPhotos/lib/photonav.php [new file with mode: 0644]
plugins/GNUsocialPhotos/lib/tempphoto.php [new file with mode: 0644]
plugins/GNUsocialPhotos/res/gnusocialphotos.js [new file with mode: 0644]
plugins/GNUsocialPhotos/res/style.css [new file with mode: 0644]
plugins/GNUsocialProfileExtensions/GNUsocialProfileExtensionsPlugin.php [new file with mode: 0644]
plugins/GNUsocialProfileExtensions/README.txt [new file with mode: 0644]
plugins/GNUsocialProfileExtensions/actions/bio.php [new file with mode: 0644]
plugins/GNUsocialProfileExtensions/actions/newresponse.php [new file with mode: 0644]
plugins/GNUsocialProfileExtensions/actions/profilefields.php [new file with mode: 0644]
plugins/GNUsocialProfileExtensions/classes/GNUsocialProfileExtensionField.php [new file with mode: 0644]
plugins/GNUsocialProfileExtensions/classes/GNUsocialProfileExtensionResponse.php [new file with mode: 0644]
plugins/GNUsocialProfileExtensions/classes/ProfileExtensionField.php [new file with mode: 0644]
plugins/GNUsocialProfileExtensions/js/profile.js [new file with mode: 0644]
plugins/GNUsocialProfileExtensions/lib/noticetree.php [new file with mode: 0644]
plugins/GNUsocialProfileExtensions/lib/profiletools.php [new file with mode: 0644]
plugins/GNUsocialProfileExtensions/res/bgstripe.gif [new file with mode: 0644]
plugins/GNUsocialProfileExtensions/res/style.css [new file with mode: 0644]
plugins/GNUsocialRelationshipsManager/GNUsocialRelationshipsManager.php [new file with mode: 0644]
plugins/GNUsocialTemplatePlugin.php [new file with mode: 0644]
plugins/GNUsocialVideo/GNUsocialVideoPlugin.php [new file with mode: 0644]
plugins/GNUsocialVideo/Video.php [new file with mode: 0644]
plugins/GNUsocialVideo/actions/postvideo.php [new file with mode: 0644]
plugins/GNUsocialVideo/showvideo.php [new file with mode: 0644]
plugins/GNUsocialVideo/videoform.php [new file with mode: 0644]
plugins/ModLog/ModLog.php [new file with mode: 0644]
plugins/ModLog/ModLogPlugin.php [new file with mode: 0644]
plugins/OStatus/actions/ostatusgroup.php
plugins/OStatus/actions/ostatusinit.php
plugins/OStatus/scripts/update_ostatus_profiles.php
plugins/OpenID/OpenIDPlugin.php
plugins/OpenID/User_openid_prefs.php [new file with mode: 0644]
plugins/OpenID/doc-src/openid
plugins/OpenID/icons/openid-16x16.gif [new file with mode: 0644]
plugins/OpenID/openidsettings.php
plugins/Poll/PollPlugin.php
plugins/Poll/User_poll_prefs.php [new file with mode: 0644]
plugins/Poll/pollsettings.php [new file with mode: 0644]
plugins/Realtime/RealtimePlugin.php
plugins/TwitterBridge/TwitterBridgePlugin.php
plugins/TwitterBridge/icons/twitter-bird-white-on-blue.png [new file with mode: 0644]
plugins/TwitterBridge/twitterstreamreader.php
scripts/backupuser.php
scripts/createsim.php
scripts/install_cli.php
socialfy-your-domain/README.txt [new file with mode: 0644]
socialfy-your-domain/dot-well-known/host-meta [new file with mode: 0644]
socialfy-your-domain/xrd/example@example.com [new file with mode: 0644]
socialfy-your-domain/xrd/index.php [new file with mode: 0644]
theme/GNUSocial/css/default.css [new file with mode: 0644]
theme/base/css/display.css
theme/gnusocial/css/combo.css [new file with mode: 0644]
theme/gnusocial/css/debug.css [new file with mode: 0644]
theme/gnusocial/css/display.css [new file with mode: 0644]
theme/gnusocial/css/social.css [new file with mode: 0755]
theme/gnusocial/default-avatar-mini.png [new file with mode: 0755]
theme/gnusocial/default-avatar-profile.png [new file with mode: 0755]
theme/gnusocial/default-avatar-stream.png [new file with mode: 0755]
theme/gnusocial/images/bg.png [new file with mode: 0644]
theme/gnusocial/images/fback.png [new file with mode: 0644]
theme/gnusocial/images/icons/icon_atom.png [new file with mode: 0755]
theme/gnusocial/images/icons/icon_disfavourite.gif [new file with mode: 0755]
theme/gnusocial/images/icons/icon_favourite.gif [new file with mode: 0755]
theme/gnusocial/images/icons/icon_foaf.gif [new file with mode: 0755]
theme/gnusocial/images/icons/icon_processing.gif [new file with mode: 0755]
theme/gnusocial/images/icons/icon_reply.gif [new file with mode: 0755]
theme/gnusocial/images/icons/icon_rss.png [new file with mode: 0755]
theme/gnusocial/images/icons/icon_trash.gif [new file with mode: 0755]
theme/gnusocial/images/icons/icon_vcard.gif [new file with mode: 0755]
theme/gnusocial/images/icons/twotone/green/arrow-left.gif [new file with mode: 0755]
theme/gnusocial/images/icons/twotone/green/arrow-right.gif [new file with mode: 0755]
theme/gnusocial/images/icons/twotone/green/edit.gif [new file with mode: 0755]
theme/gnusocial/images/icons/twotone/green/mail.gif [new file with mode: 0755]
theme/gnusocial/images/icons/twotone/green/news.gif [new file with mode: 0755]
theme/gnusocial/images/icons/twotone/green/quote.gif [new file with mode: 0755]
theme/gnusocial/images/icons/twotone/green/shield.gif [new file with mode: 0755]
theme/gnusocial/images/illustrations/illu_arrow-up-01.gif [new file with mode: 0755]
theme/gnusocial/images/illustrations/illu_clouds-01.gif [new file with mode: 0755]
theme/gnusocial/images/illustrations/illu_jcrop.gif [new file with mode: 0755]
theme/gnusocial/images/illustrations/illu_progress_loading-01.gif [new file with mode: 0755]
theme/gnusocial/images/illustrations/illu_unicorn-01.png [new file with mode: 0755]
theme/gnusocial/index.html [new file with mode: 0644]
theme/gnusocial/logo.png [new file with mode: 0755]
tpl/social.php [new file with mode: 0644]

index 0c08a46478b26a3bd784544bd5b30cd89e98bc86..49940e467fbc88d1b5309200c5a5f6a261840494 100644 (file)
@@ -1450,3 +1450,9 @@ EndNoticeListPrefill: After pre-filling a list of notices with extra data
 - &$profiles: Profiles that were pre-filled 
 - $avatarSize: The avatar size for the list
 
+OtherAccountProfiles: Hook to add account profiles to a user account profile block
+- $profile: the Profile being shown
+- &$others: Modifiable array of profile info arrays. Each one has the following fields:
+            href: link to the profile
+            text: text for the profile
+            image: mini image for the profile
diff --git a/INSTALL b/INSTALL
index 75237fa1fe97db747dc6da6e75d2c0834404f63f..2508acd7e5d71d7383d37cf0f853849622b2f32e 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -106,9 +106,9 @@ especially if you've previously installed PHP/MySQL packages.
 1. Unpack the tarball you downloaded on your Web server. Usually a
    command like this will work:
 
-       tar zxf statusnet-1.1.0-alpha1.tar.gz
+       tar zxf statusnet-1.1.1.tar.gz
 
-   ...which will make a statusnet-1.1.0-alpha1 subdirectory in your current
+   ...which will make a statusnet-1.1.1 subdirectory in your current
    directory. (If you don't have shell access on your Web server, you
    may have to unpack the tarball on your local computer and FTP the
    files to the server.)
@@ -116,7 +116,7 @@ especially if you've previously installed PHP/MySQL packages.
 2. Move the tarball to a directory of your choosing in your Web root
    directory. Usually something like this will work:
 
-       mv statusnet-1.1.0-alpha1 /var/www/statusnet
+       mv statusnet-1.1.1 /var/www/statusnet
 
    This will make your StatusNet instance available in the statusnet path of
    your server, like "http://example.net/statusnet". "microblog" or
diff --git a/README b/README
index b9ffcbaddbbab375ad449557c3f79ca716f0e5b3..3999bffbcece03cee62beda7f88427997783f9ba 100644 (file)
--- a/README
+++ b/README
@@ -2,8 +2,8 @@
 README
 ------
 
-StatusNet 1.1.0
-2 July 2012
+StatusNet 1.1.1
+16 July 2013
 
 This is the README file for StatusNet, the Open Source social
 networking platform. It includes general information about the
@@ -107,23 +107,19 @@ for additional terms.
 New this version
 ================
 
-This is a minor bug fix and feature release since 1.0.1, released 3
-October 2011. (Because the plugin interface has changed in an upwardly
-compatible way, we've incremented the minor version number. See
-http://semver.org/ for the semantic versioning scheme we follow.)
+This is a security fix and bug fix release since 1.1.0,
+released 2 July 2012. All 1.1.0 sites should upgrade to this version.
 
 It includes the following changes:
 
-- ActivitySpam plugin to check updates against spamicity.info
-- Options to hide users who've been silenced or have posted spammy updates.
-- OfflineBackup experimental plugin.
-- Fixes for TwitterBridge to correctly handle replies through the bridge.
-- Improvements in ActivityStreams JSON output to better match 1.0 spec.
-- Console scripts for managing groups.
-- Bug fix for conversation counts in conversation streams.
-- Rights for moderators to manage spam.
+- Fixes for SQL injection errors in profile lists.
+- Improved ActivityStreams JSON representation of activities and objects.
+- Upgrade to the Twitter 1.1 API.
+- More robust handling of errors in distribution.
+- Fix error in OStatus subscription for remote groups.
+- Fix error in XMPP distribution.
 
-A full changelog is available at http://status.net/wiki/StatusNet_1.1.0.
+A full changelog is available at http://status.net/wiki/StatusNet_1.1.1.
 
 Troubleshooting
 ===============
diff --git a/README.SOCIAL b/README.SOCIAL
new file mode 100644 (file)
index 0000000..3394dca
--- /dev/null
@@ -0,0 +1,23 @@
+This is GNU social, a free software, decentralized social network,
+based on StatusNet.
+
+Developers
+==========
+
+Matt Lee
+Rob Myers
+Sean Corbett
+Ian Denhardt
+Steve DuBois
+Mike Sheldon
+
+With help from
+==============
+
+Bradley M. Kuhn
+
+Special help from
+=================
+
+Craig Andrews
+
diff --git a/TODO.SOCIAL b/TODO.SOCIAL
new file mode 100644 (file)
index 0000000..4d4866f
--- /dev/null
@@ -0,0 +1,12 @@
+Things to be done
+=================
+
+* Create a theme for GNU social
+
+* Create a set of plugins to give StatusNet a more social-network UI
+
+* Work on improvements for annoying things in StatusNet (ie. no
+  redirect to login page when you need to be logged in, etc)
+
+* Work on adding further Activities, such as sharing photos/video,
+  events, UI for managing relationships.
\ No newline at end of file
diff --git a/UPGRADE b/UPGRADE
index c50e7622ff5d35bdea84349b72a5fa22559dd7ec..2ae96e0dae06a2351b9a347ce795d69e001e5dc7 100644 (file)
--- a/UPGRADE
+++ b/UPGRADE
@@ -24,7 +24,7 @@ instructions; read to the end first before trying them.
 5. Once all writing processes to your site are turned off, make a
    final backup of the Web directory and database.
 6. Move your StatusNet directory to a backup spot, like "statusnet.bak".
-7. Unpack your StatusNet 1.1.0-alpha1 tarball and move it to "statusnet" or
+7. Unpack your StatusNet 1.1.1 tarball and move it to "statusnet" or
    wherever your code used to be.
 8. Copy the config.php file and the contents of the avatar/, background/,
    file/, and local/ subdirectories from your old directory to your new
@@ -37,7 +37,7 @@ instructions; read to the end first before trying them.
     reversed. YOU CAN EASILY DESTROY YOUR SITE WITH THIS STEP. Don't
     do it without a known-good backup!
 
-    In your new StatusNet 1.1.0-alpha1 directory and AFTER YOU MAKE A
+    In your new StatusNet 1.1.1 directory and AFTER YOU MAKE A
     BACKUP run the upgrade.php script like this:
 
         php ./scripts/upgrade.php
index 79b26665541bb2b09364019c34acca81839ecbc3..612dbdda5c78abcaa5123c16e81bd083016f8908 100644 (file)
@@ -89,6 +89,12 @@ class ApiSearchJSONAction extends ApiPrivateAuthAction
         $this->since_id = $this->trimmed('since_id');
         $this->geocode  = $this->trimmed('geocode');
 
+        if (!empty($this->auth_user)) {
+            $this->auth_profile = $this->auth_user->getProfile();
+        } else {
+            $this->auth_profile = null;
+        }
+
         return true;
     }
 
@@ -112,20 +118,27 @@ class ApiSearchJSONAction extends ApiPrivateAuthAction
      */
     function showResults()
     {
-        // TODO: Support search operators like from: and to:, boolean, etc.
+        $q = strtolower($this->query);
 
-        $notice = new Notice();
+        // TODO: Support search operators like from: and to:, boolean, etc.
 
-        // lcase it for comparison
-        $q = strtolower($this->query);
+        if (preg_match('/^#([\pL\pN_\-\.]{1,64})$/ue', $q)) {
+            $stream = new TagNoticeStream(substr($q, 1), $this->auth_profile);
+        } else if ($this->isAnURL($q)) {
+            $canon = File_redirection::_canonUrl($q);
+            $file = File::staticGet('url', $canon);
+            if (!empty($file)) {
+                $stream = new FileNoticeStream($file, $this->auth_profile);
+            }
+        } else {
+            $stream = new SearchNoticeStream($q, $this->auth_profile);
+        }
 
-        $search_engine = $notice->getSearchEngine('notice');
-        $search_engine->set_sort_mode('chron');
-        $search_engine->limit(($this->page - 1) * $this->rpp, $this->rpp + 1, true);
-        if (false === $search_engine->query($q)) {
-            $cnt = 0;
+        if (empty($stream)) {
+            // XXX: This is hackish, but need some simple way to say "There's no results"
+            $notice = new ArrayWrapper(array());
         } else {
-            $cnt = $notice->find();
+            $notice = $stream->getNotices(($this->page - 1) * $this->rpp, $this->rpp + 1);
         }
 
         // TODO: max_id, lang, geocode
@@ -137,6 +150,47 @@ class ApiSearchJSONAction extends ApiPrivateAuthAction
         $this->endDocument('json');
     }
 
+    function isAnURL($q) {
+        $regex = '#^'.
+            '(?:^|[\s\<\>\(\)\[\]\{\}\\\'\\\";]+)(?![\@\!\#])'.
+            '('.
+            '(?:'.
+            '(?:'. //Known protocols
+            '(?:'.
+            '(?:(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|irc)://)'.
+            '|'.
+            '(?:(?:mailto|aim|tel|xmpp):)'.
+            ')'.
+            '(?:[\pN\pL\-\_\+\%\~]+(?::[\pN\pL\-\_\+\%\~]+)?\@)?'. //user:pass@
+            '(?:'.
+            '(?:'.
+            '\[[\pN\pL\-\_\:\.]+(?<![\.\:])\]'. //[dns]
+            ')|(?:'.
+            '[\pN\pL\-\_\:\.]+(?<![\.\:])'. //dns
+            ')'.
+            ')'.
+            ')'.
+            '|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'. //IPv4
+            '|(?:'. //IPv6
+            '\[?(?:(?:(?:[0-9A-Fa-f]{1,4}:){7}(?:(?:[0-9A-Fa-f]{1,4})|:))|(?:(?:[0-9A-Fa-f]{1,4}:){6}(?::|(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})|(?::[0-9A-Fa-f]{1,4})))|(?:(?:[0-9A-Fa-f]{1,4}:){5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){4}(?::[0-9A-Fa-f]{1,4}){0,1}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){3}(?::[0-9A-Fa-f]{1,4}){0,2}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){2}(?::[0-9A-Fa-f]{1,4}){0,3}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:)(?::[0-9A-Fa-f]{1,4}){0,4}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?::(?::[0-9A-Fa-f]{1,4}){0,5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))\]?(?<!:)'.
+            ')|(?:'. //DNS
+            '(?:[\pN\pL\-\_\+\%\~]+(?:\:[\pN\pL\-\_\+\%\~]+)?\@)?'. //user:pass@
+            '[\pN\pL\-\_]+(?:\.[\pN\pL\-\_]+)*\.'.
+            //tld list from http://data.iana.org/TLD/tlds-alpha-by-domain.txt, also added local, loc, and onion
+            '(?:AC|AD|AE|AERO|AF|AG|AI|AL|AM|AN|AO|AQ|AR|ARPA|AS|ASIA|AT|AU|AW|AX|AZ|BA|BB|BD|BE|BF|BG|BH|BI|BIZ|BJ|BM|BN|BO|BR|BS|BT|BV|BW|BY|BZ|CA|CAT|CC|CD|CF|CG|CH|CI|CK|CL|CM|CN|CO|COM|COOP|CR|CU|CV|CX|CY|CZ|DE|DJ|DK|DM|DO|DZ|EC|EDU|EE|EG|ER|ES|ET|EU|FI|FJ|FK|FM|FO|FR|GA|GB|GD|GE|GF|GG|GH|GI|GL|GM|GN|GOV|GP|GQ|GR|GS|GT|GU|GW|GY|HK|HM|HN|HR|HT|HU|ID|IE|IL|IM|IN|INFO|INT|IO|IQ|IR|IS|IT|JE|JM|JO|JOBS|JP|KE|KG|KH|KI|KM|KN|KP|KR|KW|KY|KZ|LA|LB|LC|LI|LK|LR|LS|LT|LU|LV|LY|MA|MC|MD|ME|MG|MH|MIL|MK|ML|MM|MN|MO|MOBI|MP|MQ|MR|MS|MT|MU|MUSEUM|MV|MW|MX|MY|MZ|NA|NAME|NC|NE|NET|NF|NG|NI|NL|NO|NP|NR|NU|NZ|OM|ORG|PA|PE|PF|PG|PH|PK|PL|PM|PN|PR|PRO|PS|PT|PW|PY|QA|RE|RO|RS|RU|RW|SA|SB|SC|SD|SE|SG|SH|SI|SJ|SK|SL|SM|SN|SO|SR|ST|SU|SV|SY|SZ|TC|TD|TEL|TF|TG|TH|TJ|TK|TL|TM|TN|TO|TP|TR|TRAVEL|TT|TV|TW|TZ|UA|UG|UK|US|UY|UZ|VA|VC|VE|VG|VI|VN|VU|WF|WS|XN--0ZWM56D|测试|XN--11B5BS3A9AJ6G|परीक्षा|XN--80AKHBYKNJ4F|испытание|XN--9T4B11YI5A|테스트|XN--DEBA0AD|טעסט|XN--G6W251D|測試|XN--HGBK6AJ7F53BBA|آزمایشی|XN--HLCJ6AYA9ESC7A|பரிட்சை|XN--JXALPDLP|δοκιμή|XN--KGBECHTV|إختبار|XN--ZCKZAH|テスト|YE|YT|YU|ZA|ZM|ZW|local|loc|onion)'.
+            ')(?![\pN\pL\-\_])'.
+            ')'.
+            '(?:'.
+            '(?:\:\d+)?'. //:port
+            '(?:/[\pN\pL$\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'@]*)?'. // /path
+            '(?:\?[\pN\pL\$\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'@\/]*)?'. // ?query string
+            '(?:\#[\pN\pL$\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\@/\?\#]*)?'. // #fragment
+            ')(?<![\?\.\,\#\,])'.
+            ')'.
+            '$#ixu';
+        return preg_match($regex, $q);
+    }
+
     /**
      * Do we need to write to the database?
      *
index 088b171956f560464b33e41085516b7c996f0cc2..52979101fd25e337a09e92c58285f02c1fac733c 100644 (file)
@@ -104,8 +104,6 @@ class GroupmembersAction extends GroupAction
             $cnt = $member_list->show();
         }
 
-        $members->free();
-
         $this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE,
                           $this->page, 'groupmembers',
                           array('nickname' => $this->group->nickname));
index 8873942318b04329512c6b5c8c184ab9365e5e59..d51aa9ff9c1cf5d89a21af01e2586945b1475e94 100644 (file)
@@ -161,8 +161,17 @@ class Group_member extends Managed_DataObject
     function asActivity()
     {
         $member = $this->getMember();
+
+        if (!$member) {
+            throw new Exception("No such member: " . $this->profile_id);
+        }
+
         $group  = $this->getGroup();
 
+        if (!$group) {
+            throw new Exception("No such group: " . $this->group_id);
+        }
+
         $act = new Activity();
 
         $act->id = $this->getURI();
index 44d8957838f0823772bc59abb4ce5875b2fe6ec4..b895103d1675452a7c59da35635dcb6c71523d83 100644 (file)
@@ -44,7 +44,7 @@ class Local_group extends Managed_DataObject
     function setNickname($nickname)
     {
         $this->decache();
-        $qry = 'UPDATE local_group set nickname = "'.$nickname.'" where group_id = ' . $this->group_id;
+        $qry = 'UPDATE local_group set nickname = "'.$this->escape($nickname).'" where group_id = ' . $this->group_id;
 
         $result = $this->query($qry);
 
index d19d3f3c8064279279f76b3363bf175b653be8d5..7fe626ec577524aea78e7af1321c87ce192aecc0 100644 (file)
@@ -187,7 +187,7 @@ class Message extends Managed_DataObject
             }
             
             $act->actor            = ActivityObject::fromProfile($profile);
-            $act->actor->extra[]   = $profile->profileInfo();
+            $act->actor->extra[]   = $profile->profileInfo(null);
 
             $act->verb = ActivityVerb::POST;
 
index 7516e146b2172b6cd8ffc4f6a55b94ed63d1962d..462ce8b53caf63ff90b8b53c5df89469b91d7cff 100644 (file)
@@ -1031,9 +1031,15 @@ class Notice extends Managed_DataObject
             }
 
             foreach ($ni as $id => $source) {
-                $user = User::staticGet('id', $id);
-                if (empty($user) || $user->hasBlocked($profile) ||
-                    ($originalProfile && $user->hasBlocked($originalProfile))) {
+                try {
+                    $user = User::staticGet('id', $id);
+                    if (empty($user) ||
+                        $user->hasBlocked($profile) ||
+                        ($originalProfile && $user->hasBlocked($originalProfile))) {
+                        unset($ni[$id]);
+                    }
+                } catch (UserNoProfileException $e) {
+                    // User doesn't have a profile; invalid; skip them.
                     unset($ni[$id]);
                 }
             }
@@ -1487,7 +1493,7 @@ class Notice extends Managed_DataObject
      * @return Activity activity object representing this Notice.
      */
 
-    function asActivity($cur)
+    function asActivity($cur=null)
     {
         $act = self::cacheGet(Cache::codeKey('notice:as-activity:'.$this->id));
 
index cefcaf90b5b6a31823163bf27cfd50c8dd873ff6..8a5be5f358b58916662310e25e341cb702d8e183 100644 (file)
@@ -595,6 +595,8 @@ class Profile extends Managed_DataObject
             if (Event::handle('StartJoinGroup', array($group, $this))) {
                 $join = Group_member::join($group->id, $this->id);
                 self::blow('profile:groups:%d', $this->id);
+                self::blow('group:member_ids:%d', $group->id);
+                self::blow('group:member_count:%d', $group->id);
                 Event::handle('EndJoinGroup', array($group, $this));
             }
         }
@@ -615,6 +617,8 @@ class Profile extends Managed_DataObject
         if (Event::handle('StartLeaveGroup', array($group, $this))) {
             Group_member::leave($group->id, $this->id);
             self::blow('profile:groups:%d', $this->id);
+            self::blow('group:member_ids:%d', $group->id);
+            self::blow('group:member_count:%d', $group->id);
             Event::handle('EndLeaveGroup', array($group, $this));
         }
     }
@@ -682,7 +686,7 @@ class Profile extends Managed_DataObject
         $profile = new Profile();
         $tagged = array();
 
-        $cnt = $profile->query(sprintf($qry, $this->id, $this->id, $tag));
+        $cnt = $profile->query(sprintf($qry, $this->id, $this->id, $profile->escape($tag)));
 
         while ($profile->fetch()) {
             $tagged[] = clone($profile);
index de91857eb878dc66eef896222b76f16495705259..75cca8c22c364e169967a41d749fb19566036391 100644 (file)
@@ -284,8 +284,11 @@ class Profile_tag extends Managed_DataObject
                'tag = "%s", tagger = "%s" ' .
                'WHERE tag = "%s" ' .
                'AND tagger = "%s"';
-        $result = $tags->query(sprintf($qry, $new->tag, $new->tagger,
-                                             $orig->tag, $orig->tagger));
+        $result = $tags->query(sprintf($qry,
+                                       $tags->escape($new->tag),
+                                       $tags->escape($new->tagger),
+                                       $tags->escape($orig->tag),
+                                       $tags->escape($orig->tagger)));
 
         if (!$result) {
             common_log_db_error($tags, 'UPDATE', __FILE__);
@@ -307,8 +310,8 @@ class Profile_tag extends Managed_DataObject
         $profile->query('SELECT profile.* ' .
                         'FROM profile JOIN profile_tag ' .
                         'ON profile.id = profile_tag.tagged ' .
-                        'WHERE profile_tag.tagger = ' . $tagger . ' ' .
-                        'AND profile_tag.tag = "' . $tag . '" ');
+                        'WHERE profile_tag.tagger = ' . $profile->escape($tagger) . ' ' .
+                        'AND profile_tag.tag = "' . $profile->escape($tag) . '" ');
         $tagged = array();
         while ($profile->fetch()) {
             $tagged[] = clone($profile);
index f40239989c7cd7674b7d4a954325dc546d65f700..70ccd724be66ee4f49679a528c01a2b7c20a8fdb 100644 (file)
@@ -67,7 +67,7 @@ class Subscription extends Managed_DataObject
                 'subscription_token_idx' => array('token'),
             ),
         );
-    }    
+    }
 
     /* Static get */
     function staticGet($k,$v=null)
@@ -262,6 +262,14 @@ class Subscription extends Managed_DataObject
         $subscriber = Profile::staticGet('id', $this->subscriber);
         $subscribed = Profile::staticGet('id', $this->subscribed);
 
+        if (empty($subscriber)) {
+            throw new Exception(sprintf(_('No profile for the subscriber: %d'), $this->subscriber));
+        }
+
+        if (empty($subscribed)) {
+            throw new Exception(sprintf(_('No profile for the subscribed: %d'), $this->subscribed));
+        }
+
         $act = new Activity();
 
         $act->verb = ActivityVerb::FOLLOW;
index 8d21d2bc19f305b3e5a5d9946f99cd8df368e986..e8aaaf6a103c47d1d26a420a59b08b3354b2a67d 100644 (file)
@@ -736,7 +736,7 @@ class User extends Managed_DataObject
 
         $profile = new Profile();
 
-        $cnt = $profile->query(sprintf($qry, $this->id, $tag));
+        $cnt = $profile->query(sprintf($qry, $this->id, $profile->escape($tag)));
 
         return $profile;
     }
@@ -758,7 +758,7 @@ class User extends Managed_DataObject
 
         $profile = new Profile();
 
-        $profile->query(sprintf($qry, $this->id, $tag));
+        $profile->query(sprintf($qry, $this->id, $profile->escape($tag)));
 
         return $profile;
     }
index 0f1cc40b28f38e42a50b4e93323144962a40a021..406537dd8a40f32a84d34ee4c034ae66b173c29e 100644 (file)
@@ -7,6 +7,7 @@ class User_group extends Managed_DataObject
 {
     const JOIN_POLICY_OPEN = 0;
     const JOIN_POLICY_MODERATE = 1;
+    const CACHE_WINDOW = 201;
 
     ###START_AUTOCODE
     /* the code below is auto generated do not remove the above tag */
@@ -141,27 +142,52 @@ class User_group extends Managed_DataObject
         return !in_array($nickname, $blacklist);
     }
 
-    function getMembers($offset=0, $limit=null)
+    function getMembers($offset=0, $limit=null) {
+        $ids = null;
+        if (is_null($limit) || $offset + $limit > User_group::CACHE_WINDOW) {
+            $ids = $this->getMemberIDs($offset,
+                                       $limit);
+        } else {
+            $key = sprintf('group:member_ids:%d', $this->id);
+            $window = self::cacheGet($key);
+            if ($window === false) {
+                $window = $this->getMemberIDs(0,
+                                              User_group::CACHE_WINDOW);
+                self::cacheSet($key, $window);
+            }
+
+            $ids = array_slice($window,
+                               $offset,
+                               $limit);
+        }
+
+        return Profile::multiGet('id', $ids);
+    }
+
+    function getMemberIDs($offset=0, $limit=null)
     {
-        $qry =
-          'SELECT profile.* ' .
-          'FROM profile JOIN group_member '.
-          'ON profile.id = group_member.profile_id ' .
-          'WHERE group_member.group_id = %d ' .
-          'ORDER BY group_member.created DESC ';
+        $gm = new Group_member();
 
-        if ($limit != null) {
-            if (common_config('db','type') == 'pgsql') {
-                $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
-            } else {
-                $qry .= ' LIMIT ' . $offset . ', ' . $limit;
-            }
+        $gm->selectAdd();
+        $gm->selectAdd('profile_id');
+
+        $gm->group_id = $this->id;
+
+        $gm->orderBy('created DESC');
+
+        if (!is_null($limit)) {
+            $gm->limit($offset, $limit);
         }
 
-        $members = new Profile();
+        $ids = array();
 
-        $members->query(sprintf($qry, $this->id));
-        return $members;
+        if ($gm->find()) {
+            while ($gm->fetch()) {
+                $ids[] = $gm->profile_id;
+            }
+        }
+
+        return $ids;
     }
 
     /**
@@ -196,17 +222,24 @@ class User_group extends Managed_DataObject
 
     function getMemberCount()
     {
-        // XXX: WORM cache this
+        $key = sprintf("group:member_count:%d", $this->id);
 
-        $members = $this->getMembers();
-        $member_count = 0;
+        $cnt = self::cacheGet($key);
 
-        /** $member->count() doesn't work. */
-        while ($members->fetch()) {
-            $member_count++;
+        if (is_integer($cnt)) {
+            return (int) $cnt;
         }
 
-        return $member_count;
+        $mem = new Group_member();
+        $mem->group_id = $this->id;
+
+        // XXX: why 'distinct'?
+
+        $cnt = (int) $mem->count('distinct profile_id');
+
+        self::cacheSet($key, $cnt);
+
+        return $cnt;
     }
 
     function getBlockedCount()
diff --git a/dc/better.png b/dc/better.png
new file mode 100644 (file)
index 0000000..f6b5280
Binary files /dev/null and b/dc/better.png differ
diff --git a/dc/dc.png b/dc/dc.png
new file mode 100644 (file)
index 0000000..773b872
Binary files /dev/null and b/dc/dc.png differ
diff --git a/dc/dclogo.png b/dc/dclogo.png
new file mode 100644 (file)
index 0000000..6bb75f5
Binary files /dev/null and b/dc/dclogo.png differ
diff --git a/dc/done.html b/dc/done.html
new file mode 100644 (file)
index 0000000..41cd23d
--- /dev/null
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+@import url('http://s.libre.fm/combo.css');
+html {margin: 0; padding: 0;}
+body {width: 480px; margin: 70px auto; text-align: left; font-size: 110%; }
+
+img {border: 0;}
+
+p {text-align: left; font-weight: normal; line-height: 1.6em; color: #111;}
+
+li {line-height: 1.4em; margin-bottom: 1em;}
+
+ul {margin: 0; padding-left: 1em;}
+
+h1, h2, h3 {text-align: center;}
+
+#privacy {background-color: #ffb; padding: 10px; border: 2px solid orange; margin-bottom: 1em; }
+
+#signup {text-align: center;}
+
+#allset {background-color: yellow; padding: 5px;}
+
+</style>
+<title>daisycha.in &mdash; your social circle</title>
+</head>
+<body>
+<h1><a href="/"><img src="dclogo.png" alt="daisycha.in" height="58" /></a></h1>
+
+<p id="allset">You're all set... please go check your email and click the link we
+sent you.</p>
+
+<h2><img src="getinvolved.png" alt="Get involved:" /></h2>
+
+<ul>
+  <li><a href="list.html">Join our mailing list</a> for updates and news.</li>
+  <li><a href="http://social.shapado.com">Send us your feedback, ideas and suggestions</a>.</li>
+  <li><a href="http://gitorious.org/+socialites">Check out our software</a> and get <a href="http://lists.gnu.org/mailman/listinfo/social">on the mailing list for
+  developers and artists</a>.</li>
+</ul>
+
+
+<p><small>FOO300 &mdash; &copy; 2010 <a href="http://foocorp.net/">Foo Communications, LLC</a></small></p>
+
+</body>
+</html>
diff --git a/dc/getinvolved.png b/dc/getinvolved.png
new file mode 100644 (file)
index 0000000..8c2c0fb
Binary files /dev/null and b/dc/getinvolved.png differ
diff --git a/dc/idea.png b/dc/idea.png
new file mode 100644 (file)
index 0000000..def6895
Binary files /dev/null and b/dc/idea.png differ
diff --git a/dc/index.html b/dc/index.html
new file mode 100644 (file)
index 0000000..6c8e4af
--- /dev/null
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+@import url('http://s.libre.fm/combo.css');
+html {margin: 0; padding: 0;}
+body {width: 480px; margin: 70px auto; text-align: left; font-size: 110%; }
+
+img {border: 0;}
+
+p {text-align: left; font-weight: normal; line-height: 1.6em; color: #111;}
+
+li {line-height: 1.4em; margin-bottom: 1em;}
+
+ul {margin: 0; padding-left: 1em;}
+
+h1, h2, h3 {text-align: center;}
+
+#privacy {background-color: #ffb; padding: 10px; border: 2px solid orange; margin-bottom: 1em; }
+
+#signup {text-align: center;}
+
+</style>
+<title>daisycha.in &mdash; your social circle</title>
+</head>
+<body>
+<h1><a href="/"><img src="dclogo.png" alt="daisycha.in" height="58" /></a></h1>
+
+<p>This is daisychain, our first public unveiling of a project we call
+<b>social</b>. The idea behind <b>social</b> was to make it easy to
+connect with all the people in your life, but without giving up
+control of your relationships.</p>
+
+<p>If you're using Facebook, and one day you decide to stop, or
+another service comes along (Remember MySpace? Friendster? Orkut?)
+&mdash; any and all contacts you have cannot be exported. <b>All your
+friends go away, and you have to find people again</b>.</p>
+
+<p>We think this is a bad idea.</p>
+
+<p><b>Bad for you, and bad for society.</b></p>
+
+<h2><img src="idea.png" alt="The idea:" /></h2>
+
+<p>Start simple, start with status updates like Twitter and Facebook
+already allow, and start getting people to begin updating their
+profiles from something they <b>can</b> control:</p>
+
+<p>Once you're comfortable and on-board with this, let's take your
+ideas, suggestions and feedback, and build the social networking
+software that we all want, and can all use.</p>
+
+<h2><img src="offer.png" alt="What we offer:" /></h2>
+
+<ul>
+
+<li>Let's make that software available to everyone in the world, so
+instead of having one big company in California with all of our
+secrets, we can rely on friends and family, or even run our own
+personal networks.</li>
+
+<li>Let's build the escape rope in to all these networks, so people are
+always able to pick up and leave whenever they feel like it, leaving a
+forwarding address if they choose.</li>
+
+<li>Let's make changing your social network a little more like moving
+house and a little less like starting again without friends or
+contacts in a foreign city.</li>
+
+<h2><img src="youandus.png" alt="You and us:" /></h2>
+
+<p>Who are we? We're a ragtail group of artists and geeks working to
+bring this to you.</p>
+
+<p>You don't have to trust us with your personal information, and in
+fact, we'd rather you didn't.</p>
+
+<p>We'd like you to join us and be a part of a revolution in the way
+people share and socialize on the web.</p>
+
+<p>Let's make the World Wide Web into the Social Web.</p>
+
+<h2><img src="better.png" alt="Make it better:" /></h2>
+
+<p>Anyone reading this, regardless of your ability to program
+computers or make things look pretty can help make this software
+better.</p>
+
+<p>You can tell us what you like, what you hate, how things should be
+different, and what features you'd like to see next.</p>
+
+<p>We'll take the time to listen to you.</p>
+
+
+
+
+<p id="signup"><a href="signup.html"><img src="signup.png" alt="Sign up" /></a></p>
+
+<p><small>FOO300 &mdash; &copy; 2010 <a href="http://foocorp.net/">Foo Communications, LLC</a></small></p>
+
+</body>
+</html>
diff --git a/dc/list.html b/dc/list.html
new file mode 100644 (file)
index 0000000..a1e806e
--- /dev/null
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+@import url('http://s.libre.fm/combo.css');
+html {margin: 0; padding: 0;}
+body {width: 480px; margin: 70px auto; text-align: left; font-size: 110%; }
+
+img {border: 0;}
+
+p {text-align: left; font-weight: normal; line-height: 1.6em; color: #111;}
+
+li {line-height: 1.4em; margin-bottom: 1em;}
+
+ul {margin: 0; padding-left: 1em;}
+
+h1, h2, h3 {text-align: center;}
+
+#privacy {background-color: #ffb; padding: 10px; border: 2px solid orange; margin-bottom: 1em; }
+
+#signup {text-align: center;}
+
+</style>
+<title>daisycha.in &mdash; your social circle</title>
+</head>
+<body>
+<h1><a href="/"><img src="dclogo.png" alt="daisycha.in" height="58" /></a></h1>
+
+<p>Get on our mailing list and keep up to date with news and updates with social and daisychain.</p>
+
+<form action="http://foocorp.us2.list-manage.com/subscribe/post?u=ae361e483eddafa57feb63bb0&amp;id=7b50a5946b" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank">
+       <fieldset>
+       
+<div class="mc-field-group">
+<label for="mce-EMAIL">Email Address </label>
+<input type="text" value="" name="EMAIL" class="required email" id="mce-EMAIL"> &nbsp;<input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="btn"></div>
+       </fieldset>     
+</form>
+
+<p id="signup"><a href="signup.html"><img src="signup.png" alt="Sign up" /></a></p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/dc/offer.png b/dc/offer.png
new file mode 100644 (file)
index 0000000..28dac6a
Binary files /dev/null and b/dc/offer.png differ
diff --git a/dc/privacy.png b/dc/privacy.png
new file mode 100644 (file)
index 0000000..b060d0b
Binary files /dev/null and b/dc/privacy.png differ
diff --git a/dc/signup.html b/dc/signup.html
new file mode 100644 (file)
index 0000000..957c53a
--- /dev/null
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+@import url('http://s.libre.fm/combo.css');
+html {margin: 0; padding: 0;}
+body {width: 480px; margin: 70px auto; text-align: left; font-size: 110%; }
+
+img {border: 0;}
+
+p {text-align: left; font-weight: normal; line-height: 1.6em; color: #111;}
+
+li {line-height: 1.4em; margin-bottom: 1em;}
+
+ul {margin: 0; padding-left: 1em;}
+
+h1, h2, h3 {text-align: center;}
+
+#privacy {background-color: #ffb; padding: 10px; border: 2px solid orange; margin-bottom: 1em; }
+
+</style>
+<title>daisycha.in &mdash; your social circle</title>
+</head>
+<body>
+<h1><a href="/"><img src="dclogo.png" alt="daisycha.in" height="58" /></a></h1>
+
+<h2><img src="privacy.png" alt="The idea:" /></h2>
+
+<div id="privacy">
+
+<p>To us, your privacy is vital.</p>
+
+<p>As things currently stand, <b>anything you say on daisychain is
+available to everyone in the world</b>.</p>
+
+<p>Privacy and controls for sharing is something we take extremely
+seriously, but it's also something we cannot do by ourselves.</p>
+
+<p>We invite you to join us and help us define a privacy policy and
+the privacy controls needed to make people feel comfortable sharing on
+the social web.</p>
+
+<p>Finally, we ask that you always consider that anything you put onto
+this or any other website, could be shared outside of any privacy
+controls by anyone you share it with.</p>
+
+<p><b>Put simply: if someone can see it, that person can copy it (or take
+a screenshot) and share it with the world and nobody is ever going to
+be able fix that.</b></p>
+
+<p>So please, think twice about who you identify yourself as, and what
+you post.</p>
+
+<p>With that in mind, <a href="signup2.html">we invite you to sign up</a> and please, enjoy yourself.</p>
+
+</div>
\ No newline at end of file
diff --git a/dc/signup.png b/dc/signup.png
new file mode 100644 (file)
index 0000000..046a720
Binary files /dev/null and b/dc/signup.png differ
diff --git a/dc/signup2.html b/dc/signup2.html
new file mode 100644 (file)
index 0000000..f8f4449
--- /dev/null
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+@import url('http://s.libre.fm/combo.css');
+html {margin: 0; padding: 0;}
+body {width: 480px; margin: 70px auto; text-align: left; font-size: 110%; }
+
+img {border: 0;}
+
+p {text-align: left; font-weight: normal; color: #111;}
+
+li {line-height: 1.4em; margin-bottom: 1em;}
+
+ul {margin: 0; padding-left: 1em;}
+
+h1, h2, h3 {text-align: center;}
+
+#privacy {background-color: #ffb; padding: 10px; border: 2px solid orange; margin-bottom: 1em; }
+
+#signup {text-align: center;}
+
+small {font-size: 12px; color: #222;}
+
+</style>
+<title>daisycha.in &mdash; your social circle</title>
+</head>
+<body>
+<h1><a href="/"><img src="dclogo.png" alt="daisycha.in" height="58" /></a></h1>
+
+<p>To sign up, please fill out this very simple form, or <a href="#">use your OpenID</a>:</p>
+
+<hr />
+
+<form action="">
+
+<p><b>Choose your daisychain profile address:</b></p>
+
+<p>http://<input type="text" size="18" id="url" name="url" />.daisycha.in</p>
+
+<p><small>Please don't choose an offensive word, or attempt to impersonate a brand or celebrity.</small></p>
+
+<hr />
+
+<p><b>Tell us about yourself:</b></p>
+
+<p><label for="email">Email address: <input name="email" id="email" type="text" size="30" maxlength="150" /></label></p>
+
+<p><small>Please consider using a temporary email address service such
+as <a href="http://mailinator.com">Mailinator</a>, or mail forwarding
+service such as <a href="http://sneakemail.com">Sneakemail</a>, so
+that we can't identify you.</small></p>
+
+<hr />
+
+<p><b>Pick a password, enter it twice:</b></p>
+
+<p><label for="pw1">Password: <input type="password" size="30" maxlength="150" name="pw1" id="pw1" /></label></p>
+
+<p><label for="pw2">Confirmed: <input type="password" size="30" maxlength="150" name="pw2" id="pw2" /></label></p>
+
+<hr />
+
+<p><b>Last thing, we need to give you a name:</b></p>
+
+<p><label for="nm">Name: <input type="text" size="30" maxlength="150" name="nm" id="nm" /></label></p>
+
+<p><small>This really doesn't need to be your real name.</small></p>
+
+<hr />
+
+<p><label for="privacy"><input type="checkbox" name="prv" id="prv"
+/>&nbsp; I've read and agree with the privacy policy. </label></p>
+
+<p><label for="terms"><input type="checkbox" name="terms" id="terms"
+/>&nbsp; I've read and agree with the terms and conditions. </label></p>
+
+<p><label for="bad"><input type="checkbox" name="bad" id="bad"
+/>&nbsp; I've read this form correctly and won't check this box. </label></p>
+
+<p><input type="submit" value="Sign up" /></p>
+
+</form>
+</body>
+</html>
+
diff --git a/dc/youandus.png b/dc/youandus.png
new file mode 100644 (file)
index 0000000..682d2ab
Binary files /dev/null and b/dc/youandus.png differ
index e037b616929c6c0182bee615fa85eea718b3d3a5..fcf2bf373a9776bfacd8a83cdc2a20dab32e4060 100644 (file)
@@ -2,15 +2,12 @@
 <!-- Document licensed under Creative Commons Attribution 3.0 Unported. See -->
 <!-- http://creativecommons.org/licenses/by/3.0/ for details. -->
 
-%%site.name%% is a stream-oriented social network service based on the
-Free Software [StatusNet](http://status.net/) tool.
+%%site.name%% is a
+social network based on the Free Software [GNU social](http://www.gnu.org/software/social/) tool.
 
-If you [register](%%action.register%%) for an account, you can post
-small (%%site.textlimit%% chars or less) text notices about yourself,
-where you are, what you're doing, what you're working on or what
-you're thinking about. You can also subscribe to the notices of your
-friends, or other people you're interested in, and follow them
-privately or on the Web.
-
-You can also post event invitations, bookmarks, polls, questions, or
-other kinds of data.
+If you [register](%%action.register%%) for an account,
+you can post small (%%site.textlimit%% chars or less) text notices
+about yourself, where you are, what you're doing, or practically
+anything you want. You can also subscribe to the notices of your
+friends, or other people you're interested in, and follow them on the
+Web or in an [RSS](http://en.wikipedia.org/wiki/RSS) feed.
index ee7cd506a82ababee882ace89b10ece5d8034ad6..39948723de08197f0ef5d0c1726fdcb242ddb877 100644 (file)
@@ -5,46 +5,17 @@
 These are some *Frequently Asked Questions* about this service, with
 some answers.
 
-What is %%site.name%%?
+What is this site?
 ----------------------
 
-%%site.name%% is a stream oriented social network service.
+This is a social network, running the GNU social software.
 
-You can use it to write short notices about yourself, where you are,
-and what you're doing, and those notices will be sent to all your friends
-and fans.
+You can use it to make connections between friends, family and
+colleagues -- writing short notices about yourself, where you are, and
+what you're doing, and those notices will be sent to all your contacts.
 
-You can also post event invitations, bookmarks, polls, and questions or
-other kinds of social broadcasts.
+In the future, we'll be adding support for photo, video and file
+sharing, as well as events, better contact management and mobile
+devices.
 
-How is %%site.name%% different from Twitter?
---------------------------------------------
-
-Like [Twitter](http://twitter.com/), %%site.name%% is a light service
-with a stream-oriented interface. It uses @-replies, hashtags,
-provides search, and has private messages. It provides an API, and can
-be integrated with SMS systems. You can create lists.
-
-Unlike Twitter, %%site.name%% allows more data than just plain text
-and links to travel across the network. You can install the StatusNet
-software that runs %%site.name%% on your own servers, since it's Free
-and Open Source software. You can make groups, and share privately
-with those groups.
-
-You can make your site available only to people you choose. You can
-customize it with your own themes and plugins, or download plugins
-from the StatusNet site.
-
-How is %%site.name%% different from Yammer, SocialCast, or Salesforce Chatter?
-------------------------------------------------------------------------------
-
-Like some enterprise social software services, StatusNet lets you
-share privately with other people in your company. You can use
-microapps to post richer kinds of data, use public and private groups
-to share with team members, use extended profiles to show more about
-yourself and your company, and review groups and users in a directory.
-
-Unlike those other services, StatusNet is free and open source
-software. You can install it on your own servers. You can customize
-the theme and add plugins.
 
old mode 100644 (file)
new mode 100755 (executable)
index 811d775..828477d
@@ -15,7 +15,7 @@
  * @author     Alan Knowles <alan@akbkhome.com>
  * @copyright  1997-2006 The PHP Group
  * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
- * @version    CVS: $Id: DataObject.php 301030 2010-07-07 02:26:31Z alan_k $
+ * @version    CVS: $Id: DataObject.php 329992 2013-04-03 11:38:43Z alan_k $
  * @link       http://pear.php.net/package/DB_DataObject
  */
   
@@ -176,10 +176,11 @@ $GLOBALS['_DB_DATAOBJECT']['QUERYENDTIME'] = 0;
 
  
 // this will be horrifically slow!!!!
-// NOTE: Overload SEGFAULTS ON PHP4 + Zend Optimizer (see define before..)
 // these two are BC/FC handlers for call in PHP4/5
 
-if ( substr(phpversion(),0,1) == 5) {
+if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) {
+    
     class DB_DataObject_Overload 
     {
         function __call($method,$args) 
@@ -194,27 +195,10 @@ if ( substr(phpversion(),0,1) == 5) {
         }
     }
 } else {
-    if (version_compare(phpversion(),'4.3.10','eq') && !defined('DB_DATAOBJECT_NO_OVERLOAD')) {
-        trigger_error(
-            "overload does not work with PHP4.3.10, either upgrade 
-            (snaps.php.net) or more recent version 
-            or define DB_DATAOBJECT_NO_OVERLOAD as per the manual.
-            ",E_USER_ERROR);
-    }
-
-    if (!function_exists('clone')) {
-        // emulate clone  - as per php_compact, slow but really the correct behaviour..
-        eval('function clone($t) { $r = $t; if (method_exists($r,"__clone")) { $r->__clone(); } return $r; }');
-    }
-    eval('
-        class DB_DataObject_Overload {
-            function __call($method,$args,&$return) {
-                return $this->_call($method,$args,$return); 
-            }
-        }
-    ');
+    class DB_DataObject_Overload {}
 }
 
+
     
 
 
@@ -235,7 +219,7 @@ class DB_DataObject extends DB_DataObject_Overload
     * @access   private
     * @var      string
     */
-    var $_DB_DataObject_version = "1.9.5";
+    var $_DB_DataObject_version = "1.11.0";
 
     /**
      * The Database table (used by table extends)
@@ -290,7 +274,7 @@ class DB_DataObject extends DB_DataObject_Overload
             $v = $k;
             $keys = $this->keys();
             if (!$keys) {
-                $this->raiseError("No Keys available for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+                $this->raiseError("No Keys available for {$this->tableName()}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
                 return false;
             }
             $k = $keys[0];
@@ -306,68 +290,37 @@ class DB_DataObject extends DB_DataObject_Overload
         $this->$k = $v;
         return $this->find(1);
     }
-
+    
     /**
-     * An autoloading, caching static get method  using key, value (based on get)
-     * (depreciated - use ::get / and your own caching method)
-     * 
-     * Usage:
-     * $object = DB_DataObject::staticGet("DbTable_mytable",12);
-     * or
-     * $object =  DB_DataObject::staticGet("DbTable_mytable","name","fred");
+     * Get the value of the primary id
      *
-     * or write it into your extended class:
-     * function &staticGet($k,$v=NULL) { return DB_DataObject::staticGet("This_Class",$k,$v);  }
+     * While I normally use 'id' as the PRIMARY KEY value, some database use
+     * {table}_id as the column name.
      *
-     * @param   string  $class class name
-     * @param   string  $k     column (or value if using keys)
-     * @param   string  $v     value (optional)
-     * @access  public
-     * @return  object
+     * To save a bit of typing,
+     *
+     * $id = $do->pid();
+     *
+     * @return the id 
      */
-    function &staticGet($class, $k, $v = null)
+    function pid()
     {
-        $lclass = strtolower($class);
-        global $_DB_DATAOBJECT;
-        if (empty($_DB_DATAOBJECT['CONFIG'])) {
-            DB_DataObject::_loadConfig();
-        }
-
-        
-
-        $key = "$k:$v";
-        if ($v === null) {
-            $key = $k;
-        }
-        if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
-            DB_DataObject::debug("$class $key","STATIC GET - TRY CACHE");
-        }
-        if (!empty($_DB_DATAOBJECT['CACHE'][$lclass][$key])) {
-            return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
-        }
-        if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
-            DB_DataObject::debug("$class $key","STATIC GET - NOT IN CACHE");
-        }
-
-        $obj = DB_DataObject::factory(substr($class,strlen($_DB_DATAOBJECT['CONFIG']['class_prefix'])));
-        if (PEAR::isError($obj)) {
-            DB_DataObject::raiseError("could not autoload $class", DB_DATAOBJECT_ERROR_NOCLASS);
-            $r = false;
-            return $r;
-        }
-        
-        if (!isset($_DB_DATAOBJECT['CACHE'][$lclass])) {
-            $_DB_DATAOBJECT['CACHE'][$lclass] = array();
+        $keys = $this->keys();
+        if (!$keys) {
+            $this->raiseError("No Keys available for {$this->tableName()}",
+                            DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+            return false;
         }
-        if (!$obj->get($k,$v)) {
-            DB_DataObject::raiseError("No Data return from get $k $v", DB_DATAOBJECT_ERROR_NODATA);
-            
-            $r = false;
-            return $r;
+        $k = $keys[0];
+        if (empty($this->$k)) { // we do not 
+            $this->raiseError("pid() called on Object where primary key value not available",
+                            DB_DATAOBJECT_ERROR_NODATA);
+            return false;
         }
-        $_DB_DATAOBJECT['CACHE'][$lclass][$key] = $obj;
-        return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
+        return $this->$k;
     }
+    
+
 
     /**
      * build the basic select query.
@@ -381,11 +334,35 @@ class DB_DataObject extends DB_DataObject_Overload
         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
         if ($quoteIdentifiers) {
             $this->_connect();
-            $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+            $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+        }
+        $tn = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()) ;
+        if (!empty($this->_query['derive_table']) && !empty($this->_query['derive_select']) ) {
+            
+            // this is a derived select..
+            // not much support in the api yet..
+            
+             $sql = 'SELECT ' .
+               $this->_query['derive_select']
+               .' FROM ( SELECT'.
+                    $this->_query['data_select'] . " \n" .
+                    " FROM   $tn \n" .
+                    $this->_join . " \n" .
+                    $this->_query['condition'] . " \n" .
+                    $this->_query['group_by'] . " \n" .
+                    $this->_query['having'] . " \n" .
+                ') ' . $this->_query['derive_table'];
+                     
+            return $sql;
+            
+            
         }
+        
+       
+        
         $sql = 'SELECT ' .
             $this->_query['data_select'] . " \n" .
-            ' FROM ' . ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table) . " \n" .
+            " FROM   $tn \n" .
             $this->_join . " \n" .
             $this->_query['condition'] . " \n" .
             $this->_query['group_by'] . " \n" .
@@ -408,6 +385,10 @@ class DB_DataObject extends DB_DataObject_Overload
      * will set $object->N to number of rows, and expects next command to fetch rows
      * will return $object->N
      *
+     * if an error occurs $object->N will be set to false and return value will also be false;
+     * if numRows is not supported it will 
+     * 
+     *
      * @param   boolean $n Fetch first result
      * @access  public
      * @return  mixed (number of rows returned, or true if numRows fetching is not supported)
@@ -439,7 +420,7 @@ class DB_DataObject extends DB_DataObject_Overload
         
        
         $this->_connect();
-        $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+        $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
        
         
         $sql = $this->_build_select();
@@ -467,7 +448,10 @@ class DB_DataObject extends DB_DataObject_Overload
         }
         
         
-        $this->_query($sql);
+        $err = $this->_query($sql);
+        if (is_a($err,'PEAR_Error')) {
+            return false;
+        }
         
         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
             $this->debug("CHECK autofetchd $n", "find", 1);
@@ -539,7 +523,7 @@ class DB_DataObject extends DB_DataObject_Overload
         }
         
         if (empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]) || 
-            !is_object($result = &$_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) 
+            !is_object($result = $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) 
         {
             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
                 $this->debug('fetched on object after fetch completed (no results found)');
@@ -584,10 +568,12 @@ class DB_DataObject extends DB_DataObject_Overload
             // note: we dont declare this to keep the print_r size down.
             $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]= array_flip(array_keys($array));
         }
-        
+        $replace = array('.', ' ');
         foreach($array as $k=>$v) {
-            $kk = str_replace(".", "_", $k);
-            $kk = str_replace(" ", "_", $kk);
+            // use strpos as str_replace is slow.
+            $kk =  (strpos($k, '.') === false && strpos($k, ' ') === false) ?
+                $k : str_replace($replace, '_', $k);
+                
             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
                 $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3);
             }
@@ -597,7 +583,7 @@ class DB_DataObject extends DB_DataObject_Overload
         // set link flag
         $this->_link_loaded=false;
         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
-            $this->debug("{$this->__table} DONE", "fetchrow",2);
+            $this->debug("{$this->tableName()} DONE", "fetchrow",2);
         }
         if (($this->_query !== false) &&  empty($_DB_DATAOBJECT['CONFIG']['keep_query_after_fetch'])) {
             $this->_query = false;
@@ -759,8 +745,9 @@ class DB_DataObject extends DB_DataObject_Overload
         $ar = array();
         foreach($list as $k) {
             settype($k, $type);
-            $ar[] = $type =='string' ? $this->_quote($k) : $k;
+            $ar[] = $type == 'string' ? $this->_quote($k) : $k;
         }
+      
         if (!$ar) {
             return $not ? $this->_query['condition'] : $this->whereAdd("1=0");
         }
@@ -912,7 +899,7 @@ class DB_DataObject extends DB_DataObject_Overload
         }
         global $_DB_DATAOBJECT;
         $this->_connect();
-        $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+        $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
         
         $this->_query['limit_start'] = ($b == null) ? 0 : (int)$a;
         $this->_query['limit_count'] = ($b == null) ? (int)$a : (int)$b;
@@ -992,9 +979,9 @@ class DB_DataObject extends DB_DataObject_Overload
         }
         
         
-        $table = $this->__table;
+        $table = $this->tableName();
         if (is_object($from)) {
-            $table = $from->__table;
+            $table = $from->tableName();
             $from = array_keys($from->table());
         }
         
@@ -1004,7 +991,7 @@ class DB_DataObject extends DB_DataObject_Overload
         $s = '%s';
         if (!empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers'])) {
             $this->_connect();
-            $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+            $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
             $s      = $DB->quoteIdentifier($s);
             $format = $DB->quoteIdentifier($format); 
         }
@@ -1042,25 +1029,24 @@ class DB_DataObject extends DB_DataObject_Overload
         
         $quoteIdentifiers  = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
         
-        $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+        $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
          
-        $items =  isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?   
-            $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
+        $items = $this->table();
             
         if (!$items) {
-            $this->raiseError("insert:No table definition for {$this->__table}",
+            $this->raiseError("insert:No table definition for {$this->tableName()}",
                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
             return false;
         }
-        $options = &$_DB_DATAOBJECT['CONFIG'];
+        $options = $_DB_DATAOBJECT['CONFIG'];
 
 
         $datasaved = 1;
         $leftq     = '';
         $rightq    = '';
      
-        $seqKeys   = isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table]) ?
-                        $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] : 
+        $seqKeys   = isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()]) ?
+                        $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] : 
                         $this->sequenceKey();
         
         $key       = isset($seqKeys[0]) ? $seqKeys[0] : false;
@@ -1077,7 +1063,7 @@ class DB_DataObject extends DB_DataObject_Overload
         if (($key !== false) && !$useNative) { 
         
             if (!$seq) {
-                $keyvalue =  $DB->nextId($this->__table);
+                $keyvalue =  $DB->nextId($this->tableName());
             } else {
                 $f = $DB->getOption('seqname_format');
                 $DB->setOption('seqname_format','%s');
@@ -1124,7 +1110,7 @@ class DB_DataObject extends DB_DataObject_Overload
             
             $leftq .= ($quoteIdentifiers ? ($DB->quoteIdentifier($k) . ' ')  : "$k ");
             
-            if (is_a($this->$k,'DB_DataObject_Cast')) {
+            if (is_object($this->$k) && is_a($this->$k,'DB_DataObject_Cast')) {
                 $value = $this->$k->toString($v,$DB);
                 if (PEAR::isError($value)) {
                     $this->raiseError($value->toString() ,DB_DATAOBJECT_ERROR_INVALIDARGS);
@@ -1179,7 +1165,7 @@ class DB_DataObject extends DB_DataObject_Overload
         
         
         if ($leftq || $useNative) {
-            $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table)    : $this->__table);
+            $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName())    : $this->tableName());
             
             
             if (($dbtype == 'pgsql') && empty($leftq)) {
@@ -1231,7 +1217,7 @@ class DB_DataObject extends DB_DataObject_Overload
                         
                     case 'pgsql':
                         if (!$seq) {
-                            $seq = $DB->getSequenceName(strtolower($this->__table));
+                            $seq = $DB->getSequenceName(strtolower($this->tableName()));
                         }
                         $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
                         $method = ($db_driver  == 'DB') ? 'getOne' : 'queryOne';
@@ -1314,8 +1300,7 @@ class DB_DataObject extends DB_DataObject_Overload
         
         $original_query =  $this->_query;
         
-        $items =  isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?   
-            $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
+        $items = $this->table();
         
         // only apply update against sequence key if it is set?????
         
@@ -1335,14 +1320,14 @@ class DB_DataObject extends DB_DataObject_Overload
         
          
         if (!$items) {
-            $this->raiseError("update:No table definition for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+            $this->raiseError("update:No table definition for {$this->tableName()}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
             return false;
         }
         $datasaved = 1;
         $settings  = '';
         $this->_connect();
         
-        $DB            = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+        $DB            = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
         $dbtype        = $DB->dsn["phptype"];
         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
         $options = $_DB_DATAOBJECT['CONFIG'];
@@ -1383,7 +1368,7 @@ class DB_DataObject extends DB_DataObject_Overload
             
             $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);
             
-            if (is_a($this->$k,'DB_DataObject_Cast')) {
+            if (is_object($this->$k) && is_a($this->$k,'DB_DataObject_Cast')) {
                 $value = $this->$k->toString($v,$DB);
                 if (PEAR::isError($value)) {
                     $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
@@ -1449,7 +1434,7 @@ class DB_DataObject extends DB_DataObject_Overload
         //  echo " $settings, $this->condition ";
         if ($settings && isset($this->_query) && $this->_query['condition']) {
             
-            $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
+            $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName());
         
             $r = $this->_query("UPDATE  {$table}  SET {$settings} {$this->_query['condition']} ");
             
@@ -1512,7 +1497,7 @@ class DB_DataObject extends DB_DataObject_Overload
         global $_DB_DATAOBJECT;
         // connect will load the config!
         $this->_connect();
-        $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+        $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
         $quoteIdentifiers  = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
         
         $extra_cond = ' ' . (isset($this->_query['order_by']) ? $this->_query['order_by'] : ''); 
@@ -1534,7 +1519,7 @@ class DB_DataObject extends DB_DataObject_Overload
         // don't delete without a condition
         if (($this->_query !== false) && $this->_query['condition']) {
         
-            $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
+            $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName());
             $sql = "DELETE ";
             // using a joined delete. - with useWhere..
             $sql .= (!empty($this->_join) && $useWhere) ? 
@@ -1596,9 +1581,9 @@ class DB_DataObject extends DB_DataObject_Overload
             $this->_loadConfig();
         }
         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
-            $this->debug("{$this->__table} $row of {$this->N}", "fetchrow",3);
+            $this->debug("{$this->tableName()} $row of {$this->N}", "fetchrow",3);
         }
-        if (!$this->__table) {
+        if (!$this->tableName()) {
             $this->raiseError("fetchrow: No table", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
             return false;
         }
@@ -1611,19 +1596,22 @@ class DB_DataObject extends DB_DataObject_Overload
             return false;
         }
         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
-            $this->debug("{$this->__table} $row of {$this->N}", "fetchrow",3);
+            $this->debug("{$this->tableName()} $row of {$this->N}", "fetchrow",3);
         }
 
 
-        $result = &$_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
+        $result = $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
         $array  = $result->fetchrow(DB_DATAOBJECT_FETCHMODE_ASSOC,$row);
         if (!is_array($array)) {
             $this->raiseError("fetchrow: No results available", DB_DATAOBJECT_ERROR_NODATA);
             return false;
         }
-
+        $replace = array('.', ' ');
         foreach($array as $k => $v) {
-            $kk = str_replace(".", "_", $k);
+            // use strpos as str_replace is slow.
+            $kk =  (strpos($k, '.') === false && strpos($k, ' ') === false) ?
+                $k : str_replace($replace, '_', $k);
+            
             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
                 $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3);
             }
@@ -1631,7 +1619,7 @@ class DB_DataObject extends DB_DataObject_Overload
         }
 
         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
-            $this->debug("{$this->__table} DONE", "fetchrow", 3);
+            $this->debug("{$this->tableName()} DONE", "fetchrow", 3);
         }
         return true;
     }
@@ -1685,7 +1673,7 @@ class DB_DataObject extends DB_DataObject_Overload
             return false;
         }
         $this->_connect();
-        $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+        $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
        
 
         if (!$whereAddOnly && $items)  {
@@ -1700,7 +1688,7 @@ class DB_DataObject extends DB_DataObject_Overload
             return false;
             
         }
-        $table   = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
+        $table   = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName());
         $key_col = empty($keys[0]) ? '' : (($quoteIdentifiers ? $DB->quoteIdentifier($keys[0]) : $keys[0]));
         $as      = ($quoteIdentifiers ? $DB->quoteIdentifier('DATAOBJECT_NUM') : 'DATAOBJECT_NUM');
         
@@ -1717,11 +1705,13 @@ class DB_DataObject extends DB_DataObject_Overload
             return false;
         }
          
-        $result  = &$_DB_DATAOBJECT['RESULTS'][$t->_DB_resultid];
+        $result  = $_DB_DATAOBJECT['RESULTS'][$t->_DB_resultid];
         $l = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ORDERED);
         // free the results - essential on oracle.
         $t->free();
-        
+        if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+            $this->debug('Count returned '. $l[0] ,1);
+        }
         return (int) $l[0];
     }
 
@@ -1755,7 +1745,7 @@ class DB_DataObject extends DB_DataObject_Overload
     {
         global $_DB_DATAOBJECT;
         $this->_connect();
-        $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+        $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
         // mdb2 uses escape...
         $dd = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 'DB' : $_DB_DATAOBJECT['CONFIG']['db_driver'];
         $ret = ($dd == 'DB') ? $DB->escapeSimple($string) : $DB->escape($string);
@@ -1817,7 +1807,9 @@ class DB_DataObject extends DB_DataObject_Overload
         'limit_start' => '', // the LIMIT condition
         'limit_count' => '', // the LIMIT condition
         'data_select' => '*', // the columns to be SELECTed
-        'unions'      => array(), // the added unions
+        'unions'      => array(), // the added unions,
+        'derive_table' => '', // derived table name (BETA)
+        'derive_select' => '', // derived table select (BETA)
     );
         
     
@@ -1886,7 +1878,7 @@ class DB_DataObject extends DB_DataObject_Overload
                 $x = new DB_DataObject;
                 $x->_database = $args[0];
                 $this->_connect();
-                $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+                $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
        
                 $tables = $DB->getListOf('tables');
                 class_exists('DB_DataObject_Generator') ? '' : 
@@ -1917,32 +1909,41 @@ class DB_DataObject extends DB_DataObject_Overload
             $this->_connect();
         }
         
-        // loaded already?
-        if (!empty($_DB_DATAOBJECT['INI'][$this->_database])) {
-            
-            // database loaded - but this is table is not available..
-            if (
-                    empty($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) 
-                    && !empty($_DB_DATAOBJECT['CONFIG']['proxy'])
-                ) {
-                if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
-                    $this->debug("Loading Generator to fetch Schema",1);
-                }
-                class_exists('DB_DataObject_Generator') ? '' : 
-                    require_once 'DB/DataObject/Generator.php';
-                    
-                
-                $x = new DB_DataObject_Generator;
-                $x->fillTableSchema($this->_database,$this->__table);
-            }
+        
+        // if this table is already loaded this table..
+        if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) {
             return true;
         }
         
-        
+        // initialize the ini data.. if empt..
+        if (empty($_DB_DATAOBJECT['INI'][$this->_database])) {
+            $_DB_DATAOBJECT['INI'][$this->_database] = array();
+        }
+         
         if (empty($_DB_DATAOBJECT['CONFIG'])) {
             DB_DataObject::_loadConfig();
         }
         
+        // we do not have the data for this table yet...
+        
+        // if we are configured to use the proxy..
+        
+        if ( !empty($_DB_DATAOBJECT['CONFIG']['proxy']) ) {
+            if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+                $this->debug("Loading Generator to fetch Schema",1);
+            }
+            class_exists('DB_DataObject_Generator') ? '' : 
+                require_once 'DB/DataObject/Generator.php';
+                
+            
+            $x = new DB_DataObject_Generator;
+            $x->fillTableSchema($this->_database,$this->tableName());
+            return true;
+        }
+            
+             
+       
+        
         // if you supply this with arguments, then it will take those
         // as the database and links array...
          
@@ -1980,9 +1981,18 @@ class DB_DataObject extends DB_DataObject_Overload
             }
              
         }
+        // are table name lowecased..
+        if (!empty($_DB_DATAOBJECT['CONFIG']['portability']) && $_DB_DATAOBJECT['CONFIG']['portability'] & 1) {
+            foreach($_DB_DATAOBJECT['INI'][$this->_database] as $k=>$v) {
+                // results in duplicate cols.. but not a big issue..
+                $_DB_DATAOBJECT['INI'][$this->_database][strtolower($k)] = $v;
+            }
+        }
+        
+        
         // now have we loaded the structure.. 
         
-        if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
+        if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) {
             return true;
         }
         // - if not try building it..
@@ -1991,11 +2001,11 @@ class DB_DataObject extends DB_DataObject_Overload
                 require_once 'DB/DataObject/Generator.php';
                 
             $x = new DB_DataObject_Generator;
-            $x->fillTableSchema($this->_database,$this->__table);
+            $x->fillTableSchema($this->_database,$this->tableName());
             // should this fail!!!???
             return true;
         }
-        $this->debug("Cant find database schema: {$this->_database}/{$this->__table} \n".
+        $this->debug("Cant find database schema: {$this->_database}/{$this->tableName()} \n".
                     "in links file data: " . print_r($_DB_DATAOBJECT['INI'],true),"databaseStructure",5);
         // we have to die here!! - it causes chaos if we dont (including looping forever!)
         $this->raiseError( "Unable to load schema for database and table (turn debugging up to 5 for full error message)", DB_DATAOBJECT_ERROR_INVALIDARGS, PEAR_ERROR_DIE);
@@ -2017,10 +2027,14 @@ class DB_DataObject extends DB_DataObject_Overload
      */
     function tableName()
     {
+        global $_DB_DATAOBJECT;
         $args = func_get_args();
         if (count($args)) {
             $this->__table = $args[0];
         }
+        if (!empty($_DB_DATAOBJECT['CONFIG']['portability']) && $_DB_DATAOBJECT['CONFIG']['portability'] & 1) {
+            return strtolower($this->__table);
+        }
         return $this->__table;
     }
     
@@ -2036,7 +2050,10 @@ class DB_DataObject extends DB_DataObject_Overload
         $args = func_get_args();
         if (count($args)) {
             $this->_database = $args[0];
+        } else {
+            $this->_connect();
         }
+        
         return $this->_database;
     }
   
@@ -2065,17 +2082,17 @@ class DB_DataObject extends DB_DataObject_Overload
         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
             $this->_connect();
         }
-        
-        if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
-            return $_DB_DATAOBJECT['INI'][$this->_database][$this->__table];
+          
+        if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) {
+            return $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()];
         }
         
         $this->databaseStructure();
  
         
         $ret = array();
-        if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
-            $ret =  $_DB_DATAOBJECT['INI'][$this->_database][$this->__table];
+        if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) {
+            $ret =  $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()];
         }
         
         return $ret;
@@ -2090,7 +2107,7 @@ class DB_DataObject extends DB_DataObject_Overload
      * or you do not want to use ini tables, you can override this.
      * @param  string optional set the key
      * @param  *   optional  set more keys
-     * @access private
+     * @access public
      * @return array
      */
     function keys()
@@ -2109,13 +2126,13 @@ class DB_DataObject extends DB_DataObject_Overload
         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
             $this->_connect();
         }
-        if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
-            return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]);
+        if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"])) {
+            return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"]);
         }
         $this->databaseStructure();
         
-        if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
-            return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]);
+        if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"])) {
+            return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"]);
         }
         return array();
     }
@@ -2130,7 +2147,7 @@ class DB_DataObject extends DB_DataObject_Overload
      * @param  string  optional the key sequence/autoinc. key
      * @param  boolean optional use native increment. default false 
      * @param  false|string optional native sequence name
-     * @access private
+     * @access public
      * @return array (column,use_native,sequence_name)
      */
     function sequenceKey()
@@ -2151,10 +2168,10 @@ class DB_DataObject extends DB_DataObject_Overload
         if (count($args)) {
             $args[1] = isset($args[1]) ? $args[1] : false;
             $args[2] = isset($args[2]) ? $args[2] : false;
-            $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = $args;
+            $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = $args;
         }
-        if (isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table])) {
-            return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table];
+        if (isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()])) {
+            return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()];
         }
         // end call setting (eg. $do->sequenceKeys(a,b,c); )
         
@@ -2163,13 +2180,12 @@ class DB_DataObject extends DB_DataObject_Overload
         
         $keys = $this->keys();
         if (!$keys) {
-            return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table
+            return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()
                 = array(false,false,false);
         }
  
 
-        $table =  isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?   
-            $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
+        $table =  $this->table();
        
         $dbtype    = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'];
         
@@ -2179,8 +2195,8 @@ class DB_DataObject extends DB_DataObject_Overload
         
         $seqname = false;
         
-        if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) {
-            $seqname = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table];
+        if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->tableName()])) {
+            $seqname = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->tableName()];
             if (strpos($seqname,':') !== false) {
                 list($usekey,$seqname) = explode(':',$seqname);
             }
@@ -2189,25 +2205,25 @@ class DB_DataObject extends DB_DataObject_Overload
         
         // if the key is not an integer - then it's not a sequence or native
         if (empty($table[$usekey]) || !($table[$usekey] & DB_DATAOBJECT_INT)) {
-                return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,false);
+                return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false,false,false);
         }
         
         
         if (!empty($_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'])) {
             $ignore =  $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'];
             if (is_string($ignore) && (strtoupper($ignore) == 'ALL')) {
-                return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
+                return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false,false,$seqname);
             }
             if (is_string($ignore)) {
                 $ignore = $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'] = explode(',',$ignore);
             }
-            if (in_array($this->__table,$ignore)) {
-                return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
+            if (in_array($this->tableName(),$ignore)) {
+                return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false,false,$seqname);
             }
         }
         
         
-        $realkeys = $_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"];
+        $realkeys = $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"];
         
         // if you are using an old ini file - go back to old behaviour...
         if (is_numeric($realkeys[$usekey])) {
@@ -2216,25 +2232,38 @@ class DB_DataObject extends DB_DataObject_Overload
         
         // multiple unique primary keys without a native sequence...
         if (($realkeys[$usekey] == 'K') && (count($keys) > 1)) {
-            return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
+            return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false,false,$seqname);
         }
         // use native sequence keys...
         // technically postgres native here...
         // we need to get the new improved tabledata sorted out first.
         
+        // support named sequence keys.. - currently postgres only..
+        
+        if (    in_array($dbtype , array('pgsql')) &&
+                ($table[$usekey] & DB_DATAOBJECT_INT) && 
+                isset($realkeys[$usekey]) && strlen($realkeys[$usekey]) > 1) {
+            return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array($usekey,true, $realkeys[$usekey]);
+        }
+        
         if (    in_array($dbtype , array('pgsql', 'mysql', 'mysqli', 'mssql', 'ifx')) && 
                 ($table[$usekey] & DB_DATAOBJECT_INT) && 
                 isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N')
                 ) {
-            return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array($usekey,true,$seqname);
+            return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array($usekey,true,$seqname);
         }
+        
+        
         // if not a native autoinc, and we have not assumed all primary keys are sequence
         if (($realkeys[$usekey] != 'N') && 
             !empty($_DB_DATAOBJECT['CONFIG']['dont_use_pear_sequences'])) {
             return array(false,false,false);
         }
+        
+        
+        
         // I assume it's going to try and be a nextval DB sequence.. (not native)
-        return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array($usekey,false,$seqname);
+        return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array($usekey,false,$seqname);
     }
     
     
@@ -2346,13 +2375,13 @@ class DB_DataObject extends DB_DataObject_Overload
         // it's not currently connected!
         // try and work out what to use for the dsn !
 
-        $options= &$_DB_DATAOBJECT['CONFIG'];
+        $options= $_DB_DATAOBJECT['CONFIG'];
         // if the databse dsn dis defined in the object..
         $dsn = isset($this->_database_dsn) ? $this->_database_dsn : null;
         
         if (!$dsn) {
-            if (!$this->_database) {
-                $this->_database = isset($options["table_{$this->__table}"]) ? $options["table_{$this->__table}"] : null;
+            if (!$this->_database && !empty($this->__table)) {
+                $this->_database = isset($options["table_{$this->tableName()}"]) ? $options["table_{$this->tableName()}"] : null;
             }
             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
                 $this->debug("Checking for database specific ini ('{$this->_database}') : database_{$this->_database} in options","CONNECT");
@@ -2421,9 +2450,9 @@ class DB_DataObject extends DB_DataObject_Overload
             $db_options = PEAR::getStaticProperty('DB','options');
             require_once 'DB.php';
             if ($db_options) {
-                $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = &DB::connect($dsn,$db_options);
+                $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn,$db_options);
             } else {
-                $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = &DB::connect($dsn);
+                $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn);
             }
             
         } else {
@@ -2434,13 +2463,13 @@ class DB_DataObject extends DB_DataObject_Overload
             $db_options = is_array($db_options) ? $db_options : array();
             $db_options['portability'] = isset($db_options['portability'] )
                 ? $db_options['portability']  : MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE;
-            $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = &MDB2::connect($dsn,$db_options);
+            $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = MDB2::connect($dsn,$db_options);
             
         }
         
         
         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
-            $this->debug(serialize($_DB_DATAOBJECT['CONNECTIONS']), "CONNECT",5);
+            $this->debug(print_r($_DB_DATAOBJECT['CONNECTIONS'],true), "CONNECT",5);
         }
         if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
             $this->debug($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->toString(), "CONNECT FAILED",5);
@@ -2467,7 +2496,6 @@ class DB_DataObject extends DB_DataObject_Overload
         }
         
         // Oracle need to optimize for portibility - not sure exactly what this does though :)
-        $c = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
          
         return true;
     }
@@ -2486,9 +2514,9 @@ class DB_DataObject extends DB_DataObject_Overload
         $this->_connect();
         
 
-        $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+        $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
 
-        $options = &$_DB_DATAOBJECT['CONFIG'];
+        $options = $_DB_DATAOBJECT['CONFIG'];
         
         $_DB_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 
                     'DB':  $_DB_DATAOBJECT['CONFIG']['db_driver'];
@@ -2501,10 +2529,10 @@ class DB_DataObject extends DB_DataObject_Overload
         if (strtoupper($string) == 'BEGIN') {
             if ($_DB_driver == 'DB') {
                 $DB->autoCommit(false);
+                $DB->simpleQuery('BEGIN');
             } else {
                 $DB->beginTransaction();
             }
-            // db backend adds begin anyway from now on..
             return true;
         }
         if (strtoupper($string) == 'COMMIT') {
@@ -2564,7 +2592,7 @@ class DB_DataObject extends DB_DataObject_Overload
             }
             
             // see if we got a failure.. - try again a few times..
-            if (!is_a($result,'PEAR_Error')) {
+            if (!is_object($result) || !is_a($result,'PEAR_Error')) {
                 break;
             }
             if ($result->getCode() != -14) {  // *DB_ERROR_NODBSELECTED
@@ -2575,10 +2603,11 @@ class DB_DataObject extends DB_DataObject_Overload
         }
        
 
-        if (is_a($result,'PEAR_Error')) {
+        if (is_object($result) && is_a($result,'PEAR_Error')) {
             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { 
                 $this->debug($result->toString(), "Query Error",1 );
             }
+            $this->N = false;
             return $this->raiseError($result);
         }
         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
@@ -2607,14 +2636,17 @@ class DB_DataObject extends DB_DataObject_Overload
         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
             $this->debug(serialize($result), 'RESULT',5);
         }
-        if (method_exists($result, 'numrows')) {
+        if (method_exists($result, 'numRows')) {
             if ($_DB_driver == 'DB') {
                 $DB->expectError(DB_ERROR_UNSUPPORTED);
             } else {
                 $DB->expectError(MDB2_ERROR_UNSUPPORTED);
             }
-            $this->N = $result->numrows();
-            if (is_a($this->N,'PEAR_Error')) {
+            
+            $this->N = $result->numRows();
+            //var_dump($this->N);
+            
+            if (is_object($this->N) && is_a($this->N,'PEAR_Error')) {
                 $this->N = true;
             }
             $DB->popExpect();
@@ -2634,7 +2666,7 @@ class DB_DataObject extends DB_DataObject_Overload
     {
         global $_DB_DATAOBJECT;
         $this->_connect();
-        $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+        $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
        
         $quoteIdentifiers  = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
         $options = $_DB_DATAOBJECT['CONFIG'];
@@ -2666,12 +2698,12 @@ class DB_DataObject extends DB_DataObject_Overload
             }
             
             $kSql = $quoteIdentifiers 
-                ? ( $DB->quoteIdentifier($this->__table) . '.' . $DB->quoteIdentifier($k) )  
-                : "{$this->__table}.{$k}";
+                ? ( $DB->quoteIdentifier($this->tableName()) . '.' . $DB->quoteIdentifier($k) )  
+                : "{$this->tableName()}.{$k}";
              
              
             
-            if (is_a($this->$k,'DB_DataObject_Cast')) {
+            if (is_object($this->$k) && is_a($this->$k,'DB_DataObject_Cast')) {
                 $dbtype = $DB->dsn["phptype"];
                 $value = $this->$k->toString($v,$DB);
                 if (PEAR::isError($value)) {
@@ -2711,28 +2743,6 @@ class DB_DataObject extends DB_DataObject_Overload
         }
     }
 
-    /**
-     * autoload Class relating to a table
-     * (depreciated - use ::factory)
-     *
-     * @param  string  $table  table
-     * @access private
-     * @return string classname on Success
-     */
-    function staticAutoloadTable($table)
-    {
-        global $_DB_DATAOBJECT;
-        if (empty($_DB_DATAOBJECT['CONFIG'])) {
-            DB_DataObject::_loadConfig();
-        }
-        $p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
-            $_DB_DATAOBJECT['CONFIG']['class_prefix'] : '';
-        $class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
-        
-        $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class);
-        $class = $ce ? $class  : DB_DataObject::_autoloadClass($class);
-        return $class;
-    }
     
     
      /**
@@ -2756,7 +2766,8 @@ class DB_DataObject extends DB_DataObject_Overload
     
     
 
-    function factory($table = '') {
+    function factory($table = '')
+    {
         global $_DB_DATAOBJECT;
         
         
@@ -2781,8 +2792,8 @@ class DB_DataObject extends DB_DataObject_Overload
        
         
         if ($table === '') {
-            if (is_a($this,'DB_DataObject') && strlen($this->__table)) {
-                $table = $this->__table;
+            if (is_a($this,'DB_DataObject') && strlen($this->tableName())) {
+                $table = $this->tableName();
             } else {
                 return DB_DataObject::raiseError(
                     "factory did not recieve a table name",
@@ -2794,6 +2805,8 @@ class DB_DataObject extends DB_DataObject_Overload
         $cp = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
             explode(PATH_SEPARATOR, $_DB_DATAOBJECT['CONFIG']['class_prefix']) : '';
         
+        //print_r($cp);
+        
         // multiprefix support.
         $tbl = preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
         if (is_array($cp)) {
@@ -2813,7 +2826,6 @@ class DB_DataObject extends DB_DataObject_Overload
         
         
         $rclass = $ce ? $class  : DB_DataObject::_autoloadClass($class, $table);
-        
         // proxy = full|light
         if (!$rclass && isset($_DB_DATAOBJECT['CONFIG']['proxy'])) { 
         
@@ -2828,7 +2840,9 @@ class DB_DataObject extends DB_DataObject_Overload
             $d = new DB_DataObject;
            
             $d->__table = $table;
-            if (is_a($ret = $d->_connect(), 'PEAR_Error')) {
+            
+            $ret = $d->_connect();
+            if (is_object($ret) && is_a($ret, 'PEAR_Error')) {
                 return $ret;
             }
             
@@ -2836,14 +2850,16 @@ class DB_DataObject extends DB_DataObject_Overload
             return $x->$proxyMethod( $d->_database, $table);
         }
         
-        if (!$rclass) {
+        if (!$rclass || !class_exists($rclass)) {
             return DB_DataObject::raiseError(
                 "factory could not find class " . 
                 (is_array($class) ? implode(PATH_SEPARATOR, $class)  : $class  ). 
                 "from $table",
                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
         }
-        $ret = new $rclass;
+        $ret = new $rclass();
         if (!empty($database)) {
             DB_DataObject::debug("Setting database to $database","FACTORY",1);
             $ret->database($database);
@@ -2968,11 +2984,14 @@ class DB_DataObject extends DB_DataObject_Overload
     * Should look a bit like
     *       [local_col_name] => "related_tablename:related_col_name"
     * 
+    * @param    array $new_links optional - force update of the links for this table
+    *               You probably want to restore it to it's original state after,
+    *               as modifying here does it for the whole PHP request.
     * 
     * @return   array|null    
     *           array       = if there are links defined for this table.
     *           empty array - if there is a links.ini file, but no links on this table
-    *           null        - if no links.ini exists for this database (hence try auto_links).
+    *           false       - if no links.ini exists for this database (hence try auto_links).
     * @access   public
     * @see      DB_DataObject::getLinks(), DB_DataObject::getLink()
     */
@@ -2986,232 +3005,183 @@ class DB_DataObject extends DB_DataObject_Overload
         // have to connect.. -> otherwise things break later.
         $this->_connect();
         
-        if (isset($_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table])) {
-            return $_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table];
+        // alias for shorter code..
+        $lcfg  = &$_DB_DATAOBJECT['LINKS'];
+        $cfg   =  $_DB_DATAOBJECT['CONFIG'];
+
+        if ($args = func_get_args()) {
+            // an associative array was specified, that updates the current
+            // schema... - be careful doing this
+            if (empty( $lcfg[$this->_database])) {
+                $lcfg[$this->_database] = array();
+            }
+            $lcfg[$this->_database][$this->tableName()] = $args[0];
+            
+        }
+        // loaded and available.
+        if (isset($lcfg[$this->_database][$this->tableName()])) {
+            return $lcfg[$this->_database][$this->tableName()];
+        }
+
+        // loaded 
+        if (isset($lcfg[$this->_database])) {
+            // either no file, or empty..
+            return $lcfg[$this->_database] === false ? null : array();
         }
         
-        
-        
-        
-        
-        // attempt to load links file here..
-        
-        if (!isset($_DB_DATAOBJECT['LINKS'][$this->_database])) {
-            $schemas = isset($_DB_DATAOBJECT['CONFIG']['schema_location']) ?
-                array("{$_DB_DATAOBJECT['CONFIG']['schema_location']}/{$this->_database}.ini") :
-                array() ;
-                     
-            if (isset($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"])) {
-                $schemas = is_array($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]) ?
-                    $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"] :
-                    explode(PATH_SEPARATOR,$_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]);
+        // links are same place as schema by default.
+        $schemas = isset($cfg['schema_location']) ?
+            array("{$cfg['schema_location']}/{$this->_database}.ini") :
+            array() ;
+
+        // if ini_* is set look there instead.
+        // and support multiple locations.                 
+        if (isset($cfg["ini_{$this->_database}"])) {
+            $schemas = is_array($cfg["ini_{$this->_database}"]) ?
+                $cfg["ini_{$this->_database}"] :
+                explode(PATH_SEPARATOR,$cfg["ini_{$this->_database}"]);
+        }
+                        
+        // default to not available.
+        $lcfg[$this->_database] = false;
+
+        foreach ($schemas as $ini) {
+                
+            $links = isset($cfg["links_{$this->_database}"]) ?
+                    $cfg["links_{$this->_database}"] :
+                    str_replace('.ini','.links.ini',$ini);
+            
+            // file really exists..
+            if (!file_exists($links) || !is_file($links)) {
+                if (!empty($cfg['debug'])) {
+                    $this->debug("Missing links.ini file: $links","links",1);
+                }
+                continue;
             }
+
+            // set to empty array - as we have at least one file now..
+            $lcfg[$this->_database] = empty($lcfg[$this->_database]) ? array() : $lcfg[$this->_database];
+
+            // merge schema file into lcfg..
+            $lcfg[$this->_database] = array_merge(
+                $lcfg[$this->_database],
+                parse_ini_file($links, true)
+            );
+
                         
+            if (!empty($cfg['debug'])) {
+                $this->debug("Loaded links.ini file: $links","links",1);
+            }
              
-            $_DB_DATAOBJECT['LINKS'][$this->_database] = array();
-            foreach ($schemas as $ini) {
+        }
+        
+        if (!empty($_DB_DATAOBJECT['CONFIG']['portability']) && $_DB_DATAOBJECT['CONFIG']['portability'] & 1) {
+            foreach($lcfg[$this->_database] as $k=>$v) {
                 
-                $links =
-                    isset($_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"]) ?
-                        $_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"] :
-                        str_replace('.ini','.links.ini',$ini);
-        
-                if (file_exists($links) && is_file($links)) {
-                    /* not sure why $links = ... here  - TODO check if that works */
-                    $_DB_DATAOBJECT['LINKS'][$this->_database] = array_merge(
-                        $_DB_DATAOBJECT['LINKS'][$this->_database],
-                        parse_ini_file($links, true)
-                    );
-                        
-                    if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
-                        $this->debug("Loaded links.ini file: $links","links",1);
-                    }
-                } else {
-                    if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
-                        $this->debug("Missing links.ini file: $links","links",1);
-                    }
+                $nk = strtolower($k);
+                // results in duplicate cols.. but not a big issue..
+                $lcfg[$this->_database][$nk] = isset($lcfg[$this->_database][$nk])
+                    ? $lcfg[$this->_database][$nk]  : array();
+                
+                foreach($v as $kk =>$vv) {
+                    //var_Dump($vv);exit;
+                    $vv =explode(':', $vv);
+                    $vv[0] = strtolower($vv[0]);
+                    $lcfg[$this->_database][$nk][$kk] = implode(':', $vv);
                 }
+                
+                
             }
         }
-        
+        //echo '<PRE>';print_r($lcfg);exit;
         
         // if there is no link data at all on the file!
         // we return null.
-        if (!isset($_DB_DATAOBJECT['LINKS'][$this->_database])) {
+        if ($lcfg[$this->_database] === false) {
             return null;
         }
         
-        if (isset($_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table])) {
-            return $_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table];
+        if (isset($lcfg[$this->_database][$this->tableName()])) {
+            return $lcfg[$this->_database][$this->tableName()];
         }
         
         return array();
     }
+    
+    
     /**
-     * load related objects
+     * generic getter/setter for links
      *
-     * There are two ways to use this, one is to set up a <dbname>.links.ini file
-     * into a static property named <dbname>.links and specifies the table joins,
-     * the other highly dependent on naming columns 'correctly' :)
-     * using colname = xxxxx_yyyyyy
-     * xxxxxx = related table; (yyyyy = user defined..)
-     * looks up table xxxxx, for value id=$this->xxxxx
-     * stores it in $this->_xxxxx_yyyyy
-     * you can change what object vars the links are stored in by 
-     * changeing the format parameter
+     * This is the new 'recommended' way to get get/set linked objects.
+     * must be used with links.ini
      *
+     * usage:
+     *  get:
+     *  $obj = $do->link('company_id');
+     *  $obj = $do->link(array('local_col', 'linktable:linked_col'));
+     *  
+     *  set:
+     *  $do->link('company_id',0);
+     *  $do->link('company_id',$obj);
+     *  $do->link('company_id', array($obj));
      *
-     * @param  string format (default _%s) where %s is the table name.
-     * @author Tim White <tim@cyface.com>
+     *  example function
+     *
+     *  function company() {
+     *     $this->link(array('company_id','company:id'), func_get_args());
+     *   }
+     *
+     * 
+     *
+     * @param  mixed $link_spec              link specification (normally a string)
+     *                                       uses similar rules to  joinAdd() array argument.
+     * @param  mixed $set_value (optional)   int, DataObject, or array('set')
+     * @author Alan Knowles
      * @access public
-     * @return boolean , true on success
+     * @return mixed true or false on setting, object on getting
      */
-    function getLinks($format = '_%s')
+    function link($field, $set_args = array())
     {
-         
-        // get table will load the options.
-        if ($this->_link_loaded) {
-            return true;
-        }
-        $this->_link_loaded = false;
-        $cols  = $this->table();
-        $links = $this->links();
-         
-        $loaded = array();
-        
-        if ($links) {   
-            foreach($links as $key => $match) {
-                list($table,$link) = explode(':', $match);
-                $k = sprintf($format, str_replace('.', '_', $key));
-                // makes sure that '.' is the end of the key;
-                if ($p = strpos($key,'.')) {
-                      $key = substr($key, 0, $p);
-                }
-                
-                $this->$k = $this->getLink($key, $table, $link);
-                
-                if (is_object($this->$k)) {
-                    $loaded[] = $k; 
-                }
-            }
-            $this->_link_loaded = $loaded;
-            return true;
-        }
-        // this is the autonaming stuff..
-        // it sends the column name down to getLink and lets that sort it out..
-        // if there is a links file then it is not used!
-        // IT IS DEPRECIATED!!!! - USE 
-        if (!is_null($links)) {    
-            return false;
-        }
-        
+        require_once 'DB/DataObject/Links.php';
+        $l = new DB_DataObject_Links($this);
+        return  $l->link($field,$set_args) ;
         
-        foreach (array_keys($cols) as $key) {
-            if (!($p = strpos($key, '_'))) {
-                continue;
-            }
-            // does the table exist.
-            $k =sprintf($format, $key);
-            $this->$k = $this->getLink($key);
-            if (is_object($this->$k)) {
-                $loaded[] = $k; 
-            }
-        }
-        $this->_link_loaded = $loaded;
-        return true;
     }
-
-    /**
-     * return name from related object
-     *
-     * There are two ways to use this, one is to set up a <dbname>.links.ini file
-     * into a static property named <dbname>.links and specifies the table joins,
-     * the other is highly dependant on naming columns 'correctly' :)
+    
+      /**
+     * load related objects
      *
-     * NOTE: the naming convention is depreciated!!! - use links.ini
+     * Generally not recommended to use this.
+     * The generator should support creating getter_setter methods which are better suited.
      *
-     * using colname = xxxxx_yyyyyy
-     * xxxxxx = related table; (yyyyy = user defined..)
-     * looks up table xxxxx, for value id=$this->xxxxx
-     * stores it in $this->_xxxxx_yyyyy
+     * Relies on  <dbname>.links.ini
      *
-     * you can also use $this->getLink('thisColumnName','otherTable','otherTableColumnName')
+     * Sets properties on the calling dataobject  you can change what
+     * object vars the links are stored in by  changeing the format parameter
      *
      *
-     * @param string $row    either row or row.xxxxx
-     * @param string $table  name of table to look up value in
-     * @param string $link   name of column in other table to match
+     * @param  string format (default _%s) where %s is the table name.
      * @author Tim White <tim@cyface.com>
      * @access public
-     * @return mixed object on success
+     * @return boolean , true on success
      */
-    function getLink($row, $table = null, $link = false)
+    function getLinks($format = '_%s')
     {
-        
-        
-        // GUESS THE LINKED TABLE.. (if found - recursevly call self)
-        
-        if ($table === null) {
-            $links = $this->links();
-            
-            if (is_array($links)) {
-            
-                if ($links[$row]) {
-                    list($table,$link) = explode(':', $links[$row]);
-                    if ($p = strpos($row,".")) {
-                        $row = substr($row,0,$p);
-                    }
-                    return $this->getLink($row,$table,$link);
-                    
-                } 
-                
-                $this->raiseError(
-                    "getLink: $row is not defined as a link (normally this is ok)", 
-                    DB_DATAOBJECT_ERROR_NODATA);
-                    
-                $r = false;
-                return $r;// technically a possible error condition?
-
-            }  
-            // use the old _ method - this shouldnt happen if called via getLinks()
-            if (!($p = strpos($row, '_'))) {
-                $r = null;
-                return $r; 
-            }
-            $table = substr($row, 0, $p);
-            return $this->getLink($row, $table);
-            
+        require_once 'DB/DataObject/Links.php';
+         $l = new DB_DataObject_Links($this);
+        return $l->applyLinks($format);
+           
+    }
 
-        }
-        
-        
-        
-        if (!isset($this->$row)) {
-            $this->raiseError("getLink: row not set $row", DB_DATAOBJECT_ERROR_NODATA);
-            return false;
-        }
-        
-        // check to see if we know anything about this table..
-        
-        $obj = $this->factory($table);
-        
-        if (!is_a($obj,'DB_DataObject')) {
-            $this->raiseError(
-                "getLink:Could not find class for row $row, table $table", 
-                DB_DATAOBJECT_ERROR_INVALIDCONFIG);
-            return false;
-        }
-        if ($link) {
-            if ($obj->get($link, $this->$row)) {
-                return $obj;
-            } 
-            return  false;
-        }
-        
-        if ($obj->get($this->$row)) {
-            return $obj;
-        }
-        return false;
+    /**
+     * deprecited : @use link() 
+     */
+    function getLink($row, $table = null, $link = false)
+    {
+        require_once 'DB/DataObject/Links.php';
+        $l = new DB_DataObject_Links($this);
+        return $l->getLink($row, $table === null ? false: $table, $link);
+         
         
     }
 
@@ -3242,47 +3212,12 @@ class DB_DataObject extends DB_DataObject_Overload
      * }
      * 
      */
-    function &getLinkArray($row, $table = null)
+    function getLinkArray($row, $table = null)
     {
-        
-        $ret = array();
-        if (!$table) {
-            $links = $this->links();
-            
-            if (is_array($links)) {
-                if (!isset($links[$row])) {
-                    // failed..
-                    return $ret;
-                }
-                list($table,$link) = explode(':',$links[$row]);
-            } else {
-                if (!($p = strpos($row,'_'))) {
-                    return $ret;
-                }
-                $table = substr($row,0,$p);
-            }
-        }
-        
-        $c  = $this->factory($table);
-        
-        if (!is_a($c,'DB_DataObject')) {
-            $this->raiseError(
-                "getLinkArray:Could not find class for row $row, table $table", 
-                DB_DATAOBJECT_ERROR_INVALIDCONFIG
-            );
-            return $ret;
-        }
-
-        // if the user defined method list exists - use it...
-        if (method_exists($c, 'listFind')) {
-            $c->listFind($this->id);
-        } else {
-            $c->find();
-        }
-        while ($c->fetch()) {
-            $ret[] = $c;
-        }
-        return $ret;
+        require_once 'DB/DataObject/Links.php';
+        $l = new DB_DataObject_Links($this);
+        return $l->getLinkArray($row, $table === null ? false: $table);
+     
     }
 
      /**
@@ -3362,9 +3297,10 @@ class DB_DataObject extends DB_DataObject_Overload
      * @param    optional $obj       object |array    the joining object (no value resets the join)
      *                                          If you use an array here it should be in the format:
      *                                          array('local_column','remotetable:remote_column');
-     *                                          if remotetable does not have a definition, you should
-     *                                          use @ to hide the include error message..
-     *                                      
+     *                                             if remotetable does not have a definition, you should
+     *                                             use @ to hide the include error message..
+     *                                          array('local_column',  $dataobject , 'remote_column');
+     *                                             if array has 3 args, then second is assumed to be the linked dataobject.
      *
      * @param    optional $joinType  string | array
      *                                          'LEFT'|'INNER'|'RIGHT'|'' Inner is default, '' indicates 
@@ -3424,14 +3360,21 @@ class DB_DataObject extends DB_DataObject_Overload
         $toTable = false;
         if (is_array($obj)) {
             $tfield = $obj[0];
-            list($toTable,$ofield) = explode(':',$obj[1]);
-            $obj = DB_DataObject::factory($toTable);
             
-            if (!$obj || is_a($obj,'PEAR_Error')) {
-                $obj = new DB_DataObject;
-                $obj->__table = $toTable;
+            if (count($obj) == 3) {
+                $ofield = $obj[2];
+                $obj = $obj[1];
+            } else {
+                list($toTable,$ofield) = explode(':',$obj[1]);
+            
+                $obj = is_string($toTable) ? DB_DataObject::factory($toTable) : $toTable;
+            
+                if (!$obj || !is_object($obj) || is_a($obj,'PEAR_Error')) {
+                    $obj = new DB_DataObject;
+                    $obj->__table = $toTable;
+                }
+                $obj->_connect();
             }
-            $obj->_connect();
             // set the table items to nothing.. - eg. do not try and match
             // things in the child table...???
             $items = array();
@@ -3442,7 +3385,7 @@ class DB_DataObject extends DB_DataObject_Overload
         }
         /*  make sure $this->_database is set.  */
         $this->_connect();
-        $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+        $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
        
 
         /// CHANGED 26 JUN 2009 - we prefer links from our local table over the remote one.
@@ -3473,7 +3416,7 @@ class DB_DataObject extends DB_DataObject_Overload
                         $ar[1] = explode(',', $ar[1]);
                     }
 
-                    if ($ar[0] != $obj->__table) {
+                    if ($ar[0] != $obj->tableName()) {
                         continue;
                     }
                     if ($joinCol !== false) {
@@ -3518,7 +3461,7 @@ class DB_DataObject extends DB_DataObject_Overload
                         $ar[1] = explode(',', $ar[1]);
                     }
                  
-                    if ($ar[0] != $this->__table) {
+                    if ($ar[0] != $this->tableName()) {
                         continue;
                     }
                     
@@ -3555,7 +3498,7 @@ class DB_DataObject extends DB_DataObject_Overload
 
         if ($ofield === false) {
             $this->raiseError(
-                "joinAdd: {$obj->__table} has no link with {$this->__table}",
+                "joinAdd: {$obj->tableName()} has no link with {$this->tableName()}",
                 DB_DATAOBJECT_ERROR_NODATA);
             return false;
         }
@@ -3564,7 +3507,7 @@ class DB_DataObject extends DB_DataObject_Overload
         // we default to joining as the same name (this is remvoed later..)
         
         if ($joinAs === false) {
-            $joinAs = $obj->__table;
+            $joinAs = $obj->tableName();
         }
         
         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
@@ -3572,8 +3515,8 @@ class DB_DataObject extends DB_DataObject_Overload
         
         // not sure  how portable adding database prefixes is..
         $objTable = $quoteIdentifiers ? 
-                $DB->quoteIdentifier($obj->__table) : 
-                 $obj->__table ;
+                $DB->quoteIdentifier($obj->tableName()) : 
+                 $obj->tableName() ;
                 
         $dbPrefix  = '';
         if (strlen($obj->_database) && in_array($DB->dsn['phptype'],array('mysql','mysqli'))) {
@@ -3649,7 +3592,7 @@ class DB_DataObject extends DB_DataObject_Overload
                     continue;
                 }
                             
-                if (is_a($obj->$k,'DB_DataObject_Cast')) {
+                if (is_object($obj->$k) && is_a($obj->$k,'DB_DataObject_Cast')) {
                     $value = $obj->$k->toString($v,$DB);
                     if (PEAR::isError($value)) {
                         $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
@@ -3706,7 +3649,7 @@ class DB_DataObject extends DB_DataObject_Overload
                
         
         
-        $table = $this->__table;
+        $table = $this->tableName();
         
         if ($quoteIdentifiers) {
             $joinAs   = $DB->quoteIdentifier($joinAs);
@@ -3718,7 +3661,7 @@ class DB_DataObject extends DB_DataObject_Overload
        
         
         $fullJoinAs = '';
-        $addJoinAs  = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->__table) : $obj->__table) != $joinAs;
+        $addJoinAs  = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->tableName()) : $obj->tableName()) != $joinAs;
         if ($addJoinAs) {
             // join table a AS b - is only supported by a few databases and is probably not needed
             // , however since it makes the whole Statement alot clearer we are leaving it in
@@ -3767,6 +3710,247 @@ class DB_DataObject extends DB_DataObject_Overload
 
     }
 
+    /**
+     * autoJoin - using the links.ini file, it builds a query with all the joins 
+     * usage: 
+     * $x = DB_DataObject::factory('mytable');
+     * $x->autoJoin();
+     * $x->get(123); 
+     *   will result in all of the joined data being added to the fetched object..
+     * 
+     * $x = DB_DataObject::factory('mytable');
+     * $x->autoJoin();
+     * $ar = $x->fetchAll();
+     *   will result in an array containing all the data from the table, and any joined tables..
+     * 
+     * $x = DB_DataObject::factory('mytable');
+     * $jdata = $x->autoJoin();
+     * $x->selectAdd(); //reset..
+     * foreach($_REQUEST['requested_cols'] as $c) {
+     *    if (!isset($jdata[$c])) continue; // ignore columns not available..
+     *    $x->selectAdd( $jdata[$c] . ' as ' . $c);
+     * }
+     * $ar = $x->fetchAll(); 
+     *   will result in only the columns requested being fetched...
+     *
+     *
+     *
+     * @param     array     Configuration
+     *          exclude  Array of columns to exclude from results (eg. modified_by_id)
+     *          links    The equivilant links.ini data for this table eg.
+     *                    array( 'person_id' => 'person:id', .... )
+     *          include  Array of columns to include
+     *          distinct Array of distinct columns.
+     *          
+     * @return   array      info about joins
+     *                      cols => map of resulting {joined_tablename}.{joined_table_column_name}
+     *                      join_names => map of resulting {join_name_as}.{joined_table_column_name}
+     *                      count => the column to count on.
+     * @access   public
+     */
+    function autoJoin($cfg = array())
+    {
+        //var_Dump($cfg);exit;
+        $pre_links = $this->links();
+        if (!empty($cfg['links'])) {
+            $this->links(array_merge( $pre_links , $cfg['links']));
+        }
+        $map = $this->links( );
+        
+        
+        //print_r($map);
+        $tabdef = $this->table();
+         
+        // we need this as normally it's only cleared by an empty selectAs call.
+       
+        
+        $keys = array_keys($tabdef);
+        if (!empty($cfg['exclude'])) {
+            $keys = array_intersect($keys, array_diff($keys, $cfg['exclude'])); 
+        }
+        if (!empty($cfg['include'])) {
+            
+            $keys =  array_intersect($keys,  $cfg['include']); 
+        }
+        
+        $selectAs = array();
+        
+        if (!empty($keys)) {
+            $selectAs = array(array( $keys , '%s', false));
+        }
+        
+        $ret = array(
+            'cols' => array(),
+            'join_names' => array(),
+            'count' => false,
+        );
+        
+        
+        
+        $has_distinct = false;
+        if (!empty($cfg['distinct']) && $keys) {
+            
+            // reset the columsn?
+            $cols = array();
+            
+             //echo '<PRE>' ;print_r($xx);exit;
+            foreach($keys as $c) {
+                //var_dump($c);
+                
+                if (  $cfg['distinct'] == $c) {
+                    $has_distinct = 'DISTINCT( ' . $this->tableName() .'.'. $c .') as ' . $c;
+                    $ret['count'] =  'DISTINCT  ' . $this->tableName() .'.'. $c .'';
+                    continue;
+                }
+                // cols is in our filtered keys...
+                $cols = $c;
+                
+            }
+            // apply our filtered version, which excludes the distinct column.
+            
+            $selectAs = empty($cols) ?  array() : array(array(  $cols , '%s', false)) ;
+            
+            
+            
+        } 
+                
+        foreach($keys as $k) {
+            $ret['cols'][$k] = $this->tableName(). '.' . $k;
+        }
+        
+         
+        
+        foreach($map as $ocl=>$info) {
+            
+            list($tab,$col) = explode(':', $info);
+            // what about multiple joins on the same table!!!
+            $xx = DB_DataObject::factory($tab);
+            if (!is_object($xx) || !is_a($xx, 'DB_DataObject')) {
+                continue;
+            }
+            // skip columns that are excluded.
+            
+            // we ignore include here... - as
+             
+            // this is borked ... for multiple jions..
+            $this->joinAdd($xx, 'LEFT', 'join_'.$ocl.'_'. $col, $ocl);
+            
+            if (!empty($cfg['exclude']) && in_array($ocl, $cfg['exclude'])) {
+                continue;
+            }
+            
+            $tabdef = $xx->table();
+            $table = $xx->tableName();
+            
+            $keys = array_keys($tabdef);
+            
+            
+            if (!empty($cfg['exclude'])) {
+                $keys = array_intersect($keys, array_diff($keys, $cfg['exclude']));
+                
+                foreach($keys as $k) {
+                    if (in_array($ocl.'_'.$k, $cfg['exclude'])) {
+                        $keys = array_diff($keys, $k); // removes the k..
+                    }
+                }
+                
+            }
+            
+            if (!empty($cfg['include'])) {
+                // include will basically be BASECOLNAME_joinedcolname
+                $nkeys = array();
+                foreach($keys as $k) {
+                    if (in_array( sprintf($ocl.'_%s', $k), $cfg['include'])) {
+                        $nkeys[] = $k;
+                    }
+                }
+                $keys = $nkeys;
+            }
+            
+            if (empty($keys)) {
+                continue;
+            }
+            // got distinct, and not yet found it..
+            if (!$has_distinct && !empty($cfg['distinct']))  {
+                $cols = array();
+                foreach($keys as $c) {
+                    $tn = sprintf($ocl.'_%s', $c);
+                      
+                    if ( $tn == $cfg['distinct']) {
+                        
+                        $has_distinct = 'DISTINCT( ' . 'join_'.$ocl.'_'.$col.'.'.$c .')  as ' . $tn ;
+                        $ret['count'] =  'DISTINCT  join_'.$ocl.'_'.$col.'.'.$c;
+                       // var_dump($this->countWhat );
+                        continue;
+                    }
+                    $cols[] = $c;
+                     
+                }
+                
+                if (!empty($cols)) {
+                    $selectAs[] = array($cols, $ocl.'_%s', 'join_'.$ocl.'_'. $col);
+                }
+                
+            } else {
+                $selectAs[] = array($keys, $ocl.'_%s', 'join_'.$ocl.'_'. $col);
+            }
+              
+            foreach($keys as $k) {
+                $ret['cols'][sprintf('%s_%s', $ocl, $k)] = $tab.'.'.$k;
+                $ret['join_names'][sprintf('%s_%s', $ocl, $k)] = sprintf('join_%s_%s.%s',$ocl, $col, $k);
+            }
+             
+        }
+        
+        // fill in the select details..
+        $this->selectAdd(); 
+        
+        if ($has_distinct) {
+            $this->selectAdd($has_distinct);
+        }
+         
+        foreach($selectAs as $ar) {
+            
+            $this->selectAs($ar[0], $ar[1], $ar[2]);
+        }
+        // restore links..
+        $this->links( $pre_links );
+        
+        return $ret;
+        
+    }
+    
+    /**
+     * Factory method for calling DB_DataObject_Cast
+     *
+     * if used with 1 argument DB_DataObject_Cast::sql($value) is called
+     * 
+     * if used with 2 arguments DB_DataObject_Cast::$value($callvalue) is called
+     * valid first arguments are: blob, string, date, sql
+     * 
+     * eg. $member->updated = $member->sqlValue('NOW()');
+     * 
+     * 
+     * might handle more arguments for escaping later...
+     * 
+     *
+     * @param string $value (or type if used with 2 arguments)
+     * @param string $callvalue (optional) used with date/null etc..
+     */
+    
+    function sqlValue($value)
+    {
+        $method = 'sql';
+        if (func_num_args() == 2) {
+            $method = $value;
+            $value = func_get_arg(1);
+        }
+        require_once 'DB/DataObject/Cast.php';
+        return call_user_func(array('DB_DataObject_Cast', $method), $value);
+        
+    }
+    
+    
     /**
      * Copies items that are in the table definitions from an
      * array or object into the current object
@@ -3786,7 +3970,7 @@ class DB_DataObject extends DB_DataObject_Overload
         $items = $this->table();
         if (!$items) {
             $this->raiseError(
-                "setFrom:Could not find table definition for {$this->__table}", 
+                "setFrom:Could not find table definition for {$this->tableName()}", 
                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
             return;
         }
@@ -3838,13 +4022,15 @@ class DB_DataObject extends DB_DataObject_Overload
                 }
                 continue;
             }
-            if (is_object($from[sprintf($format,$k)])) {
+            $val = $from[sprintf($format,$k)];
+            if (is_a($val, 'DB_DataObject_Cast')) {
+                $this->$k = $val;
                 continue;
             }
-            if (is_array($from[sprintf($format,$k)])) {
+            if (is_object($val) || is_array($val)) {
                 continue;
             }
-            $ret = $this->fromValue($k,$from[sprintf($format,$k)]);
+            $ret = $this->fromValue($k,$val);
             if ($ret !== true)  {
                 $overload_return[$k] = 'Not A Valid Value';
             }
@@ -3867,7 +4053,9 @@ class DB_DataObject extends DB_DataObject_Overload
      * will also return links converted to arrays.
      *
      * @param   string  sprintf format for array
-     * @param   bool    empty only return elemnts that have a value set.
+     * @param   bool||number    [true = elemnts that have a value set],
+     *                          [false = table + returned colums] ,
+     *                          [0 = returned columsn only]
      *
      * @access   public
      * @return   array of key => value for row
@@ -3876,34 +4064,40 @@ class DB_DataObject extends DB_DataObject_Overload
     function toArray($format = '%s', $hideEmpty = false) 
     {
         global $_DB_DATAOBJECT;
+        
+        // we use false to ignore sprintf.. (speed up..)
+        $format = $format == '%s' ? false : $format;
+        
         $ret = array();
         $rf = ($this->_resultFields !== false) ? $this->_resultFields : 
-                (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]) ? $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid] : false);
+                (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]) ?
+                 $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid] : false);
+        
         $ar = ($rf !== false) ?
-            array_merge($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid],$this->table()) :
+            (($hideEmpty === 0) ? $rf : array_merge($rf, $this->table())) :
             $this->table();
 
         foreach($ar as $k=>$v) {
              
             if (!isset($this->$k)) {
                 if (!$hideEmpty) {
-                    $ret[sprintf($format,$k)] = '';
+                    $ret[$format === false ? $k : sprintf($format,$k)] = '';
                 }
                 continue;
             }
             // call the overloaded getXXXX() method. - except getLink and getLinks
             if (method_exists($this,'get'.$k) && !in_array(strtolower($k),array('links','link'))) {
-                $ret[sprintf($format,$k)] = $this->{'get'.$k}();
+                $ret[$format === false ? $k : sprintf($format,$k)] = $this->{'get'.$k}();
                 continue;
             }
             // should this call toValue() ???
-            $ret[sprintf($format,$k)] = $this->$k;
+            $ret[$format === false ? $k : sprintf($format,$k)] = $this->$k;
         }
         if (!$this->_link_loaded) {
             return $ret;
         }
         foreach($this->_link_loaded as $k) {
-            $ret[sprintf($format,$k)] = $this->$k->toArray();
+            $ret[$format === false ? $k : sprintf($format,$k)] = $this->$k->toArray();
         
         }
         
@@ -4018,7 +4212,7 @@ class DB_DataObject extends DB_DataObject_Overload
      * @access public
      * @return object The DB connection
      */
-    function &getDatabaseConnection()
+    function getDatabaseConnection()
     {
         global $_DB_DATAOBJECT;
 
@@ -4041,7 +4235,7 @@ class DB_DataObject extends DB_DataObject_Overload
      * @return object The DB result object
      */
      
-    function &getDatabaseResult()
+    function getDatabaseResult()
     {
         global $_DB_DATAOBJECT;
         $this->_connect();
@@ -4173,7 +4367,7 @@ class DB_DataObject extends DB_DataObject_Overload
         $options = $_DB_DATAOBJECT['CONFIG'];
         $cols = $this->table();
         // dont know anything about this col..
-        if (!isset($cols[$col])) {
+        if (!isset($cols[$col]) || is_a($value, 'DB_DataObject_Cast')) {
             $this->$col = $value;
             return true;
         }
@@ -4433,17 +4627,9 @@ class DB_DataObject extends DB_DataObject_Overload
         if ($behaviour == PEAR_ERROR_DIE && !empty($_DB_DATAOBJECT['CONFIG']['dont_die'])) {
             $behaviour = null;
         }
-        $error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+        $error = PEAR::getStaticProperty('DB_DataObject','lastError');
         
-        // this will never work totally with PHP's object model.
-        // as this is passed on static calls (like staticGet in our case)
-
-        if (isset($this) && is_object($this) && is_subclass_of($this,'db_dataobject')) {
-            $this->_lastError = $error;
-        }
-
-        $_DB_DATAOBJECT['LASTERROR'] = $error;
-
+      
         // no checks for production here?....... - we log  errors before we throw them.
         DB_DataObject::debug($message,'ERROR',1);
         
@@ -4456,6 +4642,14 @@ class DB_DataObject extends DB_DataObject_Overload
                             $opts=null, $userinfo=null, 'DB_DataObject_Error'
                         );
         }
+        // this will never work totally with PHP's object model.
+        // as this is passed on static calls (like staticGet in our case)
+        $_DB_DATAOBJECT['LASTERROR'] = $error;
+        
+        if (isset($this) && is_object($this) && is_subclass_of($this,'db_dataobject')) {
+            $this->_lastError = $error;
+        }
    
         return $error;
     }
@@ -4562,6 +4756,76 @@ class DB_DataObject extends DB_DataObject_Overload
        
     }
     
+    /**
+     * (deprecated - use ::get / and your own caching method)
+     */
+    function staticGet($class, $k, $v = null)
+    {
+        $lclass = strtolower($class);
+        global $_DB_DATAOBJECT;
+        if (empty($_DB_DATAOBJECT['CONFIG'])) {
+            DB_DataObject::_loadConfig();
+        }
+
+        
+
+        $key = "$k:$v";
+        if ($v === null) {
+            $key = $k;
+        }
+        if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+            DB_DataObject::debug("$class $key","STATIC GET - TRY CACHE");
+        }
+        if (!empty($_DB_DATAOBJECT['CACHE'][$lclass][$key])) {
+            return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
+        }
+        if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+            DB_DataObject::debug("$class $key","STATIC GET - NOT IN CACHE");
+        }
+
+        $obj = DB_DataObject::factory(substr($class,strlen($_DB_DATAOBJECT['CONFIG']['class_prefix'])));
+        if (PEAR::isError($obj)) {
+            DB_DataObject::raiseError("could not autoload $class", DB_DATAOBJECT_ERROR_NOCLASS);
+            $r = false;
+            return $r;
+        }
+        
+        if (!isset($_DB_DATAOBJECT['CACHE'][$lclass])) {
+            $_DB_DATAOBJECT['CACHE'][$lclass] = array();
+        }
+        if (!$obj->get($k,$v)) {
+            DB_DataObject::raiseError("No Data return from get $k $v", DB_DATAOBJECT_ERROR_NODATA);
+            
+            $r = false;
+            return $r;
+        }
+        $_DB_DATAOBJECT['CACHE'][$lclass][$key] = $obj;
+        return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
+    }
+    
+    /**
+     * autoload Class relating to a table
+     * (deprecited - use ::factory)
+     *
+     * @param  string  $table  table
+     * @access private
+     * @return string classname on Success
+     */
+    function staticAutoloadTable($table)
+    {
+        global $_DB_DATAOBJECT;
+        if (empty($_DB_DATAOBJECT['CONFIG'])) {
+            DB_DataObject::_loadConfig();
+        }
+        $p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
+            $_DB_DATAOBJECT['CONFIG']['class_prefix'] : '';
+        $class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
+        
+        $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class);
+        $class = $ce ? $class  : DB_DataObject::_autoloadClass($class);
+        return $class;
+    }
+    
     /* ---- LEGACY BC METHODS - NOT DOCUMENTED - See Documentation on New Methods. ---*/
     
     function _get_table() { return $this->table(); }
old mode 100644 (file)
new mode 100755 (executable)
index 2049beb..59cc0af
@@ -17,7 +17,7 @@
  * @author     Alan Knowles <alan@akbkhome.com>
  * @copyright  1997-2008 The PHP Group
  * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
- * @version    CVS: $Id: Cast.php 287158 2009-08-12 13:58:31Z alan_k $
+ * @version    CVS: $Id: Cast.php 326604 2012-07-12 03:02:00Z alan_k $
  * @link       http://pear.php.net/package/DB_DataObject
  */
   
@@ -395,7 +395,16 @@ class DB_DataObject_Cast {
                 // this is funny - the parameter order is reversed ;)
                 return "'".sqlite_escape_string($this->value)."'";
            
+            case 'mssql':
+                
+                if(is_numeric($this->value)) {
+                    return $this->value;
+                }
+                $unpacked = unpack('H*hex', $this->value);
+                return '0x' . $unpacked['hex'];
+                        
                  
+   
             default:
                 return PEAR::raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet");
         }
@@ -422,10 +431,10 @@ class DB_DataObject_Cast {
         // perhaps we should support TEXT fields???
         // 
         
-        if (!($to & DB_DATAOBJECT_BLOB)) {
-            return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::string to something other than a blob!'.
-                ' (why not just use native features)');
-        }
+        // $to == a string field which is the default type (0)
+        // so we do not test it here. - we assume that number fields
+        // will accept a string?? - which is stretching it a bit ...
+        // should probaly add that test as some point. 
         
         switch ($db->dsn['phptype']) {
             case 'pgsql':
@@ -438,7 +447,15 @@ class DB_DataObject_Cast {
             case 'mysqli':
                 return "'".mysqli_real_escape_string($db->connection, $this->value)."'";
 
-            
+            case 'mssql':
+                // copied from the old DB mssql code...?? not sure how safe this is.
+                return "'" . str_replace(
+                        array("'", "\\\r\n", "\\\n"),
+                        array("''", "\\\\\r\n\r\n", "\\\\\n\n"),
+                        $this->value 
+                    ) . "'";
+                
+
             default:
                 return PEAR::raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet");
         }
@@ -544,6 +561,5 @@ class DB_DataObject_Cast {
     
     
     
-    
 }
 
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
index 7ea716d..c06d6ed
@@ -15,7 +15,7 @@
  * @author     Alan Knowles <alan@akbkhome.com>
  * @copyright  1997-2006 The PHP Group
  * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
- * @version    CVS: $Id: Generator.php 298560 2010-04-25 23:01:51Z alan_k $
+ * @version    CVS: $Id: Generator.php 327926 2012-10-08 02:42:09Z alan_k $
  * @link       http://pear.php.net/package/DB_DataObject
  */
  
@@ -86,6 +86,13 @@ class DB_DataObject_Generator extends DB_DataObject
      */
     var $table; // active tablename
 
+    /**
+     * links (generated)
+     *
+     * @var array
+     * @access private
+     */
+    var $_fkeys; // active tablename
 
     /**
      * The 'starter' = call this to start the process
@@ -142,6 +149,7 @@ class DB_DataObject_Generator extends DB_DataObject
                 $t->_database = basename($t->_database);
             }
             $t->_createTableList();
+            $t->_createForiegnKeys();
 
             foreach(get_class_methods($class) as $method) {
                 if (substr($method,0,8 ) != 'generate') {
@@ -173,14 +181,17 @@ class DB_DataObject_Generator extends DB_DataObject
     function _createTableList()
     {
         $this->_connect();
+        
         $options = &PEAR::getStaticProperty('DB_DataObject','options');
 
+       
+
         $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
 
         $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
         $is_MDB2 = ($db_driver != 'DB') ? true : false;
 
-        if (is_a($__DB , 'PEAR_Error')) {
+        if (is_object($__DB) && is_a($__DB , 'PEAR_Error')) {
             return PEAR::raiseError($__DB->toString(), null, PEAR_ERROR_DIE);
         }
         
@@ -202,7 +213,7 @@ class DB_DataObject_Generator extends DB_DataObject
             $__DB->loadModule('Reverse');
         }
 
-        if ((empty($this->tables) || is_a($this->tables , 'PEAR_Error'))) {
+        if ((empty($this->tables) || (is_object($this->tables) && is_a($this->tables , 'PEAR_Error')))) {
             //if that fails fall back to clasic tables list.
             if (!$is_MDB2) {
                 // try getting a list of schema tables first. (postgres)
@@ -218,18 +229,19 @@ class DB_DataObject_Generator extends DB_DataObject
             }
         }
 
-        if (is_a($this->tables , 'PEAR_Error')) {
+        if (is_object($this->tables) && is_a($this->tables , 'PEAR_Error')) {
             return PEAR::raiseError($this->tables->toString(), null, PEAR_ERROR_DIE);
         }
 
         // build views as well if asked to.
         if (!empty($options['build_views'])) {
             if (!$is_MDB2) {
-                $views = $__DB->getListOf('views');
+                $views = $__DB->getListOf(is_string($options['build_views']) ?
+                                    $options['build_views'] : 'views');
             } else {
                 $views = $__DB->manager->listViews();
             }
-            if (is_a($views,'PEAR_Error')) {
+            if (is_object($views) && is_a($views,'PEAR_Error')) {
                 return PEAR::raiseError(
                 'Error getting Views (check the PEAR bug database for the fix to DB), ' .
                 $views->toString(),
@@ -246,23 +258,36 @@ class DB_DataObject_Generator extends DB_DataObject
 
         foreach($this->tables as $table) {
             if (isset($options['generator_include_regex']) &&
-            !preg_match($options['generator_include_regex'],$table)) {
+                    !preg_match($options['generator_include_regex'],$table)) {
+                $this->debug("SKIPPING (generator_include_regex) : $table");
                 continue;
-            } else if (isset($options['generator_exclude_regex']) &&
-            preg_match($options['generator_exclude_regex'],$table)) {
+            } 
+            
+            if (isset($options['generator_exclude_regex']) &&
+                    preg_match($options['generator_exclude_regex'],$table)) {
                 continue;
             }
+            
+            $strip = empty($options['generator_strip_schema']) ? false : $options['generator_strip_schema'];
+            $strip = is_numeric($strip) ? (bool) $strip : $strip;
+            $strip = (is_string($strip) && strtolower($strip) == 'true') ? true : $strip;
+        
             // postgres strip the schema bit from the
-            if (!empty($options['generator_strip_schema'])) {
-                $bits = explode('.', $table,2);
-                $table = $bits[0];
-                if (count($bits) > 1) {
-                    $table = $bits[1];
+            if (!empty($strip) ) {
+                
+                if (!is_string($strip) || preg_match($strip, $table)) { 
+                    $bits = explode('.', $table,2);
+                    $table = $bits[0];
+                    if (count($bits) > 1) {
+                        $table = $bits[1];
+                    }
                 }
             }
+            $this->debug("EXTRACTING : $table");
+            
             $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? 
                 $__DB->quoteIdentifier($table) : $table;
-                
+          
             if (!$is_MDB2) {
                 
                 $defs =  $__DB->tableInfo($quotedTable);
@@ -272,8 +297,9 @@ class DB_DataObject_Generator extends DB_DataObject
                 
             }
 
-            if (is_a($defs,'PEAR_Error')) {
+            if (is_object($defs) && is_a($defs,'PEAR_Error')) {
                 // running in debug mode should pick this up as a big warning..
+                $this->debug("Error reading tableInfo: $table");
                 $this->raiseError('Error reading tableInfo, '. $defs->toString());
                 continue;
             }
@@ -300,6 +326,7 @@ class DB_DataObject_Generator extends DB_DataObject
         // the temporary table array is now the right one (tables names matching
         // with regex expressions have been removed)
         $this->tables = $tmp_table;
+         
         //print_r($this->_definitions);
     }
     
@@ -352,6 +379,12 @@ class DB_DataObject_Generator extends DB_DataObject
         $tmpname = tempnam(session_save_path(),'DataObject_');
         //print_r($this->_newConfig);
         $fh = fopen($tmpname,'w');
+        if (!$fh) {
+            return PEAR::raiseError(
+                "Failed to create temporary file: $tmpname\n".
+                "make sure session.save_path is set and is writable\n"
+                ,null, PEAR_ERROR_DIE);
+        }
         fwrite($fh,$this->_newConfig);
         fclose($fh);
         $perms = file_exists($file) ? fileperms($file) : 0755;
@@ -368,15 +401,15 @@ class DB_DataObject_Generator extends DB_DataObject
         //    return PEAR::raiseError($ret->message,null,PEAR_ERROR_DIE);
         // }
     }
-
-    /**
-     * generate Foreign Keys (for links.ini) 
-     * Currenly only works with mysql / mysqli
+     /**
+     * create the data for Foreign Keys (for links.ini) 
+     * Currenly only works with mysql / mysqli / posgtreas
      * to use, you must set option: generate_links=true
      * 
      * @author Pascal Schöni 
      */
-    function generateForeignKeys() 
+    
+    function _createForiegnKeys()
     {
         $options = PEAR::getStaticProperty('DB_DataObject','options');
         if (empty($options['generate_links'])) {
@@ -387,7 +420,7 @@ class DB_DataObject_Generator extends DB_DataObject
             echo "WARNING: cant handle non-mysql and pgsql introspection for defaults.";
             return; // cant handle non-mysql introspection for defaults.
         }
-
+        $this->debug("generateForeignKeys: Start");
         $DB = $this->getDatabaseConnection();
 
         $fk = array();
@@ -458,6 +491,38 @@ class DB_DataObject_Generator extends DB_DataObject
                 }
 
         }
+     
+        $this->_fkeys = $fk;
+        
+        
+        
+        
+        
+    }
+    
+
+    /**
+     * generate Foreign Keys (for links.ini) 
+     * Currenly only works with mysql / mysqli
+     * to use, you must set option: generate_links=true
+     * 
+     * @author Pascal Schöni 
+     */
+    function generateForeignKeys() 
+    {
+        $options = PEAR::getStaticProperty('DB_DataObject','options');
+        if (empty($options['generate_links'])) {
+            return false;
+        }
+        $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
+        if (!in_array($__DB->phptype, array('mysql', 'mysqli', 'pgsql'))) {
+            echo "WARNING: cant handle non-mysql and pgsql introspection for defaults.";
+            return; // cant handle non-mysql introspection for defaults.
+        }
+        $this->debug("generateForeignKeys: Start");
+        
+        $fk = $this->_fkeys;
         $links_ini = "";
 
         foreach($fk as $table => $details) {
@@ -467,21 +532,23 @@ class DB_DataObject_Generator extends DB_DataObject
             }
             $links_ini .= "\n";
         }
-
+      
         // dont generate a schema if location is not set
         // it's created on the fly!
         $options = PEAR::getStaticProperty('DB_DataObject','options');
 
-        if (empty($options['schema_location'])) {
+        if (!empty($options['schema_location'])) {
+             $file = "{$options['schema_location']}/{$this->_database}.links.ini";
+        } elseif (isset($options["ini_{$this->_database}"])) {
+            $file = preg_replace('/\.ini/','.links.ini',$options["ini_{$this->_database}"]);
+        } else {
+            $this->debug("generateForeignKeys: SKIP - schema_location or ini_{database} was not set");
             return;
         }
-
-        
-        $file = "{$options['schema_location']}/{$this->_database}.links.ini";
+         
 
         if (!file_exists(dirname($file))) {
-            require_once 'System.php';
-            System::mkdir(array('-p','-m',0755,dirname($file)));
+            mkdir(dirname($file),0755, true);
         }
 
         $this->debug("Writing ini as {$file}\n");
@@ -490,6 +557,12 @@ class DB_DataObject_Generator extends DB_DataObject
         $tmpname = tempnam(session_save_path(),'DataObject_');
        
         $fh = fopen($tmpname,'w');
+        if (!$fh) {
+            return PEAR::raiseError(
+                "Failed to create temporary file: $tmpname\n".
+                "make sure session.save_path is set and is writable\n"
+                ,null, PEAR_ERROR_DIE);
+        }
         fwrite($fh,$links_ini);
         fclose($fh);
         $perms = file_exists($file) ? fileperms($file) : 0755;
@@ -615,6 +688,7 @@ class DB_DataObject_Generator extends DB_DataObject
                 case 'TEXT':
                 case 'MEDIUMTEXT':
                 case 'LONGTEXT':
+                case '_TEXT':   //postgres (?? view ??)
                     
                     $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT;
                     break;
@@ -706,14 +780,20 @@ class DB_DataObject_Generator extends DB_DataObject
             //echo "\n{$t->name} => {$t->flags}\n";
             $secondary_key_match = isset($options['generator_secondary_key_match']) ? $options['generator_secondary_key_match'] : 'primary|unique';
             
-            if (preg_match('/(auto_increment|nextval\()/i',rawurldecode($t->flags)) 
+            $m = array();
+            if (preg_match('/(auto_increment|nextval\(([^)]*))/i',rawurldecode($t->flags),$m) 
                 || (isset($t->autoincrement) && ($t->autoincrement === true))) {
-                    
+                
+                $sn = 'N';
+                if ($DB->phptype == 'pgsql' && !empty($m[2])) { 
+                    $sn = preg_replace('/[("]+/','', $m[2]);
+                    //echo urldecode($t->flags) . "\n" ;
+                }
                 // native sequences = 2
                 if ($write_ini) {
-                    $keys_out_primary .= "{$t->name} = N\n";
+                    $keys_out_primary .= "{$t->name} = $sn\n";
                 }
-                $ret_keys_primary[$t->name] = 'N';
+                $ret_keys_primary[$t->name] = $sn;
             
             } else if ($secondary_key_match && preg_match('/('.$secondary_key_match.')/i',$t->flags)) {
                 // keys.. = 1
@@ -814,9 +894,9 @@ class DB_DataObject_Generator extends DB_DataObject
         //echo "Generating Class files:        \n";
         $options = &PEAR::getStaticProperty('DB_DataObject','options');
        
-       $this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
-       $this->_extendsFile = empty($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
+        $this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
+        $this->_extendsFile = empty($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
+     
 
         foreach($this->tables as $this->table) {
             $this->table        = trim($this->table);
@@ -835,6 +915,12 @@ class DB_DataObject_Generator extends DB_DataObject
             $tmpname = tempnam(session_save_path(),'DataObject_');
        
             $fh = fopen($tmpname, "w");
+            if (!$fh) {
+                return PEAR::raiseError(
+                    "Failed to create temporary file: $tmpname\n".
+                    "make sure session.save_path is set and is writable\n"
+                    ,null, PEAR_ERROR_DIE);
+            }
             fputs($fh,$out);
             fclose($fh);
             $perms = file_exists($outfilename) ? fileperms($outfilename) : 0755;
@@ -912,7 +998,6 @@ class DB_DataObject_Generator extends DB_DataObject
         
         $body .= "    {$var} \$__table = '{$this->table}';  {$p}// table name\n";
     
-        
         // if we are using the option database_{databasename} = dsn
         // then we should add var $_database = here
         // as database names may not always match.. 
@@ -924,7 +1009,7 @@ class DB_DataObject_Generator extends DB_DataObject
          // Only include the $_database property if the omit_database_var is unset or false
         
         if (isset($options["database_{$this->_database}"]) && empty($GLOBALS['_DB_DATAOBJECT']['CONFIG']['generator_omit_database_var'])) {
-            $p = str_repeat(' ',   max(2, (16 - strlen($this->table))));
+            $p = str_repeat(' ',   max(2, (16 - strlen($this->_database))));
             $body .= "    {$var} \$_database = '{$this->_database}';  {$p}// database name (used with database_{*} config)\n";
         }
         
@@ -972,22 +1057,27 @@ class DB_DataObject_Generator extends DB_DataObject
         // grep -r __clone * to find all it's uses
         // and replace them with $x = clone($y);
         // due to the change in the PHP5 clone design.
-        
+        $static = 'static';
         if ( substr(phpversion(),0,1) < 5) {
             $body .= "\n";
             $body .= "    /* ZE2 compatibility trick*/\n";
             $body .= "    function __clone() { return \$this;}\n";
         }
-
-        // simple creation tools ! (static stuff!)
-        $body .= "\n";
-        $body .= "    /* Static get */\n";
-        $body .= "    function staticGet(\$k,\$v=NULL) { return DB_DataObject::staticGet('{$this->classname}',\$k,\$v); }\n";
         
+        
+        // depricated - in here for BC...
+        if (!empty($options['static_get'])) {
+            
+            // simple creation tools ! (static stuff!)
+            $body .= "\n";
+            $body .= "    /* Static get */\n";
+            $body .= "    $static  function staticGet(\$k,\$v=NULL) { " .
+                    "return DB_DataObject::staticGet('{$this->classname}',\$k,\$v = null); }\n";
+        }
         // generate getter and setter methods
         $body .= $this->_generateGetters($input);
         $body .= $this->_generateSetters($input);
-        
+        $body .= $this->_generateLinkMethods($input);
         /*
         theoretically there is scope here to introduce 'list' methods
         based up 'xxxx_up' column!!! for heiracitcal trees..
@@ -1193,7 +1283,7 @@ class DB_DataObject_Generator extends DB_DataObject
         $class_prefix  = empty($options['class_prefix']) ? '' : $options['class_prefix'];
         
         $this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
-       $this->_extendsFile = empty($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
+        $this->_extendsFile = empty($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
  
         $classname = $this->classname = $this->getClassNameFromTableName($this->table);
         
@@ -1346,7 +1436,74 @@ class DB_DataObject_Generator extends DB_DataObject
 
         return $getters;
     }
+    /**
+    * Generate link setter/getter methods for class definition
+    *
+    * @param    string  Existing class contents
+    * @return   string
+    * @access   public
+    */
+    function _generateLinkMethods($input) 
+    {
+
+        $options = &PEAR::getStaticProperty('DB_DataObject','options');
+        $setters = '';
+
+        // only generate if option is set to true
+        
+        // generate_link_methods true::
+        
+        
+        if  (empty($options['generate_link_methods'])) {
+            //echo "skip lm? - not set";
+            return '';
+        }
+        
+        if (empty($this->_fkeys)) {
+            // echo "skip lm? - fkyes empty";
+            return '';
+        }
+        if (empty($this->_fkeys[$this->table])) {
+            //echo "skip lm? - no fkeys for {$this->table}";
+            return '';
+        }
+            
+        // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
+        $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
+
+        $setters .= "\n";
+        $defs     = $this->_fkeys[$this->table];
+         
+        
+        // $fk[$this->table][$tref[1]] = $tref[2] . ":" . $tref[3];
+
+        // loop through properties and create setter methods
+        foreach ($defs as $k => $info) {
 
+            // build mehtod name
+            $methodName =  is_callable($options['generate_link_methods']) ?
+                    $options['generate_link_methods']($k) : $k;
+
+            if (!strlen(trim($k)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
+                continue;
+            }
+
+            $setters .= "   /**\n";
+            $setters .= "    * Getter / Setter for \${$k}\n";
+            $setters .= "    *\n";
+            $setters .= "    * @param    mixed   (optional) value to assign\n";
+            $setters .= "    * @access   public\n";
+            
+            $setters .= "    */\n";
+            $setters .= (substr(phpversion(),0,1) > 4) ? '    public '
+                                                       : '    ';
+            $setters .= "function $methodName() {\n";
+            $setters .= "        return \$this->link('$k', func_get_args());\n";
+            $setters .= "    }\n\n";
+        }
+         
+        return $setters;
+    }
 
    /**
     * Generate setter methods for class definition
diff --git a/extlib/DB/DataObject/Links.php b/extlib/DB/DataObject/Links.php
new file mode 100644 (file)
index 0000000..9b51b56
--- /dev/null
@@ -0,0 +1,485 @@
+<?php
+/**
+ * Link tool for DB_DataObject
+ *
+ * PHP versions 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB_DataObject
+ * @author     Alan Knowles <alan@akbkhome.com>
+ * @copyright  1997-2006 The PHP Group
+ * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
+ * @version    : FIXME
+ * @link       http://pear.php.net/package/DB_DataObject
+ */
+
+
+/**
+ *
+ * Example of how this could be used..
+ * 
+ * The lind method are now in here.
+ *
+ * Currenly only supports existing methods, and new 'link()' method
+ *
+ */
+  
+  
+/**
+ * Links class
+ *
+ * @package DB_DataObject
+ */
+class DB_DataObject_Links 
+{
+     /**
+     * @property {DB_DataObject}      do   DataObject to apply this to.
+     */
+    var $do = false;
+    
+    
+    /**
+     * @property {Array|String} load    What to load, 'all' or an array of properties. (default all)
+     */
+    var $load = 'all';
+    /**
+     * @property {String|Boolean}       scanf   use part of column name as resulting
+     *                                          property name. (default false)
+     */
+    var $scanf = false;
+    /**
+     * @property {String|Boolean}       printf  use column name as sprintf for resulting property name..
+     *                                     (default %s_link if apply is true, otherwise it is %s)
+     */
+    var $printf = false;
+    /**
+     * @property {Boolean}      cached  cache the result, so future queries will use cache rather
+     *                                  than running the expensive sql query.
+     */
+    var $cached = false;
+    /**
+     * @property {Boolean}      apply   apply the result to this object, (default true)
+     */
+    var $apply = true;
+   
+    
+    //------------------------- RETURN ------------------------------------
+    /**
+     * @property {Array}      links    key value associative array of links.
+     */
+    var $links;
+    
+    
+    /**
+     * Constructor
+     *   -- good ole style..
+     *  @param {DB_DataObject}           do  DataObject to apply to.
+     *  @param {Array}           cfg  Configuration (basically properties of this object)
+     */
+    
+    function DB_DataObject_Links($do,$cfg= array())
+    {
+        // check if do is set!!!?
+        $this->do = $do;
+        
+        foreach($cfg as $k=>$v) {
+            $this->$k = $v;
+        }
+       
+        
+    }
+     
+    /**
+     * return name from related object
+     *
+     * The relies on  a <dbname>.links.ini file, unless you specify the arguments.
+     * 
+     * you can also use $this->getLink('thisColumnName','otherTable','otherTableColumnName')
+     *
+     *
+     * @param string $field|array    either row or row.xxxxx or links spec.
+     * @param string|DB_DataObject $table  (optional) name of table to look up value in
+     * @param string $link   (optional)  name of column in other table to match
+     * @author Tim White <tim@cyface.com>
+     * @access public
+     * @return mixed object on success false on failure or '0' when not linked
+     */
+    function getLink($field, $table= false, $link='')
+    {
+        
+        static $cache = array();
+        
+        // GUESS THE LINKED TABLE.. (if found - recursevly call self)
+        
+        if ($table == false) {
+            
+            
+            $info = $this->linkInfo($field);
+            
+            if ($info) {
+                return $this->getLink($field, $info[0],  $link === false ? $info[1] : $link );
+            }
+            
+            // no links defined.. - use borked BC method...
+                  // use the old _ method - this shouldnt happen if called via getLinks()
+            if (!($p = strpos($field, '_'))) {
+                return false;
+            }
+            $table = substr($field, 0, $p);
+            return $this->getLink($field, $table);
+            
+            
+
+        }
+         
+        $tn = is_string($table) ? $table : $table->tableName();
+         
+            
+        if (!isset($this->do->$field)) {
+            $this->do->raiseError("getLink: row not set $field", DB_DATAOBJECT_ERROR_NODATA);
+            return false;
+        }
+        
+        // check to see if we know anything about this table..
+        
+      
+        if (empty($this->do->$field) || $this->do->$field < 0) {
+            return 0; // no record. 
+        }
+        
+        if ($this->cached && isset($cache[$tn.':'. $link .':'. $this->do->$field])) {
+            return $cache[$tn.':'. $link .':'. $this->do->$field];    
+        }
+        
+        $obj = is_string($table) ? $this->do->factory($tn) : $table;;
+        
+        if (!is_a($obj,'DB_DataObject')) {
+            $this->do->raiseError(
+                "getLink:Could not find class for row $field, table $tn", 
+                DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+            return false;
+        }
+        // -1 or 0 -- no referenced record..
+       
+        $ret = false;
+        if ($link) {
+            
+            if ($obj->get($link, $this->do->$field)) {
+                $ret = $obj;
+            }
+            
+            
+        // this really only happens when no link config is set (old BC stuff)    
+        } else if ($obj->get($this->do->$field)) {
+            $ret= $obj;
+             
+        }
+        if ($this->cached) {
+            $cache[$tn.':'. $link .':'. $this->do->$field] = $ret;
+        }
+        return $ret;
+        
+    }
+    /**
+     * get link information for a field or field specification
+     *
+     * alll link (and join methods accept the 'link' info ) in various ways
+     * string : 'field' = which field to get (uses ???.links.ini to work out what)
+     * array(2) : 'field', 'table:remote_col' << just like the links.ini def.
+     * array(3) : 'field', $dataobject, 'remote_col'  (handy for joinAdd to do nested joins.)
+     *
+     * @param string|array $field or link spec to use. 
+     * @return (false|array) array of dataobject and linked field or false.
+     *
+     *
+     */
+    
+    function linkInfo($field)
+    {
+         
+        if (is_array($field)) {
+            if (count($field) == 3) {
+                // array with 3 args:
+                // local_col , dataobject, remote_col
+                return array(
+                    $field[1],
+                    $field[2],
+                    $field[0]
+                );
+                
+            } 
+            list($table,$link) = explode(':', $field[1]);
+            
+            return array(
+                $this->do->factory($table),
+                $link,
+                $field[0]
+            );
+            
+        }
+        // work out the link.. (classic way)
+        
+        $links = $this->do->links();
+        
+        if (empty($links) || !is_array($links)) {
+             
+            return false;
+        }
+            
+            
+        if (!isset($links[$field])) {
+            
+            return false;
+        }
+        list($table,$link) = explode(':', $links[$field]);
+    
+        
+        //??? needed???
+        if ($p = strpos($field,".")) {
+            $field = substr($field,0,$p);
+        }
+        
+        return array(
+            $this->do->factory($table),
+            $link,
+            $field
+        );
+        
+        
+         
+        
+    }
+    
+    
+        
+    /**
+     *  a generic geter/setter provider..
+     *
+     *  provides a generic getter setter for the referenced object
+     *  eg.
+     *  $link->link('company_id') returns getLink for the object
+     *  if nothing is linked (it will return an empty dataObject)
+     *  $link->link('company_id', array(1)) - just sets the 
+     *
+     *  also array as the field speck supports
+     *      $link->link(array('company_id', 'company:id'))
+     *  
+     *
+     *  @param  string|array   $field   the field to fetch or link spec.
+     *  @params array          $args    the arguments sent to the getter setter
+     *  @return mixed true of false on set, the object on getter.
+     *
+     */
+    function link($field, $args = array())
+    {
+        $info = $this->linkInfo($field);
+         
+        if (!$info) {
+            $this->do->raiseError(
+                "getLink:Could not find link for row $field", 
+                DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+            return false;
+        }
+        $field = $info[2];
+        
+        
+        if (empty($args)) { // either an empty array or really empty....
+            
+            if (!isset($this->do->$field)) {
+                return $info[0]; // empty dataobject.
+            }
+            
+            $ret = $this->getLink($field);
+            // nothing linked -- return new object..
+            return ($ret === 0) ? $info[0] : $ret;
+            
+        }
+        $assign = is_array($args) ? $args[0] : $args;
+         
+        // otherwise it's a set call..
+        if (!is_a($assign , 'DB_DataObject')) {
+            
+            if (is_numeric($assign) && is_integer($assign * 1)) {
+                if ($assign  > 0) {
+                    
+                    if (!$info) {
+                        return false;
+                    }
+                    // check that record exists..
+                    if (!$info[0]->get($info[1], $assign )) {
+                        return false;
+                    }
+                    
+                }
+                
+                $this->do->$field = $assign ;
+                return true;
+            }
+            
+            return false;
+        }
+        
+        // otherwise we are assigning it ...
+        
+        $this->do->$field = $assign->{$info[1]};
+        return true;
+        
+        
+    }
+    /**
+     * load related objects
+     *
+     * Generally not recommended to use this.
+     * The generator should support creating getter_setter methods which are better suited.
+     *
+     * Relies on  <dbname>.links.ini
+     *
+     * Sets properties on the calling dataobject  you can change what
+     * object vars the links are stored in by  changeing the format parameter
+     *
+     *
+     * @param  string format (default _%s) where %s is the table name.
+     * @author Tim White <tim@cyface.com>
+     * @access public
+     * @return boolean , true on success
+     */
+    
+    function applyLinks($format = '_%s')
+    {
+         
+        // get table will load the options.
+        if ($this->do->_link_loaded) {
+            return true;
+        }
+        
+        $this->do->_link_loaded = false;
+        $cols  = $this->do->table();
+        $links = $this->do->links();
+         
+        $loaded = array();
+        
+        if ($links) {   
+            foreach($links as $key => $match) {
+                list($table,$link) = explode(':', $match);
+                $k = sprintf($format, str_replace('.', '_', $key));
+                // makes sure that '.' is the end of the key;
+                if ($p = strpos($key,'.')) {
+                      $key = substr($key, 0, $p);
+                }
+                
+                $this->do->$k = $this->getLink($key, $table, $link);
+                
+                if (is_object($this->do->$k)) {
+                    $loaded[] = $k; 
+                }
+            }
+            $this->do->_link_loaded = $loaded;
+            return true;
+        }
+        // this is the autonaming stuff..
+        // it sends the column name down to getLink and lets that sort it out..
+        // if there is a links file then it is not used!
+        // IT IS DEPRECATED!!!! - DO NOT USE 
+        if (!is_null($links)) {    
+            return false;
+        }
+        
+        
+        foreach (array_keys($cols) as $key) {
+            if (!($p = strpos($key, '_'))) {
+                continue;
+            }
+            // does the table exist.
+            $k =sprintf($format, $key);
+            $this->do->$k = $this->getLink($key);
+            if (is_object($this->do->$k)) {
+                $loaded[] = $k; 
+            }
+        }
+        $this->do->_link_loaded = $loaded;
+        return true;
+    }
+    
+    /**
+     * getLinkArray
+     * Fetch an array of related objects. This should be used in conjunction with a
+     * <dbname>.links.ini file configuration (see the introduction on linking for details on this).
+     *
+     * You may also use this with all parameters to specify, the column and related table.
+     * 
+     * @access public
+     * @param string $field- either column or column.xxxxx
+     * @param string $table (optional) name of table to look up value in
+     * @param string $fkey (optional) fetchall key see DB_DataObject::fetchAll()
+     * @param string $fval (optional)fetchall val DB_DataObject::fetchAll()
+     * @param string $fval (optional) fetchall method DB_DataObject::fetchAll()
+     * @return array - array of results (empty array on failure)
+     * 
+     * Example - Getting the related objects
+     * 
+     * $person = new DataObjects_Person;
+     * $person->get(12);
+     * $children = $person->getLinkArray('children');
+     * 
+     * echo 'There are ', count($children), ' descendant(s):<br />';
+     * foreach ($children as $child) {
+     *     echo $child->name, '<br />';
+     * }
+     * 
+     */
+    function getLinkArray($field, $table = null, $fkey = false, $fval = false, $fmethod = false)
+    {
+        
+        $ret = array();
+        if (!$table)  {
+            
+            
+            $links = $this->do->links();
+            
+            if (is_array($links)) {
+                if (!isset($links[$field])) {
+                    // failed..
+                    return $ret;
+                }
+                list($table,$link) = explode(':',$links[$field]);
+                return $this->getLinkArray($field,$table);
+            } 
+            if (!($p = strpos($field,'_'))) {
+                return $ret;
+            }
+            return $this->getLinkArray($field,substr($field,0,$p));
+
+
+        }
+        
+        $c  = $this->do->factory($table);
+        
+        if (!is_object($c) || !is_a($c,'DB_DataObject')) {
+            $this->do->raiseError(
+                "getLinkArray:Could not find class for row $field, table $table", 
+                DB_DATAOBJECT_ERROR_INVALIDCONFIG
+            );
+            return $ret;
+        }
+
+        // if the user defined method list exists - use it...
+        if (method_exists($c, 'listFind')) {
+            $c->listFind($this->id);
+            while ($c->fetch()) {
+                $ret[] = clone($c);
+            }
+            return $ret;
+        } 
+        return $c->fetchAll($fkey, $fval, $fmethod);
+        
+        
+    }
+
+}
\ No newline at end of file
old mode 100644 (file)
new mode 100755 (executable)
index d54d28c..ed2e738
@@ -16,7 +16,7 @@
 // | Author:  Alan Knowles <alan@akbkhome.com>
 // +----------------------------------------------------------------------+
 //
-// $Id: createTables.php 277015 2009-03-12 05:51:03Z alan_k $
+// $Id: createTables.php 315758 2011-08-30 08:11:59Z alan_k $
 //
 
 // since this version doesnt use overload, 
@@ -27,13 +27,17 @@ define('DB_DATAOBJECT_NO_OVERLOAD',1);
 //require_once 'DB/DataObject/Generator.php';
 require_once 'DB/DataObject/Generator.php';
 
+if (php_sapi_name() != 'cli') {
+    PEAR::raiseError("\nERROR: You must turn use the cli sapi to run this", null, PEAR_ERROR_DIE);
+}
+
 if (!ini_get('register_argc_argv')) {
     PEAR::raiseError("\nERROR: You must turn register_argc_argv On in you php.ini file for this to work\neg.\n\nregister_argc_argv = On\n\n", null, PEAR_ERROR_DIE);
     exit;
 }
 
 if (!@$_SERVER['argv'][1]) {
-    PEAR::raiseError("\nERROR: createTable.php usage:\n\nC:\php\pear\DB\DataObjects\createTable.php example.ini\n\n", null, PEAR_ERROR_DIE);
+    PEAR::raiseError("\nERROR: createTable.php usage:\n\n" .$_SERVER['argv'][0] . " example.ini\n\n", null, PEAR_ERROR_DIE);
     exit;
 }
 
index 4eca000c9efed19ae3857a99962d5cd48e8b7e26..f49c73aeab62f51448cc8cc72e810a058dfa2651 100644 (file)
@@ -94,6 +94,15 @@ class AccountProfileBlock extends ProfileBlock
         return $this->profile->bio;
     }
 
+    function otherProfiles()
+    {
+        $others = array();
+
+        Event::handle('OtherAccountProfiles', array($this->profile, &$others));
+        
+        return $others;
+    }
+
     function showTags()
     {
         $cur = common_current_user();
index 0563fe2163624bd5f8e5e1bd6ce1af31ccaefa77..71fd7407e21e976df9b7ce88e4866356e609947f 100644 (file)
@@ -1000,17 +1000,17 @@ class Action extends HTMLOutputter // lawsuit
             // TRANS: Text between [] is a link description, text between () is the link itself.
             // TRANS: Make sure there is no whitespace between "]" and "(".
             // TRANS: "%%site.broughtby%%" is the value of the variable site.broughtby
-            $instr = _('**%%site.name%%** is a microblogging service brought to you by [%%site.broughtby%%](%%site.broughtbyurl%%).');
+            $instr = _('**%%site.name%%** is a social network, courtesy of [%%site.broughtby%%](%%site.broughtbyurl%%).');
         } else {
             // TRANS: First sentence of the StatusNet site license. Used if 'broughtby' is not set.
-            $instr = _('**%%site.name%%** is a microblogging service.');
+            $instr = _('**%%site.name%%** is a social network.');
         }
         $instr .= ' ';
         // TRANS: Second sentence of the StatusNet site license. Mentions the StatusNet source code license.
         // TRANS: Make sure there is no whitespace between "]" and "(".
         // TRANS: Text between [] is a link description, text between () is the link itself.
         // TRANS: %s is the version of StatusNet that is being used.
-        $instr .= sprintf(_('It runs the [StatusNet](http://status.net/) microblogging software, version %s, available under the [GNU Affero General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html).'), STATUSNET_VERSION);
+        $instr .= sprintf(_('It runs on [GNU social](http://www.gnu.org/software/social/), version %s, available under the [GNU Affero General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html).'), STATUSNET_VERSION);
         $output = common_markup_to_html($instr);
         $this->raw($output);
         // do it
index cd2383808d3fb3a0cfa5fc425c0af88c0b1b751a..7546e2cd432c2fc6f8f82c6ab0402d3cab8e95ef 100644 (file)
@@ -389,9 +389,10 @@ class Activity
 
             if ($object instanceof Activity) {
                 // Sharing a post activity is more like sharing the original object
-                if ($this->verb == 'share' && $object->verb == 'post') {
+                if (ActivityVerb::canonical($this->verb) == ActivityVerb::canonical(ActivityVerb::SHARE) &&
+                    ActivityVerb::canonical($object->verb) == ActivityVerb::canonical(ActivityVerb::POST)) {
                     // XXX: Here's one for the obfuscation record books
-                    $object = $object->object;
+                    $object = $object->objects[0];
                 }
             }
 
index b26fc87e24bc5fe0179b822a801689806d1f95e0..5a42a1dfb3431b847fd1804df9666efdeb699fde 100644 (file)
@@ -906,25 +906,27 @@ class ActivityObject
 
                 list($lat, $lon) = explode(' ', $this->geopoint);
 
-                $object['location'] = array(
-                    'objectType' => 'place',
-                    'position' => sprintf("%+02.5F%+03.5F/", $lat, $lon),
-                    'lat' => $lat,
-                    'lon' => $lon
-                );
+                if (!empty($lat) && !empty($lon)) {
+                    $object['location'] = array(
+                        'objectType' => 'place',
+                        'position' => sprintf("%+02.5F%+03.5F/", $lat, $lon),
+                        'lat' => $lat,
+                        'lon' => $lon
+                    );
 
-                $loc = Location::fromLatLon($lat, $lon);
+                    $loc = Location::fromLatLon((float)$lat, (float)$lon);
 
-                if ($loc) {
-                    $name = $loc->getName();
+                    if ($loc) {
+                        $name = $loc->getName();
 
-                    if ($name) {
-                        $object['location']['displayName'] = $name;
-                    }
-                    $url = $loc->getURL();
+                        if ($name) {
+                            $object['location']['displayName'] = $name;
+                        }
+                        $url = $loc->getURL();
 
-                    if ($url) {
-                        $object['location']['url'] = $url;
+                        if ($url) {
+                            $object['location']['url'] = $url;
+                        }
                     }
                 }
             }
index b8af14ac211b00f4e1967c093cff346d26c1d881..78c7c4a118ff7385bc96b5d3d27418fac2a08aa5 100644 (file)
@@ -86,4 +86,9 @@ class DefaultProfileBlock extends AccountProfileBlock
     {
         return null;
     }
+
+    function otherProfiles()
+    {
+        return array();
+    }
 }
\ No newline at end of file
index fba6183fc22c98d84c6d735731cd91ce98952c7b..f7bca1ed68e52ec1e7092e62c2267a65002cffad 100644 (file)
@@ -42,7 +42,7 @@ class FileNoticeStream extends ScopingNoticeStream
             $profile = Profile::current();
         }
         parent::__construct(new CachingNoticeStream(new RawFileNoticeStream($file),
-                                                    'file:notice-ids:'.$this->url),
+                                                    'file:notice-ids:'.$file->id),
                             $profile);
     }
 }
index f12ad8048a83961e546b3ceb07e19425710dd5fc..99955b040c137ac25857e3a25b308ed90ff9b250 100644 (file)
 
 if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
 
-define('STATUSNET_BASE_VERSION', '1.1.0');
-define('STATUSNET_LIFECYCLE', 'alpha1'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
+define('STATUSNET_BASE_VERSION', '1.1.1');
+define('STATUSNET_LIFECYCLE', 'release'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
 define('STATUSNET_VERSION', STATUSNET_BASE_VERSION . '-' . STATUSNET_LIFECYCLE);
 
 define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility
 
-define('STATUSNET_CODENAME', 'Fight for Your Right');
+define('STATUSNET_CODENAME', 'OK');
 
 define('AVATAR_PROFILE_SIZE', 96);
 define('AVATAR_STREAM_SIZE', 48);
index 58e553a4c23348aca55296f36d457d547f5abb4e..87ec174dc6f336c0a9ef1d6efc7575e573331fd5 100644 (file)
@@ -85,6 +85,11 @@ class GroupProfileBlock extends ProfileBlock
         return $this->group->description;
     }
 
+    function otherProfiles()
+    {
+        return array();
+    }
+
     function showActions()
     {
         $cur = common_current_user();
index 8bf3678d4fa12fc5fad55d875b55c8985b3e2fe4..c9b2705991f91e262f9ce53690f97e45c28d6754 100644 (file)
@@ -232,10 +232,15 @@ class ResultItem
         $this->id           = $this->notice->id;
         $this->from_user_id = $this->profile->id;
 
-        $user = User::staticGet('id', $this->profile->id);
-
-        $this->iso_language_code = $user->language;
+        $user = $this->profile->getUser();
 
+        if (empty($user)) {
+            // Gonna have to do till we can detect it
+            $this->iso_language_code = common_config('site', 'language');
+        } else {
+            $this->iso_language_code = $user->language;
+        }
+        
         $this->source = $this->getSourceLink($this->notice->source);
 
         $avatar = $this->profile->getAvatar(AVATAR_STREAM_SIZE);
index eb19a1a9aac1c240cc8143e3955fb1041b7fde80..8edc5d9ba1b8ecbbe06b1a4c570d426fcd0a4941 100644 (file)
@@ -61,6 +61,7 @@ abstract class ProfileBlock extends Widget
         $this->showName();
         $this->showLocation();
         $this->showHomepage();
+        $this->showOtherProfiles();
         $this->showDescription();
         $this->showTags();
     }
@@ -133,6 +134,33 @@ abstract class ProfileBlock extends Widget
         }
     }
 
+    function showOtherProfiles()
+    {
+        $otherProfiles = $this->otherProfiles();
+
+        if (!empty($otherProfiles)) {
+
+            $this->out->elementStart('ul',
+                                     array('class' => 'profile_block_otherprofile_list'));
+
+            foreach ($otherProfiles as $otherProfile) {
+                $this->out->elementStart('li');
+                $this->out->elementStart('a',
+                                         array('href' => $otherProfile['href'],
+                                               'rel' => 'me',
+                                               'class' => 'profile_block_otherprofile',
+                                               'title' => $otherProfile['text']));
+                $this->out->element('img',
+                                    array('src' => $otherProfile['image'],
+                                          'class' => 'profile_block_otherprofile_icon'));
+                $this->out->elementEnd('a');
+                $this->out->elementEnd('li');
+            }
+
+            $this->out->elementEnd('ul');
+        }
+    }
+
     function avatarSize()
     {
         return AVATAR_PROFILE_SIZE;
index b5c6fc304cbe827538a5aa01d95ee492d01cf669..f43f89b1f940d8986e0e1488727f434f27bc40bf 100644 (file)
@@ -99,7 +99,6 @@ class PublicSite extends SiteProfileSettings
             ),
             'plugins' => array(
                 'default' => array(
-                    'Activity'                => null,
                     'Bookmark'                => null,
                     'ClientSideShorten'       => null,
                     'Directory'               => null,
@@ -145,7 +144,6 @@ class PrivateSite extends SiteProfileSettings
             ),
             'plugins' => array(
                 'default' => array(
-                    'Activity'                => null,
                     'Bookmark'                => null,
                     'ClientSideShorten'       => null,
                     'Directory'               => null,
@@ -208,7 +206,6 @@ class CommunitySite extends SiteProfileSettings
             ),
             'plugins' => array(
                 'default' => array(
-                    'Activity'                => null,
                     'Bookmark'                => null,
                     'ClientSideShorten'       => null,
                     'Directory'               => null,
@@ -253,7 +250,6 @@ class SingleuserSite extends SiteProfileSettings
             ),
             'plugins' => array(
                 'default' => array(
-                    'Activity'                => null,
                     'Bookmark'                => null,
                     'ClientSideShorten'       => null,
                     'Event'                   => null,
index 94eec19c6cc43c0d867dffc679f9f55d49d76d28..f4a771c5f0cbec97d6033d7051ca2f585811ef4b 100644 (file)
@@ -42,11 +42,12 @@ class UserActivityStream extends AtomUserNoticeFeed
      *                           Raw output mode will attempt to stream, keeping less
      *                           data in memory but will leave $this->activities incomplete.
      */
-    function __construct($user, $indent = true, $outputMode = UserActivityStream::OUTPUT_STRING)
+    function __construct($user, $indent = true, $outputMode = UserActivityStream::OUTPUT_STRING, $after = null)
     {
         parent::__construct($user, null, $indent);
 
         $this->outputMode = $outputMode;
+
         if ($this->outputMode == self::OUTPUT_STRING) {
             // String buffering? Grab all the notices now.
             $notices = $this->getNotices();
@@ -65,6 +66,8 @@ class UserActivityStream extends AtomUserNoticeFeed
             throw new Exception('Invalid outputMode provided to ' . __METHOD__);
         }
 
+        $this->after = $after;
+
         // Assume that everything but notices is feasible
         // to pull at once and work with in memory...
 
@@ -123,7 +126,7 @@ class UserActivityStream extends AtomUserNoticeFeed
                     $notices = $this->getNoticesBetween($start, $end);
                     foreach ($notices as $noticeAct) {
                         try {
-                            $nact = $noticeAct->asActivity();
+                            $nact = $noticeAct->asActivity($this->user);
                             if ($format == Feed::ATOM) {
                                 $nact->outputTo($this, false, false);
                             } else {
@@ -172,10 +175,14 @@ class UserActivityStream extends AtomUserNoticeFeed
         if ($this->outputMode == self::OUTPUT_RAW) {
             // Grab anything after the last pre-sorted activity.
             try {
-                $notices = $this->getNoticesBetween(0, $end);
+                if (!empty($this->after)) {
+                    $notices = $this->getNoticesBetween($this->after, $end);
+                } else {
+                    $notices = $this->getNoticesBetween(0, $end);
+                }
                 foreach ($notices as $noticeAct) {
                     try {
-                        $nact = $noticeAct->asActivity();
+                        $nact = $noticeAct->asActivity($this->user);
                         if ($format == Feed::ATOM) {
                             $nact->outputTo($this, false, false);
                         } else {
@@ -195,23 +202,25 @@ class UserActivityStream extends AtomUserNoticeFeed
             }
         }
 
-        // We always add the registration activity at the end, even if
-        // they have older activities (from restored backups) in their stream.
+        if (empty($this->after) || strtotime($this->user->created) > $this->after) {
+            // We always add the registration activity at the end, even if
+            // they have older activities (from restored backups) in their stream.
 
-        try {
-            $ract = $this->user->registrationActivity();
-            if ($format == Feed::ATOM) {
-                $ract->outputTo($this, false, false);
-            } else {
-                if ($haveOne) {
-                    fwrite($handle, ",");
+            try {
+                $ract = $this->user->registrationActivity();
+                if ($format == Feed::ATOM) {
+                    $ract->outputTo($this, false, false);
+                } else {
+                    if ($haveOne) {
+                        fwrite($handle, ",");
+                    }
+                    fwrite($handle, json_encode($ract->asArray()));
+                    $haveOne = true;
                 }
-                fwrite($handle, json_encode($ract->asArray()));
-                $haveOne = true;
+            } catch (Exception $e) {
+                common_log(LOG_ERR, $e->getMessage());
+                continue;
             }
-        } catch (Exception $e) {
-            common_log(LOG_ERR, $e->getMessage());
-            continue;
         }
     }
 
@@ -231,6 +240,10 @@ class UserActivityStream extends AtomUserNoticeFeed
 
         $sub->subscriber = $this->user->id;
 
+        if (!empty($this->after)) {
+            $sub->whereAdd("created > '" . common_sql_date($this->after) . "'");
+        }
+
         if ($sub->find()) {
             while ($sub->fetch()) {
                 if ($sub->subscribed != $this->user->id) {
@@ -250,6 +263,10 @@ class UserActivityStream extends AtomUserNoticeFeed
 
         $sub->subscribed = $this->user->id;
 
+        if (!empty($this->after)) {
+            $sub->whereAdd("created > '" . common_sql_date($this->after) . "'");
+        }
+
         if ($sub->find()) {
             while ($sub->fetch()) {
                 if ($sub->subscriber != $this->user->id) {
@@ -269,6 +286,10 @@ class UserActivityStream extends AtomUserNoticeFeed
 
         $fave->user_id = $this->user->id;
 
+        if (!empty($this->after)) {
+            $fave->whereAdd("modified > '" . common_sql_date($this->after) . "'");
+        }
+
         if ($fave->find()) {
             while ($fave->fetch()) {
                 $faves[] = clone($fave);
@@ -292,6 +313,17 @@ class UserActivityStream extends AtomUserNoticeFeed
 
         $notice->profile_id = $this->user->id;
 
+        // Only stuff after $this->after
+
+        if (!empty($this->after)) {
+            if ($start) {
+                $start = max($start, $this->after);
+            }
+            if ($end) {
+                $end = max($end, $this->after);
+            }
+        }
+
         if ($start) {
             $tsstart = common_sql_date($start);
             $notice->whereAdd("created >= '$tsstart'");
@@ -314,7 +346,11 @@ class UserActivityStream extends AtomUserNoticeFeed
 
     function getNotices()
     {
-        return $this->getNoticesBetween();
+        if (!empty($this->after)) {
+            return $this->getNoticesBetween($this->after);
+        } else {
+            return $this->getNoticesBetween();
+        }
     }
 
     function getGroups()
@@ -325,6 +361,10 @@ class UserActivityStream extends AtomUserNoticeFeed
 
         $gm->profile_id = $this->user->id;
 
+        if (!empty($this->after)) {
+            $gm->whereAdd("created > '" . common_sql_date($this->after) . "'");
+        }
+
         if ($gm->find()) {
             while ($gm->fetch()) {
                 $groups[] = clone($gm);
@@ -338,14 +378,31 @@ class UserActivityStream extends AtomUserNoticeFeed
     {
         $msgMap = Memcached_DataObject::listGet('Message', 'to_profile', array($this->user->id));
 
-        return $msgMap[$this->user->id];
+        $messages = $msgMap[$this->user->id];
+
+        if (!empty($this->after)) {
+            $messages = array_filter($messages, array($this, 'createdAfter'));
+        }
+
+        return $messages;
     }
 
     function getMessagesFrom()
     {
         $msgMap = Memcached_DataObject::listGet('Message', 'from_profile', array($this->user->id));
 
-        return $msgMap[$this->user->id];
+        $messages = $msgMap[$this->user->id];
+
+        if (!empty($this->after)) {
+            $messages = array_filter($messages, array($this, 'createdAfter'));
+        }
+
+        return $messages;
+    }
+
+    function createdAfter($item) {
+        $created = strtotime((empty($item->created)) ? $item->modified : $item->created);
+        return ($created >= $this->after);
     }
 
     function writeJSON($handle)
index 9d07e7e6d2f1a0b375608dcdba76460bc2031e0f..02e8aea7c67aa86c5e006cd5ceb4176219c37434 100644 (file)
@@ -8122,8 +8122,7 @@ msgid ""
 "\n"
 "Thanks for your time, \n"
 "%2$s\n"
-msgstr ""
-"Hola, %1$s.\n"
+msgstr "Hola, %1$s.\n"
 "\n"
 "Algú ha introduït aquesta adreça electrònica a %2$s.\n"
 "\n"
diff --git a/plugins/ActivitySpam/scripts/silencespammer.php b/plugins/ActivitySpam/scripts/silencespammer.php
new file mode 100644 (file)
index 0000000..fde299e
--- /dev/null
@@ -0,0 +1,120 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2013 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:n:a';
+$longoptions = array('id=', 'nickname=', 'all');
+
+$helptext = <<<END_OF_SILENCESPAMMER_HELP
+silencespammer.php [options]
+Users who post a lot of spam get silenced
+
+  -i --id       ID of user to test and silence
+  -n --nickname nickname of the user to test and silence
+  -a --all      All users
+END_OF_SILENCESPAMMER_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+function testAllUsers($filter, $minimum, $percent) {
+    $found = false;
+    $offset = 0;
+    $limit  = 1000;
+
+    do {
+
+        $user = new User();
+        $user->orderBy('created');
+        $user->limit($offset, $limit);
+
+        $found = $user->find();
+
+        if ($found) {
+            while ($user->fetch()) {
+                try {
+                    silencespammer($filter, $user, $minimum, $percent);
+                } catch (Exception $e) {
+                    printfnq("ERROR testing user %s\n: %s", $user->nickname, $e->getMessage());
+                }
+            }
+            $offset += $found;
+        }
+
+    } while ($found > 0);
+}
+
+function silencespammer($filter, $user, $minimum, $percent) {
+
+    printfnq("Testing user %s\n", $user->nickname);
+
+    $profile = Profile::staticGet('id', $user->id);
+
+    if ($profile->isSilenced()) {
+       printfnq("Already silenced %s\n", $user->nickname);
+       return;
+    }
+    
+    $cnt = $profile->noticeCount();
+
+    if ($cnt < $minimum) {
+        printfnq("Only %d notices posted (minimum %d); skipping\n", $cnt, $minimum);
+       return;
+    }
+
+    $ss = new Spam_score();
+
+    $ss->query(sprintf("SELECT count(*) as spam_count ".
+                       "FROM notice join spam_score on notice.id = spam_score.notice_id ".
+                       "WHERE notice.profile_id = %d AND spam_score.is_spam = 1", $profile->id));
+
+    while ($ss->fetch()) {
+        $spam_count = $ss->spam_count;
+    }                 
+
+    $spam_percent = ($spam_count * 100.0 / $cnt);
+
+    if ($spam_percent > $percent) {
+        printfnq("Silencing user %s (%d/%d = %0.2f%% spam)\n", $user->nickname, $spam_count, $cnt, $spam_percent);
+        try {
+            $profile->silence();
+        } catch(Exception $e) {
+            printfnq("Error: %s", $e->getMessage());
+        }       
+    }    
+}
+
+try {
+    $filter = null;
+    $minimum = 5;
+    $percent = 80;
+    Event::handle('GetSpamFilter', array(&$filter));
+    if (empty($filter)) {
+        throw new Exception(_("No spam filter."));
+    }
+    if (have_option('a', 'all')) {
+        testAllUsers($filter, $minimum, $percent);
+    } else {
+        $user = getUser();
+        silencespammer($filter, $user, $minimum, $percent);
+    }
+} catch (Exception $e) {
+    print $e->getMessage()."\n";
+    exit(1);
+}
index bf16da337dc7137852b1c17075cfd6e11e0edc60..07a149785cf5db86aa1909dd0ad9af8def2c3c5f 100644 (file)
@@ -559,6 +559,69 @@ ENDOFSCRIPT;
         return true;
     }
 
+    /**
+     * Add links in the user's profile block to their Facebook profile URL.
+     *
+     * @param Profile $profile The profile being shown
+     * @param Array   &$links  Writeable array of arrays (href, text, image).
+     *
+     * @return boolean hook value (true)
+     */
+
+    function onOtherAccountProfiles($profile, &$links)
+    {
+        $fuser = null;
+
+        $flink = Foreign_link::getByUserID($profile->id, FACEBOOK_SERVICE);
+
+        if (!empty($flink)) {
+
+            $fuser = $this->getFacebookUser($flink->foreign_id);
+
+            if (!empty($fuser)) {
+                $links[] = array("href" => $fuser->link,
+                                 "text" => sprintf(_("%s on Facebook"), $fuser->name),
+                                 "image" => $this->path("images/f_logo.png"));
+            }
+        }
+
+        return true;
+    }
+
+    function getFacebookUser($id) {
+
+        $key = Cache::key(sprintf("FacebookBridgePlugin:userdata:%s", $id));
+
+        $c = Cache::instance();
+
+        if ($c) {
+            $obj = $c->get($key);
+            if ($obj) {
+                return $obj;
+            }
+        }
+
+        $url = sprintf("https://graph.facebook.com/%s", $id);
+        $client = new HTTPClient();
+        $resp = $client->get($url);
+
+        if (!$resp->isOK()) {
+            return null;
+        }
+
+        $user = json_decode($resp->getBody());
+
+        if ($user->error) {
+            return null;
+        }
+
+        if ($c) {
+            $c->set($key, $user);
+        }
+
+        return $user;
+    }
+
     /*
      * Add version info for this plugin
      *
diff --git a/plugins/FacebookBridge/images/f_logo.png b/plugins/FacebookBridge/images/f_logo.png
new file mode 100644 (file)
index 0000000..b54e21c
Binary files /dev/null and b/plugins/FacebookBridge/images/f_logo.png differ
index 0fa5b09a8627afb69b06eaca876d0b720c421645..dee6c3c77121be677932a5e1dc7ad98c15c57c29 100644 (file)
@@ -344,7 +344,6 @@ msgstr ""
 "Cordiali saluti,\n"
 "\n"
 "%2$s\n"
-" "
 
 #. TRANS: E-mail subject. %s is the StatusNet sitename.
 #, php-format
diff --git a/plugins/GNUsocialDukiDukiSnail/GNUsocialDukiDukiSnail.php b/plugins/GNUsocialDukiDukiSnail/GNUsocialDukiDukiSnail.php
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/plugins/GNUsocialDukiDukiSnail/README b/plugins/GNUsocialDukiDukiSnail/README
new file mode 100644 (file)
index 0000000..98ae652
--- /dev/null
@@ -0,0 +1,151 @@
+GNU Social -- DukiDuki Snail
+============================
+
+Duki Duki Snail is a classic PC game, released in Czechoslovakia in
+the 1980s, and later reborn in free software in the mid 1990s.
+
+It is our intention to create a free software version of DukiDuki
+Snail for GNU social in which players can share their high scores.
+
+The game is built like such:
+
+A maze is drawn:
+
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXX                                          XXX                XXXX
+XXXX                                          XXX                XXXX
+XXXX                                          XXX                XXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXXXXXXXX   XXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXXXXXXXX   XXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXXXXXXXX   XXXX
+XXXX                                   XXXX                      XXXX
+XXXX                                   XXXX                      XXXX
+XXXX                                   XXXX                      XXXX
+XXXX   XXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXX
+XXXX   XXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXX
+XXXX   XXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXX
+XXXX   XXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXX
+XXXX   XXX          XXX          XXX          XXX   XXXX         XXXX
+XXXX   XXX          XXX          XXX          XXX   XXXX         XXXX
+XXXX   XXX          XXX          XXX          XXX   XXXX         XXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXXXXXXXXX   XXX   XXXX   XXXXXXXXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXXXXXXXXX   XXX   XXXX   XXXXXXXXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXXXXXXXXX   XXX   XXXX   XXXXXXXXXX
+XXXX   XXX   XXXX         XXXX         XXXX         XXXX         XXXX
+XXXX   XXX   XXXX         XXXX         XXXX         XXXX         XXXX
+XXXX   XXX   XXXX         XXXX         XXXX         XXXX         XXXX
+XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXX   XXXXXXXXXX   XXXX
+XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXX   XXXXXXXXXX   XXXX
+XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXX   XXXXXXXXXX   XXXX
+XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXX   XXXXXXXXXX   XXXX
+XXXX   XXX   XXXX                XXX   XXXX                XXX   XXXX
+XXXX   XXX   XXXX                XXX   XXXX                XXX   XXXX
+XXXX   XXX   XXXX                XXX   XXXX                XXX   XXXX
+XXXX   XXX   XXXX   XXXXXXXXXXXXXXXX   XXXX   XXXXXXXXXXXXXXXX   XXXX
+XXXX   XXX   XXXX   XXXXXXXXXXXXXXXX   XXXX   XXXXXXXXXXXXXXXX   XXXX
+XXXX   XXX   XXXX   XXXXXXXXXXXXXXXX   XXXX   XXXXXXXXXXXXXXXX   XXXX
+XXXX   XXX   XXXX   XXX          XXX   XXXX         XXXX         XXXX
+XXXX   XXX   XXXX   XXX          XXX   XXXX         XXXX         XXXX
+XXXX   XXX   XXXX   XXX          XXX   XXXX         XXXX         XXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXX   XXXX   XXXXXXXXXX   XXXXXXXXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXX   XXXX   XXXXXXXXXX   XXXXXXXXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXX   XXXX   XXXXXXXXXX   XXXXXXXXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXX   XXXX   XXXXXXXXXX   XXXXXXXXXX
+XXXX   XXX   XXXX   XXX   XXXX         XXXX   XXX          XXX   XXXX
+XXXX   XXX   XXXX   XXX   XXXX         XXXX   XXX          XXX   XXXX
+XXXX   XXX   XXXX   XXX   XXXX         XXXX   XXX          XXX   XXXX
+XXXX   XXX   XXXX   XXX   XXXXXXXXXXXXXXXXX   XXX   XXXXXXXXXX   XXXX
+XXXX   XXX   XXXX   XXX   XXXXXXXXXXXXXXXXX   XXX   XXXXXXXXXX   XXXX
+XXXX   XXX   XXXX   XXX   XXXXXXXXXXXXXXXXX   XXX   XXXXXXXXXX   XXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXX          XXX                XXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXX          XXX                XXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXX          XXX                XXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXX
+XXXX   XXX   XXXX                XXX                       XXX   XXXX
+XXXX   XXX   XXXX                XXX                       XXX   XXXX
+XXXX   XXX   XXXX                XXX                       XXX   XXXX
+XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXX   XXXX
+XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXX   XXXX
+XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXX   XXXX
+XXXX   XXX   XXXX                             XXX   XXXX         XXXX
+XXXX   XXX   XXXX                             XXX   XXXX         XXXX
+XXXX   XXX   XXXX                             XXX   XXXX         XXXX
+XXXX   XXX   XXXX   XXXXXXXXXXXXXXXX   XXXXXXXXXX   XXXX   XXXXXXXXXX
+XXXX   XXX   XXXX   XXXXXXXXXXXXXXXX   XXXXXXXXXX   XXXX   XXXXXXXXXX
+XXXX   XXX   XXXX   XXXXXXXXXXXXXXXX   XXXXXXXXXX   XXXX   XXXXXXXXXX
+XXXX   XXX   XXXX   XXXXXXXXXXXXXXXX   XXXXXXXXXX   XXXX   XXXXXXXXXX
+XXXX   XXX          XXX          XXX                XXXX   XXX   XXXX
+XXXX   XXX          XXX          XXX                XXXX   XXX   XXXX
+XXXX   XXX          XXX          XXX                XXXX   XXX   XXXX
+XXXX   XXX   XXXXXXXXXX   XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXX   XXXX
+XXXX   XXX   XXXXXXXXXX   XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXX   XXXX
+XXXX   XXX   XXXXXXXXXX   XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXX   XXXX
+XXXX   XXX                XXXX                XXX          XXX   XXXX
+XXXX   XXX                XXXX                XXX          XXX   XXXX
+XXXX   XXX                XXXX                XXX          XXX   XXXX
+XXXX   XXXXXXXXXXXXXXXX   XXXX   XXXXXXXXXX   XXX   XXXXXXXXXX   XXXX
+XXXX   XXXXXXXXXXXXXXXX   XXXX   XXXXXXXXXX   XXX   XXXXXXXXXX   XXXX
+XXXX   XXXXXXXXXXXXXXXX   XXXX   XXXXXXXXXX   XXX   XXXXXXXXXX   XXXX
+XXXX   XXXXXXXXXXXXXXXX   XXXX   XXXXXXXXXX   XXX   XXXXXXXXXX   XXXX
+XXXX   XXX                XXXX         XXXX         XXXX         XXXX
+XXXX   XXX                XXXX         XXXX         XXXX         XXXX
+XXXX   XXX                XXXX         XXXX         XXXX         XXXX
+XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXXXXXXXXX   XXX   XXXX
+XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXXXXXXXXX   XXX   XXXX
+XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXXXXXXXXX   XXX   XXXX
+XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXXXXXXXXX   XXX   XXXX
+XXXX   XXX   XXXX                XXX          XXX          XXX   XXXX
+XXXX   XXX   XXXX                XXX          XXX          XXX   XXXX
+XXXX   XXXXXXXXXX   XXXXXXXXXX   XXXXXXXXXX   XXX   XXXXXXXXXX   XXXX
+XXXX   XXXXXXXXXX   XXXXXXXXXX   XXXXXXXXXX   XXX   XXXXXXXXXX   XXXX
+XXXX   XXXXXXXXXX   XXXXXXXXXX   XXXXXXXXXX   XXX   XXXXXXXXXX   XXXX
+XXXX   XXXXXXXXXX   XXXXXXXXXX   XXXXXXXXXX   XXX   XXXXXXXXXX   XXXX
+XXXX                XXX          XXX   XXXX         XXXX   XXX   XXXX
+XXXX                XXX          XXX   XXXX         XXXX   XXX   XXXX
+XXXX                XXX          XXX   XXXX         XXXX   XXX   XXXX
+XXXX   XXXXXXXXXXXXXXXX   XXXX   XXX   XXXXXXXXXXXXXXXXX   XXX   XXXX
+XXXX   XXXXXXXXXXXXXXXX   XXXX   XXX   XXXXXXXXXXXXXXXXX   XXX   XXXX
+XXXX   XXXXXXXXXXXXXXXX   XXXX   XXX   XXXXXXXXXXXXXXXXX   XXX   XXXX
+XXXX   XXXXXXXXXXXXXXXX   XXXX   XXX   XXXXXXXXXXXXXXXXX   XXX   XXXX
+XXXX   XXX          XXX   XXXX                             XXX   XXXX
+XXXX   XXX          XXX   XXXX                             XXX   XXXX
+XXXXXXXXXX   XXX    XXX   XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXX
+XXXXXXXXXX   XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXX
+XXXXXXXXXX   XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXX
+XXXXXXXXXX   XXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXX
+XXXX         XXXX         XXXX                             XXX   XXXX
+XXXX         XXXX         XXXX                             XXX   XXXX
+XXXX         XXXX         XXXX                             XXX   XXXX
+XXXX   XXXXXXXXXXXXXXXX   XXXX   XXXXXXXXXXXXXXXXXXXXXXX   XXX   XXXX
+XXXX   XXXXXXXXXXXXXXXX   XXXX   XXXXXXXXXXXXXXXXXXXXXXX   XXX   XXXX
+XXXX   XXXXXXXXXXXXXXXX   XXXX   XXXXXXXXXXXXXXXXXXXXXXX   XXX   XXXX
+XXXX   XXXXXXXXXXXXXXXX   XXXX   XXXXXXXXXXXXXXXXXXXXXXX   XXX   XXXX
+XXXX   XXX          XXX   XXXX         XXXX         XXXX   XXX   XXXX
+XXXX   XXX          XXX   XXXX         XXXX         XXXX   XXX   XXXX
+XXXX   XXX          XXX   XXXX         XXXX         XXXX   XXX   XXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXXXXXXXXX   XXX   XXXX   XXX   XXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXXXXXXXXX   XXX   XXXX   XXX   XXXX
+XXXX   XXX   XXXX   XXX   XXXX   XXXXXXXXXX   XXX   XXXX   XXX   XXXX
+XXXX   XXX   XXXX         XXXX   XXX          XXX          XXX   XXXX
+XXXX   XXX   XXXX         XXXX   XXX          XXX          XXX   XXXX
+XXXX   XXX   XXXX         XXXX   XXX          XXX          XXX   XXXX
+XXXX   XXXXXXXXXXXXXXXXXXXXXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXX
+XXXX   XXXXXXXXXXXXXXXXXXXXXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXX
+XXXX   XXXXXXXXXXXXXXXXXXXXXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXX
+XXXX   XXXXXXXXXXXXXXXXXXXXXXX   XXX   XXXXXXXXXXXXXXXXXXXXXXX   XXXX
+                XXXX                             XXX
+@_K             XXXX                             XXX        [ exit ]
+                XXXX                             XXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXX
+
+and you must guide your snail @_K through the maze in record time.
+
+This seems like an ideal thing to build using something like <canvas> too.
\ No newline at end of file
diff --git a/plugins/GNUsocialLayout/GNUsocialLayout.php b/plugins/GNUsocialLayout/GNUsocialLayout.php
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/plugins/GNUsocialOffTheRecordMessaging/GNUsocialOffTheRecordMessaging.php b/plugins/GNUsocialOffTheRecordMessaging/GNUsocialOffTheRecordMessaging.php
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/plugins/GNUsocialPhoto/GNUsocialPhotoPlugin.php b/plugins/GNUsocialPhoto/GNUsocialPhotoPlugin.php
new file mode 100644 (file)
index 0000000..27a3285
--- /dev/null
@@ -0,0 +1,162 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2011, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @copyright 2011 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+class GNUsocialPhotoPlugin extends MicroAppPlugin
+{
+
+    function onCheckSchema()
+    {
+        $schema = Schema::get();
+
+        $schema->ensureTable('photo', Photo::schemaDef());
+
+        return true;
+    }
+
+    function onAutoload($cls)
+    {
+        $dir = dirname(__FILE__);
+        switch($cls)
+        {
+        case 'Photo':
+            include_once $dir . '/Photo.php';
+            break;
+        case 'NewPhotoForm':
+            include_once $dir . '/newphotoform.php';
+            break;
+        case 'NewphotoAction':
+            include_once $dir . '/newphoto.php';
+            break;
+        default:
+            break;
+        }
+        return true;
+    }
+
+    function onRouterInitialized($m)
+    {
+        $m->connect('main/photo/new', array('action' => 'newphoto'));
+        $m->connect('main/photo/:id', array('action' => 'showphoto'));
+        return true;
+    }
+
+    function entryForm($out)
+    {
+        return new NewPhotoForm($out);
+    }
+
+    function appTitle()
+    {
+        return _('Photo');
+    }
+
+    function tag()
+    {
+        return 'Photo';
+    }
+
+    function types()
+    {
+        return array(Photo::OBJECT_TYPE);
+    }
+
+    function saveNoticeFromActivity($activity, $actor, $options=array())
+    {
+
+        if(count($activity->objects) != 1) {
+            throw new Exception('Too many activity objects.');
+        }
+
+        $photoObj = $activity->objects[0];
+
+        if ($photoObj->type != Photo::OBJECT_TYPE) {
+            throw new Exception('Wrong type for object.');
+        }
+
+        $photo_uri = $photoObj->largerImage;
+        $thumb_uri = $photo_uri;
+        if(!empty($photoObj->thumbnail)){
+            $thumb_uri = $photoObj->thumbnail;
+        }
+
+        $description = $photoObj->description;
+        $title = $photoObj->title;
+        
+        $options['object_type'] = Photo::OBJECT_TYPE;
+
+        Photo::saveNew($actor, $photo_uri, $thumb_uri, $title, $description, $options);
+   
+    }
+
+    function activityObjectFromNotice($notice)
+    {
+
+        $photo = Photo::getByNotice($notice);
+        
+        $object = new ActivityObject();
+        $object->id = $notice->uri;
+        $object->type = Photo::OBJECT_TYPE;
+        $object->title = $photo->title;
+        $object->summary = $notice->content;
+        $object->link = $notice->bestUrl();
+
+        $object->largerImage = $photo->photo_uri;
+        $object->thumbnail = $photo->thumb_uri;
+        $object->description = $photo->description;
+        
+        return $object;
+        
+    }
+
+    function showNotice($notice, $out)
+    {
+        $photo = Photo::getByNotice($notice);
+        if ($photo) {
+            if($photo->title){
+                // TODO: ugly. feel like we should have a more abstract way
+                // of choosing the h-level.
+                $out->element('h3', array(), $title);
+            }
+            $out->element('img', array('src' => $photo->photo_uri,
+                'width' => '100%'));
+            // TODO: add description
+        }
+    }
+
+    function deleteRelated($notice)
+    {
+        $photo = Photo::getByNotice($notice);
+        if ($photo) {
+            $photo->delete();
+        }
+    }
+}
diff --git a/plugins/GNUsocialPhoto/Photo.php b/plugins/GNUsocialPhoto/Photo.php
new file mode 100644 (file)
index 0000000..f6d932a
--- /dev/null
@@ -0,0 +1,120 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2011, Free Software Foundation, Inc.
+ *
+ * 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/>.
+ *
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @copyright 2011 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if(!defined('STATUSNET')){
+    exit(1);
+}
+
+/**
+ * Data class for photos.
+ */
+
+class Photo extends Managed_DataObject
+{
+    const OBJECT_TYPE = 'http://activitystrea.ms/schema/1.0/photo';
+
+    public $__table = 'photo'; // table name
+    public $id;                // char (36) // UUID
+    public $uri;               // varchar (255)  // This is the corresponding notice's uri.
+    public $photo_uri;         // varchar (255)
+    public $thumb_uri;         // varchar (255)
+    public $title;             // varchar (255)
+    public $description;       // text
+    public $profile_id;        // int
+    
+    public function staticGet($k, $v=null)
+    {
+        return Memcached_DataObject::staticGet('photo', $k, $v);
+    }
+
+    public function getByNotice($notice)
+    {
+        return self::staticGet('uri', $notice->uri);
+    }
+
+    public function getNotice()
+    {
+        return Notice::staticGet('uri', $this->uri);
+    }
+
+    public static function schemaDef()
+    {
+        return array(
+            'description' => 'A photograph',
+            'fields' => array(
+                'id' => array('type' => 'char',
+                              'length' => 36,
+                              'not null' => true,
+                              'description' => 'UUID'),
+                'uri' => array('type' => 'varchar',
+                               'length' => 255,
+                               'not null' => true),
+                'photo_uri' => array('type' => 'varchar',
+                               'length' => 255,
+                               'not null' => true),
+                'photo_uri' => array('type' => 'varchar',
+                               'length' => 255,
+                               'not null' => true),
+                'profile_id' => array('type' => 'int', 'not null' => true),
+            ),
+            'primary key' => array('id'),
+            'foreign keys' => array('photo_profile_id__key' => array('profile' => array('profile_id' => 'id'))),
+        );
+    }
+
+    function saveNew($profile, $photo_uri, $thumb_uri, $title, $description, $options=array())
+    {
+        $photo = new Photo();
+
+        $photo->id =  UUID::gen();
+        $photo->profile_id = $profile->id;
+        $photo->photo_uri = $photo_uri;
+        $photo->thumb_uri = $thumb_uri;
+
+
+        $options['object_type'] = Photo::OBJECT_TYPE;
+
+        if (!array_key_exists('uri', $options)) { 
+            $options['uri'] = common_local_url('showphoto', array('id' => $photo->id));
+        }
+
+        if (!array_key_exists('rendered', $options)) {
+            $options['rendered'] = sprintf("<img src=\"%s\" alt=\"%s\"></img>", $photo_uri,
+                $title);
+        }
+
+        $photo->uri = $options['uri'];
+        
+        $photo->insert();
+
+        return Notice::saveNew($profile->id,
+                               '',
+                               'web',
+                               $options);
+
+    }
+}
diff --git a/plugins/GNUsocialPhoto/newphoto.php b/plugins/GNUsocialPhoto/newphoto.php
new file mode 100644 (file)
index 0000000..18ae552
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2011, Free Software Foundation, Inc.
+ *
+ * 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/>.
+ *
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @copyright 2011 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if(!defined('STATUSNET')){
+    exit(1);
+}
+
+class NewphotoAction extends Action
+{
+    var $user = null;
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        $this->user = common_current_user();
+
+        if(empty($this->user)){
+            throw new ClientException(_('Must be logged in to post a photo'),
+                403);
+        }
+
+        if($this->isPost()){
+            $this->checkSessionToken();
+        }
+
+        return true;
+    }
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($this->isPost()) {
+            $this->handlePost($args);
+        } else {
+            $this->showPage();
+        }
+    }
+    
+    function handlePost($args)
+    {
+
+        /*
+        // Workaround for PHP returning empty $_POST and $_FILES when POST
+        // length > post_max_size in php.ini
+        if (empty($_FILES)
+            && empty($_POST)
+            && ($_SERVER['CONTENT_LENGTH'] > 0)
+        ) {
+            $msg = _('The server was unable to handle that much POST ' .
+                'data (%s bytes) due to its current configuration.');
+            $this->showForm(sprintf($msg, $_SERVER['CONTENT_LENGTH']));
+            return;
+        } */
+
+        $profile = $this->user->getProfile();
+
+        $options = array();
+        
+        ToSelector::fillOptions($this, $options);
+
+        try {
+            $this->handleUpload();
+        } catch (Exception $e) {
+            $this->showForm($e->getMessage());
+            return;
+        }
+        
+
+        common_redirect($photo->uri, 303);
+    }
+
+    function getUpload()
+    {
+            $imagefile = ImageFile::fromUpload('photo_upload');
+
+            if($imagefile === null) {
+                throw new Exception(_('No file uploaded'));
+            }
+
+            $title = $this->trimmed('title');
+            $description = $this->trimmed('description');
+
+            $new_filename = UUID::gen() . image_type_to_extension($imagefile->type);
+            move_uploaded_file($imagefile->filepath, INSTALLDIR . '/file/' . $new_filename);
+           
+            // XXX: we should be using https where we can. TODO: detect whether the server
+            // supports this.
+            $photo_uri = 'http://' . common_config('site', 'server') . '/file/'
+                . $new_filename;
+            $thumb_uri = $photo_uri;
+
+            $photo = Photo::saveNew($profile, $photo_uri, $thumb_uri, $title,
+                $description, $options);
+        
+    }
+
+    function showContent()
+    {
+        $form = new NewPhotoForm();
+        $form->show();
+    }
+}
+
+
diff --git a/plugins/GNUsocialPhoto/newphotoform.php b/plugins/GNUsocialPhoto/newphotoform.php
new file mode 100644 (file)
index 0000000..4ef51ca
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2011, Free Software Foundation, Inc.
+ *
+ * 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/>.
+ *
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @copyright 2011 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if(!defined('STATUSNET')){
+    exit(1);
+}
+
+class NewPhotoForm extends Form
+{
+    function id()
+    {
+        return "form_new_photo";
+    }
+
+    function action()
+    {
+        return common_local_url('newphoto');
+    }
+
+    function formClass()
+    {
+        return 'form_settings ajax-notice';
+    }
+
+    function formData()
+    {
+        $this->out->elementStart('fieldset', array('id' => 'new_photo_data'));
+        $this->out->elementStart('ul', 'form_data');
+
+        $this->li();
+        $this->out->input('title', _('Title'), null, _('Photo title (optional).'));
+        $this->unli();
+
+        $this->li();
+        $this->out->element('input', array('name' => 'photo_upload',
+                                           'type' => 'file',
+                                           'id' => 'photo_upload'));
+        $this->unli();
+
+        $this->li();
+        $this->textarea('description', _('Description'), null, _('Description of the photo (optional).'));
+        $this->unli();
+
+        $this->out->elementEnd('ul');
+
+        $toWidget = new ToSelector($this->out,
+                                   common_current_user(),
+                                   null);
+        $toWidget->show();
+
+        $this->out->elementEnd('fieldset');
+    }
+
+    function formActions()
+    {
+        $this->out->submit('photo-submit', _m('BUTTON', 'Save'));
+    }
+}
diff --git a/plugins/GNUsocialPhoto/showphoto.php b/plugins/GNUsocialPhoto/showphoto.php
new file mode 100644 (file)
index 0000000..b97a056
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2011, Free Software Foundation, Inc.
+ *
+ * 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/>.
+ *
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @copyright 2011 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if(!defined('STATUSNET')){
+    exit(1);
+}
+
+
diff --git a/plugins/GNUsocialPhotos/GNUsocialPhotosPlugin.php b/plugins/GNUsocialPhotos/GNUsocialPhotosPlugin.php
new file mode 100644 (file)
index 0000000..499d376
--- /dev/null
@@ -0,0 +1,213 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @author    Max Shinn    <trombonechamp@gmail.com>
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+/* Photo sharing plugin */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+class GNUsocialPhotosPlugin extends Plugin
+{
+
+    function onAutoload($cls)
+    {
+        $dir = dirname(__FILE__);
+
+        include_once $dir . '/lib/tempphoto.php';
+        include_once $dir . '/lib/photonav.php';
+        switch ($cls)
+        {
+        case 'PhotosAction':
+            include_once $dir . '/lib/photolib.php';
+            include_once $dir . '/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
+            break;
+        case 'PhotouploadAction':
+            include_once $dir . '/lib/photolib.php';
+            include_once $dir . '/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
+            break;
+        case 'PhotoAction':
+            include_once $dir . '/lib/photolib.php';
+            include_once $dir . '/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
+            break;
+        case 'EditphotoAction':
+            include_once $dir . '/lib/photolib.php';
+            include_once $dir . '/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
+            break;
+        default:
+            break;
+        }
+        include_once $dir . '/classes/gnusocialphoto.php';
+        include_once $dir . '/classes/gnusocialphotoalbum.php';
+        return true;
+    }
+
+    function onCheckSchema()
+    {
+        $schema = Schema::get();
+        $schema->ensureTable('GNUsocialPhoto',
+                                array(new ColumnDef('id', 'int(11)', null, false, 'PRI', null, null, true),
+                                      new ColumnDef('notice_id', 'int(11)', null, false),
+                                      new ColumnDef('album_id', 'int(11)', null, false),
+                                      new ColumnDef('uri', 'varchar(512)', null, false),
+                                      new ColumnDef('thumb_uri', 'varchar(512)', null, false),
+                                      new ColumnDef('title', 'varchar(512)', null, false),
+                                      new ColumnDef('photo_description', 'text', null, false)));
+        $schema->ensureTable('GNUsocialPhotoAlbum',
+                                array(new ColumnDef('album_id', 'int(11)', null, false, 'PRI', null, null, true),
+                                      new ColumnDef('profile_id', 'int(11)', null, false),
+                                      new ColumnDef('album_name', 'varchar(256)', null, false),
+                                      new ColumnDef('album_description', 'text', null, false)));
+                                          
+    }
+
+    function onRouterInitialized($m)
+    {
+        $m->connect(':nickname/photos', array('action' => 'photos'));
+        $m->connect(':nickname/photos/:albumid', array('action' => 'photos'));
+        $m->connect('main/uploadphoto', array('action' => 'photoupload'));
+        $m->connect('photo/:photoid', array('action' => 'photo'));
+        $m->connect('editphoto/:photoid', array('action' => 'editphoto'));
+        return true;
+    }
+
+    function onStartNoticeDistribute($notice)
+    {
+        common_log(LOG_INFO, "event: StartNoticeDistribute");
+        if (GNUsocialPhotoTemp::$tmp) {
+            GNUsocialPhotoTemp::$tmp->notice_id = $notice->id;
+            $photo_id = GNUsocialPhotoTemp::$tmp->insert();
+            if (!$photo_id) {
+                common_log_db_error($photo, 'INSERT', __FILE__);
+                throw new ServerException(_m('Problem saving photo.'));
+            }
+        }
+        return true;
+    }
+
+    function onEndNoticeAsActivity($notice, &$activity)
+    {
+        common_log(LOG_INFO, 'photo plugin: EndNoticeAsActivity');
+        $photo = GNUsocialPhoto::staticGet('notice_id', $notice->id);
+        if(!$photo) {
+            common_log(LOG_INFO, 'not a photo.');
+            return true;
+        }
+
+        $activity->objects[0]->type = ActivityObject::PHOTO;
+        $activity->objects[0]->thumbnail = $photo->thumb_uri;
+        $activity->objects[0]->largerImage = $photo->uri;
+        return false;
+    }
+
+
+    function onStartHandleFeedEntry($activity)
+    {
+        common_log(LOG_INFO, 'photo plugin: onEndAtomPubNewActivity');
+        $oprofile = Ostatus_profile::ensureActorProfile($activity);
+        foreach ($activity->objects as $object) {
+            if($object->type == ActivityObject::PHOTO) {
+                $uri = $object->largerImage;
+                $thumb_uri = $object->thumbnail;
+                $profile_id = $oprofile->profile_id;
+                $source = 'unknown'; // TODO: put something better here.
+
+                common_log(LOG_INFO, 'uri : ' .  $uri);
+                common_log(LOG_INFO, 'thumb_uri : ' . $thumb_uri);
+
+                // It's possible this is validated elsewhere, but I'm not sure and
+                // would rather be safe.
+                $uri = filter_var($uri, FILTER_SANITIZE_URL);
+                $thumb_uri = filter_var($thumb_uri, FILTER_SANITIZE_URL);
+                $uri = filter_var($uri, FILTER_VALIDATE_URL);
+                $thumb_uri = filter_var($thumb_uri, FILTER_VALIDATE_URL);
+
+                if(empty($thumb_uri)) {
+                    // We need a thumbnail, so if we aren't given one, use the actual picture for now.
+                    $thumb_uri = $uri;
+                }
+
+                if (!empty($uri) && !empty($thumb_uri)) {
+                    GNUsocialPhoto::saveNew($profile_id, $thumb_uri, $uri, $source, false);
+                } else {
+                    common_log(LOG_INFO, 'bad URI for photo');
+                }
+                return false;
+            }
+        }
+        return true;
+    }
+
+    function onStartShowNoticeItem($action)
+    {
+        $photo = GNUsocialPhoto::staticGet('notice_id', $action->notice->id);
+        if($photo) { 
+            $action->out->elementStart('div', 'entry-title');
+            $action->showAuthor();
+            $action->out->elementStart('a', array('href' => $photo->getPageLink()));
+            $action->out->element('img', array('src' => $photo->thumb_uri,
+                                    'width' => 256, 'height' => 192));
+            $action->out->elementEnd('a');
+            $action->out->elementEnd('div');
+            $action->showNoticeInfo();
+            $action->showNoticeOptions();
+            return false;
+        }
+        return true;
+    } 
+
+    /*    function onEndShowNoticeFormData($action)
+    {
+        $link = "/main/uploadphoto";
+        $action->out->element('label', array('for' => 'photofile'),_('Attach'));
+        $action->out->element('input', array('id' => 'photofile',
+                                     'type' => 'file',
+                                     'name' => 'photofile',
+                                     'title' => _('Upload a photo')));
+    }
+    */
+    function onEndPersonalGroupNav($nav)
+    {
+      
+        $nav->out->menuItem(common_local_url('photos',
+                           array('nickname' => $nav->action->trimmed('nickname'))), _('Photos'), 
+                           _('Photo gallery'), $nav->action->trimmed('action') == 'photos', 'nav_photos');
+    }
+
+    function onEndShowStyles($action)
+    {
+        $action->cssLink('/plugins/GNUsocialPhotos/res/style.css');
+    }
+
+    function onEndShowScripts($action)
+    {
+        $action->script('plugins/GNUsocialPhotos/res/gnusocialphotos.js');
+    }
+}
+
diff --git a/plugins/GNUsocialPhotos/actions/editphoto.php b/plugins/GNUsocialPhotos/actions/editphoto.php
new file mode 100644 (file)
index 0000000..34f41a2
--- /dev/null
@@ -0,0 +1,204 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @author    Sean Corbett <sean@gnu.org>
+ * @author    Max Shinn    <trombonechamp@gmail.com>
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+class EditphotoAction extends Action
+{
+    var $user = null;
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        $args = $this->returnToArgs();
+        $this->user = common_current_user();
+        $this->photoid = $args[1]['photoid'];
+        $this->photo = GNUsocialPhoto::staticGet('id', $this->photoid);
+        return true;
+    }
+
+    function handle($args)
+    {
+        parent::handle($args);
+        if($_SERVER['REQUEST_METHOD'] == 'POST') {
+            $this->handlePost();
+        }
+        $this->showPage();
+    }
+
+    function title()
+    {
+        if ($this->photo->title)
+            return _m('Edit photo - ' . $this->photo->title);
+        else
+            return _m('Edit photo');
+    }
+
+    function showContent()
+    {
+
+        if ($this->photo->album_id == 0) {
+            $this->element('p', array(), _('This photo does not exist or was deleted.'));
+            return;
+        }
+
+        if ($this->user->profile_id != $this->photo->profile_id) {
+            $this->element('p', array(), _('You are not authorized to edit this photo.'));
+            return;
+        } 
+
+        //showForm() data
+        if(!empty($this->msg)) {
+            $class = ($this->success) ? 'success' : 'error';
+            $this->element('p', array('class' => $class), $this->msg);
+        }
+
+        $this->element('img', array('src' => $this->photo->uri));
+        $this->elementStart('form', array('method' => 'post',
+                                          'action' => '/editphoto/' . $this->photo->id));
+        $this->elementStart('ul', 'form_data');
+        $this->elementStart('li');
+        $this->input('phototitle', _("Title"), $this->photo->title, _("The title of the photo. (Optional)"));
+        $this->elementEnd('li');
+        $this->elementStart('li');
+        $this->textarea('photo_description', _("Description"), $this->photo->photo_description, _("A description of the photo. (Optional)"));
+        $this->elementEnd('li');
+        $this->elementStart('li');
+        $this->dropdown('album', _("Album"), $this->albumList(), _("The album in which to place this photo"), false, $this->photo->album_id);
+        $this->elementEnd('li');
+        $this->elementEnd('ul');
+        $this->submit('update', _('Update'));
+        $this->elementEnd('form');
+        $this->element('br');            
+        $this->elementStart('form', array('method' => 'post',
+                                          'action' => '/editphoto/' . $this->photo->id));
+        $this->element('input', array('type' => 'submit',
+                                      'name' => 'delete',
+                                      'value' => _('Delete Photo'),
+                                      'id' => 'delete',
+                                      'class' => 'submit',
+                                      'onclick' => 'return confirm(\'Are you sure you would like to delete this photo?\')'));
+        $this->elementEnd('form');
+
+    }
+
+    function handlePost()
+    {
+
+        common_log(LOG_INFO, 'handlPost()!');
+
+        if ($this->photo->album_id == 0) {
+            $this->element('p', array(), _('This photo does not exist or was deleted.'));
+            return;
+        }
+
+        if ($this->user->profile_id != $this->photo->profile_id) {
+            $this->element('p', array(), _('You are not authorized to edit this photo.'));
+            return;
+        } 
+
+        if ($this->arg('update')) {
+            $this->updatePhoto();
+        }
+        if ($this->arg('delete')) {
+            $this->deletePhoto();
+        }
+    }
+
+    function showForm($msg, $success=false)
+    {
+        $this->msg = $msg;
+        $this->success = $success;
+
+//        $this->showPage();
+    }
+
+    function albumList() 
+    {
+        $cur = common_current_user();
+        $album = new GNUsocialPhotoAlbum();
+        $album->user_id = $cur->id;
+
+        $albumlist = array();
+        if (!$album->find()) {
+            GNUsocialPhotoAlbum::newAlbum($cur->id, 'Default');
+        }
+        while ($album->fetch()) {
+            $albumlist[$album->album_id] = $album->album_name;
+        }
+        return $albumlist;
+    }
+
+    function updatePhoto()
+    {
+        $cur = common_current_user();
+        $this->photo->title = $this->trimmed('phototitle');
+        $this->photo->photo_description = $this->trimmed('photo_description');
+
+        $profile_id = $cur->id;
+       
+        $album = GNUsocialPhotoAlbum::staticGet('album_id', $this->trimmed('album'));
+        if ($album->profile_id != $profile_id) {
+            $this->showForm(_('Error: This is not your album!'));
+            return;
+        }
+        $this->photo->album_id = $album->album_id;
+        if ($this->photo->validate())
+            $this->photo->update();
+        else {
+            $this->showForm(_('Error: The photo data is not valid.'));
+            return;
+        }
+        common_redirect('/photo/' . $this->photo->id, '303');
+        $this->showForm(_('Success!'), true);
+
+    }
+
+    function deletePhoto()
+    {
+        //For redirection
+        $oldalbum = $this->album_id;
+
+        $notice = Notice::staticGet('id', $this->photo->notice_id);
+
+        $this->photo->delete();
+        
+        if (Event::handle('StartDeleteOwnNotice', array($this->user, $notice))) {
+            $notice->delete();
+            Event::handle('EndDeleteOwnNotice', array($this->user, $notice));
+        }
+       $this->showForm(_('Success!'));
+        common_redirect('/' . $this->user->nickname . '/photos/' . $oldalbum, '303');
+        return;
+    }
+
+}
diff --git a/plugins/GNUsocialPhotos/actions/photo.php b/plugins/GNUsocialPhotos/actions/photo.php
new file mode 100644 (file)
index 0000000..4d08e5b
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @author    Sean Corbett <sean@gnu.org>
+ * @author    Max Shinn    <trombonechamp@gmail.com>
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+include_once INSTALLDIR . '/actions/conversation.php';
+include_once INSTALLDIR . '/classes/Notice.php';
+
+class PhotoAction extends Action
+{
+    var $user = null;
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $args = $this->returnToArgs();
+        $this->photoid = $args[1]['photoid'];
+        $this->photo = GNUsocialPhoto::staticGet('id', $this->photoid);
+        $this->notice = Notice::staticGet('id', $this->photo->notice_id);
+
+        $this->user = Profile::staticGet('id', $this->notice->profile_id);
+        
+        $notices = Notice::conversationStream((int)$this->notice->conversation, null, null); 
+        $this->conversation = new ConversationTree($notices, $this);
+        return true;
+
+    }
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showPage();
+    }
+
+    function title()
+    {
+        if (empty($this->user)) {
+            return _m('No such user.');
+        } else if (empty($this->photo)) {
+            return _m('No such photo.');
+        } else if (!empty($this->photo->title)) {
+            return $this->photo->title;
+        } else {
+            return sprintf(_m("%s's Photo."), $this->user->nickname);
+        }
+    }
+
+    function showLocalNav()
+    {
+        $nav = new GNUsocialPhotoNav($this, $this->user->nickname);
+        $nav->show();
+    }
+
+    function showContent()
+    {
+        if(empty($this->user)) {
+            return;
+        }
+
+        $this->elementStart('a', array('href' => $this->photo->uri));
+        $this->element('img', array('src' => $this->photo->uri));
+        $this->elementEnd('a');
+
+        //Image "toolbar"
+        $cur = common_current_user();
+        if($this->photo->profile_id == $cur->profile_id) {
+            $this->elementStart('div', array('id' => 'image_toolbar'));
+            $this->element('a', array('href' => '/editphoto/' . $this->photo->id), 'Edit');
+            $this->elementEnd('div');
+        }
+
+        $this->element('p', array('class' => 'photodescription'), $this->photo->photo_description);
+        //This is a hack to hide the top-level comment
+        $this->element('style', array(), "#notice-{$this->photo->notice_id} div { display: none } #notice-{$this->photo->notice_id} ol li div { display: inline }");
+        $this->conversation->show();
+    }
+}
diff --git a/plugins/GNUsocialPhotos/actions/photos.php b/plugins/GNUsocialPhotos/actions/photos.php
new file mode 100644 (file)
index 0000000..3b8a0fd
--- /dev/null
@@ -0,0 +1,174 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @author    Sean Corbett <sean@gnu.org>
+ * @author    Max Shinn    <trombonechamp@gmail.com>
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/lib/personalgroupnav.php';
+
+class PhotosAction extends Action
+{
+    var $user = null;
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $args = $this->returnToArgs();
+        $username = $args[1]['nickname'];
+        $this->albumid = $args[1]['albumid'];
+        if (common_valid_profile_tag($username) == 0) {
+            $this->user = null;
+        } else {
+            $this->user = Profile::staticGet('nickname', $username);
+        }
+        return true;
+    }
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showPage();
+    }
+
+    function title()
+    {
+        if (empty($this->user)) {
+            return _m('No such user.');
+        } else {
+            return sprintf(_m("%s's Photos."), $this->user->nickname);
+        }
+    }
+
+    function showLocalNav()
+    {
+        $nav = new PersonalGroupNav($this);
+        $nav->show();
+    }
+
+    function showResizeImagesBox()
+    {
+        $this->elementStart('select', array('onchange' => 'return scalePhotosToSize(this.value)'));
+        $this->element('option', array('value' => ''), "");
+        $this->element('option', array('value' => '60'), _("Thumbnail"));
+        $this->element('option', array('value' => '120'), _("Medium"));
+        $this->element('option', array('value' => '400'), _("Normal"));
+        $this->elementEnd('select');
+    }        
+
+    function showAlbums()
+    {
+        $album = new GNUsocialPhotoAlbum();
+        $album->profile_id = $this->user->id;
+
+        $albums = array();
+        if (!$album->find()) {
+            GNUsocialPhotoAlbum::newAlbum($this->user->id, 'Default');
+        }
+
+        $this->elementStart('div', array('class' => 'galleryheader'));
+        //$this->element('a', array('href' => '#',
+        //                          'onclick' => 'return increasePhotoSize()'), '+');
+        //$this->raw(' | ');
+        //$this->element('a', array('href' => '#',
+        //                          'onclick' => 'return decreasePhotoSize()'), '-');
+
+        $this->showResizeImagesBox();
+        $this->elementEnd('div');
+
+
+
+        while ($album->fetch()) {
+            $this->elementStart('div', array('class' => 'photocontainer'));
+            $this->elementStart('a', array('href' => $album->getPageLink()));
+            $this->element('img', array('src' => $album->getThumbUri(),
+                                        'class' => 'albumingallery'));
+            $this->elementEnd('a');
+            $this->element('h3', array(), $album->album_name);
+            $this->elementEnd('div');
+        }
+        
+    }
+    
+    function showAlbum($album_id)
+    {
+        $album = GNUSocialPhotoAlbum::staticGet('album_id', $album_id);
+        if (!$album) {
+            return;
+        }
+
+        $page = $_GET['pageid'];
+        if (!filter_var($page, FILTER_VALIDATE_INT)){
+            $page = 1;
+        }
+
+        $photos = GNUsocialPhoto::getGalleryPage($page, $album->album_id, 9);
+        $this->elementStart('div', array('class' => 'galleryheader'));
+        if ($page > 1) { 
+            $this->element('a', array('href' => $album->getPageLink() . '?pageid=' . ($page-1)), 'Previous page');
+            $this->raw(' | ');
+        }
+        if (GNUsocialPhoto::getGalleryPage($page+1, $album->album_id, 9)) {
+            $this->element('a', array('href' => $album->getPageLink() . '?pageid=' . ($page+1) ), 'Next page');
+            $this->raw(' | ');
+        }
+
+        //$this->element('a', array('href' => '#',
+        //                          'onclick' => 'return increasePhotoSize()'), '+');
+        //$this->raw(' | ');
+        //$this->element('a', array('href' => '#',
+        //                          'onclick' => 'return decreasePhotoSize()'), '-');
+        //$this->raw(' | ');
+
+        $this->showResizeImagesBox();
+        $this->elementEnd('div');
+
+        foreach ($photos as $photo) {
+            $this->elementStart('a', array('href' => $photo->getPageLink()));
+            $this->elementStart('div', array('class' => 'photocontainer'));
+            $this->element('img', array('src' => $photo->thumb_uri,
+                                        'class' => 'photoingallery'));
+            $this->element('div', array('class' => 'phototitle'), $photo->title);
+            $this->elementEnd('div');
+            $this->elementEnd('a');
+        }
+
+    }
+
+
+    function showContent()
+    {
+        if (!empty($this->albumid))
+            $this->showAlbum($this->albumid);
+        else
+            $this->showAlbums();
+    }
+}
diff --git a/plugins/GNUsocialPhotos/actions/photoupload.php b/plugins/GNUsocialPhotos/actions/photoupload.php
new file mode 100644 (file)
index 0000000..5411ec8
--- /dev/null
@@ -0,0 +1,259 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @author    Sean Corbett <sean@gnu.org>
+ * @author    Max Shinn    <trombonechamp@gmail.com>
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+class PhotouploadAction extends Action
+{
+    var $user = null;
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        $this->user = common_current_user();
+        return true;
+    }
+
+    function handle($args)
+    {
+        parent::handle($args);
+        if($_SERVER['REQUEST_METHOD'] == 'POST') {
+            $this->handlePost();
+        }
+        $this->showPage();
+    }
+
+    function title()
+    {
+        return _m('Upload Photos');
+    }
+
+    function showContent()
+    {
+        //Upload a photo
+        if(empty($this->user)) {
+            $this->element('p', array(), 'You are not logged in.');
+        } else {
+            //showForm() data
+            if(!empty($this->msg)) {
+                $class = ($this->success) ? 'success' : 'error';
+                $this->element('p', array('class' => $class), $this->msg);
+            }
+
+            $this->elementStart('form', array('enctype' => 'multipart/form-data',
+                                              'method' => 'post',
+                                              'action' => common_local_url('photoupload')));
+            $this->elementStart('ul', 'form_data');
+            $this->elementStart('li');
+            $this->element('input', array('name' => 'photofile',
+                                          'type' => 'file',
+                                          'id' => 'photofile'));
+            $this->elementEnd('li');
+            //$this->element('br');
+            $this->elementStart('li');
+            $this->input('phototitle', _("Title"), null, _("The title of the photo. (Optional)"));
+            $this->elementEnd('li');
+            $this->elementStart('li');
+            $this->textarea('photo_description', _("Description"), null, _("A description of the photo. (Optional)"));
+            $this->elementEnd('li');
+            $this->elementStart('li');
+            $this->dropdown('album', _("Album"), $this->albumList(), _("The album in which to place this photo"), false);
+            $this->elementEnd('li');
+            $this->elementEnd('ul');
+            $this->submit('upload', _('Upload'));
+            $this->elementEnd('form');
+            $this->element('br');
+
+            //Create a new album
+            $this->element('h3', array(), _("Create a new album"));
+            $this->elementStart('form', array('method' => 'post',
+                                              'action' =>common_local_url('photoupload')));
+            $this->elementStart('ul', 'form_data');
+            $this->elementStart('li');
+            $this->input('album_name', _("Title"), null, _("The title of the album."));
+            $this->elementEnd('li');
+            $this->elementStart('li');
+            $this->textarea('album_description', _("Description"), null, _("A description of the album. (Optional)"));
+            $this->elementEnd('li');
+            $this->elementEnd('ul');
+            $this->submit('create', _('Create'));
+            $this->elementEnd('form');
+
+            //Delete an album
+            $this->element('h3', array(), _("Delete an album"));
+            $this->elementStart('form', array('method' => 'post',
+                                              'action' =>common_local_url('photoupload')));
+            $this->elementStart('ul', 'form_data');
+            $this->elementStart('li');
+            $this->dropdown('album', _("Album"), $this->albumList(), _("The album in which to place this photo"), false);
+            $this->elementEnd('li');
+            $this->elementEnd('ul');
+            $this->submit('deletealbum', _('Delete'));
+            $this->elementEnd('form');
+            
+        }
+    }
+
+    function handlePost()
+    {
+
+        common_log(LOG_INFO, 'handlPost()!');
+        // Workaround for PHP returning empty $_POST and $_FILES when POST
+        // length > post_max_size in php.ini
+
+        if (empty($_FILES)
+            && empty($_POST)
+            && ($_SERVER['CONTENT_LENGTH'] > 0)
+        ) {
+            $msg = _('The server was unable to handle that much POST ' .
+                'data (%s bytes) due to its current configuration.');
+
+            $this->showForm(sprintf($msg, $_SERVER['CONTENT_LENGTH']));
+            return;
+        }
+
+        // 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('upload')) {
+            $this->uploadPhoto();
+        }
+        if ($this->arg('create')) {
+            $this->createAlbum();
+        }
+        if ($this->arg('deletealbum')) {
+            $this->deleteAlbum();
+        }
+    }
+
+    function showForm($msg, $success=false)
+    {
+        $this->msg = $msg;
+        $this->success = $success;
+
+//        $this->showPage();
+    }
+
+    function albumList() 
+    {
+        $cur = common_current_user();
+        $album = new GNUsocialPhotoAlbum();
+        $album->user_id = $cur->id;
+
+        $albumlist = array();
+        if (!$album->find()) {
+            GNUsocialPhotoAlbum::newAlbum($cur->id, 'Default');
+        }
+        while ($album->fetch()) {
+            $albumlist[$album->album_id] = $album->album_name;
+        }
+        return $albumlist;
+    }
+
+    function uploadPhoto()
+    {
+        $cur = common_current_user();
+        if(empty($cur)) {
+            return;
+        }
+        try {
+            $imagefile = ImageFile::fromUpload('photofile');
+        } catch (Exception $e) {
+            $this->showForm($e->getMessage());
+            return;
+        }
+        if ($imagefile === null) {
+            $this->showForm(_('No file uploaded.'));
+            return;
+        }
+
+        $title = $this->trimmed('phototitle');
+        $photo_description = $this->trimmed('photo_description');
+
+        common_log(LOG_INFO, 'upload path : ' . $imagefile->filepath);
+
+        $filename = $cur->nickname . '-' . common_timestamp() . sha1_file($imagefile->filepath) .  image_type_to_extension($imagefile->type);
+        move_uploaded_file($imagefile->filepath, INSTALLDIR . '/file/' . $filename);
+        photo_make_thumbnail($filename);
+        $uri = 'http://' . common_config('site', 'server') . '/file/' . $filename;
+        $thumb_uri = 'http://' . common_config('site', 'server') . '/file/thumb.' . $filename;
+        $profile_id = $cur->id;
+       
+        $album = GNUsocialPhotoAlbum::staticGet('album_id', $this->trimmed('album'));
+        if ($album->profile_id != $profile_id) {
+            $this->showForm(_('Error: This is not your album!'));
+            return;
+        }
+        GNUsocialPhoto::saveNew($profile_id, $album->album_id, $thumb_uri, $uri, 'web', false, $title, $photo_description);
+    }
+
+    function createAlbum()
+    {
+        $cur = common_current_user();
+        if(empty($cur)) {
+            return;
+        }
+
+        $album_name = $this->trimmed('album_name');
+        $album_description = $this->trimmed('album_description');
+       
+        GNUsocialPhotoAlbum::newAlbum($cur->id, $album_name, $album_description);
+    }
+
+    function deleteAlbum()
+    {
+        $cur = common_current_user();
+        if(empty($cur)) return;
+        
+        $album_id = $this->trimmed('album');
+        $album = GNUsocialPhotoAlbum::staticGet('album_id', $album_id);
+        if (empty($album)) {
+            $this->showForm(_('This album does not exist or has been deleted.'));
+            return;
+        }
+        //Check if the album has any photos in it before deleting
+        $photos = GNUsocialPhoto::staticGet('album_id', $album_id);
+        if(empty($photos)) {
+            $album->delete();
+            $this->showForm(_('Album deleted'), true);
+        }
+        else {
+            $this->showForm(_('Cannot delete album when there are photos in it!'), false);
+        }
+    }
+}
diff --git a/plugins/GNUsocialPhotos/classes/gnusocialphoto.php b/plugins/GNUsocialPhotos/classes/gnusocialphoto.php
new file mode 100644 (file)
index 0000000..a122d44
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+
+class GNUsocialPhoto extends Memcached_DataObject
+{
+    public $__table = 'GNUsocialPhoto';
+    public $id;         // int(11)
+    public $notice_id;  // int(11)
+    public $album_id;   // int(11)
+    public $uri;        // varchar(512)
+    public $thumb_uri;  // varchar(512)
+       public $title;      // varchar(512)
+       public $photo_description; // text
+    
+
+    /**
+     *
+     * k key
+     * v value
+     */
+    function staticGet($k,$v=NULL)
+    {
+        return Memcached_DataObject::staticGet('GNUsocialPhoto',$k,$v);
+    }
+
+/*    function delete()
+    {
+        if(!unlink(INSTALLDIR . $this->thumb_uri)) {
+            return false;
+        }
+        if(!unlink(INSTALLDIR . $this->path)) {
+            return false;
+        }
+        return parent::delete();
+    } */
+
+
+    /*
+     * TODO: Foriegn key on album_id.
+     */
+    function table()
+    {
+        return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'album_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'uri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'thumb_uri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'title' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'photo_description' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL);
+    }
+    
+    function keys()
+    {
+        return array_keys($this->keyTypes());
+    }
+
+    function keyTypes()
+    {
+        return array('notice_id' => 'K');
+    }
+
+    function sequenceKey()
+    {
+        return array(false, false, false);
+    }
+
+    function saveNew($profile_id, $album_id, $thumb_uri, $uri, $source, $insert_now, $title = null, $photo_description = null)
+    {
+        $photo = new GNUsocialPhoto();
+        $photo->thumb_uri = $thumb_uri;
+        $photo->uri = $uri;
+               $photo->album_id = $album_id;
+               if(!empty($title)) $photo->title = $title;
+               if(!empty($photo_description)) $photo->photo_description = (string)$photo_description;
+
+        if($insert_now) {
+            $notice = Notice::saveNew($profile_id, $uri, $source);
+            $photo->notice_id = $notice->id;
+            $photo_id = $photo->insert();
+            if (!$photo_id) {
+                common_log_db_error($photo, 'INSERT', __FILE__);
+                throw new ServerException(_m('Problem Saving Photo.'));
+            }
+        } else {
+            GNUsocialPhotoTemp::$tmp = $photo;
+            Notice::saveNew($profile_id, $uri, $source);
+        }
+    }
+
+    function getPageLink()
+    {
+        return '/photo/' . $this->id;
+    }
+
+    /*
+     * TODO: -Sanitize input
+     * @param int page_id The desired page of the gallery to show.
+     * @param int album_id The id of the album to get photos from.
+     * @param int gallery_size The number of thumbnails to show per page in the gallery.
+     * @return array Array of GNUsocialPhotos for this gallery page.
+     */
+    static function getGalleryPage($page_id, $album_id, $gallery_size)
+    {
+               $page_offset = ($page_id-1) * $gallery_size; 
+        $sql = 'SELECT * FROM GNUsocialPhoto WHERE album_id = ' . $album_id . 
+               ' ORDER BY notice_id LIMIT ' . $page_offset . ',' . $gallery_size;
+        $photo = new GNUsocialPhoto();
+        $photo->query($sql);
+        $photos = array();
+
+        while ($photo->fetch()) {
+            $photos[] = clone($photo);
+        }
+
+        return $photos;
+    }
+}
diff --git a/plugins/GNUsocialPhotos/classes/gnusocialphotoalbum.php b/plugins/GNUsocialPhotos/classes/gnusocialphotoalbum.php
new file mode 100644 (file)
index 0000000..848f92d
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @author    Sean Corbett <sean@gnu.org>
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+
+class GNUsocialPhotoAlbum extends Memcached_DataObject
+{
+    public $__table = 'GNUsocialPhotoAlbum';
+    public $album_id;          // int(11) -- Unique identifier for the album
+    public $profile_id;        // int(11) -- Profile ID for the owner of the album
+    public $album_name;        // varchar(256) -- Title for this album
+    public $album_description; // text -- A description of the album
+    
+
+    function staticGet($k,$v=NULL)
+    {
+        return Memcached_DataObject::staticGet('GNUsocialPhotoAlbum',$k,$v);
+    }
+
+
+    /* TODO: Primary key on both album_id, profile_id / foriegn key on profile_id */
+    function table()
+    {
+        return array('album_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'album_name' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'album_description' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL);
+    }
+    
+    function keys()
+    {
+        return array_keys($this->keyTypes());
+    }
+
+    
+    /* Using album_id as the primary key for now.. */
+    function keyTypes()
+    {
+        return array('album_id' => 'K');
+    }
+
+    function sequenceKey()
+    {
+        return array('album_id', true, false);
+    }
+
+    function getPageLink()
+    {
+        $profile = Profile::StaticGet('id', $this->profile_id);
+        return '/' . $profile->nickname . '/photos/' . $this->album_id;
+    }
+
+    function getThumbUri()
+    {
+        $photo = GNUsocialPhoto::staticGet('album_id', $this->album_id);
+        if (empty($photo))
+            return '/theme/default/default-avatar-profile.png'; //For now...
+        return $photo->thumb_uri;
+    }
+
+    static function newAlbum($profile_id, $album_name, $album_description)
+    {
+        //TODO: Should use foreign key instead...
+        if (!Profile::staticGet('id', $profile_id)){
+            //Is this a bit extreme?
+            throw new ServerException(_m('No such user exists with id ' . $profile_id . ', couldn\'t create album.'));
+        }
+        
+        $album = new GNUsocialPhotoAlbum();
+        $album->profile_id = $profile_id;
+        $album->album_name = $album_name;
+        $album->album_description = $album_description;
+       
+        $album->album_id = $album->insert();
+        if (!$album->album_id){
+            common_log_db_error($album, 'INSERT', __FILE__);
+            throw new ServerException(_m('Error creating new album.'));
+        }
+        common_log(LOG_INFO, 'album_id : ' . $album->album_id);
+        return $album;
+    }
+
+}
diff --git a/plugins/GNUsocialPhotos/lib/photolib.php b/plugins/GNUsocialPhotos/lib/photolib.php
new file mode 100644 (file)
index 0000000..9e5ff61
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+
+function photo_make_thumbnail($filename)
+{
+    $height_dest = 192;
+    $width_dest = 256;
+
+    $size_src = getimagesize(INSTALLDIR . '/file/' . $filename);
+    $image_type = $size_src[2];
+    
+    switch($image_type) {
+    case IMAGETYPE_JPEG:
+        $image_src = imagecreatefromjpeg(INSTALLDIR . '/file/' . $filename);
+        break;
+    case IMAGETYPE_PNG:
+        $image_src = imagecreatefrompng(INSTALLDIR . '/file/' . $filename);
+        break;
+    case IMAGETYPE_GIF:
+        $image_src = imagecreatefromgif(INSTALLDIR . '/file/' . $filename);
+        break;
+    default:
+        return false;
+    } 
+
+    $width_src = $size_src[0];
+    $height_src = $size_src[1];
+
+    $ratio_src = (float) $width_src / (float) $height_src;
+    $ratio_dest = (float) $width_dest / (float) $height_dest;
+
+    if ($ratio_src > $ratio_dest) {
+        $height_crop = $height_src;
+        $width_crop = (int)($height_crop * $ratio_dest);
+        $x_crop = ($width_src - $width_crop) / 2;
+    } else {
+        $width_crop = $width_src;
+        $height_crop = (int)($width_crop / $ratio_dest);
+        $x_crop = 0;
+    }
+    
+    $image_dest = imagecreatetruecolor($width_dest, $height_dest);
+    
+    imagecopyresampled($image_dest, $image_src, 0, 0, $x_crop, 0, $width_dest, $height_dest, $width_crop, $height_crop);
+    switch ($image_type) {
+    case IMAGETYPE_JPEG:
+        imagejpeg($image_dest, INSTALLDIR . '/file/' . 'thumb.' . $filename, 100);
+        break;
+    case IMAGETYPE_PNG:
+        imagepng($image_dest, INSTALLDIR . '/file/thumb.' . $filename);
+        break;
+    case IMAGETYPE_GIF:
+        imagegif($image_dest, INSTALLDIR . '/file/thumb.' . $filename);
+        break;
+    }
+        
+    imagedestroy($image_src);
+    imagedestroy($image_dest);
+
+    return true;
+}
diff --git a/plugins/GNUsocialPhotos/lib/photonav.php b/plugins/GNUsocialPhotos/lib/photonav.php
new file mode 100644 (file)
index 0000000..180f0ad
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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/>.
+ *
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @author    Max Shinn <trombonechamp@gmail.com>
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if(!defined('STATUSNET')) {
+    exit(1);
+}
+
+class GNUsocialPhotoNav extends Widget {
+    var $action = null;
+
+    function __construct($action = null, $nickname = null)
+    {
+        parent::__construct($action);
+        $this->action = $action;
+        $this->nickname = $nickname;
+    }
+
+    function show()
+    {
+        if (empty($this->nickname)) 
+            $this->nickname = $this->action->trimmed('nickname');
+        
+        $this->out->elementStart('ul', array('class' => 'nav'));
+
+        $this->out->menuItem(common_local_url('showstream', array('nickname' => $this->nickname)), _('Profile'));
+
+        $this->out->menuItem(common_local_url('photos', array('nickname' => $this->nickname)),
+            _('Photos'));
+
+        $user = common_current_user();
+        if (!empty($user)) {
+            $this->out->menuItem(common_local_url('photoupload', array()),
+                _('Upload Photos'));
+        }
+
+        $this->out->elementEnd('ul');
+    }
+}
diff --git a/plugins/GNUsocialPhotos/lib/tempphoto.php b/plugins/GNUsocialPhotos/lib/tempphoto.php
new file mode 100644 (file)
index 0000000..119c301
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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/>.
+ *
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+// XXX: This entire file is an ugly hack and needs to be replaced. It is essentially a global variable
+// used to store information about a photo we want to insert in onStartNoticeDistribute(), which we
+// can't actually pass to that function.
+class GNUsocialPhotoTemp {
+    public static $tmp = null; 
+}
diff --git a/plugins/GNUsocialPhotos/res/gnusocialphotos.js b/plugins/GNUsocialPhotos/res/gnusocialphotos.js
new file mode 100644 (file)
index 0000000..8681f61
--- /dev/null
@@ -0,0 +1,29 @@
+function increasePhotoSize() {
+    $('.photoingallery, .albumingallery').each(function(index) {
+            this.height *= 1.1;
+            this.width *= 1.1;
+        });
+    return false;
+}
+
+function decreasePhotoSize() {
+    $('.photoingallery, .albumingallery').each(function(index) {
+            this.height /= 1.1;
+            this.width /= 1.1;
+        });
+    return false;
+}
+
+function scalePhotosToSize(size) {
+    $('.photoingallery, .albumingallery').each(function(index) {
+            if(this.height > this.width) {
+                this.width = this.width*size/this.height;
+                this.height = size;
+            }
+            else {
+                this.height = this.height*size/this.width;
+                this.width = size;
+            }
+        });
+    return false;
+}
\ No newline at end of file
diff --git a/plugins/GNUsocialPhotos/res/style.css b/plugins/GNUsocialPhotos/res/style.css
new file mode 100644 (file)
index 0000000..bb7b619
--- /dev/null
@@ -0,0 +1,14 @@
+.photocontainer, .albumcontainer { 
+    display: block; 
+    background-color: yellow; 
+    float: left; 
+    padding: 20px; 
+    margin: 15px; 
+}
+
+.photodescription {
+    display: block;
+    background-color: #dddddd;
+    padding: 20px;
+    margin: 15px;
+}
\ No newline at end of file
diff --git a/plugins/GNUsocialProfileExtensions/GNUsocialProfileExtensionsPlugin.php b/plugins/GNUsocialProfileExtensions/GNUsocialProfileExtensionsPlugin.php
new file mode 100644 (file)
index 0000000..cf5fb0a
--- /dev/null
@@ -0,0 +1,187 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Max Shinn <trombonechamp@gmail.com>
+ * @copyright 2011 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+class GNUsocialProfileExtensionsPlugin extends Plugin
+{
+
+    function onAutoload($cls)
+    {
+        $dir = dirname(__FILE__);
+
+        switch ($cls)
+        {
+        case 'BioAction':
+        case 'NewresponseAction':
+            include_once $dir . '/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
+            break;
+        case 'ProfilefieldsAdminPanelAction':
+            include_once $dir . '/actions/' . strtolower(mb_substr($cls, 0, -16)) . '.php';
+            break;
+        default:
+            break;
+        }
+        include_once $dir . '/classes/GNUsocialProfileExtensionField.php';
+        include_once $dir . '/classes/GNUsocialProfileExtensionResponse.php';
+        include_once $dir . '/lib/profiletools.php';
+        include_once $dir . '/lib/noticetree.php';
+        return true;
+    }
+
+    function onCheckSchema()
+    {
+        $schema = Schema::get();
+        $schema->ensureTable('GNUsocialProfileExtensionField',
+                                array(new ColumnDef('id', 'int(11)', null, false, 'PRI', null, null, true),
+                                      new ColumnDef('systemname', 'varchar(64)', null, false),
+                                      new ColumnDef('title', 'varchar(256)', null, false),
+                                      new ColumnDef('description', 'text', null, false),
+                                      new ColumnDef('type', 'varchar(256)', null, false)));
+        $schema->ensureTable('GNUsocialProfileExtensionResponse',
+                                array(new ColumnDef('id', 'int(11)', null, false, 'PRI', null, null, true),
+                                      new ColumnDef('extension_id', 'int(11)', null, false),
+                                      new ColumnDef('profile_id', 'int(11)', null, false),
+                                      new ColumnDef('value', 'text', null, false)));
+                                          
+    }
+
+    function onRouterInitialized($m)
+    {
+        $m->connect(':nickname/bio', array('action' => 'bio'));
+        $m->connect('admin/profilefields', array('action' => 'profilefieldsAdminPanel'));
+        $m->connect('notice/respond', array('action' => 'newresponse'));
+        return true;
+    }
+
+    function onEndProfileFormData($action)
+    {
+        $fields = GNUsocialProfileExtensionField::allFields();
+        $user = common_current_user();
+        $profile = $user->getProfile();
+        gnusocial_profile_merge($profile);
+        foreach ($fields as $field) {
+            $action->elementStart('li');
+            $fieldname = $field->systemname;
+            if ($field->type == 'str') {
+                $action->input($fieldname, $field->title, 
+                               ($action->arg($fieldname)) ? $action->arg($fieldname) : $profile->$fieldname, 
+                               $field->description);
+            }
+            else if ($field->type == 'text') {
+                $action->textarea($fieldname, $field->title,
+                                  ($action->arg($fieldname)) ? $action->arg($fieldname) : $profile->$fieldname,
+                                  $field->description);
+            }
+            $action->elementEnd('li');
+        }
+    }
+
+    function onEndProfileSaveForm($action)
+    {
+        $fields = GNUsocialProfileExtensionField::allFields();
+        $user = common_current_user();
+        $profile = $user->getProfile();
+        foreach ($fields as $field) {
+            $val = $action->trimmed($field->systemname);
+
+            $response = new GNUsocialProfileExtensionResponse();
+            $response->profile_id = $profile->id;
+            $response->extension_id = $field->id;
+            
+            if ($response->find()) {
+                $response->fetch();
+                $response->value = $val;
+                if ($response->validate()) {
+                    if (empty($val))
+                        $response->delete();
+                    else
+                        $response->update();
+                }
+            }
+            else {
+                $response->value = $val;
+                $response->insert();
+            }
+        }
+    }
+    
+    function onEndShowStyles($action)
+    {
+        $action->cssLink('/plugins/GNUsocialProfileExtensions/res/style.css');
+    }
+
+    function onEndShowScripts($action)
+    {
+        $action->script('plugins/GNUsocialProfileExtensions/js/profile.js');
+    }
+
+    function onEndAdminPanelNav($nav)
+    {
+        if (AdminPanelAction::canAdmin('profilefields')) {
+
+            $action_name = $nav->action->trimmed('action');
+
+            $nav->out->menuItem(
+                '/admin/profilefields',
+                _m('Profile Fields'),
+                _m('Custom profile fields'),
+                $action_name == 'profilefieldsadminpanel',
+                'nav_profilefields_admin_panel'
+            );
+        }
+
+        return true;
+    }
+
+    function onStartPersonalGroupNav($nav)
+    { 
+        $nav->out->menuItem(common_local_url('bio',
+                           array('nickname' => $nav->action->trimmed('nickname'))), _('Bio'), 
+                           _('The user\'s extended profile'), $nav->action->trimmed('action') == 'bio', 'nav_bio');
+    }
+
+    //Why the heck is this shoved into this plugin!?!?  It deserves its own!
+    function onShowStreamNoticeList($notice, $action, &$pnl)
+    {
+        //TODO: This function is called after the notices in $notice are superfluously retrieved in showstream.php
+        $newnotice = new Notice();
+        $newnotice->profile_id = $action->user->id;
+        $newnotice->orderBy('modified DESC');
+        $newnotice->whereAdd('reply_to IS NULL');
+        $newnotice->limit(($action->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+        $newnotice->find();
+
+        $pnl = new NoticeTree($newnotice, $action);
+        return false;
+    }
+
+}
+
diff --git a/plugins/GNUsocialProfileExtensions/README.txt b/plugins/GNUsocialProfileExtensions/README.txt
new file mode 100644 (file)
index 0000000..fd496cd
--- /dev/null
@@ -0,0 +1,16 @@
+GNU Social Profile Extensions
+=============================
+
+Allows administrators to define additional profile fields for the
+users of a GNU Social installation.
+
+
+Installation
+------------
+
+To install, copy this directory into your plugins directory and add
+the following lines to your config.php file:
+
+addPlugin('GNUsocialProfileExtensions');
+$config['admin']['panels'][] = 'profilefields';
+
diff --git a/plugins/GNUsocialProfileExtensions/actions/bio.php b/plugins/GNUsocialProfileExtensions/actions/bio.php
new file mode 100644 (file)
index 0000000..bc9f12c
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Max Shinn <trombonechamp@gmail.com>
+ * @copyright 2011 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/personalgroupnav.php';
+require_once INSTALLDIR . '/classes/Profile.php';
+require_once INSTALLDIR . '/lib/profilelist.php';
+
+class BioAction extends Action
+{
+    var $user = null;
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $args = $this->returnToArgs();
+        $this->profile = Profile::staticGet('nickname', $args[1]['nickname']);
+        //die(print_r($this->profile));
+        gnusocial_profile_merge($this->profile);
+        $this->avatar = $this->profile->getAvatar(96);
+
+        return true;
+
+    }
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showPage();
+    }
+
+    function title()
+    {
+        return sprintf(_m("%s's Bio."), $this->profile->nickname);
+    }
+
+    function showLocalNav()
+    {
+        $nav = new PersonalGroupNav($this);
+        $nav->show();
+    }
+
+    function showContent()
+    {
+        if(empty($this->profile)) {
+            return;
+        }
+
+        $profilelistitem = new ProfileListItem($this->profile, $this);
+        $profilelistitem->show();
+        $this->elementStart('ul');
+        $fields = GNUsocialProfileExtensionField::allFields();
+        foreach ($fields as $field) {
+            $fieldname = $field->systemname;
+            if (!empty($this->profile->$fieldname)) {
+                $this->elementStart('li', array('class' => 'biolistitem'));
+                $this->elementStart('div', array('class' => 'biolistitemcontainer'));
+                if ($field->type == 'text') {
+                    $this->element('h3', array(), $field->title);
+                    $this->element('p', array('class' => 'biovalue'), $this->profile->$fieldname);
+                }
+                else {
+                    $this->element('span', array('class' => 'biotitle'), $field->title);
+                    $this->text(' ');
+                    $this->element('span', array('class' => 'biovalue'), $this->profile->$fieldname);
+                }
+                $this->elementEnd('div');
+                $this->elementEnd('li');
+            }
+        }
+        $this->elementEnd('ul');
+    }
+
+}
+
+
diff --git a/plugins/GNUsocialProfileExtensions/actions/newresponse.php b/plugins/GNUsocialProfileExtensions/actions/newresponse.php
new file mode 100644 (file)
index 0000000..0e71ee3
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Max Shinn <trombonechamp@gmail.com>
+ * @copyright 2011 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+require_once INSTALLDIR . '/actions/newnotice.php';
+
+class NewresponseAction extends NewnoticeAction
+{
+     /**
+     * Same as the parent, but not including the @-whoever in replies
+     *
+     * @return void
+     */
+
+    function showNoticeForm()
+    {
+        $content = $this->trimmed('status_textarea');
+        if (!$content) {
+            $replyto = $this->trimmed('replyto');
+            $inreplyto = $this->trimmed('inreplyto');
+        } else {
+            // @fixme most of these bits above aren't being passed on above
+            $inreplyto = null;
+        }
+
+        $notice_form = new NoticeForm($this, '', $content, null, $inreplyto);
+        $notice_form->show();
+    }
+}
\ No newline at end of file
diff --git a/plugins/GNUsocialProfileExtensions/actions/profilefields.php b/plugins/GNUsocialProfileExtensions/actions/profilefields.php
new file mode 100644 (file)
index 0000000..d8c4e3d
--- /dev/null
@@ -0,0 +1,177 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Max Shinn <trombonechamp@gmail.com>
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/adminpanelaction.php';
+
+class ProfilefieldsAdminPanelAction extends AdminPanelAction
+{
+
+    function title()
+    {
+        return _('Profile fields');
+    }
+
+    function getInstructions()
+    {
+        return _('GNU Social custom profile fields');
+    }
+
+    function showForm()
+    {
+        $form = new ProfilefieldsAdminForm($this);
+        $form->show();
+        return;
+    }
+
+    function saveSettings()
+    {
+        $field = GNUsocialProfileExtensionField::staticGet('id', $this->trimmed('id'));
+        if (!$field)
+            $field = new GNUsocialProfileExtensionField();
+        $field->title = $this->trimmed('title');
+        $field->description = $this->trimmed('description');
+        $field->type = $this->trimmed('type');
+        $field->systemname = $this->trimmed('systemname');
+        if (!gnusocial_field_systemname_validate($field->systemname)) {
+            $this->clientError(_('Internal system name must be unique and consist of only alphanumeric characters!'));
+            return false;
+        }
+        if ($field->id) {
+            if ($field->validate())
+                $field->update();
+            else {
+                $this->clientError(_('There was an error with the field data.'));
+                return false;
+            }
+        }
+        else {
+            $field->insert();
+        }
+
+        return;
+    }
+
+}
+
+class ProfilefieldsAdminForm extends AdminForm
+{
+
+    function id()
+    {
+        return 'form_profilefields_admin_panel';
+    }
+
+    function formClass()
+    {
+        return 'form_settings';
+    }
+
+    function action()
+    {
+        return '/admin/profilefields';
+    }
+
+    function formData()
+    {
+        $title = null;
+        $description = null;
+        $type = null;
+        $systemname = null;
+        $id = null;
+        $fieldsettitle = _("New Profile Field");
+        //Edit a field
+        if ($this->out->trimmed('edit')) { 
+            $field = GNUsocialProfileExtensionField::staticGet('id', $this->out->trimmed('edit'));
+            $title = $field->title;
+            $description = $field->description;
+            $type = $field->type;
+            $systemname = $field->systemname;
+            $this->out->hidden('id', $field->id, 'id');
+            $fieldsettitle = _("Edit Profile Field");
+        }
+        //Don't show the list of all fields when editing one
+        else { 
+            $this->out->elementStart('fieldset');
+            $this->out->element('legend', null, _('Existing Custom Profile Fields'));
+            $this->out->elementStart('ul', 'form_data');
+            $fields = GNUsocialProfileExtensionField::allFields();
+            foreach ($fields as $field) {
+                $this->li();
+                $content = 
+                $this->out->elementStart('div');
+                $this->out->element('a', array('href' => '/admin/profilefields?edit=' . $field->id),
+                                    $field->title);
+                $this->out->text(' (' . $field->type . '): ' . $field->description);
+                $this->out->elementEnd('div');
+                $this->unli();
+            }
+            $this->out->elementEnd('ul');
+            $this->out->elementEnd('fieldset');
+        }
+
+        //New fields
+        $this->out->elementStart('fieldset');
+        $this->out->element('legend', null, $fieldsettitle);
+        $this->out->elementStart('ul', 'form_data');
+
+        $this->li();
+        $this->out->input('title', _('Title'), $title,
+                     _('The title of the field'));
+        $this->unli();
+        $this->li();
+        $this->out->input('systemname', _('Internal name'), $systemname,
+                     _('The alphanumeric name used internally for this field.  Also the key used in OStatus user info. (optional)'));
+        $this->unli();
+        $this->li();
+        $this->out->input('description', _('Description'), $description,
+                     _('An optional more detailed description of the field'));
+        $this->unli();
+        $this->li();
+        $this->out->dropdown('type', _('Type'), array('text' => _("Text"),
+                                                      'str' => _("String")), 
+                             _('The type of the datafield'), false, $type);
+        $this->unli();
+        $this->out->elementEnd('ul');
+        $this->out->elementEnd('fieldset');
+    }
+
+    /**
+     * Action elements
+     *
+     * @return void
+     */
+
+    function formActions()
+    {
+        $this->out->submit('submit', _('Save'), 'submit', null, _('Save new field'));
+    }
+}
diff --git a/plugins/GNUsocialProfileExtensions/classes/GNUsocialProfileExtensionField.php b/plugins/GNUsocialProfileExtensions/classes/GNUsocialProfileExtensionField.php
new file mode 100644 (file)
index 0000000..6877eab
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Max Shinn <trombonechamp@gmail.com>
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+
+class GNUsocialProfileExtensionField extends Memcached_DataObject
+{
+    public $__table = 'GNUsocialProfileExtensionField';
+    public $id;          // int(11)
+    public $systemname;  // varchar(64)
+    public $title;       // varchar(256)
+    public $description; // text
+    public $type;        // varchar(256)
+
+    /**
+     *
+     * k key
+     * v value
+     */
+    function staticGet($k,$v=NULL)
+    {
+        return Memcached_DataObject::staticGet('GNUsocialProfileExtensionField',$k,$v);
+    }
+
+
+    function table()
+    {
+        return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'systemname' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'title' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'description' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'type' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL);
+    }
+    
+    function keys()
+    {
+        return array_keys($this->keyTypes());
+    }
+
+    function keyTypes()
+    {
+        return array('id' => 'K');
+    }
+
+    function sequenceKey()
+    {
+        return array(false, false, false);
+    }
+
+    static function newField($title, $description=null, $type='str', $systemname=null)
+    {
+        $field = new GNUsocialProfileExtensionField();
+        $field->title = $title;
+        $field->description = $description;
+        $field->type = $type;
+        if (empty($systemname))
+            $field->systemname = 'field' + $field->id;
+        else
+            $field->systemname = $systemname;
+       
+        $field->id = $field->insert();
+        if (!$field->id){
+            common_log_db_error($field, 'INSERT', __FILE__);
+            throw new ServerException(_m('Error creating new field.'));
+        }
+        return $field;
+    }
+
+    static function allFields()
+    {
+        $field = new GNUsocialProfileExtensionField();
+        $fields = array();
+        if ($field->find()) {
+            while($field->fetch()) {
+                $fields[] = clone($field);
+            }
+        }
+        return $fields;
+    }
+}
diff --git a/plugins/GNUsocialProfileExtensions/classes/GNUsocialProfileExtensionResponse.php b/plugins/GNUsocialProfileExtensions/classes/GNUsocialProfileExtensionResponse.php
new file mode 100644 (file)
index 0000000..fbf5af0
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Max Shinn <trombonechamp@gmail.com>
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+
+class GNUsocialProfileExtensionResponse extends Memcached_DataObject
+{
+    public $__table = 'GNUsocialProfileExtensionResponse';
+    public $id;           // int(11)
+    public $extension_id; // int(11)
+    public $profile_id;   // int(11)
+    public $value;     // text
+
+    /**
+     *
+     * k key
+     * v value
+     */
+    function staticGet($k,$v=NULL)
+    {
+        return Memcached_DataObject::staticGet('GNUsocialProfileExtensionResponse',$k,$v);
+    }
+
+
+    function table()
+    {
+        return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'extension_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'value' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL);
+    }
+    
+    function keys()
+    {
+        return array_keys($this->keyTypes());
+    }
+
+    function keyTypes()
+    {
+        return array('id' => 'K');
+    }
+
+    function sequenceKey()
+    {
+        return array(false, false, false);
+    }
+
+    static function newResponse($extension_id, $profile_id, $value)
+    {
+
+        $response = new GNUsocialProfileExtensionResponse();
+        $response->extension_id = $extension_id;
+        $response->profile_id = $profile_id;
+        $response->value = $value;
+       
+        $response->id = $response->insert();
+        if (!$response->id){
+            common_log_db_error($response, 'INSERT', __FILE__);
+            throw new ServerException(_m('Error creating new response.'));
+        }
+        return $response;
+    }
+
+    static function findResponsesByProfile($id)
+    {
+        $extf = 'GNUsocialProfileExtensionField';
+        $extr = 'GNUsocialProfileExtensionResponse';
+        $sql = "SELECT $extr.*, $extf.title, $extf.description, $extf.type, $extf.systemname FROM $extr JOIN $extf ON $extr.extension_id=$extf.id WHERE $extr.profile_id = $id";
+        $response = new GNUsocialProfileExtensionResponse();
+        $response->query($sql);
+        $responses = array();
+
+        while ($response->fetch()) {
+            $responses[] = clone($response);
+        }
+
+        return $responses;
+    }
+
+}
diff --git a/plugins/GNUsocialProfileExtensions/classes/ProfileExtensionField.php b/plugins/GNUsocialProfileExtensions/classes/ProfileExtensionField.php
new file mode 100644 (file)
index 0000000..142b702
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Max Shinn <trombonechamp@gmail.com>
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+
+class GNUsocialPhoto extends Memcached_DataObject
+{
+    public $__table = 'GNUsocialPhoto';
+    public $id;         // int(11)
+    public $_id;  // int(11)
+    public $album_id;   // int(11)
+    public $uri;        // varchar(512)
+    public $thumb_uri;  // varchar(512)
+       public $title;      // varchar(512)
+       public $photo_description; // text
+    
+
+    /**
+     *
+     * k key
+     * v value
+     */
+    function staticGet($k,$v=NULL)
+    {
+        return Memcached_DataObject::staticGet('GNUsocialPhoto',$k,$v);
+    }
+
+/*    function delete()
+    {
+        if(!unlink(INSTALLDIR . $this->thumb_uri)) {
+            return false;
+        }
+        if(!unlink(INSTALLDIR . $this->path)) {
+            return false;
+        }
+        return parent::delete();
+    } */
+
+
+    /*
+     * TODO: Foriegn key on album_id.
+     */
+    function table()
+    {
+        return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'album_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'uri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'thumb_uri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'title' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'photo_description' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL);
+    }
+    
+    function keys()
+    {
+        return array_keys($this->keyTypes());
+    }
+
+    function keyTypes()
+    {
+        return array('notice_id' => 'K');
+    }
+
+    function sequenceKey()
+    {
+        return array(false, false, false);
+    }
+
+    function saveNew($profile_id, $album_id, $thumb_uri, $uri, $source, $insert_now, $title = null, $photo_description = null)
+    {
+        $photo = new GNUsocialPhoto();
+        $photo->thumb_uri = $thumb_uri;
+        $photo->uri = $uri;
+               $photo->album_id = $album_id;
+               if(!empty($title)) $photo->title = $title;
+               if(!empty($photo_description)) $photo->photo_description = (string)$photo_description;
+
+        if($insert_now) {
+            $notice = Notice::saveNew($profile_id, $uri, $source);
+            $photo->notice_id = $notice->id;
+            $photo_id = $photo->insert();
+            if (!$photo_id) {
+                common_log_db_error($photo, 'INSERT', __FILE__);
+                throw new ServerException(_m('Problem Saving Photo.'));
+            }
+        } else {
+            GNUsocialPhotoTemp::$tmp = $photo;
+            Notice::saveNew($profile_id, $uri, $source);
+        }
+    }
+
+    function getPageLink()
+    {
+        return '/photo/' . $this->id;
+    }
+
+    /*
+     * TODO: -Sanitize input
+     * @param int page_id The desired page of the gallery to show.
+     * @param int album_id The id of the album to get photos from.
+     * @param int gallery_size The number of thumbnails to show per page in the gallery.
+     * @return array Array of GNUsocialPhotos for this gallery page.
+     */
+    static function getGalleryPage($page_id, $album_id, $gallery_size)
+    {
+               $page_offset = ($page_id-1) * $gallery_size; 
+        $sql = 'SELECT * FROM GNUsocialPhoto WHERE album_id = ' . $album_id . 
+               ' ORDER BY notice_id LIMIT ' . $page_offset . ',' . $gallery_size;
+        $photo = new GNUsocialPhoto();
+        $photo->query($sql);
+        $photos = array();
+
+        while ($photo->fetch()) {
+            $photos[] = clone($photo);
+        }
+
+        return $photos;
+    }
+}
diff --git a/plugins/GNUsocialProfileExtensions/js/profile.js b/plugins/GNUsocialProfileExtensions/js/profile.js
new file mode 100644 (file)
index 0000000..4a2f1c5
--- /dev/null
@@ -0,0 +1,4 @@
+SN.U.NoticeReplySet = function(nick,id) {
+    $('div.replyform').hide();
+    $('div#form'+id).show();
+}
diff --git a/plugins/GNUsocialProfileExtensions/lib/noticetree.php b/plugins/GNUsocialProfileExtensions/lib/noticetree.php
new file mode 100644 (file)
index 0000000..bdf9d32
--- /dev/null
@@ -0,0 +1,228 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Max Shinn <trombonechamp@gmail.com>
+ * @copyright 2011 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+            //functions to sort replies
+class NoticeTree extends NoticeList
+{
+    function show()
+    {
+        $this->out->elementStart('div', array('id' =>'notices_primary'));
+        // TRANS: Header on conversation page. Hidden by default (h2).
+        $this->out->element('h2', null, _('Notices'));
+        $this->out->elementStart('ol', array('class' => 'notices xoxo'));
+
+        $cnt = 0;
+
+        while ($this->notice->fetch() && $cnt <= NOTICES_PER_PAGE) {
+            if (!empty($this->notice->reply_to))
+                continue;
+
+            $cnt++;
+
+            if ($cnt > NOTICES_PER_PAGE) {
+                break;
+            }
+
+            try {
+                $this->showNoticePlus($this->newListItem($this->notice));
+            } catch (Exception $e) {
+                // we log exceptions and continue
+                print "ERROR!" . $e->getMessage();
+                common_log(LOG_ERR, $e->getMessage());
+                continue;
+            }
+        }
+
+        $this->out->elementEnd('ol');
+        $this->out->elementEnd('div');
+
+        return $cnt;
+    }
+
+    function _cmpDate($a, $b) { return strcmp($a->notice->modified, $b->notice->modified); }
+    function _cmpFav($a, $b) { return (int)$a->faves < (int)$b->faves; }
+
+    function showNoticePlus($item)
+    {
+        $replies = new Notice();
+        $replies->reply_to = $item->notice->id;
+
+        // We take responsibility for doing the li
+
+        $this->out->elementStart('li', array('class' => 'hentry notice',
+                                             'id' => 'notice-' . $item->notice->id));
+
+        $item->show();
+
+        if ($replies->find()) {
+            $this->out->elementStart('ol', array('class' => 'notices'));
+
+            $replieslist = array();
+            while ($replies->fetch()) {
+                $replieslist[] = $this->newListItem(clone($replies));
+            }
+
+            //Sorting based on url argument
+            if($_GET['sort'] == 'faves')
+                usort($replieslist, array($this, '_cmpFav'));
+            else
+                usort($replieslist, array($this, '_cmpDate'));
+
+
+            foreach($replieslist as $reply) {
+                $this->showNoticePlus($reply);
+            }
+
+            $this->out->elementEnd('ol');
+        }
+
+        $this->out->elementEnd('li');
+    }
+
+    function newListItem($notice)
+    {
+        return new NoticeTreeItem($notice, $this->out);
+    }
+}
+
+class NoticeTreeItem extends NoticeListItem
+{
+    function __construct($notice, $out=null)
+    {
+        parent::__construct($notice, $out);
+        //TODO: Rewrite this
+        //Showing number of favorites
+        $fave = new Fave();
+        $fave->notice_id = $this->notice->id;
+        $cnt = 0;
+        if ($fave->find()) {
+            while ($fave->fetch())
+                $cnt++;
+        }
+        $this->faves = $cnt;
+    }        
+
+    function showStart()
+    {
+        return;
+    }
+
+    function showEnd()
+    {
+        if ($this->faves > 0) {
+            $this->out->text(_m("Favorited by $this->faves user"));
+            if ($this->faves > 1) $this->out->text("s"); //there has to be a better way to do this...
+        }
+
+        //Show response form
+        $this->out->elementStart('div', array('id' => 'form' . $this->notice->id, 'class' => 'replyform'));
+        $noticeform = new ReplyForm($this->out, null, null, null, $this->notice->id);
+        $noticeform->show();
+        $this->out->elementEnd('div');
+        return;
+    }
+
+    function showContext()
+    {
+        return;
+    }
+
+    //Just changing the link...
+    function showReplyLink()
+    {
+        if (common_logged_in()) {
+            $this->out->text(' ');
+            //Why doesn't common_local_url work here?
+            $reply_url = '/notice/respond?replyto=' . $this->profile->nickname . '&inreplyto=' . $this->notice->id;
+            $this->out->elementStart('a', array('href' => $reply_url,
+                                                'class' => 'notice_reply',
+                                                'title' => _('Reply to this notice')));
+            $this->out->text(_('Reply'));
+            $this->out->text(' ');
+            $this->out->element('span', 'notice_id', $this->notice->id);
+            $this->out->elementEnd('a');
+        }
+    }
+
+}
+
+class ReplyForm extends NoticeForm
+{
+    //Changing the text.  We have to either repeat ids or get hacky.  I vote repeat ids.
+    function formData()
+    {
+        if (Event::handle('StartShowNoticeFormData', array($this))) {
+            // 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();
+
+            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');
+
+            if ($this->user->shareLocation()) {
+                $this->out->hidden('notice_data-lat', empty($this->lat) ? (empty($this->profile->lat) ? null : $this->profile->lat) : $this->lat, 'lat');
+                $this->out->hidden('notice_data-lon', empty($this->lon) ? (empty($this->profile->lon) ? null : $this->profile->lon) : $this->lon, 'lon');
+                $this->out->hidden('notice_data-location_id', empty($this->location_id) ? (empty($this->profile->location_id) ? null : $this->profile->location_id) : $this->location_id, 'location_id');
+                $this->out->hidden('notice_data-location_ns', empty($this->location_ns) ? (empty($this->profile->location_ns) ? null : $this->profile->location_ns) : $this->location_ns, 'location_ns');
+
+                $this->out->elementStart('div', array('id' => 'notice_data-geo_wrap',
+                                                      'title' => common_local_url('geocode')));
+                $this->out->checkbox('notice_data-geo', _('Share my location'), true);
+                $this->out->elementEnd('div');
+                $this->out->inlineScript(' var NoticeDataGeo_text = {'.
+                    'ShareDisable: ' .json_encode(_('Do not share my location')).','.
+                    'ErrorTimeout: ' .json_encode(_('Sorry, retrieving your geo location is taking longer than expected, please try again later')).
+                    '}');
+            }
+
+            Event::handle('EndShowNoticeFormData', array($this));
+        }
+    }
+}
\ No newline at end of file
diff --git a/plugins/GNUsocialProfileExtensions/lib/profiletools.php b/plugins/GNUsocialProfileExtensions/lib/profiletools.php
new file mode 100644 (file)
index 0000000..fa3cfa9
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2010, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Max Shinn <trombonechamp@gmail.com>
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+function gnusocial_profile_merge(&$profile)
+{
+    $responses = GNUsocialProfileExtensionResponse::findResponsesByProfile($profile->id);
+    $profile->customfields = array();
+    foreach ($responses as $response) {
+        $title = $response->systemname;
+        $profile->$title = $response->value;
+        $profile->customfields[] = $title;
+    }
+}
+    
+function gnusocial_field_systemname_validate($systemname)
+{
+    $fields = GNUsocialProfileExtensionField::allFields();
+    foreach ($fields as $field)
+        if ($field->systemname == $systemname)
+            return false;
+    return ctype_alphanum($systemname);
+}
\ No newline at end of file
diff --git a/plugins/GNUsocialProfileExtensions/res/bgstripe.gif b/plugins/GNUsocialProfileExtensions/res/bgstripe.gif
new file mode 100644 (file)
index 0000000..035ef80
Binary files /dev/null and b/plugins/GNUsocialProfileExtensions/res/bgstripe.gif differ
diff --git a/plugins/GNUsocialProfileExtensions/res/style.css b/plugins/GNUsocialProfileExtensions/res/style.css
new file mode 100644 (file)
index 0000000..db545b8
--- /dev/null
@@ -0,0 +1,23 @@
+.biotitle {
+    font-weight: bold;
+}
+.biovalue {
+    font-style: italic;
+}
+#showstream ol.notices ol.notices {
+    background-image: url(/plugins/GNUsocialProfileExtensions/res/bgstripe.gif); 
+    background-repeat: repeat-y;
+    background-position: left top;
+    padding-left: 15px;
+    color: #333333;
+}
+#showstream ol.notices ol.notices ol.notices {
+    padding-left: 5px;
+}
+div.replyform {
+    display: none;
+    padding-left: 15px;
+}
+.replyform .form_notice {
+    width: 75%;
+}
\ No newline at end of file
diff --git a/plugins/GNUsocialRelationshipsManager/GNUsocialRelationshipsManager.php b/plugins/GNUsocialRelationshipsManager/GNUsocialRelationshipsManager.php
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/plugins/GNUsocialTemplatePlugin.php b/plugins/GNUsocialTemplatePlugin.php
new file mode 100644 (file)
index 0000000..7db2f73
--- /dev/null
@@ -0,0 +1,359 @@
+<?php
+/**
+ * Plugin to render GNU social
+ *
+ * Captures rendered parts from the output buffer, passes them through a template file: tpl/social.php
+ * Adds an API method at index.php/template/update which lets you overwrite the template file
+ * Requires username/password and a single POST parameter called "template"
+ * The method is disabled unless the user is #1, the first user of the system
+ *
+ * @category  Plugin
+ * @package   StatusNet
+ * @author    Brian Hendrickson <brian@megapump.com>
+ * @author    Matt Lee <mattl@cnuk.org>
+ * @copyright 2009 Megapump, Inc.
+ * @copyright 2010 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://megapump.com/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+define('TEMPLATEPLUGIN_VERSION', '0.1');
+
+class TemplatePlugin extends Plugin {
+
+  var $blocks = array();
+
+  function __construct() {
+    parent::__construct();
+  }
+
+  // capture the RouterInitialized event
+  // and connect a new API method
+  // for updating the template
+  function onRouterInitialized( $m ) {
+    $m->connect( 'template/update', array(
+      'action'      => 'template',
+    ));
+  }
+
+  // <%styles%>
+  // <%scripts%>
+  // <%search%>
+  // <%feeds%>
+  // <%description%>
+  // <%head%>
+  function onStartShowHead( $act ) {
+    $this->clear_xmlWriter($act);
+    $act->extraHead();
+    $this->blocks['head'] = $act->xw->flush();
+    $act->showStylesheets();
+    $this->blocks['styles'] = $act->xw->flush();
+    $act->showScripts();
+    $this->blocks['scripts'] = $act->xw->flush();
+    $act->showFeeds();
+    $this->blocks['feeds'] = $act->xw->flush();
+    $act->showOpenSearch();
+    $this->blocks['search'] = $act->xw->flush();
+    $act->showDescription();
+    $this->blocks['description'] = $act->xw->flush();
+    return false;
+  }
+
+  // <%bodytext%>
+  function onStartShowContentBlock( $act ) {
+    $this->clear_xmlWriter($act);
+    return true;
+  }
+  function onEndShowContentBlock( $act ) {
+    $this->blocks['bodytext'] = $act->xw->flush();
+  }
+
+  // <%localnav%>
+  function onStartShowLocalNavBlock( $act ) {
+    $this->clear_xmlWriter($act);
+    return true;
+  }
+  function onEndShowLocalNavBlock( $act ) {
+    $this->blocks['localnav'] = $act->xw->flush();
+  }
+
+  // <%export%>
+  function onStartShowExportData( $act ) {
+    $this->clear_xmlWriter($act);
+    return true;
+  }
+  function onEndShowExportData( $act ) {
+    $this->blocks['export'] = $act->xw->flush();
+  }
+
+  // <%subscriptions%>
+  // <%subscribers%>
+  // <%groups%>
+  // <%statistics%>
+  // <%cloud%>
+  // <%groupmembers%>
+  // <%groupstatistics%>
+  // <%groupcloud%>
+  // <%popular%>
+  // <%groupsbyposts%>
+  // <%featuredusers%>
+  // <%groupsbymembers%>
+  function onStartShowSections( $act ) {
+    global $action;
+    $this->clear_xmlWriter($act);
+    switch ($action) {
+      case "showstream":
+        $act->showSubscriptions();
+        $this->blocks['subscriptions'] = $act->xw->flush();
+        $act->showSubscribers();
+        $this->blocks['subscribers'] = $act->xw->flush();
+        $act->showGroups();
+        $this->blocks['groups'] = $act->xw->flush();
+        $act->showStatistics();
+        $this->blocks['statistics'] = $act->xw->flush();
+        $cloud = new PersonalTagCloudSection($act, $act->user);
+        $cloud->show();
+        $this->blocks['cloud'] = $act->xw->flush();
+        break;
+      case "showgroup":
+        $act->showMembers();
+        $this->blocks['groupmembers'] = $act->xw->flush();
+        $act->showStatistics();
+        $this->blocks['groupstatistics'] = $act->xw->flush();
+        $cloud = new GroupTagCloudSection($act, $act->group);
+        $cloud->show();
+        $this->blocks['groupcloud'] = $act->xw->flush();
+        break;
+      case "public":
+        $pop = new PopularNoticeSection($act);
+        $pop->show();
+        $this->blocks['popular'] = $act->xw->flush();
+        $gbp = new GroupsByPostsSection($act);
+        $gbp->show();
+        $this->blocks['groupsbyposts'] = $act->xw->flush();
+        $feat = new FeaturedUsersSection($act);
+        $feat->show();
+        $this->blocks['featuredusers'] = $act->xw->flush();
+        break;
+      case "groups":
+        $gbp = new GroupsByPostsSection($act);
+        $gbp->show();
+        $this->blocks['groupsbyposts'] = $act->xw->flush();
+        $gbm = new GroupsByMembersSection($act);
+        $gbm->show();
+        $this->blocks['groupsbymembers'] = $act->xw->flush();
+        break;
+    }
+    return false;
+  }
+
+  // <%logo%>
+  // <%nav%>
+  // <%notice%>
+  // <%noticeform%>
+  function onStartShowHeader( $act ) {
+    $this->clear_xmlWriter($act);
+    $act->showLogo();
+    $this->blocks['logo'] = $act->xw->flush();
+    $act->showPrimaryNav();
+    $this->blocks['nav'] = $act->xw->flush();
+    $act->showSiteNotice();
+    $this->blocks['notice'] = $act->xw->flush();
+    if (common_logged_in()) {
+        $act->showNoticeForm();
+    } else {
+        $act->showAnonymousMessage();
+    }
+    $this->blocks['noticeform'] = $act->xw->flush();
+    return false;
+  }
+
+  // <%secondarynav%>
+  // <%licenses%>
+  function onStartShowFooter( $act ) {
+    $this->clear_xmlWriter($act);
+    $act->showSecondaryNav();
+    $this->blocks['secondarynav'] = $act->xw->flush();
+    $act->showLicenses();
+    $this->blocks['licenses'] = $act->xw->flush();
+    return false;
+  }
+
+  // capture the EndHTML event
+  // and include the template
+  function onEndEndHTML($act) {
+
+    global $action, $tags;
+
+    // set the action and title values
+    $vars = array(
+      'action'=>$action,
+      'title'=>$act->title(). " - ". common_config('site', 'name')
+    );
+
+    // use the PHP template
+    // unless statusnet config:
+    //   $config['template']['mode'] = 'html';
+    if (!(common_config('template', 'mode') == 'html')) {
+      $tpl_file = $this->templateFolder() . '/social.php';
+      $tags = array_merge($vars,$this->blocks);
+      include $tpl_file;
+      return;
+    }
+
+    $tpl_file = $this->templateFolder() . '/index.html';
+
+    // read the static template
+    $output = file_get_contents( $tpl_file );
+
+    $tags = array();
+
+    // get a list of the <%tags%> in the template
+    $pattern='/<%([a-z]+)%>/';
+
+    if ( 1 <= preg_match_all( $pattern, $output, $found ))
+      $tags[] = $found;
+
+    // for each found tag, set its value from the rendered blocks
+    foreach( $tags[0][1] as $pos=>$tag ) {
+      if (isset($this->blocks[$tag]))
+        $vars[$tag] = $this->blocks[$tag];
+
+      // didn't find a block for the tag
+      elseif (!isset($vars[$tag]))
+        $vars[$tag] = '';
+    }
+
+    // replace the tags in the template
+    foreach( $vars as $key=>$val )
+      $output = str_replace( '<%'.$key.'%>', $val, $output );
+
+    echo $output;
+
+    return true;
+
+  }
+  function templateFolder() {
+    return 'tpl';
+  }
+
+  // catching the StartShowHTML event to halt the rendering
+  function onStartShowHTML( $act ) {
+    $this->clear_xmlWriter($act);
+    return true;
+  }
+
+  // clear the xmlWriter
+  function clear_xmlWriter( $act ) {
+    $act->xw->openMemory();
+    $act->xw->setIndent(true);
+  }
+
+}
+
+/**
+ * Action for updating the template remotely
+ *
+ * "template/update" -- a POST method that requires a single
+ * parameter "template", containing the new template code
+ *
+ * @category Plugin
+ * @package  StatusNet
+ * @author   Brian Hendrickson <brian@megapump.com>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://megapump.com/
+ *
+ */
+
+class TemplateAction extends Action
+{
+
+  function prepare($args) {
+    parent::prepare($args);
+    return true;
+  }
+
+  function handle($args) {
+
+    parent::handle($args);
+
+    if (!isset($_SERVER['PHP_AUTH_USER'])) {
+
+      // not authenticated, show login form
+      header('WWW-Authenticate: Basic realm="StatusNet API"');
+
+      // cancelled the browser login form
+      $this->clientError(_('Authentication error!'), $code = 401);
+
+    } else {
+
+      $nick = $_SERVER['PHP_AUTH_USER'];
+      $pass = $_SERVER['PHP_AUTH_PW'];
+
+      // check username and password
+      $user = common_check_user($nick,$pass);
+
+      if ($user) {
+
+        // verify that user is admin
+        if (!($user->id == 1))
+          $this->clientError(_('Only User #1 can update the template.'), $code = 401);
+
+        // open the old template
+        $tpl_file = $this->templateFolder() . '/index.html';
+        $fp = fopen( $tpl_file, 'w+' );
+
+        // overwrite with the new template
+        fwrite($fp, $this->arg('template'));
+        fclose($fp);
+
+        header('HTTP/1.1 200 OK');
+        header('Content-type: text/plain');
+        print "Template Updated!";
+
+      } else {
+
+        // bad username and password
+        $this->clientError(_('Authentication error!'), $code = 401);
+
+      }
+
+    }
+  }
+    function onPluginVersion(&$versions)
+    {
+        $versions[] = array('name' => 'Template',
+                            'version' => TEMPLATEPLUGIN_VERSION,
+                            'author' => 'Brian Hendrickson',
+                            'homepage' => 'http://status.net/wiki/Plugin:Template',
+                            'rawdescription' =>
+                            _m('Use an HTML template for Web output.'));
+        return true;
+    }
+
+}
+
+/**
+ * Function for retrieving a statusnet display section
+ *
+ * requires one parameter, the name of the section
+ * section names are listed in the comments of the TemplatePlugin class
+ *
+ * @category Plugin
+ * @package  StatusNet
+ * @author   Brian Hendrickson <brian@megapump.com>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://megapump.com/
+ *
+ */
+
+function section($tagname) {
+  global $tags;
+  if (isset($tags[$tagname]))
+    return $tags[$tagname];
+}
+
diff --git a/plugins/GNUsocialVideo/GNUsocialVideoPlugin.php b/plugins/GNUsocialVideo/GNUsocialVideoPlugin.php
new file mode 100644 (file)
index 0000000..296b451
--- /dev/null
@@ -0,0 +1,153 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2011, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @copyright 2011 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+class GNUsocialVideoPlugin extends MicroAppPlugin
+{
+
+    function onCheckSchema()
+    {
+        $schema = Schema::get();
+
+        $schema->ensureTable('video', Video::schemaDef());
+
+        return true;
+    }
+
+    function onAutoload($cls)
+    {
+        $dir = dirname(__FILE__);
+        switch($cls)
+        {
+        case 'PostvideoAction':
+            include_once $dir . '/actions/postvideo.php';
+            break;
+        case 'Video':
+            include_once $dir . '/Video.php';
+            break;
+        case 'VideoForm':
+            include_once $dir . '/videoform.php';
+            break;
+        case 'ShowvideoAction':
+            include_once $dir . '/showvideo.php';
+            break;
+        default:
+            break;
+        }
+        return true;
+    }
+
+    function onRouterInitialized($m)
+    {
+        $m->connect('main/postvideo', array('action' => 'postvideo'));
+        $m->connect('showvideo/:id', array('action' => 'showvideo'));
+        return true;
+    }
+
+    function entryForm($out)
+    {
+        return new VideoForm($out);
+    }
+
+    function appTitle()
+    {
+        return _('Video');
+    }
+
+    function tag()
+    {
+        return 'GNUsocialVideo';
+    }
+
+    function types()
+    {
+        return array(Video::OBJECT_TYPE);
+    }
+
+    function saveNoticeFromActivity($activity, $actor, $options=array())
+    {
+        if(count($activity->objects) != 1) {
+            throw new Exception('Too many activity objects.');
+        }
+
+        $videoObj = $activity->objects[0];
+
+        if ($videoObj->type != Video::OBJECT_TYPE) {
+            throw new Exception('Wrong type for object.');
+        }
+
+        // For now we read straight from the xml tree, no other way to get this information.
+        // When there's a better API for this, we should change to it.
+        $uri = ActivityUtils::getLink($activity->entry, 'enclosure');
+
+        $options['object_type'] = Video::OBJECT_TYPE;
+
+        Video::saveNew($actor, $uri, $options);
+   
+    }
+
+    function activityObjectFromNotice($notice)
+    {
+        $object = new ActivityObject();
+        $object->id = $notice->uri;
+        $object->type = Video::OBJECT_TYPE;
+        $object->title = $notice->content;
+        $object->summary = $notice->content;
+        $object->link = $notice->bestUrl();
+
+        $vid = Video::getByNotice($notice);
+
+        if ($vid) {
+            $object->extra[] = array('link', array('rel' => 'enclosure', 'href' => $vid->url), array());
+        }
+        
+        return $object;
+        
+    }
+
+    function showNotice($notice, $out)
+    {
+        $vid = Video::getByNotice($notice);
+        if ($vid) {
+            $out->element('video', array('src' => $vid->url,
+                'width' => '100%',
+                'controls' => 'controls'));
+        }
+    }
+
+    function deleteRelated($notice)
+    {
+        $vid = Video::getByNotice($notice);
+        if ($vid) {
+            $vid->delete();
+        }
+    }
+}
diff --git a/plugins/GNUsocialVideo/Video.php b/plugins/GNUsocialVideo/Video.php
new file mode 100644 (file)
index 0000000..73e240c
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2011, Free Software Foundation, Inc.
+ *
+ * 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/>.
+ *
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @copyright 2011 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if(!defined('STATUSNET')){
+    exit(1);
+}
+
+/**
+ * Data class for videos.
+ */
+
+class Video extends Managed_DataObject
+{
+    const OBJECT_TYPE = 'http://activitystrea.ms/schema/1.0/video';
+
+    public $__table = 'video'; // table name
+    public $id;                // char (36) // UUID
+    public $uri;               // varchar (255)  // This is the corresponding notice's uri.
+    public $url;               // varchar (255)
+    public $profile_id;        // int
+    
+    public function staticGet($k, $v=null)
+    {
+        return Memcached_DataObject::staticGet('Video', $k, $v);
+    }
+
+    public function getByNotice($notice)
+    {
+        return self::staticGet('uri', $notice->uri);
+    }
+
+    public function getNotice()
+    {
+        return Notice::staticGet('uri', $this->uri);
+    }
+
+    public static function schemaDef()
+    {
+        return array(
+            'description' => 'A video clip',
+            'fields' => array(
+                'id' => array('type' => 'char',
+                              'length' => 36,
+                              'not null' => true,
+                              'description' => 'UUID'),
+                'uri' => array('type' => 'varchar',
+                               'length' => 255,
+                               'not null' => true),
+                'url' => array('type' => 'varchar',
+                               'length' => 255,
+                               'not null' => true),
+                'profile_id' => array('type' => 'int', 'not null' => true),
+            ),
+            'primary key' => array('id'),
+            'foreign keys' => array('video_profile_id__key' => array('profile' => array('profile_id' => 'id'))),
+        );
+    }
+
+    function saveNew($profile, $url, $options=array())
+    {
+        $vid = new Video();
+
+        $vid->id =  UUID::gen();
+        $vid->profile_id = $profile->id;
+        $vid->url = $url;
+
+
+        $options['object_type'] = Video::OBJECT_TYPE;
+
+        if (!array_key_exists('uri', $options)) { 
+            $options['uri'] = common_local_url('showvideo', array('id' => $vid->id));
+        }
+
+        if (!array_key_exists('rendered', $options)) {
+            $options['rendered'] = sprintf("<video src=\"%s\">Sorry, your browser doesn't support the video tag.</video>", $url);
+        }
+
+        $vid->uri = $options['uri'];
+        
+        $vid->insert();
+
+        return Notice::saveNew($profile->id,
+                               '',
+                               'web',
+                               $options);
+
+    }
+}
diff --git a/plugins/GNUsocialVideo/actions/postvideo.php b/plugins/GNUsocialVideo/actions/postvideo.php
new file mode 100644 (file)
index 0000000..bdd86ab
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2011, Free Software Foundation, Inc.
+ *
+ * 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  Widget
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @copyright 2011 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+class PostvideoAction extends Action {
+    var $user = null;
+    var $url = null;
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        $this->user = common_current_user();
+
+        if(empty($this->user)){
+            throw new ClientException(_('Must be logged in to post a video'),
+                403);
+        }
+
+        if($this->isPost()){
+            $this->checkSessionToken();
+        }
+
+        $this->url = filter_var($this->trimmed('url'), FILTER_SANITIZE_URL);
+        $this->url = filter_var($this->url, FILTER_VALIDATE_URL);
+
+        return true;
+    }
+   
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($this->isPost()) {
+            $this->handlePost($args);
+        } else {
+            $this->showPage();
+        }
+    }
+
+    function handlePost($args)
+    {
+        if (empty($this->url)) {
+            throw new ClientException(_('Bad URL.'));
+        }
+
+        $profile = $this->user->getProfile();
+
+        $options = array();
+        
+        ToSelector::fillOptions($this, $options);
+
+        $vid = Video::saveNew($profile, $this->url, $options);
+
+        common_redirect($vid->uri, 303);
+    }
+    function showContent()
+    {
+        $form  = new VideoForm();
+        $form->show();
+    }
+}
diff --git a/plugins/GNUsocialVideo/showvideo.php b/plugins/GNUsocialVideo/showvideo.php
new file mode 100644 (file)
index 0000000..3046a6c
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2011, Free Software Foundation, Inc.
+ *
+ * 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/>.
+ *
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @copyright 2011 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if(!defined('STATUSNET')){
+    exit(1);
+}
+
+class ShowvideoAction extends ShownoticeAction
+{
+    protected $id = null;
+    protected $vid = null;
+
+    function prepare($args)
+    {
+        OwnerDesignAction::prepare($args);
+        $this->id = $this->trimmed('id');
+        $this->vid = Video::staticGet('id', $this->id);
+
+        if (empty($this->vid)) {
+            throw new ClientException(_('No such video.'), 404);
+        }
+
+        $this->notice = $this->vid->getNotice();
+
+        if (empty($this->notice)) {
+            throw new ClientException(_('No such video'), 404);
+        }
+
+        $this->user = User::staticGet('id', $this->vid->profile_id);
+
+        if (empty($this->user)) {
+            throw new ClientException(_('No such user.'), 404);
+        }
+
+        $this->profile = $this->user->getProfile();
+
+        if (empty($this->profile)) {
+            throw new ServerException(_('User without a profile.'));
+        }
+
+        return true;
+    }
+}
diff --git a/plugins/GNUsocialVideo/videoform.php b/plugins/GNUsocialVideo/videoform.php
new file mode 100644 (file)
index 0000000..12f4563
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+/**
+ * GNU Social
+ * Copyright (C) 2011, Free Software Foundation, Inc.
+ *
+ * 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/>.
+ *
+ * @package   GNU Social
+ * @author    Ian Denhardt <ian@zenhack.net>
+ * @copyright 2011 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+if(!defined('STATUSNET')){
+    exit(1);
+}
+
+class VideoForm extends Form
+{
+    function id()
+    {
+        return "form_new_video";
+    }
+
+    function action()
+    {
+        return common_local_url('postvideo');
+    }
+
+    function formClass()
+    {
+        return 'form_settings ajax-notice';
+    }
+
+    function formData()
+    {
+        $this->out->elementStart('fieldset', array('id' => 'new_video_data'));
+        $this->out->elementStart('ul', 'form_data');
+
+        $this->li();
+        $this->out->input('url', _('URL'), null, _('URL of the video'));
+        $this->unli();
+
+        $this->out->elementEnd('ul');
+
+        $toWidget = new ToSelector($this->out,
+                                   common_current_user(),
+                                   null);
+        $toWidget->show();
+
+        $this->out->elementEnd('fieldset');
+    }
+
+    function formActions()
+    {
+        $this->out->submit('submit', _m('BUTTON', 'Save'));
+    }
+}
diff --git a/plugins/ModLog/ModLog.php b/plugins/ModLog/ModLog.php
new file mode 100644 (file)
index 0000000..b2c6546
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2012, StatusNet, Inc.
+ *
+ * ModLog.php -- data object to store moderation logs
+ * 
+ * PHP version 5
+ *
+ * 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  Moderation
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2012 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+/**
+ * Class comment here
+ *
+ * @category Category here
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ *
+ * @see      DB_DataObject
+ */
+
+class ModLog extends Managed_DataObject
+{
+    public $__table = 'mod_log'; // table name
+
+    public $id;           // UUID
+    public $profile_id;   // profile id
+    public $moderator_id; // profile id
+    public $role;         // the role
+    public $grant;        // 1 = grant, 0 = revoke
+    public $created;      // datetime
+
+    /**
+     * Get an instance by key
+     *
+     * @param string $k Key to use to lookup (usually 'user_id' for this class)
+     * @param mixed  $v Value to lookup
+     *
+     * @return TagSub object found, or null for no hits
+     *
+     */
+    function staticGet($k, $v=null)
+    {
+        return Managed_DataObject::staticGet('ModLog', $k, $v);
+    }
+
+    /**
+     * Get an instance by compound key
+     *
+     * @param array $kv array of key-value mappings
+     *
+     * @return TagSub object found, or null for no hits
+     *
+     */
+    function pkeyGet($kv)
+    {
+        return Managed_DataObject::pkeyGet('ModLog', $kv);
+    }
+
+    /**
+     * The One True Thingy that must be defined and declared.
+     */
+    public static function schemaDef()
+    {
+        return array('description' => 'Log of moderation events',
+                     'fields' => array(
+                                       'id' => array('type' => 'varchar',
+                                                     'length' => 36,
+                                                     'not null' => true,
+                                                     'description' => 'unique event ID'),
+                                       'profile_id' => array('type' => 'int',
+                                                             'not null' => true,
+                                                             'description' => 'profile getting the role'),
+                                       'moderator_id' => array('type' => 'int',
+                                                               'description' => 'profile granting or revoking the role'),
+                                       'role' => array('type' => 'varchar',
+                                                       'length' => 32,
+                                                       'not null' => true,
+                                                       'description' => 'role granted or revoked'),
+                                       'is_grant' => array('type' => 'int',
+                                                           'size' => 'tiny',
+                                                           'default' => 1,
+                                                           'description' => 'Was this a grant or revocation of a role'),
+                                       'created' => array('type' => 'datetime',
+                                                          'not null' => true,
+                                                          'description' => 'date this record was created')
+                                       ),
+                     'primary key' => array('id'),
+                     'foreign keys' => array(
+                                             'mod_log_profile_id_fkey' => array('profile', array('profile_id' => 'id')),
+                                             'mod_log_moderator_id_fkey' => array('user', array('user_id' => 'id'))
+                                             ),
+                     'indexes' => array(
+                                        'mod_log_profile_id_created_idx' => array('profile_id', 'created'),
+                                        ),
+                     );
+    }
+}
diff --git a/plugins/ModLog/ModLogPlugin.php b/plugins/ModLog/ModLogPlugin.php
new file mode 100644 (file)
index 0000000..459df63
--- /dev/null
@@ -0,0 +1,218 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2012, StatusNet, Inc.
+ *
+ * ModLogPlugin.php
+ *
+ * PHP version 5
+ *
+ * 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  Moderation
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2012 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    // This check helps protect against security problems;
+    // your code file can't be executed directly from the web.
+    exit(1);
+}
+
+/**
+ * Moderation logging
+ *
+ * Shows a history of moderation for this user in the sidebar
+ *
+ * @category  Moderation
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2012 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+class ModLogPlugin extends Plugin
+{
+    const VIEWMODLOG = 'ModLogPlugin::VIEWMODLOG';
+
+    /**
+     * Database schema setup
+     *
+     * We keep a moderation log table
+     *
+     * @see Schema
+     * @see ColumnDef
+     *
+     * @return boolean hook value; true means continue processing, false means stop.
+     */
+
+    function onCheckSchema()
+    {
+        $schema = Schema::get();
+
+        $schema->ensureTable('mod_log', ModLog::schemaDef());
+
+        return true;
+    }
+
+    /**
+     * Load related modules when needed
+     *
+     * @param string $cls Name of the class to be loaded
+     *
+     * @return boolean hook value; true means continue processing, false means stop.
+     */
+
+    function onAutoload($cls)
+    {
+        $dir = dirname(__FILE__);
+
+        switch ($cls)
+        {
+        case 'ModLog':
+            include_once $dir . '/'.$cls.'.php';
+            return false;
+        default:
+            return true;
+        }
+    }
+
+    function onEndGrantRole($profile, $role)
+    {
+        $modlog = new ModLog();
+
+        $modlog->id         = UUID::gen();
+        $modlog->profile_id = $profile->id;
+
+        $cur = common_current_user();
+        
+        if (!empty($cur)) {
+            $modlog->moderator_id = $cur->id;
+        }
+
+        $modlog->role     = $role;
+        $modlog->is_grant = 1;
+        $modlog->created  = common_sql_now();
+
+        $modlog->insert();
+
+        return true;
+    }
+
+    function onEndRevokeRole($profile, $role)
+    {
+        $modlog = new ModLog();
+
+        $modlog->id = UUID::gen();
+
+        $modlog->profile_id = $profile->id;
+
+        $cur = common_current_user();
+        
+        if (!empty($cur)) {
+            $modlog->moderator_id = $cur->id;
+        }
+
+        $modlog->role     = $role;
+        $modlog->is_grant = 0;
+        $modlog->created  = common_sql_now();
+
+        $modlog->insert();
+
+        return true;
+    }
+
+    function onEndShowSections($action)
+    {
+        if ($action->arg('action') != 'showstream') {
+            return true;
+        }
+
+        $cur = common_current_user();
+
+        if (empty($cur) || !$cur->hasRight(self::VIEWMODLOG)) {
+            return true;
+        }
+
+        $profile = $action->profile;
+
+        $ml = new ModLog();
+
+        $ml->profile_id = $profile->id;
+        $ml->orderBy("created");
+
+        $cnt = $ml->find();
+
+        if ($cnt > 0) {
+
+            $action->elementStart('div', array('id' => 'entity_mod_log',
+                                               'class' => 'section'));
+
+            $action->element('h2', null, _('Moderation'));
+
+            $action->elementStart('table');
+
+            while ($ml->fetch()) {
+                $action->elementStart('tr');
+                $action->element('td', null, strftime('%y-%m-%d', strtotime($ml->created)));
+                $action->element('td', null, sprintf(($ml->is_grant) ? _('+%s') : _('-%s'), $ml->role));
+                $action->elementStart('td');
+                if ($ml->moderator_id) {
+                    $mod = Profile::staticGet('id', $ml->moderator_id);
+                    if (empty($mod)) {
+                        $action->text(_('[unknown]'));
+                    } else {
+                        $action->element('a', array('href' => $mod->profileurl,
+                                                    'title' => $mod->fullname),
+                                         $mod->nickname);
+                    }
+                } else {
+                    $action->text(_('[unknown]'));
+                }
+                $action->elementEnd('td');
+                $action->elementEnd('tr');
+            }
+
+            $action->elementEnd('table');
+
+            $action->elementEnd('div');
+        }
+    }
+
+    function onUserRightsCheck($profile, $right, &$result) {
+        switch ($right) {
+        case self::VIEWMODLOG:
+            $result = ($profile->hasRole(Profile_role::MODERATOR) || $profile->hasRole('modhelper'));
+            return false;
+        default:
+            return true;
+        }
+    }
+
+    function onPluginVersion(&$versions)
+    {
+        $versions[] = array('name' => 'ModLog',
+                            'version' => STATUSNET_VERSION,
+                            'author' => 'Evan Prodromou',
+                            'homepage' => 'http://status.net/wiki/Plugin:ModLog',
+                            'description' =>
+                            _m('Show the moderation history for a profile in the sidebar'));
+        return true;
+    }
+}
index 2c8e318c1f439ee4a1fadf696d71934d35c5c3de..41e933498be63e190839273a2bfa5047e79adfc6 100644 (file)
@@ -146,6 +146,8 @@ class OStatusGroupAction extends OStatusSubAction
         try {
             $user->joinGroup($group);
         } catch (Exception $e) {
+            common_log(LOG_ERR, "Exception on remote group join: " . $e->getMessage());
+            common_log(LOG_ERR, $e->getTraceAsString());
             // TRANS: OStatus remote group subscription dialog error.
             $this->showForm(_m('Remote group join failed!'));
             return;
index dc3922896c1ede19c88b3298693784afa143fba9..1c862aa9974351f3269f368ca4e08ee2bed7dbcd 100644 (file)
@@ -98,6 +98,7 @@ class OStatusInitAction extends Action
 
     function showContent()
     {
+    
         if ($this->group) {
             // TRANS: Form legend. %s is a group name.
             $header = sprintf(_m('Join group %s'), $this->group);
index bca136bbbce3a27285b99276df20f8ecaf604ff2..f0bc3e12dcff684d4b932025b5a713b66dd4d86d 100644 (file)
@@ -18,7 +18,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
 
 $shortoptions = 'u:a';
 $longoptions = array('uri=', 'all');
index ed6d6534c09dbdaff0ebef609fabd235cee9b628..3d8dab8213c30f94d11889ae5090ad1e4924029b 100644 (file)
@@ -20,7 +20,7 @@
  * @category  Plugin
  * @package   StatusNet
  * @author    Evan Prodromou <evan@status.net>
- * @author   Craig Andrews <candrews@integralblue.com>
+ * @author    Craig Andrews <candrews@integralblue.com>
  * @copyright 2009-2010 StatusNet, Inc.
  * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
@@ -362,10 +362,9 @@ class OpenIDPlugin extends Plugin
             require_once dirname(__FILE__) . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
             return false;
         case 'User_openid':
-            require_once dirname(__FILE__) . '/User_openid.php';
-            return false;
+        case 'User_openid_prefs':
         case 'User_openid_trustroot':
-            require_once dirname(__FILE__) . '/User_openid_trustroot.php';
+            require_once dirname(__FILE__) . '/' . $cls . '.php';
             return false;
         case 'Auth_OpenID_TeamsExtension':
         case 'Auth_OpenID_TeamsRequest':
@@ -574,6 +573,8 @@ class OpenIDPlugin extends Plugin
                                                  null, false),
                                    new ColumnDef('modified', 'timestamp')));
 
+        $schema->ensureTable('user_openid_prefs', User_openid_prefs::schemaDef());
+
         /* These are used by JanRain OpenID library */
 
         $schema->ensureTable('oid_associations',
@@ -814,4 +815,35 @@ class OpenIDPlugin extends Plugin
 
         return true;
     }
+
+    /**
+     * Add links in the user's profile block to their OpenID URLs.
+     *
+     * @param Profile $profile The profile being shown
+     * @param Array   &$links  Writeable array of arrays (href, text, image).
+     *
+     * @return boolean hook value (true)
+     */
+    
+    function onOtherAccountProfiles($profile, &$links)
+    {
+        $prefs = User_openid_prefs::staticGet('user_id', $profile->id);
+
+        if (empty($prefs) || !$prefs->hide_profile_link) {
+
+            $oid = new User_openid();
+
+            $oid->user_id = $profile->id;
+
+            if ($oid->find()) {
+                while ($oid->fetch()) {
+                    $links[] = array('href' => $oid->display,
+                                     'text' => _('OpenID'),
+                                     'image' => $this->path("icons/openid-16x16.gif"));
+                }
+            }
+        }
+
+        return true;
+    }
 }
diff --git a/plugins/OpenID/User_openid_prefs.php b/plugins/OpenID/User_openid_prefs.php
new file mode 100644 (file)
index 0000000..74a21f6
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2012, StatusNet, Inc.
+ *
+ * User_openid_prefs.php
+ * 
+ * PHP version 5
+ *
+ * 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  OpenID
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2012 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+/**
+ * Store preferences for OpenID use in StatusNet
+ *
+ * @category OpenID
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ *
+ * @see      DB_DataObject
+ */
+
+class User_openid_prefs extends Managed_DataObject
+{
+    public $__table = 'user_openid_prefs'; // table name
+
+    public $user_id;            // The User with the prefs
+    public $hide_profile_link;  // Hide the link on the profile block?
+    public $created;            // datetime
+    public $modified;           // datetime
+
+    /**
+     * Get an instance by key
+     *
+     * This is a utility method to get a single instance with a given key value.
+     *
+     * @param string $k Key to use to lookup (usually 'user_id' for this class)
+     * @param mixed  $v Value to lookup
+     *
+     * @return TagSub object found, or null for no hits
+     *
+     */
+    function staticGet($k, $v=null)
+    {
+        return Managed_DataObject::staticGet('User_openid_prefs', $k, $v);
+    }
+
+    /**
+     * The One True Thingy that must be defined and declared.
+     */
+
+    public static function schemaDef()
+    {
+        return array(
+                     'description' => 'Per-user preferences for OpenID display',
+                     'fields' => array('user_id' => array('type' => 'integer',
+                                                          'not null' => true,
+                                                          'description' => 'User whose prefs we are saving'),
+                                       'hide_profile_link' => array('type' => 'int',
+                                                                    'not null' => true,
+                                                                    'default' => 0,
+                                                                    'description' => 'Whether to hide profile links from profile block'),
+                                       'created' => array('type' => 'datetime',
+                                                          'not null' => true,
+                                                          'description' => 'date this record was created'),
+                                       'modified' => array('type' => 'datetime',
+                                                           'not null' => true,
+                                                           'description' => 'date this record was modified'),
+                                       ),
+                     'primary key' => array('user_id'),
+                     'foreign keys' => array('user_openid_prefs_user_id_fkey' => array('user', array('user_id' => 'id')),
+                                             ),
+                     'indexes' => array(),
+                     );
+    }
+}
index 08768fb7168acd51c75d374faf263b0e368641a4..0f081285055fb43ce69ebb813ac2503a7b2bd6d4 100644 (file)
@@ -3,9 +3,11 @@
 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://openid.net/get-an-openid/), and you may already have an OpenID-enabled account on another service.
+There are many [Public OpenID providers](http://wiki.openid.net/OpenID-Providers), and you may already have an OpenID-enabled account on another service.
 
-* [Google](http://www.google.com/) : If you have a Google profile, you can log in to this site by entering your profile URL.
-* [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. 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. 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): 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/*.
+* 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/*.
+
+Additionally, once you have an account on %%site.name%%, you can use your profile's URL (https://%%site.server%%/yourusername) as an OpenID elsewhere as well.
diff --git a/plugins/OpenID/icons/openid-16x16.gif b/plugins/OpenID/icons/openid-16x16.gif
new file mode 100644 (file)
index 0000000..e2d8377
Binary files /dev/null and b/plugins/OpenID/icons/openid-16x16.gif differ
index f1a62384b4d2c4f5e86359b13fabbd449b8cd70a..0dc4930ce1b8931841207c5d0b94806f4ca5313c 100644 (file)
@@ -222,6 +222,22 @@ class OpenidsettingsAction extends SettingsAction
                                       // TRANS: Button text to remove an OpenID trustroot.
                                       'value' => _m('BUTTON','Remove')));
         $this->elementEnd('fieldset');
+        
+        $prefs = User_openid_prefs::staticGet('user_id', $user->id);
+
+        $this->elementStart('fieldset');
+        $this->element('legend', null, _m('LEGEND','Preferences'));
+        $this->elementStart('ul', 'form_data');
+        $this->checkBox('hide_profile_link', "Hide OpenID links from my profile", !empty($prefs) && $prefs->hide_profile_link);
+        $this->element('input', array('type' => 'submit',
+                                      'id' => 'settings_openid_prefs_save',
+                                      'name' => 'save_prefs',
+                                      'class' => 'submit',
+                                      // TRANS: Button text to save OpenID prefs
+                                      'value' => _m('BUTTON','Save')));
+        $this->elementEnd('ul');
+        $this->elementEnd('fieldset');
+
         $this->elementEnd('form');
     }
 
@@ -258,6 +274,8 @@ class OpenidsettingsAction extends SettingsAction
             $this->removeOpenid();
         } else if($this->arg('remove_trustroots')) {
             $this->removeTrustroots();
+        } else if($this->arg('save_prefs')) {
+            $this->savePrefs();
         } else {
             // TRANS: Unexpected form validation error.
             $this->showForm(_m('Something weird happened.'));
@@ -326,4 +344,43 @@ class OpenidsettingsAction extends SettingsAction
         $this->showForm(_m('OpenID removed.'), true);
         return;
     }
+
+    /**
+     * Handles a request to save preferences
+     *
+     * Validates input and, if everything is OK, deletes the OpenID.
+     * Reloads the form with a success or error notification.
+     *
+     * @return void
+     */
+    function savePrefs()
+    {
+        $cur = common_current_user();
+
+        if (empty($cur)) {
+            throw new ClientException(_("Not logged in."));
+        }
+
+        $orig  = null;
+        $prefs = User_openid_prefs::staticGet('user_id', $cur->id);
+
+        if (empty($prefs)) {
+            $prefs          = new User_openid_prefs();
+            $prefs->user_id = $cur->id;
+            $prefs->created = common_sql_now();
+        } else {
+            $orig = clone($prefs);
+        }
+
+        $prefs->hide_profile_link = $this->boolean('hide_profile_link');
+
+        if (empty($orig)) {
+            $prefs->insert();
+        } else {
+            $prefs->update($orig);
+        }
+
+        $this->showForm(_m('OpenID preferences saved.'), true);
+        return;
+    }
 }
index 1246f6c2f89e23f5e5bcfe64a83bd1a661fb79c6..1fadcf108c386885b5c6273d58b9327a80eda9d5 100644 (file)
@@ -64,6 +64,7 @@ class PollPlugin extends MicroAppPlugin
         $schema = Schema::get();
         $schema->ensureTable('poll', Poll::schemaDef());
         $schema->ensureTable('poll_response', Poll_response::schemaDef());
+        $schema->ensureTable('user_poll_prefs', User_poll_prefs::schemaDef());
         return true;
     }
 
@@ -96,10 +97,12 @@ class PollPlugin extends MicroAppPlugin
         case 'ShowpollAction':
         case 'NewpollAction':
         case 'RespondpollAction':
+        case 'PollsettingsAction':
             include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
             return false;
         case 'Poll':
         case 'Poll_response':
+        case 'User_poll_prefs':
             include_once $dir.'/'.$cls.'.php';
             return false;
         case 'NewPollForm':
@@ -136,6 +139,9 @@ class PollPlugin extends MicroAppPlugin
                     array('action' => 'respondpoll'),
                     array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
 
+        $m->connect('settings/poll',
+                    array('action' => 'pollsettings'));
+
         return true;
     }
 
@@ -491,4 +497,43 @@ class PollPlugin extends MicroAppPlugin
         }
         return true;
     }
+
+    // Hide poll responses for @chuck
+
+    function onEndNoticeWhoGets($notice, &$ni) {
+        if ($notice->object_type == self::POLL_RESPONSE_OBJECT) {
+            foreach ($ni as $id => $source) {
+                $user = User::staticGet('id', $id);
+                if (!empty($user)) {
+                    $pollPrefs = User_poll_prefs::staticGet('user_id', $user->id);
+                    if (!empty($pollPrefs) && ($pollPrefs->hide_responses)) {
+                        unset($ni[$id]);
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Menu item for personal subscriptions/groups area
+     *
+     * @param Action $action action being executed
+     *
+     * @return boolean hook return
+     */
+
+    function onEndAccountSettingsNav($action)
+    {
+        $action_name = $action->trimmed('action');
+
+        $action->menuItem(common_local_url('pollsettings'),
+                          // TRANS: Poll plugin menu item on user settings page.
+                          _m('MENU', 'Polls'),
+                          // TRANS: Poll plugin tooltip for user settings menu item.
+                          _m('Configure poll behavior'),
+                          $action_name === 'pollsettings');
+
+        return true;
+    }
 }
diff --git a/plugins/Poll/User_poll_prefs.php b/plugins/Poll/User_poll_prefs.php
new file mode 100644 (file)
index 0000000..338e811
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Data class to record user prefs for polls
+ *
+ * PHP version 5
+ *
+ * @category PollPlugin
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@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) 2012, 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')) {
+    exit(1);
+}
+
+/**
+ * For storing the poll prefs
+ *
+ * @category PollPlugin
+ * @package  StatusNet
+ * @author   Brion Vibber <brion@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://status.net/
+ *
+ * @see      DB_DataObject
+ */
+class User_poll_prefs extends Managed_DataObject
+{
+    public $__table = 'user_poll_prefs'; // table name
+    public $user_id;          // int id
+    public $hide_responses;   // boolean
+    public $created;          // datetime
+    public $modified;         // datetime
+
+    /**
+     * Get an instance by key
+     *
+     * This is a utility method to get a single instance with a given key value.
+     *
+     * @param string $k Key to use to lookup (usually 'user_id' for this class)
+     * @param mixed  $v Value to lookup
+     *
+     * @return User_greeting_count object found, or null for no hits
+     *
+     */
+    function staticGet($k, $v=null)
+    {
+        return Memcached_DataObject::staticGet('User_poll_prefs', $k, $v);
+    }
+
+    /**
+     * The One True Thingy that must be defined and declared.
+     */
+    public static function schemaDef()
+    {
+        return array(
+            'description' => 'Record of user preferences for polls',
+            'fields' => array(
+                'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'user id'),
+                'hide_responses' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'Hide all poll responses'),
+                'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
+                'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
+            ),
+            'primary key' => array('user_id')
+        );
+    }
+}
diff --git a/plugins/Poll/pollsettings.php b/plugins/Poll/pollsettings.php
new file mode 100644 (file)
index 0000000..c312889
--- /dev/null
@@ -0,0 +1,198 @@
+<?php
+/**
+ * Form to set your personal poll settings
+ *
+ * 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  Plugins
+ * @package   StatusNet
+ * @author    Brion Vibber <brion@status.net>
+ * @copyright 2012 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 PollSettingsAction extends SettingsAction
+{
+    /**
+     * Title of the page
+     *
+     * @return string Page title
+     */
+    function title()
+    {
+        // TRANS: Page title.
+        return _m('Poll settings');
+    }
+
+    /**
+     * Instructions for use
+     *
+     * @return string Instructions for use
+     */
+
+    function getInstructions()
+    {
+        // TRANS: Page instructions.
+        return _m('Set your poll preferences');
+    }
+
+    /**
+     * Show the form for Poll
+     *
+     * @return void
+     */
+    function showContent()
+    {
+        $user = common_current_user();
+
+        $prefs = User_poll_prefs::staticGet('user_id', $user->id);
+
+        $form = new PollPrefsForm($this, $prefs);
+
+        $form->show();
+    }
+
+    /**
+     * Handler method
+     *
+     * @param array $argarray is ignored since it's now passed in in prepare()
+     *
+     * @return void
+     */
+
+    function handlePost()
+    {
+        $user = common_current_user();
+
+        $upp = User_poll_prefs::staticGet('user_id', $user->id);
+        $orig = null;
+
+        if (!empty($upp)) {
+            $orig = clone($upp);
+        } else {
+            $upp = new User_poll_prefs();
+            $upp->user_id = $user->id;
+            $upp->created = common_sql_now();
+        }
+
+        $upp->hide_responses = $this->boolean('hide_responses');
+        $upp->modified       = common_sql_now();
+
+        if (!empty($orig)) {
+            $upp->update($orig);
+        } else {
+            $upp->insert();
+        }
+
+        // TRANS: Confirmation shown when user profile settings are saved.
+        $this->showForm(_('Settings saved.'), true);
+
+        return;
+    }
+}
+
+class PollPrefsForm extends Form
+{
+    var $prefs;
+
+    function __construct($out, $prefs)
+    {
+        parent::__construct($out);
+        $this->prefs = $prefs;
+    }
+
+    /**
+     * Visible or invisible data elements
+     *
+     * Display the form fields that make up the data of the form.
+     * Sub-classes should overload this to show their data.
+     *
+     * @return void
+     */
+
+    function formData()
+    {
+        $this->elementStart('fieldset');
+        $this->elementStart('ul', 'form_data');
+        $this->elementStart('li');
+        $this->checkbox('hide_responses',
+                        _('Do not deliver poll responses to my home timeline'),
+                        (!empty($this->prefs) && $this->prefs->hide_responses));
+        $this->elementEnd('li');
+        $this->elementEnd('ul');
+        $this->elementEnd('fieldset');
+    }
+
+    /**
+     * Buttons for form actions
+     *
+     * Submit and cancel buttons (or whatever)
+     * Sub-classes should overload this to show their own buttons.
+     *
+     * @return void
+     */
+
+    function formActions()
+    {
+        $this->submit('submit', _('Save'));
+    }
+
+    /**
+     * ID of the form
+     *
+     * Should be unique on the page. Sub-classes should overload this
+     * to show their own IDs.
+     *
+     * @return int ID of the form
+     */
+
+    function id()
+    {
+        return 'form_poll_prefs';
+    }
+
+    /**
+     * Action of the form.
+     *
+     * URL to post to. Should be overloaded by subclasses to give
+     * somewhere to post to.
+     *
+     * @return string URL to post to
+     */
+
+    function action()
+    {
+        return common_local_url('pollsettings');
+    }
+
+    /**
+     * Class of the form. May include space-separated list of multiple classes.
+     *
+     * @return string the form's class
+     */
+
+    function formClass()
+    {
+        return 'form_settings';
+    }
+}
index 287e48e48475e4cb267a3ed2ec97c8754ce30ecf..0fa51ce8612372eac33e2c34fa3010932f8bc9b8 100644 (file)
@@ -253,6 +253,9 @@ class RealtimePlugin extends Plugin
                 list($action, $arg1, $arg2) = $path;
 
                 $channels = Realtime_channel::getAllChannels($action, $arg1, $arg2);
+                $this->log(LOG_INFO, sprintf(_("%d candidate channels for notice %d"),
+                                             count($channels), 
+                                             $notice->id));
 
                 foreach ($channels as $channel) {
 
@@ -265,6 +268,13 @@ class RealtimePlugin extends Plugin
                         $profile = Profile::staticGet('id', $channel->user_id);
                     }
                     if ($notice->inScope($profile)) {
+                        $this->log(LOG_INFO, 
+                                   sprintf(_("Delivering notice %d to channel (%s, %s, %s) for user '%s'"),
+                                           $notice->id,
+                                           $channel->action,
+                                           $channel->arg1,
+                                           $channel->arg2,
+                                           ($profile) ? ($profile->nickname) : "<public>"));
                         $timeline = $this->_pathToChannel(array($channel->channel_key));
                         $this->_publish($timeline, $json);
                     }
index 4d697509dc72fe36e98258d1e41244dcad68acaf..672c1b84510f871c0a1581b67a697bbc14007b93 100644 (file)
@@ -558,4 +558,32 @@ class TwitterBridgePlugin extends Plugin
         }
         return true;
     }
+
+    /**
+     * Add links in the user's profile block to their Twitter profile URL.
+     *
+     * @param Profile $profile The profile being shown
+     * @param Array   &$links  Writeable array of arrays (href, text, image).
+     *
+     * @return boolean hook value (true)
+     */
+
+    function onOtherAccountProfiles($profile, &$links)
+    {
+        $fuser = null;
+
+        $flink = Foreign_link::getByUserID($profile->id, TWITTER_SERVICE);
+
+        if (!empty($flink)) {
+            $fuser = $flink->getForeignUser();
+
+            if (!empty($fuser)) {
+                $links[] = array("href" => $fuser->uri,
+                                 "text" => sprintf(_("@%s on Twitter"), $fuser->nickname),
+                                 "image" => $this->path("icons/twitter-bird-white-on-blue.png"));
+            }
+        }
+
+        return true;
+    }
 }
diff --git a/plugins/TwitterBridge/icons/twitter-bird-white-on-blue.png b/plugins/TwitterBridge/icons/twitter-bird-white-on-blue.png
new file mode 100644 (file)
index 0000000..2c42b08
Binary files /dev/null and b/plugins/TwitterBridge/icons/twitter-bird-white-on-blue.png differ
index f55fca40ae8201523c7e4f6b89c57810c8649eba..2441b1335700289ab822ac34238182eebe324b1a 100644 (file)
@@ -208,7 +208,7 @@ class TwitterSiteStream extends TwitterStreamReader
 {
     protected $userIds;
 
-    public function __construct(TwitterOAuthClient $auth, $baseUrl='http://betastream.twitter.com')
+    public function __construct(TwitterOAuthClient $auth, $baseUrl='https://sitestream.twitter.com')
     {
         parent::__construct($auth, $baseUrl);
     }
index d183dba2b9b633667c9fef43d89d94c87d903ce9..740ae25d446bbd0210f9af4aa6945b57a9d41fa1 100644 (file)
@@ -19,8 +19,8 @@
 
 define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
 
-$shortoptions = 'i:n:f:j';
-$longoptions = array('id=', 'nickname=', 'file=', 'json');
+$shortoptions = 'i:n:f:a:j';
+$longoptions = array('id=', 'nickname=', 'file=', 'after=', 'json');
 
 $helptext = <<<END_OF_EXPORTACTIVITYSTREAM_HELP
 exportactivitystream.php [options]
@@ -29,6 +29,7 @@ Export a StatusNet user history to a file
   -i --id       ID of user to export
   -n --nickname nickname of the user to export
   -j --json     Output JSON (default Atom)
+  -a --after    Only activities after the given date
 
 END_OF_EXPORTACTIVITYSTREAM_HELP;
 
@@ -36,7 +37,13 @@ require_once INSTALLDIR.'/scripts/commandline.inc';
 
 try {
     $user = getUser();
-    $actstr = new UserActivityStream($user, true, UserActivityStream::OUTPUT_RAW);
+    if (have_option('a', 'after')) {
+        $afterStr = get_option_value('a', 'after');
+        $after = strtotime($afterStr);
+        $actstr = new UserActivityStream($user, true, UserActivityStream::OUTPUT_RAW, $after);
+    } else {
+        $actstr = new UserActivityStream($user, true, UserActivityStream::OUTPUT_RAW);
+    }
     if (have_option('j', 'json')) {
         $actstr->writeJSON(STDOUT);
     } else {
index 21ed38fd56341d76fde20bc0449ff128397eceb0..d5ed22758165a4eef35c25de47de9942985cdb26 100644 (file)
@@ -30,7 +30,8 @@ $longoptions = array(
     'users=',
     'words=',
     'prefix=',
-    'groupprefix'
+    'groupprefix=',
+    'faves='
 );
 
 $helptext = <<<END_OF_CREATESIM_HELP
@@ -39,6 +40,7 @@ Creates a lot of test users and notices to (loosely) simulate a real server.
     -b --subscriptions Average subscriptions per user (default no. users/20)
     -g --groups        Number of groups (default 20)
     -j --joins         Number of groups per user (default 5)
+    -f --faves         Number of faves per user (default notices/10)
     -n --notices       Average notices per user (default 100)
     -t --tags          Number of distinct hash tags (default 10000)
     -u --users         Number of users (default 100)
@@ -112,6 +114,39 @@ function newNotice($i, $tagmax)
                 $options['scope'] |= Notice::ADDRESSEE_SCOPE;
             }
         }
+    } else {
+        $is_directed = rand(0, 4);
+
+        if ($is_directed == 0) {
+            $subs = $user->getSubscriptions(0, 100)->fetchAll();
+            if (count($subs) > 0) {
+                $seen = array();
+                $f = rand(0, 9);
+                if ($f <= 6) {
+                    $addrs = 1;
+                } else if ($f <= 8) {
+                    $addrs = 2;
+                } else {
+                    $addrs = 3;
+                }
+                for ($m = 0; $m < $addrs; $m++) {
+                    $x = rand(0, count($subs) - 1);
+                    if ($seen[$x]) {
+                        continue;
+                    }
+                    if ($subs[$x]->id == $user->id) {
+                        continue;
+                    }
+                    $seen[$x] = true;
+                    $rprofile = $subs[$x];
+                    $content = "@".$rprofile->nickname." ".$content;
+                }
+                $private_to_addressees = rand(0, 4);
+                if ($private_to_addressees == 0) {
+                    $options['scope'] |= Notice::ADDRESSEE_SCOPE;
+                }
+            }
+        }
     }
 
     $has_hash = rand(0, 2);
@@ -152,6 +187,28 @@ function newNotice($i, $tagmax)
     $notice = Notice::saveNew($user->id, $content, 'createsim', $options);
 }
 
+function newMessage($i)
+{
+    global $userprefix;
+
+    $n = rand(0, $i - 1);
+    $user = User::staticGet('nickname', sprintf('%s%d', $userprefix, $n));
+
+    $content = testNoticeContent();
+
+    $friends = $user->mutuallySubscribedUsers()->fetchAll();
+
+    if (count($friends) == 0) {
+        return;
+    }
+
+    $j = rand(0, count($friends) - 1);
+    
+    $other = $friends[$j];
+
+    $message = Message::saveNew($user->id, $other->id, $content, 'createsim');
+}
+
 function newSub($i)
 {
     global $userprefix;
@@ -218,6 +275,50 @@ function newJoin($u, $g)
     }
 }
 
+function newFave($u)
+{
+    global $userprefix;
+    global $groupprefix;
+
+    $userNumber = rand(0, $u - 1);
+
+    $userNick = sprintf('%s%d', $userprefix, $userNumber);
+
+    $user = User::staticGet('nickname', $userNick);
+
+    if (empty($user)) {
+        throw new Exception("Can't find user '$userNick'.");
+    }
+
+    // NB: it's OK to like your own stuff!
+
+    $otherNumber = rand(0, $u - 1);
+
+    $otherNick = sprintf('%s%d', $userprefix, $otherNumber);
+
+    $other = User::staticGet('nickname', $otherNick);
+
+    if (empty($other)) {
+        throw new Exception("Can't find user '$otherNick'.");
+    }
+
+    $notices = $other->getNotices()->fetchAll();
+
+    if (count($notices) == 0) {
+        return;
+    }
+
+    $idx = rand(0, count($notices) - 1);
+
+    $notice = $notices[$idx];
+
+    if ($user->hasFave($notice)) {
+        return;
+    }
+
+    Fave::addNew($user->getProfile(), $notice);
+}
+
 function testNoticeContent()
 {
     global $words;
@@ -243,7 +344,7 @@ function testNoticeContent()
     return $text;
 }
 
-function main($usercount, $groupcount, $noticeavg, $subsavg, $joinsavg, $tagmax)
+function main($usercount, $groupcount, $noticeavg, $subsavg, $joinsavg, $favesavg, $messageavg, $tagmax)
 {
     global $config;
     $config['site']['dupelimit'] = -1;
@@ -271,7 +372,7 @@ function main($usercount, $groupcount, $noticeavg, $subsavg, $joinsavg, $tagmax)
 
     // # registrations + # notices + # subs
 
-    $events = $usercount + $groupcount + ($usercount * ($noticeavg + $subsavg + $joinsavg));
+    $events = $usercount + $groupcount + ($usercount * ($noticeavg + $subsavg + $joinsavg + $favesavg + $messageavg));
 
     $events -= $preuser;
     $events -= $pregroup;
@@ -281,8 +382,10 @@ function main($usercount, $groupcount, $noticeavg, $subsavg, $joinsavg, $tagmax)
     $nt = $gt + ($usercount * $noticeavg);
     $st = $nt + ($usercount * $subsavg);
     $jt = $st + ($usercount * $joinsavg);
+    $ft = $jt + ($usercount * $favesavg);
+    $mt = $ft + ($usercount * $messageavg);
 
-    printfv("$events events ($ut, $gt, $nt, $st, $jt)\n");
+    printfv("$events events ($ut, $gt, $nt, $st, $jt, $ft, $mt)\n");
 
     for ($i = 0; $i < $events; $i++)
     {
@@ -305,6 +408,12 @@ function main($usercount, $groupcount, $noticeavg, $subsavg, $joinsavg, $tagmax)
         } else if ($e > $st && $e <= $jt) {
             printfv("$i Making a new group join\n");
             newJoin($n, $g);
+        } else if ($e > $jt && $e <= $ft) {
+            printfv("$i Making a new fave\n");
+            newFave($n);
+        } else if ($e > $ft && $e <= $mt) {
+            printfv("$i Making a new message\n");
+            newMessage($n);
         } else {
             printfv("No event for $i!");
         }
@@ -318,6 +427,8 @@ $groupcount  = (have_option('g', 'groups')) ? get_option_value('g', 'groups') :
 $noticeavg   = (have_option('n', 'notices')) ? get_option_value('n', 'notices') : 100;
 $subsavg     = (have_option('b', 'subscriptions')) ? get_option_value('b', 'subscriptions') : max($usercount/20, 10);
 $joinsavg    = (have_option('j', 'joins')) ? get_option_value('j', 'joins') : 5;
+$favesavg    = (have_option('f', 'faves')) ? get_option_value('f', 'faves') : max($noticeavg/10, 5);
+$messageavg  = (have_option('m', 'messages')) ? get_option_value('m', 'messages') : max($noticeavg/10, 5);
 $tagmax      = (have_option('t', 'tags')) ? get_option_value('t', 'tags') : 10000;
 $userprefix  = (have_option('x', 'prefix')) ? get_option_value('x', 'prefix') : 'testuser';
 $groupprefix = (have_option('z', 'groupprefix')) ? get_option_value('z', 'groupprefix') : 'testgroup';
@@ -334,7 +445,7 @@ if (is_readable($wordsfile)) {
 }
 
 try {
-    main($usercount, $groupcount, $noticeavg, $subsavg, $joinsavg, $tagmax);
+    main($usercount, $groupcount, $noticeavg, $subsavg, $joinsavg, $favesavg, $messageavg, $tagmax);
 } catch (Exception $e) {
     printfv("Got an exception: ".$e->getMessage());
 }
index dadbcf66f0e80329aeb95776ba9b4a0c080fc003..3e18f28f3ea1ef3cbaad1b910f1360b7d9747c74 100755 (executable)
@@ -82,7 +82,9 @@ class CliInstaller extends Installer
             '--admin-nick' => 'adminNick',
             '--admin-pass' => 'adminPass',
             '--admin-email' => 'adminEmail',
-            '--admin-updates' => 'adminUpdates'
+            '--admin-updates' => 'adminUpdates',
+
+            '--site-profile' => 'siteProfile'
         );
         foreach ($map as $arg => $target) {
             if (substr($arg, 0, 2) == '--') {
@@ -170,6 +172,8 @@ install_cli.php - StatusNet command-line installer
        --admin-updates   'yes' (default) or 'no', whether to subscribe
                          admin to update@status.net (default yes)
        
+       --site-profile    site profile ['public', 'private' (default), 'community', 'singleuser']
+       
        --skip-config     Don't write a config.php -- use with caution,
                          requires a global configuration file.
 
diff --git a/socialfy-your-domain/README.txt b/socialfy-your-domain/README.txt
new file mode 100644 (file)
index 0000000..ead4380
--- /dev/null
@@ -0,0 +1,57 @@
+Initial simple way to Webfinger enable your domain -- needs PHP.
+================================================================
+
+Step 1
+======
+
+First, put the folders 'xrd' and 'dot-well-known' on your website, so
+they load at:
+
+     http://yourname.com/xrd/
+
+     and
+
+     http://yourname.com/.well-known/
+
+     (Remember the . at the beginning of this one)
+
+Step 2
+======
+
+Next, edit xrd/index.php and enter a secret in this line:
+
+$s = "";
+
+This can be anything you like...
+
+$s = "johnny-five";
+
+or 
+
+$s = "12345";
+
+It really doesn't matter too much.
+
+Step 3
+======
+
+For each user on your site, and this might only be you...
+
+Make a copy of the example@example.com.xml file so that it's called...
+
+     yoursecretusername@domain.com.xml
+
+     So, if your secret is 'johnny5' and your name is ben and your
+     domain is titanictoycorp.biz, your file should be called
+     johnny5ben@titanictoycorp.biz.xml
+
+Finally, edit the file to point at your account on your social
+site. If you are the only user, then you probably don't need to worry
+about user/1 as this will be you. For multi user sites, the user ID is
+on the profile page.
+
+Finally
+=======
+
+Using this method, though fiddly, you can now be @user@domain without
+the need for any prefixes for subdomains, etc.
diff --git a/socialfy-your-domain/dot-well-known/host-meta b/socialfy-your-domain/dot-well-known/host-meta
new file mode 100644 (file)
index 0000000..e44591a
--- /dev/null
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0" xmlns:hm="http://host-meta.net/xrd/1.0"><hm:Host>example.com</hm:Host><Link rel="lrdd" template="http://example.com/xrd?uri={uri}"><Title>Resource Descriptor</Title></Link></XRD>
\ No newline at end of file
diff --git a/socialfy-your-domain/xrd/example@example.com b/socialfy-your-domain/xrd/example@example.com
new file mode 100644 (file)
index 0000000..0698752
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
+  <Subject>acct:example@example.com</Subject>
+  <Alias>acct:example@social.example.com</Alias>
+  <Alias>http://social.example.com/user/1</Alias>
+  <Link rel="http://webfinger.net/rel/profile-page" type="text/html" href="http://social.example.com/user/1"/>
+  <Link rel="http://schemas.google.com/g/2010#updates-from" type="application/atom+xml" href="http://social.example.com/api/statuses/user_timeline/1.atom"/>
+  <Link rel="http://microformats.org/profile/hcard" type="text/html" href="http://social.example.com/hcard"/>
+  <Link rel="http://gmpg.org/xfn/11" type="text/html" href="http://social.example.com/user/1"/>
+  <Link rel="describedby" type="application/rdf+xml" href="http://social.example.com/foaf"/>
+  <Link rel="http://salmon-protocol.org/ns/salmon-replies" href="http://social.example.com/main/salmon/user/1"/>
+  <Link rel="http://salmon-protocol.org/ns/salmon-mention" href="http://social.example.com/main/salmon/user/1"/>
+  <Link rel="http://ostatus.org/schema/1.0/subscribe" template="http://social.example.com/main/ostatussub?profile={uri}"/>
+</XRD>
diff --git a/socialfy-your-domain/xrd/index.php b/socialfy-your-domain/xrd/index.php
new file mode 100644 (file)
index 0000000..25f1d8b
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * GNU social
+ * Copyright (C) 2010, Free Software Foundation, 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/>.
+ */
+
+
+$s = "";
+
+/* this should be a secret */
+
+$u = $_GET['uri'];
+
+$u = substr($u, 5);
+
+$f = $s . $u . ".xml";
+
+if (file_exists($f)) {
+  $fh = fopen($f, 'r');
+  $c = fread($fh, filesize($f));
+  fclose($fh);
+  header('Content-type: text/xml');
+  echo $c;
+}
+
+
+?>
\ No newline at end of file
diff --git a/theme/GNUSocial/css/default.css b/theme/GNUSocial/css/default.css
new file mode 100644 (file)
index 0000000..e69de29
index b0b4f26098054d7d985560d56ec45aadeb4180d0..b73078295e757b6e5a9ea9bb2b3be9a874dde486 100644 (file)
@@ -2553,6 +2553,11 @@ display:none;
     display:none;
 }
 
+.profile_block_otherprofile_list li {
+    display: inline;
+    list-style-type: none;
+}
+
 /*end of @media screen, projection, tv*/
 
 
diff --git a/theme/gnusocial/css/combo.css b/theme/gnusocial/css/combo.css
new file mode 100644 (file)
index 0000000..3f31a52
--- /dev/null
@@ -0,0 +1,13 @@
+/*
+Copyright (c) 2009, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 2.7.0
+*/
+html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}del,ins{text-decoration:none;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:baseline;}sub{vertical-align:baseline;}legend{color:#000;}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;}input,button,textarea,select{*font-size:100%;}body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}select,input,button,textarea,button{font:99% arial,helvetica,clean,sans-serif;}table{font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;}body{text-align:center;}#doc,#doc2,#doc3,#doc4,.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7{margin:auto;text-align:left;width:57.69em;*width:56.25em;}#doc2{width:73.076em;*width:71.25em;}#doc3{margin:auto 10px;width:auto;}#doc4{width:74.923em;*width:73.05em;}.yui-b{position:relative;}.yui-b{_position:static;}#yui-main .yui-b{position:static;}#yui-main,.yui-g .yui-u .yui-g{width:100%;}.yui-t1 #yui-main,.yui-t2 #yui-main,.yui-t3 #yui-main{float:right;margin-left:-25em;}.yui-t4 #yui-main,.yui-t5 #yui-main,.yui-t6 #yui-main{float:left;margin-right:-25em;}.yui-t1 .yui-b{float:left;width:12.30769em;*width:12.00em;}.yui-t1 #yui-main .yui-b{margin-left:13.30769em;*margin-left:13.05em;}.yui-t2 .yui-b{float:left;width:13.8461em;*width:13.50em;}.yui-t2 #yui-main .yui-b{margin-left:14.8461em;*margin-left:14.55em;}.yui-t3 .yui-b{float:left;width:23.0769em;*width:22.50em;}.yui-t3 #yui-main .yui-b{margin-left:24.0769em;*margin-left:23.62em;}.yui-t4 .yui-b{float:right;width:13.8456em;*width:13.50em;}.yui-t4 #yui-main .yui-b{margin-right:14.8456em;*margin-right:14.55em;}.yui-t5 .yui-b{float:right;width:18.4615em;*width:18.00em;}.yui-t5 #yui-main .yui-b{margin-right:19.4615em;*margin-right:19.125em;}.yui-t6 .yui-b{float:right;width:23.0769em;*width:22.50em;}.yui-t6 #yui-main .yui-b{margin-right:24.0769em;*margin-right:23.62em;}.yui-t7 #yui-main .yui-b{display:block;margin:0 0 1em 0;}#yui-main .yui-b{float:none;width:auto;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf,.yui-gc .yui-u,.yui-gd .yui-g,.yui-g .yui-gc .yui-u,.yui-ge .yui-u,.yui-ge .yui-g,.yui-gf .yui-g,.yui-gf .yui-u{float:right;}.yui-g div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first,.yui-ge div.first,.yui-gf div.first,.yui-g .yui-gc div.first,.yui-g .yui-ge div.first,.yui-gc div.first div.first{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf{width:49.1%;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{width:32%;margin-left:1.99%;}.yui-gb .yui-u{*margin-left:1.9%;*width:31.9%;}.yui-gc div.first,.yui-gd .yui-u{width:66%;}.yui-gd div.first{width:32%;}.yui-ge div.first,.yui-gf .yui-u{width:74.2%;}.yui-ge .yui-u,.yui-gf div.first{width:24%;}.yui-g .yui-gb div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first{margin-left:0;}.yui-g .yui-g .yui-u,.yui-gb .yui-g .yui-u,.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u,.yui-ge .yui-g .yui-u,.yui-gf .yui-g .yui-u{width:49%;*width:48.1%;*margin-left:0;}.yui-g .yui-g .yui-u{width:48.1%;}.yui-g .yui-gb div.first,.yui-gb .yui-gb div.first{*margin-right:0;*width:32%;_width:31.7%;}.yui-g .yui-gc div.first,.yui-gd .yui-g{width:66%;}.yui-gb .yui-g div.first{*margin-right:4%;_margin-right:1.3%;}.yui-gb .yui-gc div.first,.yui-gb .yui-gd div.first{*margin-right:0;}.yui-gb .yui-gb .yui-u,.yui-gb .yui-gc .yui-u{*margin-left:1.8%;_margin-left:4%;}.yui-g .yui-gb .yui-u{_margin-left:1.0%;}.yui-gb .yui-gd .yui-u{*width:66%;_width:61.2%;}.yui-gb .yui-gd div.first{*width:31%;_width:29.5%;}.yui-g .yui-gc .yui-u,.yui-gb .yui-gc .yui-u{width:32%;_float:right;margin-right:0;_margin-left:0;}.yui-gb .yui-gc div.first{width:66%;*float:left;*margin-left:0;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf .yui-u{margin:0;}.yui-gb .yui-gb .yui-u{_margin-left:.7%;}.yui-gb .yui-g div.first,.yui-gb .yui-gb div.first{*margin-left:0;}.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u{*width:48.1%;*margin-left:0;}.yui-gb .yui-gd div.first{width:32%;}.yui-g .yui-gd div.first{_width:29.9%;}.yui-ge .yui-g{width:24%;}.yui-gf .yui-g{width:74.2%;}.yui-gb .yui-ge div.yui-u,.yui-gb .yui-gf div.yui-u{float:right;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf div.first{float:left;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf div.first{*width:24%;_width:20%;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;}.yui-ge div.first .yui-gd .yui-u{width:65%;}.yui-ge div.first .yui-gd div.first{width:32%;}#hd:after,#bd:after,#ft:after,.yui-g:after,.yui-gb:after,.yui-gc:after,.yui-gd:after,.yui-ge:after,.yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;}#hd,#bd,#ft,.yui-g,.yui-gb,.yui-gc,.yui-gd,.yui-ge,.yui-gf{zoom:1;}/*
+Copyright (c) 2009, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 2.7.0
+*/
+body{margin:10px;}h1{font-size:138.5%;}h2{font-size:123.1%;}h3{font-size:108%;}h1,h2,h3{margin:1em 0;}h1,h2,h3,h4,h5,h6,strong,dt{font-weight:bold;}optgroup{font-weight:normal;}abbr,acronym{border-bottom:1px dotted #000;cursor:help;}em{font-style:italic;}del{text-decoration:line-through;}blockquote,ul,ol,dl{margin:1em;}ol,ul,dl{margin-left:2em;}ol li{list-style:decimal outside;}ul li{list-style:disc outside;}dl dd{margin-left:1em;}th,td{border:1px solid #000;padding:.5em;}th{font-weight:bold;text-align:center;}caption{margin-bottom:.5em;text-align:center;}sup{vertical-align:super;}sub{vertical-align:sub;}p,fieldset,table,pre{margin-bottom:1em;}button,input[type="checkbox"],input[type="radio"],input[type="reset"],input[type="submit"]{padding:1px;}
\ No newline at end of file
diff --git a/theme/gnusocial/css/debug.css b/theme/gnusocial/css/debug.css
new file mode 100644 (file)
index 0000000..a77e2bc
--- /dev/null
@@ -0,0 +1,7 @@
+#hd { background-color: orange !important; }
+
+#bd { background-color: red !important; }
+
+#ft { background-color: lime !important; }
+
+#yui-main { background-color: yellow !important; }
\ No newline at end of file
diff --git a/theme/gnusocial/css/display.css b/theme/gnusocial/css/display.css
new file mode 100644 (file)
index 0000000..3f31a52
--- /dev/null
@@ -0,0 +1,13 @@
+/*
+Copyright (c) 2009, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 2.7.0
+*/
+html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}del,ins{text-decoration:none;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:baseline;}sub{vertical-align:baseline;}legend{color:#000;}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;}input,button,textarea,select{*font-size:100%;}body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}select,input,button,textarea,button{font:99% arial,helvetica,clean,sans-serif;}table{font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;}body{text-align:center;}#doc,#doc2,#doc3,#doc4,.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7{margin:auto;text-align:left;width:57.69em;*width:56.25em;}#doc2{width:73.076em;*width:71.25em;}#doc3{margin:auto 10px;width:auto;}#doc4{width:74.923em;*width:73.05em;}.yui-b{position:relative;}.yui-b{_position:static;}#yui-main .yui-b{position:static;}#yui-main,.yui-g .yui-u .yui-g{width:100%;}.yui-t1 #yui-main,.yui-t2 #yui-main,.yui-t3 #yui-main{float:right;margin-left:-25em;}.yui-t4 #yui-main,.yui-t5 #yui-main,.yui-t6 #yui-main{float:left;margin-right:-25em;}.yui-t1 .yui-b{float:left;width:12.30769em;*width:12.00em;}.yui-t1 #yui-main .yui-b{margin-left:13.30769em;*margin-left:13.05em;}.yui-t2 .yui-b{float:left;width:13.8461em;*width:13.50em;}.yui-t2 #yui-main .yui-b{margin-left:14.8461em;*margin-left:14.55em;}.yui-t3 .yui-b{float:left;width:23.0769em;*width:22.50em;}.yui-t3 #yui-main .yui-b{margin-left:24.0769em;*margin-left:23.62em;}.yui-t4 .yui-b{float:right;width:13.8456em;*width:13.50em;}.yui-t4 #yui-main .yui-b{margin-right:14.8456em;*margin-right:14.55em;}.yui-t5 .yui-b{float:right;width:18.4615em;*width:18.00em;}.yui-t5 #yui-main .yui-b{margin-right:19.4615em;*margin-right:19.125em;}.yui-t6 .yui-b{float:right;width:23.0769em;*width:22.50em;}.yui-t6 #yui-main .yui-b{margin-right:24.0769em;*margin-right:23.62em;}.yui-t7 #yui-main .yui-b{display:block;margin:0 0 1em 0;}#yui-main .yui-b{float:none;width:auto;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf,.yui-gc .yui-u,.yui-gd .yui-g,.yui-g .yui-gc .yui-u,.yui-ge .yui-u,.yui-ge .yui-g,.yui-gf .yui-g,.yui-gf .yui-u{float:right;}.yui-g div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first,.yui-ge div.first,.yui-gf div.first,.yui-g .yui-gc div.first,.yui-g .yui-ge div.first,.yui-gc div.first div.first{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf{width:49.1%;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{width:32%;margin-left:1.99%;}.yui-gb .yui-u{*margin-left:1.9%;*width:31.9%;}.yui-gc div.first,.yui-gd .yui-u{width:66%;}.yui-gd div.first{width:32%;}.yui-ge div.first,.yui-gf .yui-u{width:74.2%;}.yui-ge .yui-u,.yui-gf div.first{width:24%;}.yui-g .yui-gb div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first{margin-left:0;}.yui-g .yui-g .yui-u,.yui-gb .yui-g .yui-u,.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u,.yui-ge .yui-g .yui-u,.yui-gf .yui-g .yui-u{width:49%;*width:48.1%;*margin-left:0;}.yui-g .yui-g .yui-u{width:48.1%;}.yui-g .yui-gb div.first,.yui-gb .yui-gb div.first{*margin-right:0;*width:32%;_width:31.7%;}.yui-g .yui-gc div.first,.yui-gd .yui-g{width:66%;}.yui-gb .yui-g div.first{*margin-right:4%;_margin-right:1.3%;}.yui-gb .yui-gc div.first,.yui-gb .yui-gd div.first{*margin-right:0;}.yui-gb .yui-gb .yui-u,.yui-gb .yui-gc .yui-u{*margin-left:1.8%;_margin-left:4%;}.yui-g .yui-gb .yui-u{_margin-left:1.0%;}.yui-gb .yui-gd .yui-u{*width:66%;_width:61.2%;}.yui-gb .yui-gd div.first{*width:31%;_width:29.5%;}.yui-g .yui-gc .yui-u,.yui-gb .yui-gc .yui-u{width:32%;_float:right;margin-right:0;_margin-left:0;}.yui-gb .yui-gc div.first{width:66%;*float:left;*margin-left:0;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf .yui-u{margin:0;}.yui-gb .yui-gb .yui-u{_margin-left:.7%;}.yui-gb .yui-g div.first,.yui-gb .yui-gb div.first{*margin-left:0;}.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u{*width:48.1%;*margin-left:0;}.yui-gb .yui-gd div.first{width:32%;}.yui-g .yui-gd div.first{_width:29.9%;}.yui-ge .yui-g{width:24%;}.yui-gf .yui-g{width:74.2%;}.yui-gb .yui-ge div.yui-u,.yui-gb .yui-gf div.yui-u{float:right;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf div.first{float:left;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf div.first{*width:24%;_width:20%;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;}.yui-ge div.first .yui-gd .yui-u{width:65%;}.yui-ge div.first .yui-gd div.first{width:32%;}#hd:after,#bd:after,#ft:after,.yui-g:after,.yui-gb:after,.yui-gc:after,.yui-gd:after,.yui-ge:after,.yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;}#hd,#bd,#ft,.yui-g,.yui-gb,.yui-gc,.yui-gd,.yui-ge,.yui-gf{zoom:1;}/*
+Copyright (c) 2009, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 2.7.0
+*/
+body{margin:10px;}h1{font-size:138.5%;}h2{font-size:123.1%;}h3{font-size:108%;}h1,h2,h3{margin:1em 0;}h1,h2,h3,h4,h5,h6,strong,dt{font-weight:bold;}optgroup{font-weight:normal;}abbr,acronym{border-bottom:1px dotted #000;cursor:help;}em{font-style:italic;}del{text-decoration:line-through;}blockquote,ul,ol,dl{margin:1em;}ol,ul,dl{margin-left:2em;}ol li{list-style:decimal outside;}ul li{list-style:disc outside;}dl dd{margin-left:1em;}th,td{border:1px solid #000;padding:.5em;}th{font-weight:bold;text-align:center;}caption{margin-bottom:.5em;text-align:center;}sup{vertical-align:super;}sub{vertical-align:sub;}p,fieldset,table,pre{margin-bottom:1em;}button,input[type="checkbox"],input[type="radio"],input[type="reset"],input[type="submit"]{padding:1px;}
\ No newline at end of file
diff --git a/theme/gnusocial/css/social.css b/theme/gnusocial/css/social.css
new file mode 100755 (executable)
index 0000000..320ccfc
--- /dev/null
@@ -0,0 +1,360 @@
+/*
+
+GNU social alpha CSS
+
+Enable this line to debug:
+
+@import url('./debug.css');
+
+*/
+
+/** theme: GNU social (portions from StatusNet base CSS)
+ *
+ * @package   GNU social
+ * @author    Matt Lee <mattl@cnuk.org>
+ * @copyright 2010 Free Software Foundation, Inc
+ * @copyright 2009-2010 StatusNet, Inc.
+ * @license   http://creativecommons.org/licenses/by/3.0/ Creative Commons Attribution 3.0 Unported
+ */
+
+/* stuff we want to hide..... */
+
+legend, #anon_notice, #notices_primary h2, #site_nav_local_views dt, #ft dt, .entity_profile dt, .entity_profile h2, .entity_actions h2, .entity_nickname, .entities .nickname, .anon_notice, .entity_profile, .entity_subscribers { display: none !important; }
+
+#hd a{ color: white !important; }
+
+.entities li { display: inline; list-style: none !important; }
+
+.entity_profile dd { margin-bottom: 1em; }
+
+.notice { background-color: #eee; margin-bottom: 1em; border-top: 1px solid #ddd; padding: 0.4em; }
+
+.entity_fn { font-size: 130%; }
+
+dl, dd { margin: 0 !important; padding: 0 !important;}
+
+#yui-main { margin-bottom: 0 !important; padding-bottom: 0 !important;}
+
+.notices { margin: 0; padding: 0; }
+.notices li { list-style: none; }
+
+#ft { padding-top: 12px;}
+
+#custom-doc {  width:76.23em;*width:74.39em;min-width:991px; margin:auto; text-align:left; }
+
+#yui-main { background-color: white; position: relative; }
+
+#sidebar *, #right-nav * { background: none !important; border: none !important; }
+  
+   html, body{padding: 0; margin: 0;}
+
+   body {background-image: url(/theme/gnusocial/images/bg.png) !important; background-repeat: repeat-x !important; background-color: white;}
+
+   #hd h1 {margin: 0; line-height: 57px; font-size: 14px; font-weight: bold;}
+
+   #hd h1 a{color: #111; text-decoration: none;}
+
+   #hd dt {display: none;}
+
+   #hd ul {padding: 0; margin: 0; line-height: 48px; position: absolute; top: 0; right: 10px; }
+
+   #hd li {display: inline; list-style: none; margin-left: 12px;}
+
+   #hd {height: 40px; position: relative;}
+
+
+   form {margin: 0 auto; width: 70%;}
+
+   table {width: 100%;}
+
+   tr, td{border: 0;}
+
+   .update-text{ font-size: 12px; font-weight: bold;}
+
+   .update-icon{ text-align: center;}
+
+    #stream li{list-style: none; position: relative; margin-top: 12px; }
+
+    #stream dl {position: absolute; top: 0; left: 50px;}
+
+    #stream dd {color: #333; font-size: 80%; padding: 0; margin: 0; margin-top: 6px;}
+
+
+    #social { padding-left: 10px;}
+
+    #sidebar ul{margin: 0; padding: 0;}
+
+    #sidebar li {list-style: none;}
+
+    #sidebar li a{display: block; width: 180px; padding: 4px;}
+
+    #sidebar li a:hover {background-color: #ececec;}
+
+    .selected {background-color: cyan; width: 180px;}
+
+    #right-nav {background-color: #ececec;}
+
+    #right-nav div {padding: 10px;}
+
+    .form_notice { position: relative; top: 0; left: 0; }
+
+
+
+form label.submit {
+display:none;
+}
+
+.form_settings {
+clear:both;
+}
+
+.form_settings fieldset {
+margin-bottom:29px;
+}
+.form_settings input.remove {
+margin-left:11px;
+}
+.form_settings .form_data li {
+width:100%;
+float:left;
+}
+.form_settings .form_data label {
+float:left;
+}
+.form_settings .form_data textarea,
+.form_settings .form_data select,
+.form_settings .form_data input {
+margin-left:11px;
+float:left;
+}
+.form_settings .form_data textarea {
+width:325px;
+}
+
+.form_settings .form_data input.submit {
+margin-left:0;
+}
+
+.form_settings label {
+margin-top:2px;
+width:143px;
+}
+
+.form_actions label {
+display:none;
+}
+.form_guide {
+font-style:italic;
+}
+
+.form_settings #settings_autosubscribe label {
+display:inline;
+font-weight:bold;
+}
+
+#form_settings_profile legend,
+#form_login legend,
+#form_register legend,
+#form_password legend,
+#form_settings_avatar legend,
+#newgroup legend,
+#editgroup legend,
+#form_tag_user legend,
+#form_remote_subscribe legend,
+#form_openid_login legend,
+#form_search legend,
+#form_invite legend,
+#form_notice_delete legend,
+#form_password_recover legend,
+#form_password_change legend {
+display:none;
+}
+
+.form_settings .form_data p.form_guide {
+clear:both;
+margin-left:155px;
+margin-bottom:0;
+}
+
+.form_settings p {
+margin-bottom:11px;
+}
+
+.form_settings input.checkbox {
+margin-top:0;
+margin-left:0;
+}
+.form_settings label.checkbox {
+font-weight:normal;
+margin-top:0;
+margin-right:0;
+margin-left:11px;
+float:left;
+width:90%;
+}
+
+
+#form_login p.form_guide,
+#form_register #settings_rememberme p.form_guide,
+#form_openid_login #settings_rememberme p.form_guide,
+#settings_twitter_remove p.form_guide,
+#form_search ul.form_data #q {
+margin-left:0;
+}
+
+.form_settings .form_note {
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+padding:0 7px;
+}
+
+
+.form_settings input.form_action-primary {
+padding:0;
+}
+.form_settings input.form_action-secondary {
+margin-left:29px;
+}
+
+#form_search .submit {
+margin-left:11px;
+}
+caption {
+font-weight:bold;
+}
+legend {
+font-weight:bold;
+font-size:1.3em;
+}
+input, textarea, select, option {
+padding:4px;
+font-family:sans-serif;
+font-size:1em;
+}
+input, textarea, select {
+border-width:2px;
+border-style: solid;
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+
+input.submit {
+font-weight:bold;
+cursor:pointer;
+}
+textarea {
+overflow:auto;
+}
+option {
+padding-bottom:0;
+}
+fieldset {
+padding:0;
+border:0;
+}
+form ul li {
+list-style-type:none;
+margin:0 0 18px 0;
+}
+form label {
+font-weight:bold;
+}
+input.checkbox {
+position:relative;
+top:2px;
+left:0;
+border:0;
+}
+
+.error,
+.success {
+padding:4px 7px;
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+margin-bottom:18px;
+}
+
+/* #all .notice, #public .notice { padding-bottom: 12px;} */
+
+/* #all .notice .entry-title, #public .notice .entry-title { position: relative;} */
+
+/* #all .notice .entry-title .entry-content, #public .notice .entry-title .entry-content { position: absolute; top: 25px; left: 55px; } */
+
+/* #all .notice .entry-content .timestamp, #public .notice .entry-content .timestamp { color: #666; margin-left: 55px; font-size: 80%; text-decoration: none !important; } */
+
+.notices div.entry-content { font-size: 80%; }
+
+.notices div.entry-content a, .notices div.entry-content abbr{ font-color: #666 !important; text-decoration: none !important; }
+
+abbr { border: 0px !important;  }
+
+#all .entry-title .author .nickname, #public .entry-title .author .nickname { position: absolute; top: 0; left: 55px; font-weight: bold;  }
+
+#showstream #i { position: absolute; top: 0; left: 0; background-color: white; z-index: 100; width: 185px; }
+
+
+.notice-options, .form_favor .submit, .form_repeat .submit { background-color: white; border: 0; display: none !important; }
+
+#form_notice { margin-top: 10px;}
+
+
+/* tagcloud */
+.tag-cloud {
+list-style-type:none;
+text-align:center;
+}
+.aside .tag-cloud {
+font-size:0.8em;
+word-wrap:break-word;
+}
+.tag-cloud li {
+display:inline;
+margin-right:7px;
+line-height:1.25;
+}
+
+.tag-cloud li:before {
+content:'\0009';
+}
+
+.aside .tag-cloud li {
+line-height:1.5;
+}
+.tag-cloud li a {
+text-decoration:none;
+}
+#tagcloud.section dt {
+text-transform:uppercase;
+font-weight:bold;
+}
+.tag-cloud-1 {
+font-size:1em;
+}
+.tag-cloud-2 {
+font-size:1.25em;
+}
+.tag-cloud-3 {
+font-size:1.75em;
+}
+.tag-cloud-4 {
+font-size:2em;
+}
+.tag-cloud-5 {
+font-size:2.25em;
+}
+.tag-cloud-6 {
+font-size:2.75em;
+}
+.tag-cloud-7 {
+font-size:3.25em;
+}
+
+
+
+
+.entity_subscriptions ul:before { content: 'Fan of...'; }
+
+#feedback-button-of-doom { position: fixed; top: 350px; left: 0; }
\ No newline at end of file
diff --git a/theme/gnusocial/default-avatar-mini.png b/theme/gnusocial/default-avatar-mini.png
new file mode 100755 (executable)
index 0000000..4fd8bd9
Binary files /dev/null and b/theme/gnusocial/default-avatar-mini.png differ
diff --git a/theme/gnusocial/default-avatar-profile.png b/theme/gnusocial/default-avatar-profile.png
new file mode 100755 (executable)
index 0000000..eb08571
Binary files /dev/null and b/theme/gnusocial/default-avatar-profile.png differ
diff --git a/theme/gnusocial/default-avatar-stream.png b/theme/gnusocial/default-avatar-stream.png
new file mode 100755 (executable)
index 0000000..926b8a9
Binary files /dev/null and b/theme/gnusocial/default-avatar-stream.png differ
diff --git a/theme/gnusocial/images/bg.png b/theme/gnusocial/images/bg.png
new file mode 100644 (file)
index 0000000..a30fca0
Binary files /dev/null and b/theme/gnusocial/images/bg.png differ
diff --git a/theme/gnusocial/images/fback.png b/theme/gnusocial/images/fback.png
new file mode 100644 (file)
index 0000000..cad0194
Binary files /dev/null and b/theme/gnusocial/images/fback.png differ
diff --git a/theme/gnusocial/images/icons/icon_atom.png b/theme/gnusocial/images/icons/icon_atom.png
new file mode 100755 (executable)
index 0000000..6a001f1
Binary files /dev/null and b/theme/gnusocial/images/icons/icon_atom.png differ
diff --git a/theme/gnusocial/images/icons/icon_disfavourite.gif b/theme/gnusocial/images/icons/icon_disfavourite.gif
new file mode 100755 (executable)
index 0000000..2b02ac8
Binary files /dev/null and b/theme/gnusocial/images/icons/icon_disfavourite.gif differ
diff --git a/theme/gnusocial/images/icons/icon_favourite.gif b/theme/gnusocial/images/icons/icon_favourite.gif
new file mode 100755 (executable)
index 0000000..716ce35
Binary files /dev/null and b/theme/gnusocial/images/icons/icon_favourite.gif differ
diff --git a/theme/gnusocial/images/icons/icon_foaf.gif b/theme/gnusocial/images/icons/icon_foaf.gif
new file mode 100755 (executable)
index 0000000..f8f7844
Binary files /dev/null and b/theme/gnusocial/images/icons/icon_foaf.gif differ
diff --git a/theme/gnusocial/images/icons/icon_processing.gif b/theme/gnusocial/images/icons/icon_processing.gif
new file mode 100755 (executable)
index 0000000..d0bce15
Binary files /dev/null and b/theme/gnusocial/images/icons/icon_processing.gif differ
diff --git a/theme/gnusocial/images/icons/icon_reply.gif b/theme/gnusocial/images/icons/icon_reply.gif
new file mode 100755 (executable)
index 0000000..a4379a7
Binary files /dev/null and b/theme/gnusocial/images/icons/icon_reply.gif differ
diff --git a/theme/gnusocial/images/icons/icon_rss.png b/theme/gnusocial/images/icons/icon_rss.png
new file mode 100755 (executable)
index 0000000..0ccd1ce
Binary files /dev/null and b/theme/gnusocial/images/icons/icon_rss.png differ
diff --git a/theme/gnusocial/images/icons/icon_trash.gif b/theme/gnusocial/images/icons/icon_trash.gif
new file mode 100755 (executable)
index 0000000..916a332
Binary files /dev/null and b/theme/gnusocial/images/icons/icon_trash.gif differ
diff --git a/theme/gnusocial/images/icons/icon_vcard.gif b/theme/gnusocial/images/icons/icon_vcard.gif
new file mode 100755 (executable)
index 0000000..6d52947
Binary files /dev/null and b/theme/gnusocial/images/icons/icon_vcard.gif differ
diff --git a/theme/gnusocial/images/icons/twotone/green/arrow-left.gif b/theme/gnusocial/images/icons/twotone/green/arrow-left.gif
new file mode 100755 (executable)
index 0000000..afed190
Binary files /dev/null and b/theme/gnusocial/images/icons/twotone/green/arrow-left.gif differ
diff --git a/theme/gnusocial/images/icons/twotone/green/arrow-right.gif b/theme/gnusocial/images/icons/twotone/green/arrow-right.gif
new file mode 100755 (executable)
index 0000000..ee1707e
Binary files /dev/null and b/theme/gnusocial/images/icons/twotone/green/arrow-right.gif differ
diff --git a/theme/gnusocial/images/icons/twotone/green/edit.gif b/theme/gnusocial/images/icons/twotone/green/edit.gif
new file mode 100755 (executable)
index 0000000..c746aca
Binary files /dev/null and b/theme/gnusocial/images/icons/twotone/green/edit.gif differ
diff --git a/theme/gnusocial/images/icons/twotone/green/mail.gif b/theme/gnusocial/images/icons/twotone/green/mail.gif
new file mode 100755 (executable)
index 0000000..1084c86
Binary files /dev/null and b/theme/gnusocial/images/icons/twotone/green/mail.gif differ
diff --git a/theme/gnusocial/images/icons/twotone/green/news.gif b/theme/gnusocial/images/icons/twotone/green/news.gif
new file mode 100755 (executable)
index 0000000..712c685
Binary files /dev/null and b/theme/gnusocial/images/icons/twotone/green/news.gif differ
diff --git a/theme/gnusocial/images/icons/twotone/green/quote.gif b/theme/gnusocial/images/icons/twotone/green/quote.gif
new file mode 100755 (executable)
index 0000000..4ba1f0c
Binary files /dev/null and b/theme/gnusocial/images/icons/twotone/green/quote.gif differ
diff --git a/theme/gnusocial/images/icons/twotone/green/shield.gif b/theme/gnusocial/images/icons/twotone/green/shield.gif
new file mode 100755 (executable)
index 0000000..419d5ee
Binary files /dev/null and b/theme/gnusocial/images/icons/twotone/green/shield.gif differ
diff --git a/theme/gnusocial/images/illustrations/illu_arrow-up-01.gif b/theme/gnusocial/images/illustrations/illu_arrow-up-01.gif
new file mode 100755 (executable)
index 0000000..577be18
Binary files /dev/null and b/theme/gnusocial/images/illustrations/illu_arrow-up-01.gif differ
diff --git a/theme/gnusocial/images/illustrations/illu_clouds-01.gif b/theme/gnusocial/images/illustrations/illu_clouds-01.gif
new file mode 100755 (executable)
index 0000000..41cd622
Binary files /dev/null and b/theme/gnusocial/images/illustrations/illu_clouds-01.gif differ
diff --git a/theme/gnusocial/images/illustrations/illu_jcrop.gif b/theme/gnusocial/images/illustrations/illu_jcrop.gif
new file mode 100755 (executable)
index 0000000..72ea7cc
Binary files /dev/null and b/theme/gnusocial/images/illustrations/illu_jcrop.gif differ
diff --git a/theme/gnusocial/images/illustrations/illu_progress_loading-01.gif b/theme/gnusocial/images/illustrations/illu_progress_loading-01.gif
new file mode 100755 (executable)
index 0000000..82290f4
Binary files /dev/null and b/theme/gnusocial/images/illustrations/illu_progress_loading-01.gif differ
diff --git a/theme/gnusocial/images/illustrations/illu_unicorn-01.png b/theme/gnusocial/images/illustrations/illu_unicorn-01.png
new file mode 100755 (executable)
index 0000000..6cb51b2
Binary files /dev/null and b/theme/gnusocial/images/illustrations/illu_unicorn-01.png differ
diff --git a/theme/gnusocial/index.html b/theme/gnusocial/index.html
new file mode 100644 (file)
index 0000000..c1a3b7a
--- /dev/null
@@ -0,0 +1,213 @@
+<!DOCTYPE html>
+       <head>
+               <title>Public timeline - Lorraine Lee &mdash; GNU social</title>
+
+
+   <link rel="stylesheet" href="/theme/gnusocial/combo.css" type="text/css">
+   <style type="text/css">
+   #custom-doc { width:76.23em;*width:74.39em;min-width:991px; margin:auto; text-align:left; }
+  
+   html, body{padding: 0; margin: 0;}
+
+   body {background-image: url(/theme/gnusocial/bg.png); background-repeat: repeat-x;}
+
+   #hd h1 {margin: 0; line-height: 48px; font-size: 30px; font-weight: bold;}
+
+   #hd h1 a{color: #111; text-decoration: none;}
+
+   #hd dt {display: none;}
+
+   #hd ul {padding: 0; margin: 0; line-height: 48px; position: absolute; top: 0; right: 10px; }
+
+   #hd li {display: inline; list-style: none; margin-left: 12px;}
+
+   #hd {height: 48px; position: relative;}
+
+   #bd {margin-top: 12px;}
+
+   #ft {font-size: 10px; text-align: right; margin-top: 12px}
+
+   form {margin: 0 auto; width: 70%;}
+
+   table {width: 100%;}
+
+   tr, td{border: 0;}
+
+   .update-text{ font-size: 12px; font-weight: bold;}
+
+   .update-icon{ text-align: center;}
+
+    #stream li{list-style: none; position: relative; margin-top: 12px; }
+
+    #stream dl {position: absolute; top: 0; left: 50px;}
+
+    #stream dd {color: #333; font-size: 80%; padding: 0; margin: 0; margin-top: 6px;}
+
+
+    #social {border-left: 1px solid #999; border-right: 1px solid #999; padding-left: 10px;}
+
+    #sidebar ul{margin: 0; padding: 0;}
+
+    #sidebar li {list-style: none;}
+
+    #sidebar li a{display: block; width: 180px; padding: 4px;}
+
+    #sidebar li a:hover {background-color: #ececec;}
+
+    .selected {background-color: cyan; width: 180px;}
+
+    #right-nav {background-color: #ececec;}
+
+    #right-nav div {padding: 10px;}
+
+   </style>
+
+
+                                                                                               </head>
+       <body id="public">
+<div id="custom-doc" class="yui-t2">
+   <div id="hd">
+   <h1>GNU social</h1>                 
+
+                               <dl id="site_nav_global_primary">
+ <dt>Primary site navigation</dt>
+ <dd>
+  <ul class="nav">
+   <li id="nav_login">
+    <a href="http://lorrainelee.co.uk/main/login" title="Login to the site">Login</a>
+</li>
+   <li id="nav_help">
+    <a href="http://lorrainelee.co.uk/doc/help" title="Help me!">Help</a>
+</li>
+   <li id="nav_search">
+    <a href="http://lorrainelee.co.uk/search/people" title="Search for people or text">Search</a>
+</li>
+</ul>
+</dd>
+</dl>
+                       </div>
+                       <div id="bd">
+
+                       <div id="yui-main">
+
+                       <div class="yui-b">
+
+                       <div class="yui-gc">
+
+                       <div class="yui-u first">
+
+                               <dl id="site_notice" class="system_notice">
+ <dt>Site notice</dt>
+ <dd>Powered by <a href="http://www.gnu.org/software/social/">GNU social</a></dd>
+</dl>
+                               <div id="anon_notice"><p>This is Lorraine Lee, a <a href="http://en.wikipedia.org/wiki/Micro-blogging">micro-blogging</a> service based on the Free Software <a href="http://status.net/">StatusNet</a> tool.</p>
+</div>
+                               <div id="content">
+ <h1>Public timeline</h1>
+ <div id="content_inner">
+  <div id="notices_primary">
+   <h2>Notices</h2>
+   <ol class="notices xoxo">
+    <li class="hentry notice" id="notice-5">
+     <div class="entry-title">
+      <span class="vcard author">
+       <a href="http://lorrainelee.co.uk/lorraine" class="url" title="Lorraine Lee (lorraine)">
+        <img src="http://lorrainelee.co.uk/avatar/3-48-20100722212232.jpeg" class="avatar photo" width="48" height="48" alt="Lorraine Lee"/>
+ <span class="nickname fn">lorraine</span></a>
+</span>
+      <p class="entry-content">im going to brush my teeth</p>
+</div>
+     <div class="entry-content">
+      <a rel="bookmark" class="timestamp" href="http://lorrainelee.co.uk/notice/5">
+       <abbr class="published" title="2010-07-22T21:41:40+00:00">about 5 hours ago</abbr>
+</a>
+       <span class="source">from        <span class="device">web</span>
+</span>
+       <span class="location">at <a href="http://www.geonames.org/2651292" rel="external"><abbr class="geo" title="50.7500000;-3.7500000">County of Devon, England, United Kingdom of Great Britain and Northern Ireland</abbr></a></span>
+</div>
+</li>
+    <li class="hentry notice" id="notice-2">
+     <div class="entry-title">
+      <span class="vcard author">
+       <a href="http://lorrainelee.co.uk/lorraine" class="url" title="Lorraine Lee (lorraine)">
+        <img src="http://lorrainelee.co.uk/avatar/3-48-20100722212232.jpeg" class="avatar photo" width="48" height="48" alt="Lorraine Lee"/>
+ <span class="nickname fn">lorraine</span></a>
+</span>
+      <p class="entry-content">nothing  im fine thank you</p>
+</div>
+     <div class="entry-content">
+      <a rel="bookmark" class="timestamp" href="http://lorrainelee.co.uk/notice/2">
+       <abbr class="published" title="2010-07-22T21:38:18+00:00">about 5 hours ago</abbr>
+</a>
+       <span class="source">from        <span class="device">web</span>
+</span>
+       <span class="location">at <a href="http://www.geonames.org/2651292" rel="external"><abbr class="geo" title="50.7500000;-3.7500000">County of Devon, England, United Kingdom of Great Britain and Northern Ireland</abbr></a></span>
+       <a href="http://lorrainelee.co.uk/conversation/2#notice-2" class="response">in context</a>
+</div>
+</li>
+</ol>
+</div>
+</div>
+</div>
+
+                               </div>
+
+
+    <div class="yui-u" id="right-nav">
+
+
+
+
+
+                               <div id="aside_primary" class="aside">
+                                       <div id="featured_users" class="section">
+ <h2>Featured users</h2>
+</div>
+                                       </div>
+
+
+ </div>
+</div>
+ </div>
+</div>
+
+       <div class="yui-b" id="sidebar">
+                               <dl id="site_nav_local_views">
+ <dt>Local views</dt>
+ <dd>
+  <ul class="nav">
+   <li class="current" id="nav_timeline_public">
+    <a href="http://lorrainelee.co.uk/" title="Public timeline">Public</a>
+</li>
+   <li id="nav_groups">
+    <a href="http://lorrainelee.co.uk/group" title="User groups">Groups</a>
+</li>
+   <li id="nav_recent-tags">
+    <a href="http://lorrainelee.co.uk/tags" title="Recent tags">Recent tags</a>
+</li>
+   <li id="nav_timeline_favorited">
+    <a href="http://lorrainelee.co.uk/favorited" title="Popular notices">Popular</a>
+</li>
+</ul>
+</dd>
+</dl>
+       </div>
+
+                               </div>
+                       <div id="ft">
+
+                               <dl id="licenses">
+ <dt id="site_statusnet_license">StatusNet software licence</dt>
+ <dd><p><strong>Lorraine Lee</strong> is a microblogging service brought to you by <a href="http://www.gnu.org/s/social/">GNU social</a>. It runs the <a href="http://status.net/">StatusNet</a> microblogging software, version 0.9.3, available under the <a href="http://www.fsf.org/licensing/licenses/agpl-3.0.html">GNU Affero General Public Licence</a>.</p>
+</dd>
+ <dt id="site_content_license">Site content license</dt>
+ <dd id="site_content_license_cc">
+  <p>
+   <img id="license_cc" src="http://i.creativecommons.org/l/by/3.0/80x15.png" alt="Creative Commons Attribution 3.0" width="80" height="15"/>
+ All Lorraine Lee content and data are available under the <a class="license" rel="external license" href="http://creativecommons.org/licenses/by/3.0/">Creative Commons Attribution 3.0</a> licence.</p>
+</dd>
+</dl>
+                       </div>
+                       </div>
+               </body>
+       </html>
diff --git a/theme/gnusocial/logo.png b/theme/gnusocial/logo.png
new file mode 100755 (executable)
index 0000000..cf18391
Binary files /dev/null and b/theme/gnusocial/logo.png differ
diff --git a/tpl/social.php b/tpl/social.php
new file mode 100644 (file)
index 0000000..a76e008
--- /dev/null
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title><?php echo section('title'); ?> &mdash; GNU social</title>
+
+
+   <link rel="stylesheet" href="/theme/gnusocial/css/combo.css" type="text/css">
+   <link rel="stylesheet" href="/theme/gnusocial/css/social.css" type="text/css">
+        <?php echo section('scripts'); ?>
+        <?php echo section('search'); ?>
+        <?php echo section('feeds'); ?>
+        <?php echo section('description'); ?>
+        <?php echo section('head'); ?>
+        </head>
+    <body id="<?php echo section('action'); ?>">
+
+       <div id="feedback-button-of-doom"><a href="http://social.foocorp.net/shapado.html"><img src="/theme/gnusocial/images/fback.png" title="Send us your ideas and suggestions" alt="Feedback" /></a></div>
+
+
+        <div id="doc2" class="yui-t6">
+           <div id="hd">
+                <h1><a href="/">GNU social</a></h1>            
+                <?php echo section('nav'); ?>
+            </div>
+            <div id="bd">
+                <div id="yui-main">
+                    <div class="yui-b" id="social">
+                        <div class="yui-g">
+                                <?php echo section('noticeform'); ?>
+                                <?php echo section('bodytext'); ?>
+                            </div>
+</div>
+</div>
+
+
+                            <div class="yui-b" id="right-nav">
+                                <div id="aside_primary" class="aside">
+                                    <?php echo section('subscriptions'); ?>
+                                    <?php echo section('subscribers'); ?>
+                                    <?php echo section('groups'); ?>
+                                    <?php echo section('cloud'); ?>
+                                    <?php echo section('popular'); ?>
+                                    <?php echo section('localnav'); ?>
+                                </div>
+                            </div>
+            <div id="ft">
+             <p>This is <a href="http://www.gnu.org/software/social">GNU social</a> &mdash; licensed under the <a href="http://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License</a> version 3.0 or later. <a href="http://gitorious.org/+socialites/statusnet/gnu-social">Get the code</a>.</p>
+            </div>
+        </div>
+    </body>
+</html>