]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge 1.1.x into master
authorEvan Prodromou <evan@e14n.com>
Tue, 16 Jul 2013 17:57:06 +0000 (10:57 -0700)
committerEvan Prodromou <evan@e14n.com>
Tue, 16 Jul 2013 17:57:06 +0000 (10:57 -0700)
41 files changed:
EVENTS.txt
actions/apisearchjson.php
actions/groupmembers.php
classes/Group_member.php
classes/Message.php
classes/Notice.php
classes/Profile.php
classes/Subscription.php
classes/User_group.php
lib/accountprofileblock.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
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/ModLog/ModLog.php [new file with mode: 0644]
plugins/ModLog/ModLogPlugin.php [new file with mode: 0644]
plugins/OStatus/actions/ostatusgroup.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/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/createsim.php
scripts/install_cli.php
theme/base/css/display.css

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
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 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 540699eb3afdb692de6f1dfd5e3d5177061a8660..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));
         }
     }
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 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()
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 5074b6d205cdff4e2102f554cfe0f561170b2540..7546e2cd432c2fc6f8f82c6ab0402d3cab8e95ef 100644 (file)
@@ -389,7 +389,8 @@ 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->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..415530ad58f317312065b1576f3d48e1a9714421 100644 (file)
@@ -20,7 +20,7 @@
 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_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
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,
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/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 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(),
+                     );
+    }
+}
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 1b8e55e9b78d30c7ddc5ff04f378107676651d3e..cae0dbfcac889144c14abef0fcff31c496c983f5 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 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.
 
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*/