]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - classes/Notice.php
Merge branch 'master' into 0.9.x
[quix0rs-gnu-social.git] / classes / Notice.php
index d65f0da9a9247a0af8df42e5cdc7460aac0b95d2..12467c8501379fe0fb991a11cf2837b1db5a51ee 100644 (file)
@@ -29,6 +29,7 @@
  * @author   Robin Millette <millette@controlyourself.ca>
  * @author   Sarven Capadisli <csarven@controlyourself.ca>
  * @author   Tom Adams <tom@holizz.com>
+ * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
  * @license  GNU Affero General Public License http://www.gnu.org/licenses/
  */
 
@@ -41,10 +42,10 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
  */
 require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
 
-/* We keep the first three 20-notice pages, plus one for pagination check,
+/* We keep 200 notices, the max number of notices available per API request,
  * in the memcached cache. */
 
-define('NOTICE_CACHE_WINDOW', 61);
+define('NOTICE_CACHE_WINDOW', 200);
 
 define('MAX_BOXCARS', 128);
 
@@ -89,7 +90,15 @@ class Notice extends Memcached_DataObject
 
     function getProfile()
     {
-        return Profile::staticGet('id', $this->profile_id);
+        $profile = Profile::staticGet('id', $this->profile_id);
+
+        if (empty($profile)) {
+            // TRANS: Server exception thrown when a user profile for a notice cannot be found.
+            // TRANS: %1$d is a profile ID (number), %2$d is a notice ID (number).
+            throw new ServerException(sprintf(_('No such profile (%1$d) for notice (%2$d).'), $this->profile_id, $this->id));
+        }
+
+        return $profile;
     }
 
     function delete()
@@ -97,15 +106,20 @@ class Notice extends Memcached_DataObject
         // For auditing purposes, save a record that the notice
         // was deleted.
 
-        $deleted = new Deleted_notice();
+        // @fixme we have some cases where things get re-run and so the
+        // insert fails.
+        $deleted = Deleted_notice::staticGet('id', $this->id);
+        if (!$deleted) {
+            $deleted = new Deleted_notice();
 
-        $deleted->id         = $this->id;
-        $deleted->profile_id = $this->profile_id;
-        $deleted->uri        = $this->uri;
-        $deleted->created    = $this->created;
-        $deleted->deleted    = common_sql_now();
+            $deleted->id         = $this->id;
+            $deleted->profile_id = $this->profile_id;
+            $deleted->uri        = $this->uri;
+            $deleted->created    = $this->created;
+            $deleted->deleted    = common_sql_now();
 
-        $deleted->insert();
+            $deleted->insert();
+        }
 
         // Clear related records
 
@@ -242,28 +256,34 @@ class Notice extends Memcached_DataObject
         $final = common_shorten_links($content);
 
         if (Notice::contentTooLong($final)) {
+            // TRANS: Client exception thrown if a notice contains too many characters.
             throw new ClientException(_('Problem saving notice. Too long.'));
         }
 
         if (empty($profile)) {
+            // TRANS: Client exception thrown when trying to save a notice for an unknown user.
             throw new ClientException(_('Problem saving notice. Unknown user.'));
         }
 
         if (common_config('throttle', 'enabled') && !Notice::checkEditThrottle($profile_id)) {
             common_log(LOG_WARNING, 'Excessive posting by profile #' . $profile_id . '; throttled.');
+            // TRANS: Client exception thrown when a user tries to post too many notices in a given time frame.
             throw new ClientException(_('Too many notices too fast; take a breather '.
                                         'and post again in a few minutes.'));
         }
 
         if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $final)) {
             common_log(LOG_WARNING, 'Dupe posting by profile #' . $profile_id . '; throttled.');
+            // TRANS: Client exception thrown when a user tries to post too many duplicate notices in a given time frame.
             throw new ClientException(_('Too many duplicate messages too quickly;'.
                                         ' take a breather and post again in a few minutes.'));
         }
 
         if (!$profile->hasRight(Right::NEWNOTICE)) {
             common_log(LOG_WARNING, "Attempted post from user disallowed to post: " . $profile->nickname);
-            throw new ClientException(_('You are banned from posting notices on this site.'));
+
+            // TRANS: Client exception thrown when a user tries to post while being banned.
+            throw new ClientException(_('You are banned from posting notices on this site.'), 403);
         }
 
         $notice = new Notice();
@@ -329,6 +349,7 @@ class Notice extends Memcached_DataObject
 
             if (!$id) {
                 common_log_db_error($notice, 'INSERT', __FILE__);
+                // TRANS: Server exception thrown when a notice cannot be saved.
                 throw new ServerException(_('Problem saving notice.'));
             }
 
@@ -355,6 +376,7 @@ class Notice extends Memcached_DataObject
             if ($changed) {
                 if (!$notice->update($orig)) {
                     common_log_db_error($notice, 'UPDATE', __FILE__);
+                    // TRANS: Server exception thrown when a notice cannot be updated.
                     throw new ServerException(_('Problem saving notice.'));
                 }
             }
@@ -704,7 +726,7 @@ class Notice extends Memcached_DataObject
 
     /**
      * Is this notice part of an active conversation?
-     * 
+     *
      * @return boolean true if other messages exist in the same
      *                 conversation, false if this is the only one
      */
@@ -866,7 +888,8 @@ class Notice extends Memcached_DataObject
     function saveKnownGroups($group_ids)
     {
         if (!is_array($group_ids)) {
-            throw new ServerException("Bad type provided to saveKnownGroups");
+            // TRANS: Server exception thrown when no array is provided to the method saveKnownGroups().
+            throw new ServerException(_("Bad type provided to saveKnownGroups"));
         }
 
         $groups = array();
@@ -964,6 +987,7 @@ class Notice extends Memcached_DataObject
 
             if (!$result) {
                 common_log_db_error($gi, 'INSERT', __FILE__);
+                // TRANS: Server exception thrown when an update for a group inbox fails.
                 throw new ServerException(_('Problem saving group inbox.'));
             }
 
@@ -981,8 +1005,7 @@ class Notice extends Memcached_DataObject
      * messages, we won't deliver to any remote targets as that's the
      * source service's responsibility.
      *
-     * @fixme Unlike saveReplies() there's no mail notification here.
-     *        Move that to distrib queue handler?
+     * Mail notifications etc will be handled later.
      *
      * @param array of unique identifier URIs for recipients
      */
@@ -1008,8 +1031,6 @@ class Notice extends Memcached_DataObject
                 $reply->profile_id = $user->id;
 
                 $id = $reply->insert();
-
-                self::blow('reply:stream:%d', $user->id);
             }
         }
 
@@ -1021,8 +1042,7 @@ class Notice extends Memcached_DataObject
      * and save reply records indicating that this message needs to be
      * delivered to those users.
      *
-     * Side effect: local recipients get e-mail notifications here.
-     * @fixme move mail notifications to distrib?
+     * Mail notifications to local profiles will be sent later.
      *
      * @return array of integer profile IDs
      */
@@ -1073,26 +1093,26 @@ class Notice extends Memcached_DataObject
 
                 if (!$id) {
                     common_log_db_error($reply, 'INSERT', __FILE__);
-                    throw new ServerException("Couldn't save reply for {$this->id}, {$mentioned->id}");
+                    // TRANS: Server exception thrown when a reply cannot be saved.
+                    // TRANS: First arg is a notice ID, second ID is the ID of the mentioned user.
+                    throw new ServerException(_("Couldn't save reply for {$this->id}, {$mentioned->id}"));
                 } else {
                     $replied[$mentioned->id] = 1;
+                    self::blow('reply:stream:%d', $mentioned->id);
                 }
             }
         }
 
         $recipientIds = array_keys($replied);
 
-        foreach ($recipientIds as $recipientId) {
-            $user = User::staticGet('id', $recipientId);
-            if (!empty($user)) {
-                self::blow('reply:stream:%d', $reply->profile_id);
-                mail_notify_attn($user, $this);
-            }
-        }
-
         return $recipientIds;
     }
 
+    /**
+     * Pull the complete list of @-reply targets for this notice.
+     *
+     * @return array of integer profile ids
+     */
     function getReplies()
     {
         // XXX: cache me
@@ -1115,6 +1135,30 @@ class Notice extends Memcached_DataObject
         return $ids;
     }
 
+    /**
+     * Send e-mail notifications to local @-reply targets.
+     *
+     * Replies must already have been saved; this is expected to be run
+     * from the distrib queue handler.
+     */
+    function sendReplyNotifications()
+    {
+        // Don't send reply notifications for repeats
+
+        if (!empty($this->repeat_of)) {
+            return array();
+        }
+
+        $recipientIds = $this->getReplies();
+
+        foreach ($recipientIds as $recipientId) {
+            $user = User::staticGet('id', $recipientId);
+            if (!empty($user)) {
+                mail_notify_attn($user, $this);
+            }
+        }
+    }
+
     /**
      * Pull list of groups this notice needs to be delivered to,
      * as previously recorded by saveGroups() or saveKnownGroups().
@@ -1154,7 +1198,7 @@ class Notice extends Memcached_DataObject
         return $groups;
     }
 
-    function asAtomEntry($namespace=false, $source=false, $author=true)
+    function asAtomEntry($namespace=false, $source=false, $author=true, $cur=null)
     {
         $profile = $this->getProfile();
 
@@ -1167,7 +1211,8 @@ class Notice extends Memcached_DataObject
                            'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/',
                            'xmlns:media' => 'http://purl.org/syndication/atommedia',
                            'xmlns:poco' => 'http://portablecontacts.net/spec/1.0',
-                           'xmlns:ostatus' => 'http://ostatus.org/schema/1.0');
+                           'xmlns:ostatus' => 'http://ostatus.org/schema/1.0',
+                           'xmlns:statusnet' => 'http://status.net/schema/api/1/');
         } else {
             $attrs = array();
         }
@@ -1202,7 +1247,7 @@ class Notice extends Memcached_DataObject
         $xs->element('title', null, common_xml_safe_str($this->content));
 
         if ($author) {
-            $xs->raw($profile->asAtomAuthor());
+            $xs->raw($profile->asAtomAuthor($cur));
             $xs->raw($profile->asActivityActor());
         }
 
@@ -1215,6 +1260,46 @@ class Notice extends Memcached_DataObject
         $xs->element('published', null, common_date_w3dtf($this->created));
         $xs->element('updated', null, common_date_w3dtf($this->created));
 
+        $source = null;
+
+        $ns = $this->getSource();
+
+        if ($ns) {
+            if (!empty($ns->name) && !empty($ns->url)) {
+                $source = '<a href="'
+                  . htmlspecialchars($ns->url)
+                  . '" rel="nofollow">'
+                  . htmlspecialchars($ns->name)
+                   . '</a>';
+            } else {
+                $source = $ns->code;
+            }
+        }
+
+        $noticeInfoAttr = array(
+            'local_id'   => $this->id, // local notice ID (useful to clients for ordering)
+            'source'     => $source,   // the client name (source attribution)
+        );
+
+        $ns = $this->getSource();
+        if ($ns) {
+            if (!empty($ns->url)) {
+                $noticeInfoAttr['source_link'] = $ns->url;
+            }
+        }
+
+        if (!empty($cur)) {
+            $noticeInfoAttr['favorite'] = ($cur->hasFave($this)) ? "true" : "false";
+            $profile = $cur->getProfile();
+            $noticeInfoAttr['repeated'] = ($profile->hasRepeated($this->id)) ? "true" : "false";
+        }
+
+        if (!empty($this->repeat_of)) {
+            $noticeInfoAttr['repeat_of'] = $this->repeat_of;
+        }
+
+        $xs->element('statusnet:notice_info', $noticeInfoAttr, null);
+
         if ($this->reply_to) {
             $reply_notice = Notice::staticGet('id', $this->reply_to);
             if (!empty($reply_notice)) {
@@ -1781,4 +1866,53 @@ class Notice extends Memcached_DataObject
 
         return $result;
     }
+
+    /**
+     * Get the source of the notice
+     *
+     * @return Notice_source $ns A notice source object. 'code' is the only attribute
+     *                           guaranteed to be populated.
+     */
+    function getSource()
+    {
+        $ns = new Notice_source();
+        if (!empty($this->source)) {
+            switch ($this->source) {
+            case 'web':
+            case 'xmpp':
+            case 'mail':
+            case 'omb':
+            case 'system':
+            case 'api':
+                $ns->code = $this->source;
+                break;
+            default:
+                $ns = Notice_source::staticGet($this->source);
+                if (!$ns) {
+                    $ns = new Notice_source();
+                    $ns->code = $this->source;
+                    $app = Oauth_application::staticGet('name', $this->source);
+                    if ($app) {
+                        $ns->name = $app->name;
+                        $ns->url  = $app->source_url;
+                    }
+                }
+                break;
+            }
+        }
+        return $ns;
+    }
+
+    /**
+     * Determine whether the notice was locally created
+     *
+     * @return boolean locality
+     */
+
+    public function isLocal()
+    {
+        return ($this->is_local == Notice::LOCAL_PUBLIC ||
+                $this->is_local == Notice::LOCAL_NONPUBLIC);
+    }
+
 }