]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - classes/Notice.php
Ticket #1568 - API should return full user objects
[quix0rs-gnu-social.git] / classes / Notice.php
index 907239b084bf43027c1d95d8f8016d66c886ffc3..3f6c5cebbeacf1451c45aaa291e2ef2917326c33 100644 (file)
@@ -67,6 +67,8 @@ class Notice extends Memcached_DataObject
         $this->blowSubsCache(true);
 
         $this->query('BEGIN');
+        //Null any notices that are replies to this notice
+        $this->query(sprintf("UPDATE notice set reply_to = null WHERE reply_to = %d", $this->id));
         $related = array('Reply',
                          'Fave',
                          'Notice_tag',
@@ -121,6 +123,8 @@ class Notice extends Memcached_DataObject
 
         $profile = Profile::staticGet($profile_id);
 
+        $final =  common_shorten_links($content);
+
         if (!$profile) {
             common_log(LOG_ERR, 'Problem saving notice. Unknown user.');
             return _('Problem saving notice. Unknown user.');
@@ -131,7 +135,12 @@ class Notice extends Memcached_DataObject
             return _('Too many notices too fast; take a breather and post again in a few minutes.');
         }
 
-        $banned = common_config('profile', 'banned');
+        if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $final)) {
+            common_log(LOG_WARNING, 'Dupe posting by profile #' . $profile_id . '; throttled.');
+                       return _('Too many duplicate messages too quickly; take a breather and post again in a few minutes.');
+        }
+
+               $banned = common_config('profile', 'banned');
 
         if ( in_array($profile_id, $banned) || in_array($profile->nickname, $banned)) {
             common_log(LOG_WARNING, "Attempted post from banned user: $profile->nickname (user id = $profile_id).");
@@ -155,12 +164,12 @@ class Notice extends Memcached_DataObject
 
                $notice->query('BEGIN');
 
-        $notice->reply_to = $reply_to;
-        $notice->created = common_sql_now();
-        $notice->content = common_shorten_links($content);
-        $notice->rendered = common_render_content($notice->content, $notice);
-        $notice->source = $source;
-        $notice->uri = $uri;
+               $notice->reply_to = $reply_to;
+               $notice->created = common_sql_now();
+               $notice->content = $final;
+               $notice->rendered = common_render_content($final, $notice);
+               $notice->source = $source;
+               $notice->uri = $uri;
 
         if (Event::handle('StartNoticeSave', array(&$notice))) {
 
@@ -186,9 +195,14 @@ class Notice extends Memcached_DataObject
 
             $notice->saveReplies();
             $notice->saveTags();
-            $notice->saveGroups();
 
-            $notice->addToInboxes();
+            if (common_config('queue', 'enabled')) {
+                $notice->addToAuthorInbox();
+            } else {
+                $notice->addToInboxes();
+                $notice->saveGroups();
+            }
+
             $notice->query('COMMIT');
 
             Event::handle('EndNoticeSave', array($notice));
@@ -198,12 +212,46 @@ class Notice extends Memcached_DataObject
         # XXX: someone clever could prepend instead of clearing the cache
 
         if (common_config('memcached', 'enabled')) {
-            $notice->blowCaches();
+            if (common_config('queue', 'enabled')) {
+                $notice->blowAuthorCaches();
+            } else {
+                $notice->blowCaches();
+            }
         }
 
         return $notice;
     }
 
+    static function checkDupes($profile_id, $content) {
+        $profile = Profile::staticGet($profile_id);
+        if (!$profile) {
+            return false;
+        }
+        $notice = $profile->getNotices(0, NOTICE_CACHE_WINDOW);
+        if ($notice) {
+            $last = 0;
+            while ($notice->fetch()) {
+                if (time() - strtotime($notice->created) >= common_config('site', 'dupelimit')) {
+                    return true;
+                } else if ($notice->content == $content) {
+                    return false;
+                }
+            }
+        }
+        # If we get here, oldest item in cache window is not
+        # old enough for dupe limit; do direct check against DB
+        $notice = new Notice();
+        $notice->profile_id = $profile_id;
+        $notice->content = $content;
+        if (common_config('db','type') == 'pgsql')
+            $notice->whereAdd('extract(epoch from now() - created) < ' . common_config('site', 'dupelimit'));
+        else
+            $notice->whereAdd('now() - created < ' . common_config('site', 'dupelimit'));
+
+        $cnt = $notice->count();
+        return ($cnt == 0);
+    }
+
     static function checkEditThrottle($profile_id) {
         $profile = Profile::staticGet($profile_id);
         if (!$profile) {
@@ -232,6 +280,17 @@ class Notice extends Memcached_DataObject
         $this->blowGroupCache($blowLast);
     }
 
+    function blowAuthorCaches($blowLast=false)
+    {
+        // Clear the user's cache
+        $cache = common_memcache();
+        if (!empty($cache)) {
+            $cache->delete(common_cache_key('notice_inbox:by_user:'.$this->profile_id));
+        }
+        $this->blowNoticeCache($blowLast);
+        $this->blowPublicCache($blowLast);
+    }
+
     function blowGroupCache($blowLast=false)
     {
         $cache = common_memcache();
@@ -240,17 +299,17 @@ class Notice extends Memcached_DataObject
             $group_inbox->notice_id = $this->id;
             if ($group_inbox->find()) {
                 while ($group_inbox->fetch()) {
-                    $cache->delete(common_cache_key('group:notices:'.$group_inbox->group_id));
+                    $cache->delete(common_cache_key('user_group:notice_ids:' . $group_inbox->group_id));
                     if ($blowLast) {
-                        $cache->delete(common_cache_key('group:notices:'.$group_inbox->group_id.';last'));
+                        $cache->delete(common_cache_key('user_group:notice_ids:' . $group_inbox->group_id.';last'));
                     }
                     $member = new Group_member();
                     $member->group_id = $group_inbox->group_id;
                     if ($member->find()) {
                         while ($member->fetch()) {
-                            $cache->delete(common_cache_key('user:notices_with_friends:' . $member->profile_id));
+                            $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id));
                             if ($blowLast) {
-                                $cache->delete(common_cache_key('user:notices_with_friends:' . $member->profile_id . ';last'));
+                                $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id . ';last'));
                             }
                         }
                     }
@@ -269,10 +328,7 @@ class Notice extends Memcached_DataObject
             $tag->notice_id = $this->id;
             if ($tag->find()) {
                 while ($tag->fetch()) {
-                    $cache->delete(common_cache_key('notice_tag:notice_stream:' . $tag->tag));
-                    if ($blowLast) {
-                        $cache->delete(common_cache_key('notice_tag:notice_stream:' . $tag->tag . ';last'));
-                    }
+                    $tag->blowCache($blowLast);
                 }
             }
             $tag->free();
@@ -293,9 +349,9 @@ class Notice extends Memcached_DataObject
                          'WHERE subscription.subscribed = ' . $this->profile_id);
 
             while ($user->fetch()) {
-                $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
+                $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id));
                 if ($blowLast) {
-                    $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id . ';last'));
+                    $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id.';last'));
                 }
             }
             $user->free();
@@ -307,10 +363,10 @@ class Notice extends Memcached_DataObject
     {
         if ($this->is_local) {
             $cache = common_memcache();
-            if ($cache) {
-                $cache->delete(common_cache_key('profile:notices:'.$this->profile_id));
+            if (!empty($cache)) {
+                $cache->delete(common_cache_key('profile:notice_ids:'.$this->profile_id));
                 if ($blowLast) {
-                    $cache->delete(common_cache_key('profile:notices:'.$this->profile_id.';last'));
+                    $cache->delete(common_cache_key('profile:notice_ids:'.$this->profile_id.';last'));
                 }
             }
         }
@@ -324,9 +380,9 @@ class Notice extends Memcached_DataObject
             $reply->notice_id = $this->id;
             if ($reply->find()) {
                 while ($reply->fetch()) {
-                    $cache->delete(common_cache_key('user:replies:'.$reply->profile_id));
+                    $cache->delete(common_cache_key('reply:stream:'.$reply->profile_id));
                     if ($blowLast) {
-                        $cache->delete(common_cache_key('user:replies:'.$reply->profile_id.';last'));
+                        $cache->delete(common_cache_key('reply:stream:'.$reply->profile_id.';last'));
                     }
                 }
             }
@@ -356,9 +412,9 @@ class Notice extends Memcached_DataObject
             $fave->notice_id = $this->id;
             if ($fave->find()) {
                 while ($fave->fetch()) {
-                    $cache->delete(common_cache_key('user:faves:'.$fave->user_id));
+                    $cache->delete(common_cache_key('fave:ids_by_user:'.$fave->user_id));
                     if ($blowLast) {
-                        $cache->delete(common_cache_key('user:faves:'.$fave->user_id.';last'));
+                        $cache->delete(common_cache_key('fave:ids_by_user:'.$fave->user_id.';last'));
                     }
                 }
             }
@@ -370,22 +426,22 @@ class Notice extends Memcached_DataObject
     # XXX: too many args; we need to move to named params or even a separate
     # class for notice streams
 
-    static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $order=null, $since=null) {
+    static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $order=null, $since=null) {
 
         if (common_config('memcached', 'enabled')) {
 
-            # Skip the cache if this is a since, since_id or before_id qry
-            if ($since_id > 0 || $before_id > 0 || $since) {
-                return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since);
+            # Skip the cache if this is a since, since_id or max_id qry
+            if ($since_id > 0 || $max_id > 0 || $since) {
+                return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since);
             } else {
                 return Notice::getCachedStream($qry, $cachekey, $offset, $limit, $order);
             }
         }
 
-        return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since);
+        return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since);
     }
 
-    static function getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since) {
+    static function getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since) {
 
         $needAnd = false;
         $needWhere = true;
@@ -407,7 +463,7 @@ class Notice extends Memcached_DataObject
             $qry .= ' notice.id > ' . $since_id;
         }
 
-        if ($before_id > 0) {
+        if ($max_id > 0) {
 
             if ($needWhere) {
                 $qry .= ' WHERE ';
@@ -416,7 +472,7 @@ class Notice extends Memcached_DataObject
                 $qry .= ' AND ';
             }
 
-            $qry .= ' notice.id < ' . $before_id;
+            $qry .= ' notice.id <= ' . $max_id;
         }
 
         if ($since) {
@@ -554,27 +610,80 @@ class Notice extends Memcached_DataObject
         return $wrapper;
     }
 
-    function publicStream($offset=0, $limit=20, $since_id=0, $before_id=0, $since=null)
+    function getStreamByIds($ids)
+    {
+        $cache = common_memcache();
+
+        if (!empty($cache)) {
+            $notices = array();
+            foreach ($ids as $id) {
+                $notices[] = Notice::staticGet('id', $id);
+            }
+            return new ArrayWrapper($notices);
+        } else {
+            $notice = new Notice();
+            $notice->whereAdd('id in (' . implode(', ', $ids) . ')');
+            $notice->orderBy('id DESC');
+
+            $notice->find();
+            return $notice;
+        }
+    }
+
+    function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
     {
+        $ids = Notice::stream(array('Notice', '_publicStreamDirect'),
+                              array(),
+                              'public',
+                              $offset, $limit, $since_id, $max_id, $since);
+
+        return Notice::getStreamByIds($ids);
+    }
+
+    function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
+    {
+        $notice = new Notice();
 
-        $parts = array();
+        $notice->selectAdd(); // clears it
+        $notice->selectAdd('id');
 
-        $qry = 'SELECT * FROM notice ';
+        $notice->orderBy('id DESC');
+
+        if (!is_null($offset)) {
+            $notice->limit($offset, $limit);
+        }
 
         if (common_config('public', 'localonly')) {
-            $parts[] = 'is_local = 1';
+            $notice->whereAdd('is_local = 1');
         } else {
             # -1 == blacklisted
-            $parts[] = 'is_local != -1';
+            $notice->whereAdd('is_local != -1');
         }
 
-        if ($parts) {
-            $qry .= ' WHERE ' . implode(' AND ', $parts);
+        if ($since_id != 0) {
+            $notice->whereAdd('id > ' . $since_id);
         }
 
-        return Notice::getStream($qry,
-                                 'public',
-                                 $offset, $limit, $since_id, $before_id, null, $since);
+        if ($max_id != 0) {
+            $notice->whereAdd('id <= ' . $max_id);
+        }
+
+        if (!is_null($since)) {
+            $notice->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\'');
+        }
+
+        $ids = array();
+
+        if ($notice->find()) {
+            while ($notice->fetch()) {
+                $ids[] = $notice->id;
+            }
+        }
+
+        $notice->free();
+        $notice = NULL;
+
+        return $ids;
     }
 
     function addToInboxes()
@@ -600,6 +709,33 @@ class Notice extends Memcached_DataObject
         return;
     }
 
+    function addToAuthorInbox()
+    {
+        $enabled = common_config('inboxes', 'enabled');
+
+        if ($enabled === true || $enabled === 'transitional') {
+            $user = User::staticGet('id', $this->profile_id);
+            if (empty($user)) {
+                return;
+            }
+            $inbox = new Notice_inbox();
+            $UT = common_config('db','type')=='pgsql'?'"user"':'user';
+            $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created) ' .
+              "SELECT $UT.id, " . $this->id . ", '" . $this->created . "' " .
+              "FROM $UT " .
+              "WHERE $UT.id = " . $this->profile_id . ' ' .
+              'AND NOT EXISTS (SELECT user_id, notice_id ' .
+              'FROM notice_inbox ' .
+              "WHERE user_id = " . $this->profile_id . ' '.
+              'AND notice_id = ' . $this->id . ' )';
+            if ($enabled === 'transitional') {
+                $qry .= " AND $UT.inboxed = 1";
+            }
+            $inbox->query($qry);
+        }
+        return;
+    }
+
     function saveGroups()
     {
         $enabled = common_config('inboxes', 'enabled');
@@ -652,24 +788,29 @@ class Notice extends Memcached_DataObject
 
                 // FIXME: do this in an offline daemon
 
-                $inbox = new Notice_inbox();
-                $UT = common_config('db','type')=='pgsql'?'"user"':'user';
-                $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created, source) ' .
-                  "SELECT $UT.id, " . $this->id . ", '" . $this->created . "', 2 " .
-                  "FROM $UT JOIN group_member ON $UT.id = group_member.profile_id " .
-                  'WHERE group_member.group_id = ' . $group->id . ' ' .
-                  'AND NOT EXISTS (SELECT user_id, notice_id ' .
-                  'FROM notice_inbox ' .
-                  "WHERE user_id = $UT.id " .
-                  'AND notice_id = ' . $this->id . ' )';
-                if ($enabled === 'transitional') {
-                    $qry .= " AND $UT.inboxed = 1";
-                }
-                $result = $inbox->query($qry);
+                $this->addToGroupInboxes($group);
             }
         }
     }
 
+    function addToGroupInboxes($group)
+    {
+        $inbox = new Notice_inbox();
+        $UT = common_config('db','type')=='pgsql'?'"user"':'user';
+        $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created, source) ' .
+          "SELECT $UT.id, " . $this->id . ", '" . $this->created . "', 2 " .
+          "FROM $UT JOIN group_member ON $UT.id = group_member.profile_id " .
+          'WHERE group_member.group_id = ' . $group->id . ' ' .
+          'AND NOT EXISTS (SELECT user_id, notice_id ' .
+          'FROM notice_inbox ' .
+          "WHERE user_id = $UT.id " .
+          'AND notice_id = ' . $this->id . ' )';
+        if ($enabled === 'transitional') {
+            $qry .= " AND $UT.inboxed = 1";
+        }
+        $result = $inbox->query($qry);
+    }
+
     function saveReplies()
     {
         // Alternative reply format
@@ -762,4 +903,153 @@ class Notice extends Memcached_DataObject
             }
         }
     }
+
+    function asAtomEntry($namespace=false, $source=false)
+    {
+        $profile = $this->getProfile();
+
+        $xs = new XMLStringer(true);
+
+        if ($namespace) {
+            $attrs = array('xmlns' => 'http://www.w3.org/2005/Atom',
+                           'xmlns:thr' => 'http://purl.org/syndication/thread/1.0');
+        } else {
+            $attrs = array();
+        }
+
+        $xs->elementStart('entry', $attrs);
+
+        if ($source) {
+            $xs->elementStart('source');
+            $xs->element('title', null, $profile->nickname . " - " . common_config('site', 'name'));
+            $xs->element('link', array('href' => $profile->profileurl));
+            $user = User::staticGet('id', $profile->id);
+            if (!empty($user)) {
+                $atom_feed = common_local_url('api',
+                                              array('apiaction' => 'statuses',
+                                                    'method' => 'user_timeline',
+                                                    'argument' => $profile->nickname.'.atom'));
+                $xs->element('link', array('rel' => 'self',
+                                           'type' => 'application/atom+xml',
+                                           'href' => $profile->profileurl));
+                $xs->element('link', array('rel' => 'license',
+                                           'href' => common_config('license', 'url')));
+            }
+
+            $xs->element('icon', null, $profile->avatarUrl(AVATAR_PROFILE_SIZE));
+        }
+
+        $xs->elementStart('author');
+        $xs->element('name', null, $profile->nickname);
+        $xs->element('uri', null, $profile->profileurl);
+        $xs->elementEnd('author');
+
+        if ($source) {
+            $xs->elementEnd('source');
+        }
+
+        $xs->element('title', null, $this->content);
+        $xs->element('summary', null, $this->content);
+
+        $xs->element('link', array('rel' => 'alternate',
+                                   'href' => $this->bestUrl()));
+
+        $xs->element('id', null, $this->uri);
+
+        $xs->element('published', null, common_date_w3dtf($this->created));
+        $xs->element('updated', null, common_date_w3dtf($this->modified));
+
+        if ($this->reply_to) {
+            $reply_notice = Notice::staticGet('id', $this->reply_to);
+            if (!empty($reply_notice)) {
+                $xs->element('link', array('rel' => 'related',
+                                           'href' => $reply_notice->bestUrl()));
+                $xs->element('thr:in-reply-to',
+                             array('ref' => $reply_notice->uri,
+                                   'href' => $reply_notice->bestUrl()));
+            }
+        }
+
+        $xs->element('content', array('type' => 'html'), $this->rendered);
+
+        $tag = new Notice_tag();
+        $tag->notice_id = $this->id;
+        if ($tag->find()) {
+            while ($tag->fetch()) {
+                $xs->element('category', array('term' => $tag->tag));
+            }
+        }
+        $tag->free();
+
+        $xs->elementEnd('entry');
+
+        return $xs->getString();
+    }
+
+    function bestUrl()
+    {
+        if (!empty($this->url)) {
+            return $this->url;
+        } else if (!empty($this->uri) && preg_match('/^https?:/', $this->uri)) {
+            return $this->uri;
+        } else {
+            return common_local_url('shownotice',
+                                    array('notice' => $this->id));
+        }
+    }
+
+    function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
+    {
+        $cache = common_memcache();
+
+        if (empty($cache) ||
+            $since_id != 0 || $max_id != 0 || !is_null($since) ||
+            ($offset + $limit) > NOTICE_CACHE_WINDOW) {
+            return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id,
+                                                                      $max_id, $since)));
+        }
+
+        $idkey = common_cache_key($cachekey);
+
+        $idstr = $cache->get($idkey);
+
+        if (!empty($idstr)) {
+            // Cache hit! Woohoo!
+            $window = explode(',', $idstr);
+            $ids = array_slice($window, $offset, $limit);
+            return $ids;
+        }
+
+        $laststr = $cache->get($idkey.';last');
+
+        if (!empty($laststr)) {
+            $window = explode(',', $laststr);
+            $last_id = $window[0];
+            $new_ids = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
+                                                                          $last_id, 0, null)));
+
+            $new_window = array_merge($new_ids, $window);
+
+            $new_windowstr = implode(',', $new_window);
+
+            $result = $cache->set($idkey, $new_windowstr);
+            $result = $cache->set($idkey . ';last', $new_windowstr);
+
+            $ids = array_slice($new_window, $offset, $limit);
+
+            return $ids;
+        }
+
+        $window = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
+                                                                     0, 0, null)));
+
+        $windowstr = implode(',', $window);
+
+        $result = $cache->set($idkey, $windowstr);
+        $result = $cache->set($idkey . ';last', $windowstr);
+
+        $ids = array_slice($window, $offset, $limit);
+
+        return $ids;
+    }
 }