]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - classes/Notice.php
No more needed (for this fix) but maybe later. So I always only comment them out.
[quix0rs-gnu-social.git] / classes / Notice.php
index df7105116d9b0f475411ea6076436c783ef179c4..263cc4e32394ecbcd2880ac2f8cc91e3067a9779 100644 (file)
@@ -276,19 +276,21 @@ class Notice extends Managed_DataObject
 
     /*
      * Get the original representation URL of this notice.
+     *
+     * @param boolean $fallback     Whether to fall back to generate a local URL or throw InvalidUrlException
      */
-    public function getUrl()
+    public function getUrl($fallback=false)
     {
         // The risk is we start having empty urls and non-http uris...
         // and we can't really handle any other protocol right now.
         switch (true) {
         case common_valid_http_url($this->url): // should we allow non-http/https URLs?
             return $this->url;
-        case $this->isLocal():
+        case !$this->isLocal() && common_valid_http_url($this->uri): // Sometimes we only have the URI for remote posts.
+            return $this->uri;
+        case $this->isLocal() || $fallback:
             // let's generate a valid link to our locally available notice on demand
             return common_local_url('shownotice', array('notice' => $this->id), null, null, false);
-        case common_valid_http_url($this->uri):
-            return $this->uri;
         default:
             common_debug('No URL available for notice: id='.$this->id);
             throw new InvalidUrlException($this->url);
@@ -330,7 +332,7 @@ class Notice extends Managed_DataObject
      * Record the given set of hash tags in the db for this notice.
      * Given tag strings will be normalized and checked for dupes.
      */
-    function saveKnownTags($hashtags)
+    function saveKnownTags(array $hashtags)
     {
         //turn each into their canonical tag
         //this is needed to remove dupes before saving e.g. #hash.tag = #hashtag
@@ -414,23 +416,27 @@ class Notice extends Managed_DataObject
      * @return Notice
      * @throws ClientException
      */
-    static function saveNew($profile_id, $content, $source, array $options=null) {
+    static function saveNew($profile_id, $content, $source, array $options=array()) {
         $defaults = array('uri' => null,
                           'url' => null,
-                          'reply_to' => null,
-                          'repeat_of' => null,
+                          'conversation' => null,   // URI of conversation
+                          'reply_to' => null,       // This will override convo URI if the parent is known
+                          'repeat_of' => null,      // This will override convo URI if the repeated notice is known
                           'scope' => null,
                           'distribute' => true,
                           'object_type' => null,
                           'verb' => null);
 
-        if (!empty($options) && is_array($options)) {
+        /*
+         * Above type-hint is already array, so simply count it, this saves
+         * "some" CPU cycles.
+         */
+        if (count($options) > 0) {
             $options = array_merge($defaults, $options);
-            extract($options);
-        } else {
-            extract($defaults);
         }
 
+        extract($options);
+
         if (!isset($is_local)) {
             $is_local = Notice::LOCAL_PUBLIC;
         }
@@ -536,8 +542,7 @@ class Notice extends Managed_DataObject
                 throw new ClientException(_('You cannot repeat your own notice.'));
             }
 
-            if ($repeat->scope != Notice::SITE_SCOPE &&
-                $repeat->scope != Notice::PUBLIC_SCOPE) {
+            if ($repeat->isPrivateScope()) {
                 // TRANS: Client error displayed when trying to repeat a non-public notice.
                 throw new ClientException(_('Cannot repeat a private notice.'), 403);
             }
@@ -600,6 +605,26 @@ class Notice extends Managed_DataObject
 
                 // Scope set below
             }
+
+            // If we don't know the reply, we might know the conversation!
+            // This will happen if a known remote user replies to an
+            // unknown remote user - within a known conversation.
+            if (empty($notice->conversation) and !empty($options['conversation'])) {
+                $conv = Conversation::getKV('uri', $options['conversation']);
+                if ($conv instanceof Conversation) {
+                    common_debug('Conversation stitched together from (probably) reply to unknown remote user. Activity creation time ('.$notice->created.') should maybe be compared to conversation creation time ('.$conv->created.').');
+                    $notice->conversation = $conv->id;
+                } else {
+                    // Conversation URI was not found, so we must create it. But we can't create it
+                    // until we have a Notice ID because of the database layout...
+                    $notice->tmp_conv_uri = $options['conversation'];
+                }
+            } else {
+                // If we're not using the attached conversation URI let's remove it
+                // so we don't mistake ourselves later, when creating our own Conversation.
+                // This implies that the notice knows which conversation it belongs to.
+                $options['conversation'] = null;
+            }
         }
 
         if (!empty($lat) && !empty($lon)) {
@@ -649,6 +674,15 @@ class Notice extends Managed_DataObject
 
             try {
                 $notice->insert();  // throws exception on failure
+                // If it's not part of a conversation, it's
+                // the beginning of a new conversation.
+                if (empty($notice->conversation)) { 
+                    $orig = clone($notice);
+                    // $act->context->conversation will be null if it was not provided
+                    $conv = Conversation::create($notice, $options['conversation']);
+                    $notice->conversation = $conv->id;
+                    $notice->update($orig);
+                }
             } catch (Exception $e) {
                 // Let's test if we managed initial insert, which would imply
                 // failing on some update-part (check 'insert()'). Delete if
@@ -656,6 +690,7 @@ class Notice extends Managed_DataObject
                 if (!empty($notice->id)) {
                     $notice->delete();
                 }
+                throw $e;
             }
         }
 
@@ -712,24 +747,22 @@ class Notice extends Managed_DataObject
                                         'and post again in a few minutes.'));
         }
 
-/* This interferes with stuff like Favorites from old StatusNet installations (first object in objects is the favored notice)
         // Get ActivityObject properties
-        $actobj = count($act->objects)==1 ? $act->objects[0] : null;
-        if (!is_null($actobj) && $actobj->id) {
-            $options['uri'] = $actobj->id;
-            if ($actobj->link) {
-                $options['url'] = $actobj->link;
-            } elseif ($act->link) {
-                $options['url'] = $act->link;
-            } elseif (preg_match('!^https?://!', $actobj->id)) {
-                $options['url'] = $actobj->id;
-            }
-        } else {
+        if (!empty($act->id)) {
             // implied object
             $options['uri'] = $act->id;
             $options['url'] = $act->link;
+        } else {
+            $actobj = count($act->objects)==1 ? $act->objects[0] : null;
+            if (!is_null($actobj) && !empty($actobj->id)) {
+                $options['uri'] = $actobj->id;
+                if ($actobj->link) {
+                    $options['url'] = $actobj->link;
+                } elseif (preg_match('!^https?://!', $actobj->id)) {
+                    $options['url'] = $actobj->id;
+                }
+            }
         }
-*/
 
         $defaults = array(
                           'groups'   => array(),
@@ -769,9 +802,10 @@ class Notice extends Managed_DataObject
         $stored->verb = $act->verb;
 
         // Use the local user's shortening preferences, if applicable.
-        $stored->content = $actor->isLocal()
+        $stored->rendered = $actor->isLocal()
                                 ? $actor->shortenLinks($act->content)
                                 : $act->content;
+        $stored->content = common_strip_html($stored->rendered);
 
         $autosource = common_config('public', 'autosource');
 
@@ -858,13 +892,21 @@ class Notice extends Managed_DataObject
 
             try {
                 $stored->insert();    // throws exception on error
+                $orig = clone($stored); // for updating later in this try clause
+
+                // If it's not part of a conversation, it's
+                // the beginning of a new conversation.
+                if (empty($stored->conversation)) {
+                    // $act->context->conversation will be null if it was not provided
+                    $conv = Conversation::create($stored, $act->context->conversation);
+                    $stored->conversation = $conv->id;
+                }
 
                 $object = null;
                 Event::handle('StoreActivityObject', array($act, $stored, $options, &$object));
                 if (empty($object)) {
                     throw new ServerException('No object from StoreActivityObject '.$stored->uri . ': '.$act->asString());
                 }
-                $orig = clone($stored);
                 $stored->object_type = ActivityUtils::resolveUri($object->getObjectType(), true);
                 $stored->update($orig);
             } catch (Exception $e) {
@@ -957,6 +999,7 @@ class Notice extends Managed_DataObject
 
         if ($this->isPublic()) {
             $this->blowStream('public');
+            $this->blowStream('networkpublic');
         }
 
         self::blow('notice:list-ids:conversation:%s', $this->conversation);
@@ -1001,6 +1044,7 @@ class Notice extends Managed_DataObject
 
         if ($this->isPublic()) {
             self::blow('public;last');
+            self::blow('networkpublic;last');
         }
 
         self::blow('fave:by_notice', $this->id);
@@ -1073,7 +1117,7 @@ class Notice extends Managed_DataObject
      *
      * @return void
      */
-    function saveKnownUrls($urls)
+    function saveKnownUrls(array $urls)
     {
         if (common_config('attachments', 'process_links')) {
             // @fixme validation?
@@ -1233,10 +1277,16 @@ class Notice extends Managed_DataObject
         }
 
         // If this isn't a reply to anything, then it's its own
-        // root.
+        // root if it's the earliest notice in the conversation:
 
         if (empty($this->reply_to)) {
-            return $this;
+            $root = new Notice;
+            $root->conversation = $this->conversation;
+            $root->orderBy('notice.created ASC');
+            $root->find();
+            $root->fetch();
+            $root->free();
+            return $root;
         }
         
         if (is_null($profile)) {
@@ -1445,7 +1495,7 @@ class Notice extends Managed_DataObject
         foreach (array_unique($group_ids) as $id) {
             $group = User_group::getKV('id', $id);
             if ($group instanceof User_group) {
-                common_log(LOG_ERR, "Local delivery to group id $id, $group->nickname");
+                common_log(LOG_DEBUG, "Local delivery to group id $id, $group->nickname");
                 $result = $this->addToGroupInbox($group);
                 if (!$result) {
                     common_log_db_error($gi, 'INSERT', __FILE__);
@@ -1641,12 +1691,12 @@ class Notice extends Managed_DataObject
             $ids[] = $reply->profile_id;
         }
 
-        $this->_replies[$this->id] = $ids;
+        $this->_setReplies($ids);
 
         return $ids;
     }
 
-    function _setReplies($replies)
+    function _setReplies(array $replies)
     {
         $this->_replies[$this->id] = $replies;
     }
@@ -1724,13 +1774,11 @@ class Notice extends Managed_DataObject
                }
                
                $groups = User_group::multiGet('id', $ids);
-               
-               $this->_groups[$this->id] = $groups->fetchAll();
-               
+               $this->_setGroups($groups->fetchAll());
                return $this->_groups[$this->id];
     }
-    
-    function _setGroups($groups)
+
+    function _setGroups(array $groups)
     {
         $this->_groups[$this->id] = $groups;
     }
@@ -1738,12 +1786,12 @@ class Notice extends Managed_DataObject
     /**
      * Convert a notice into an activity for export.
      *
-     * @param User $cur Current user
+     * @param Profile $scoped   The currently logged in/scoped profile
      *
      * @return Activity activity object representing this Notice.
      */
 
-    function asActivity($cur=null)
+    function asActivity(Profile $scoped=null)
     {
         $act = self::cacheGet(Cache::codeKey('notice:as-activity:'.$this->id));
 
@@ -1752,7 +1800,7 @@ class Notice extends Managed_DataObject
         }
         $act = new Activity();
 
-        if (Event::handle('StartNoticeAsActivity', array($this, &$act))) {
+        if (Event::handle('StartNoticeAsActivity', array($this, $act, $scoped))) {
 
             $act->id      = $this->uri;
             $act->time    = strtotime($this->created);
@@ -1767,14 +1815,19 @@ class Notice extends Managed_DataObject
             $profile = $this->getProfile();
 
             $act->actor            = $profile->asActivityObject();
-            $act->actor->extra[]   = $profile->profileInfo($cur);
+            $act->actor->extra[]   = $profile->profileInfo($scoped);
 
             $act->verb = $this->verb;
 
             if ($this->repeat_of) {
                 $repeated = Notice::getKV('id', $this->repeat_of);
                 if ($repeated instanceof Notice) {
-                    $act->objects[] = $repeated->asActivity($cur);
+                    // TRANS: A repeat activity's title. %1$s is repeater's nickname
+                    //        and %2$s is the repeated user's nickname.
+                    $act->title = sprintf(_('%1$s repeated a notice by %2$s'),
+                                          $this->getProfile()->getNickname(),
+                                          $repeated->getProfile()->getNickname());
+                    $act->objects[] = $repeated->asActivity($scoped);
                 }
             } else {
                 $act->objects[] = $this->asActivityObject();
@@ -1899,7 +1952,7 @@ class Notice extends Managed_DataObject
                 $act->editLink = $act->selfLink;
             }
 
-            Event::handle('EndNoticeAsActivity', array($this, &$act));
+            Event::handle('EndNoticeAsActivity', array($this, $act, $scoped));
         }
 
         self::cacheSet(Cache::codeKey('notice:as-activity:'.$this->id), $act);
@@ -1913,10 +1966,10 @@ class Notice extends Managed_DataObject
     function asAtomEntry($namespace=false,
                          $source=false,
                          $author=true,
-                         $cur=null)
+                         Profile $scoped=null)
     {
-        $act = $this->asActivity($cur);
-        $act->extra[] = $this->noticeInfo($cur);
+        $act = $this->asActivity($scoped);
+        $act->extra[] = $this->noticeInfo($scoped);
         return $act->asString($namespace, $author, $source);
     }
 
@@ -1926,12 +1979,12 @@ class Notice extends Managed_DataObject
      * Clients use some extra notice info in the atom stream.
      * This gives it to them.
      *
-     * @param User $cur Current user
+     * @param Profile $scoped   The currently logged in/scoped profile
      *
      * @return array representation of <statusnet:notice_info> element
      */
 
-    function noticeInfo($cur)
+    function noticeInfo(Profile $scoped=null)
     {
         // local notice ID (useful to clients for ordering)
 
@@ -1957,9 +2010,7 @@ class Notice extends Managed_DataObject
 
         // favorite and repeated
 
-        $scoped = null;
-        if (!empty($cur)) {
-            $scoped = $cur->getProfile();
+        if ($scoped instanceof Profile) {
             $noticeInfoAttr['repeated'] = ($scoped->hasRepeated($this)) ? "true" : "false";
         }
 
@@ -2099,6 +2150,18 @@ class Notice extends Managed_DataObject
                            $author->getNickname(),
                            $this->content);
 
+        $maxlen = self::maxContent();
+        if ($maxlen > 0 && mb_strlen($content) > $maxlen) {
+            // Web interface and current Twitter API clients will
+            // pull the original notice's text, but some older
+            // clients and RSS/Atom feeds will see this trimmed text.
+            //
+            // Unfortunately this is likely to lose tags or URLs
+            // at the end of long notices.
+            $content = mb_substr($content, 0, $maxlen - 4) . ' ...';
+        }     
+
+
         // Scope is same as this one's
         return self::saveNew($repeater->id,
                              $content,
@@ -2366,14 +2429,6 @@ class Notice extends Managed_DataObject
             $changed = true;
         }
 
-        // If it's not part of a conversation, it's
-        // the beginning of a new conversation.
-        if (empty($this->conversation)) {
-            $conv = Conversation::create($this);
-            $this->conversation = $conv->id;
-            $changed = true;
-        }
-
         if ($changed && $this->update($orig) === false) {
             common_log_db_error($notice, 'UPDATE', __FILE__);
             // TRANS: Server exception thrown when a notice cannot be updated.
@@ -2393,31 +2448,34 @@ class Notice extends Managed_DataObject
      */
     function getSource()
     {
+        if (empty($this->source)) {
+            return false;
+        }
+
         $ns = new Notice_source();
-        if (!empty($this->source)) {
-            switch ($this->source) {
-            case 'web':
-            case 'xmpp':
-            case 'mail':
-            case 'omb':
-            case 'system':
-            case 'api':
+        switch ($this->source) {
+        case 'web':
+        case 'xmpp':
+        case 'mail':
+        case 'omb':
+        case 'system':
+        case 'api':
+            $ns->code = $this->source;
+            break;
+        default:
+            $ns = Notice_source::getKV($this->source);
+            if (!$ns) {
+                $ns = new Notice_source();
                 $ns->code = $this->source;
-                break;
-            default:
-                $ns = Notice_source::getKV($this->source);
-                if (!$ns) {
-                    $ns = new Notice_source();
-                    $ns->code = $this->source;
-                    $app = Oauth_application::getKV('name', $this->source);
-                    if ($app) {
-                        $ns->name = $app->name;
-                        $ns->url  = $app->source_url;
-                    }
+                $app = Oauth_application::getKV('name', $this->source);
+                if ($app) {
+                    $ns->name = $app->name;
+                    $ns->url  = $app->source_url;
                 }
-                break;
             }
+            break;
         }
+
         return $ns;
     }
 
@@ -2445,6 +2503,37 @@ class Notice extends Managed_DataObject
      */
     public function getTags()
     {
+        // Check default scope (non-private notices)
+        $inScope = (!$this->isPrivateScope());
+
+        // Get current profile
+        $profile = Profile::current();
+
+        // Is the general scope check okay and the user in logged in?
+        //* NOISY-DEBUG: */ common_debug('[' . __METHOD__ . ':' . __LINE__ . ']: inScope=' . intval($inScope) . ',profile[]=' . gettype($profile));
+        if (($inScope === TRUE) && ($profile instanceof Profile)) {
+            /*
+             * Check scope, else a privacy leaks happens this way:
+             *
+             * 1) Bob and Alice follow each other and write private notices
+             *    (this->scope=2) to each other.
+             * 2) Bob uses tags in his private notice to alice (which she can
+             *    read from him).
+             * 3) Alice adds that notice (with tags) to her favorites
+             *    ("faving") it.
+             * 4) The tags from Bob's private notice becomes visible in Alice's
+             *    profile.
+             *
+             * This has the simple background that the scope is not being
+             * re-checked. This has to be done here at this point because given
+             * above scenario is a privacy leak as the tags may be *really*
+             * private (nobody else shall see them) such as initmate words or
+             * very political words.
+             */
+            $inScope = $this->inScope($profile);
+            //* NOISY-DEBUG: */ common_debug('[' . __METHOD__ . ':' . __LINE__ . ']: inScope=' . intval($inScope) . ' - After inScope() has been called.');
+        }
+
         $tags = array();
 
         $keypart = sprintf('notice:tags:%d', $this->id);
@@ -2456,7 +2545,9 @@ class Notice extends Managed_DataObject
         } else {
             $tag = new Notice_tag();
             $tag->notice_id = $this->id;
-            if ($tag->find()) {
+
+            // Check scope for privacy-leak protection (see some lines above why)
+            if (($inScope === TRUE) && ($tag->find())) {
                 while ($tag->fetch()) {
                     $tags[] = $tag->tag;
                 }
@@ -2580,12 +2671,8 @@ class Notice extends Managed_DataObject
 
     function isPublic()
     {
-        if (common_config('public', 'localonly')) {
-            return ($this->is_local == Notice::LOCAL_PUBLIC);
-        } else {
-            return (($this->is_local != Notice::LOCAL_NONPUBLIC) &&
-                    ($this->is_local != Notice::GATEWAY));
-        }
+        return (($this->is_local != Notice::LOCAL_NONPUBLIC) &&
+                ($this->is_local != Notice::GATEWAY));
     }
 
     /**
@@ -2602,7 +2689,7 @@ class Notice extends Managed_DataObject
      *
      * @return boolean whether the profile is in the notice's scope
      */
-    function inScope($profile)
+    function inScope(Profile $profile=null)
     {
         if (is_null($profile)) {
             $keypart = sprintf('notice:in-scope-for:%d:null', $this->id);
@@ -2625,7 +2712,7 @@ class Notice extends Managed_DataObject
         return ($result == 1) ? true : false;
     }
 
-    protected function _inScope($profile)
+    protected function _inScope(Profile $profile=null)
     {
         if (!is_null($this->scope)) {
             $scope = $this->scope;
@@ -2665,7 +2752,7 @@ class Notice extends Managed_DataObject
 
                 $reply = Reply::pkeyGet(array('notice_id' => $this->id,
                                              'profile_id' => $profile->id));
-                                                                                
+
                 if (!$reply instanceof Reply) {
                     return false;
                 }
@@ -2714,8 +2801,8 @@ class Notice extends Managed_DataObject
         }
     }
 
-    function isHiddenSpam($profile) {
-        
+    function isHiddenSpam(Profile $profile=null) {
+
         // Hide posts by silenced users from everyone but moderators.
 
         if (common_config('notice', 'hidespam')) {
@@ -2779,7 +2866,7 @@ class Notice extends Managed_DataObject
        return $scope;
     }
 
-       static function fillProfiles($notices)
+       static function fillProfiles(array $notices)
        {
                $map = self::getProfiles($notices);
                
@@ -2796,8 +2883,8 @@ class Notice extends Managed_DataObject
                
                return array_values($map);
        }
-       
-       static function getProfiles(&$notices)
+
+       static function getProfiles(array &$notices)
        {
                $ids = array();
                foreach ($notices as $notice) {
@@ -2808,8 +2895,8 @@ class Notice extends Managed_DataObject
                
                return Profile::pivotGet('id', $ids); 
        }
-       
-       static function fillGroups(&$notices)
+
+       static function fillGroups(array &$notices)
        {
         $ids = self::_idsOf($notices);
                
@@ -2849,7 +2936,7 @@ class Notice extends Managed_DataObject
                return array_keys($ids);
     }
 
-    static function fillAttachments(&$notices)
+    static function fillAttachments(array &$notices)
     {
         $ids = self::_idsOf($notices);
 
@@ -2878,7 +2965,7 @@ class Notice extends Managed_DataObject
                }
     }
 
-    static function fillReplies(&$notices)
+    static function fillReplies(array &$notices)
     {
         $ids = self::_idsOf($notices);
         $replyMap = Reply::listGet('notice_id', $ids);
@@ -2900,22 +2987,33 @@ class Notice extends Managed_DataObject
             return $this->_repeats[$this->id];
         }
         $repeatMap = Notice::listGet('repeat_of', array($this->id));
-        $this->_repeats[$this->id] = $repeatMap[$this->id];
+        $this->_setRepeats($repeatMap[$this->id]);
         return $this->_repeats[$this->id];
     }
 
-    function _setRepeats($repeats)
+    function _setRepeats(array $repeats)
     {
         $this->_repeats[$this->id] = $repeats;
     }
 
-    static function fillRepeats(&$notices)
+    static function fillRepeats(array &$notices)
     {
         $ids = self::_idsOf($notices);
         $repeatMap = Notice::listGet('repeat_of', $ids);
         foreach ($notices as $notice) {
-               $repeats = $repeatMap[$notice->id];
+            $repeats = $repeatMap[$notice->id];
             $notice->_setRepeats($repeats);
         }
     }
+
+    /**
+     * Checks whether this notice is in "private scope" (non-public notice)
+     *
+     * @return $isPrivate Whether this notice is private
+     */
+    public function isPrivateScope ()
+    {
+        return ($this->scope != Notice::SITE_SCOPE &&
+                $this->scope != Notice::PUBLIC_SCOPE);
+    }
 }