]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - classes/Notice.php
Merge branch 'testing' of gitorious.org:statusnet/mainline into 0.9.x
[quix0rs-gnu-social.git] / classes / Notice.php
index 22dcbcd741d33d18b8f4da6974d3036f0ee0821a..cf6f9c279da30a482676058029cc84cc9626e1f3 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/
  */
 
@@ -97,15 +98,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
 
@@ -119,6 +125,9 @@ class Notice extends Memcached_DataObject
         // NOTE: we don't clear queue items
 
         $result = parent::delete();
+
+        $this->blowOnDelete();
+        return $result;
     }
 
     /**
@@ -145,11 +154,11 @@ class Notice extends Memcached_DataObject
         //turn each into their canonical tag
         //this is needed to remove dupes before saving e.g. #hash.tag = #hashtag
         for($i=0; $i<count($hashtags); $i++) {
+            /* elide characters we don't want in the tag */
             $hashtags[$i] = common_canonical_tag($hashtags[$i]);
         }
 
         foreach(array_unique($hashtags) as $hashtag) {
-            /* elide characters we don't want in the tag */
             $this->saveTag($hashtag);
             self::blow('profile:notice_ids_tagged:%d:%s', $this->profile_id, $hashtag);
         }
@@ -169,7 +178,8 @@ class Notice extends Memcached_DataObject
         $id = $tag->insert();
 
         if (!$id) {
-            throw new ServerException(sprintf(_('DB error inserting hashtag: %s'),
+            // TRANS: Server exception. %s are the error details.
+            throw new ServerException(sprintf(_('Database error inserting hashtag: %s'),
                                               $last_error->message));
             return;
         }
@@ -211,6 +221,8 @@ class Notice extends Memcached_DataObject
      *                              extracting ! tags from content
      *              array 'tags' list of hashtag strings to save with the notice
      *                           in place of extracting # tags from content
+     *              array 'urls' list of attached/referred URLs to save with the
+     *                           notice in place of extracting links from content
      * @fixme tag override
      *
      * @return Notice
@@ -368,21 +380,26 @@ class Notice extends Memcached_DataObject
             $notice->saveReplies();
         }
 
+        if (isset($tags)) {
+            $notice->saveKnownTags($tags);
+        } else {
+            $notice->saveTags();
+        }
+
+        // Note: groups may save tags, so must be run after tags are saved
+        // to avoid errors on duplicates.
         if (isset($groups)) {
             $notice->saveKnownGroups($groups);
         } else {
             $notice->saveGroups();
         }
 
-        if (isset($tags)) {
-            $notice->saveKnownTags($tags);
+        if (isset($urls)) {
+            $notice->saveKnownUrls($urls);
         } else {
-            $notice->saveTags();
+            $notice->saveUrls();
         }
 
-        // @fixme pass in data for URLs too?
-        $notice->saveUrls();
-
         // Prepare inbox delivery, may be queued to background.
         $notice->distribute();
 
@@ -413,7 +430,21 @@ class Notice extends Memcached_DataObject
         }
 
         $profile = Profile::staticGet($this->profile_id);
-        $profile->blowNoticeCount();
+        if (!empty($profile)) {
+            $profile->blowNoticeCount();
+        }
+    }
+
+    /**
+     * Clear cache entries related to this notice at delete time.
+     * Necessary to avoid breaking paging on public, profile timelines.
+     */
+    function blowOnDelete()
+    {
+        $this->blowOnInsert();
+
+        self::blow('profile:notice_ids:%d;last', $this->profile_id);
+        self::blow('public;last');
     }
 
     /** save all urls in the notice to the db
@@ -427,6 +458,25 @@ class Notice extends Memcached_DataObject
         common_replace_urls_callback($this->content, array($this, 'saveUrl'), $this->id);
     }
 
+    /**
+     * Save the given URLs as related links/attachments to the db
+     *
+     * follow redirects and save all available file information
+     * (mimetype, date, size, oembed, etc.)
+     *
+     * @return void
+     */
+    function saveKnownUrls($urls)
+    {
+        // @fixme validation?
+        foreach ($urls as $url) {
+            File::processNew($url, $this->id);
+        }
+    }
+
+    /**
+     * @private callback
+     */
     function saveUrl($data) {
         list($url, $notice_id) = $data;
         File::processNew($url, $notice_id);
@@ -565,7 +615,6 @@ class Notice extends Memcached_DataObject
                               array(),
                               'public',
                               $offset, $limit, $since_id, $max_id);
-
         return Notice::getStreamByIds($ids);
     }
 
@@ -659,6 +708,27 @@ class Notice extends Memcached_DataObject
         return $ids;
     }
 
+    /**
+     * 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
+     */
+    function hasConversation()
+    {
+        if (!empty($this->conversation)) {
+            $conversation = Notice::conversationStream(
+                $this->conversation,
+                1,
+                1
+            );
+            if ($conversation->N > 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * @param $groups array of Group *objects*
      * @param $recipients array of profile *ids*
@@ -853,7 +923,7 @@ class Notice extends Memcached_DataObject
 
         foreach (array_unique($match[1]) as $nickname) {
             /* XXX: remote groups. */
-            $group = User_group::getForNickname($nickname);
+            $group = User_group::getForNickname($nickname, $profile);
 
             if (empty($group)) {
                 continue;
@@ -917,18 +987,25 @@ 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
      */
     function saveKnownReplies($uris)
     {
+        if (empty($uris)) {
+            return;
+        }
+        $sender = Profile::staticGet($this->profile_id);
+
         foreach ($uris as $uri) {
 
             $user = User::staticGet('uri', $uri);
 
             if (!empty($user)) {
+                if ($user->hasBlocked($sender)) {
+                    continue;
+                }
 
                 $reply = new Reply();
 
@@ -936,8 +1013,6 @@ class Notice extends Memcached_DataObject
                 $reply->profile_id = $user->id;
 
                 $id = $reply->insert();
-
-                self::blow('reply:stream:%d', $user->id);
             }
         }
 
@@ -949,8 +1024,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
      */
@@ -1004,23 +1078,21 @@ class Notice extends Memcached_DataObject
                     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
@@ -1043,6 +1115,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().
@@ -1082,7 +1178,7 @@ class Notice extends Memcached_DataObject
         return $groups;
     }
 
-    function asAtomEntry($namespace=false, $source=false)
+    function asAtomEntry($namespace=false, $source=false, $author=true, $cur=null)
     {
         $profile = $this->getProfile();
 
@@ -1095,7 +1191,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/ont/');
         } else {
             $attrs = array();
         }
@@ -1104,6 +1201,7 @@ class Notice extends Memcached_DataObject
 
         if ($source) {
             $xs->elementStart('source');
+            $xs->element('id', null, $profile->profileurl);
             $xs->element('title', null, $profile->nickname . " - " . common_config('site', 'name'));
             $xs->element('link', array('href' => $profile->profileurl));
             $user = User::staticGet('id', $profile->id);
@@ -1119,17 +1217,19 @@ class Notice extends Memcached_DataObject
             }
 
             $xs->element('icon', null, $profile->avatarUrl(AVATAR_PROFILE_SIZE));
+            $xs->element('updated', null, common_date_w3dtf($this->created));
         }
 
         if ($source) {
             $xs->elementEnd('source');
         }
 
-        $xs->element('title', null, $this->content);
-        $xs->element('summary', null, $this->content);
+        $xs->element('title', null, common_xml_safe_str($this->content));
 
-        $xs->raw($profile->asAtomAuthor());
-        $xs->raw($profile->asActivityActor());
+        if ($author) {
+            $xs->raw($profile->asAtomAuthor());
+            $xs->raw($profile->asActivityActor());
+        }
 
         $xs->element('link', array('rel' => 'alternate',
                                    'type' => 'text/html',
@@ -1140,6 +1240,28 @@ class Notice extends Memcached_DataObject
         $xs->element('published', null, common_date_w3dtf($this->created));
         $xs->element('updated', null, common_date_w3dtf($this->created));
 
+        $noticeInfoAttr = array(
+            'local_id'   => $this->id,    // local notice ID (useful to clients for ordering)
+            'source'     => $this->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";
+        }
+
+        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)) {
@@ -1200,7 +1322,11 @@ class Notice extends Memcached_DataObject
             }
         }
 
-        $xs->element('content', array('type' => 'html'), $this->rendered);
+        $xs->element(
+            'content',
+            array('type' => 'html'),
+            common_xml_safe_str($this->rendered)
+        );
 
         $tag = new Notice_tag();
         $tag->notice_id = $this->id;
@@ -1431,6 +1557,8 @@ class Notice extends Memcached_DataObject
     {
         $author = Profile::staticGet('id', $this->profile_id);
 
+        // TRANS: Message used to repeat a notice. RT is the abbreviation of 'retweet'.
+        // TRANS: %1$s is the repeated user's name, %2$s is the repeated notice.
         $content = sprintf(_('RT @%1$s %2$s'),
                            $author->nickname,
                            $this->content);
@@ -1700,4 +1828,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);
+    }
+
 }