]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge from chimo: Catch GeoCookie JSON parsing error
authorMikael Nordfeldth <mmn@hethane.se>
Thu, 5 Jun 2014 07:33:30 +0000 (09:33 +0200)
committerMikael Nordfeldth <mmn@hethane.se>
Thu, 5 Jun 2014 07:33:30 +0000 (09:33 +0200)
43 files changed:
actions/newnotice.php
actions/shownotice.php
classes/File.php
classes/Memcached_DataObject.php
classes/Notice.php
classes/Profile.php
lib/activityobject.php
lib/apiaction.php
lib/noticelist.php
lib/noticelistitem.php
lib/noticesection.php
lib/rssaction.php
lib/threadednoticelist.php
lib/util.php
lib/widget.php
plugins/Bookmark/BookmarkPlugin.php
plugins/Bookmark/actions/bookmarkforurl.php
plugins/Bookmark/actions/newbookmark.php
plugins/Bookmark/bookmark.css [deleted file]
plugins/Bookmark/css/bookmark.css [new file with mode: 0644]
plugins/Event/EventPlugin.php
plugins/Event/actions/newevent.php
plugins/Event/css/event.css [new file with mode: 0644]
plugins/Event/event.css [deleted file]
plugins/FacebookBridge/lib/facebookclient.php
plugins/OStatus/OStatusPlugin.php
plugins/OStatus/actions/usersalmon.php
plugins/OStatus/classes/Magicsig.php
plugins/OStatus/classes/Ostatus_profile.php
plugins/OStatus/lib/magicenvelope.php
plugins/OStatus/lib/salmon.php
plugins/OStatus/lib/salmonaction.php
plugins/OStatus/lib/salmonqueuehandler.php
plugins/OStatus/tests/slap.php
plugins/Poll/PollPlugin.php
plugins/Poll/actions/newpoll.php
plugins/Poll/css/poll.css [new file with mode: 0644]
plugins/Poll/poll.css [deleted file]
plugins/QnA/QnAPlugin.php
plugins/QnA/css/qna.css
plugins/TwitterBridge/lib/twitterimport.php
plugins/WebFinger/actions/webfinger.php
theme/neo/css/display.css

index 0c12c7d74ad70ca022dfe57b9c0f71ff462473da..060f1ecd7d5c2231f66cc067dd60c713d81a319d 100644 (file)
@@ -170,6 +170,9 @@ class NewnoticeAction extends FormAction
 
             Event::handle('EndNoticeSaveWeb', array($this, $notice));
         }
+
+        assert($notice instanceof Notice);
+
         Event::handle('EndSaveNewNoticeWeb', array($this, $user, &$content_shortened, &$options));
 
         if (StatusNet::isAjax()) {
@@ -345,7 +348,7 @@ class NewnoticeAction extends FormAction
      *
      * @return void
      */
-    function showNotice($notice)
+    function showNotice(Notice $notice)
     {
         $nli = new NoticeListItem($notice, $this);
         $nli->show();
index 28cb68c121f224c9b1b93f4c3f5eb09ba1d212bd..5e2be9f9d2b2ff48c685026779178a76e3b6e157 100644 (file)
@@ -114,20 +114,19 @@ class ShownoticeAction extends Action
         $id = $this->arg('notice');
 
         $notice = Notice::getKV('id', $id);
+        if ($notice instanceof Notice) {
+            // Alright, got it!
+            return $notice;
+        }
 
-        if (!$notice instanceof Notice) {
-            // Did we used to have it, and it got deleted?
-            $deleted = Deleted_notice::getKV($id);
-            if ($deleted instanceof Deleted_notice) {
-                // TRANS: Client error displayed trying to show a deleted notice.
-                $this->clientError(_('Notice deleted.'), 410);
-            } else {
-                // TRANS: Client error displayed trying to show a non-existing notice.
-                $this->clientError(_('No such notice.'), 404);
-            }
-            return false;
+        // Did we use to have it, and it got deleted?
+        $deleted = Deleted_notice::getKV('id', $id);
+        if ($deleted instanceof Deleted_notice) {
+            // TRANS: Client error displayed trying to show a deleted notice.
+            $this->clientError(_('Notice deleted.'), 410);
         }
-        return $notice;
+        // TRANS: Client error displayed trying to show a non-existing notice.
+        $this->clientError(_('No such notice.'), 404);
     }
 
     /**
index 6db1c7b806c79a5f6219d7dcbdacb10db5d885e8..9b703aa425ae25d9fc3b6e4c976b8fd778897151 100644 (file)
@@ -91,6 +91,7 @@ class File extends Managed_DataObject
         }
 
         Event::handle('EndFileSaveNew', array($file, $redir_data, $given_url));
+        assert ($file instanceof File);
         return $file;
     }
 
@@ -109,16 +110,32 @@ class File extends Managed_DataObject
      *
      * @return mixed File on success, -1 on some errors
      *
-     * @throws ServerException on some errors
+     * @throws ServerException on failure
      */
-    public function processNew($given_url, $notice_id=null, $followRedirects=true) {
-        if (empty($given_url)) return -1;   // error, no url to process
+    public static function processNew($given_url, $notice_id=null, $followRedirects=true) {
+        if (empty($given_url)) {
+            throw new ServerException('No given URL to process');
+        }
+
         $given_url = File_redirection::_canonUrl($given_url);
-        if (empty($given_url)) return -1;   // error, no url to process
+        if (empty($given_url)) {
+            throw new ServerException('No canonical URL from given URL to process');
+        }
+
         $file = File::getKV('url', $given_url);
-        if (empty($file)) {
+        if (!$file instanceof File) {
+            // First check if we have a lookup trace for this URL already
             $file_redir = File_redirection::getKV('url', $given_url);
-            if (empty($file_redir)) {
+            if ($file_redir instanceof File_redirection) {
+                $file = File::getKV('id', $file_redir->file_id);
+                if (!$file instanceof File) {
+                    // File did not exist, let's clean up the File_redirection entry
+                    $file_redir->delete();
+                }
+            }
+
+            // If we still don't have a File object, let's create one now!
+            if (!$file instanceof File) {
                 // @fixme for new URLs this also looks up non-redirect data
                 // such as target content type, size, etc, which we need
                 // for File::saveNew(); so we call it even if not following
@@ -133,10 +150,11 @@ class File extends Managed_DataObject
                     // TRANS: Server exception thrown when a URL cannot be processed.
                     throw new ServerException(sprintf(_("Cannot process URL '%s'"), $given_url));
                 }
+
                 // TODO: max field length
                 if ($redir_url === $given_url || strlen($redir_url) > 255 || !$followRedirects) {
-                    $x = File::saveNew($redir_data, $given_url);
-                    $file_id = $x->id;
+                    // Save the File object based on our lookup trace
+                    $file = File::saveNew($redir_data, $given_url);
                 } else {
                     // This seems kind of messed up... for now skipping this part
                     // if we're already under a redirect, so we don't go into
@@ -146,31 +164,23 @@ class File extends Managed_DataObject
                     //
                     // Seen in the wild with clojure.org, which redirects through
                     // wikispaces for auth and appends session data in the URL params.
-                    $x = File::processNew($redir_url, $notice_id, /*followRedirects*/false);
-                    $file_id = $x->id;
-                    File_redirection::saveNew($redir_data, $file_id, $given_url);
+                    $file = self::processNew($redir_url, $notice_id, /*followRedirects*/false);
+                    File_redirection::saveNew($redir_data, $file->id, $given_url);
                 }
-            } else {
-                $file_id = $file_redir->file_id;
             }
-        } else {
-            $file_id = $file->id;
-            $x = $file;
-        }
 
-        if (empty($x)) {
-            $x = File::getKV('id', $file_id);
-            if (empty($x)) {
-                // @todo FIXME: This could possibly be a clearer message :)
-                // TRANS: Server exception thrown when... Robin thinks something is impossible!
-                throw new ServerException(_('Robin thinks something is impossible.'));
+            if (!$file instanceof File) {
+                // This should only happen if File::saveNew somehow did not return a File object,
+                // though we have an assert for that in case the event there might've gone wrong.
+                // If anything else goes wrong, there should've been an exception thrown.
+                throw new ServerException('URL processing failed without new File object');
             }
         }
 
         if (!empty($notice_id)) {
-            File_to_post::processNew($file_id, $notice_id);
+            File_to_post::processNew($file->id, $notice_id);
         }
-        return $x;
+        return $file;
     }
 
     public static function respectsQuota(Profile $scoped, $fileSize) {
@@ -364,16 +374,13 @@ class File extends Managed_DataObject
                 Event::handle('FileEnclosureMetadata', array($this, &$enclosure));
             }
         }
+        if (empty($enclosure->mimetype)) {
+            // This means we don't know what it is, so it can't be an enclosure!
+            throw new ServerException('Unknown enclosure mimetype, not enough metadata');
+        }
         return $enclosure;
     }
 
-    // quick back-compat hack, since there's still code using this
-    function isEnclosure()
-    {
-        $enclosure = $this->getEnclosure();
-        return !empty($enclosure);
-    }
-
     /**
      * Get the attachment's thumbnail record, if any.
      * Make sure you supply proper 'int' typed variables (or null).
index 445b29e77c053a5e6c35f25c4be9de22ea09669d..2bd9581cf6dcf17fe4454f8244c7bc4d2942c7da 100644 (file)
@@ -67,27 +67,35 @@ class Memcached_DataObject extends Safe_DataObject
      * @param string  $cls       Class to fetch
      * @param string  $keyCol    name of column for key
      * @param array   $keyVals   key values to fetch
-     * @param boolean $skipNulls return only non-null results?
      *
      * @return array Array of objects, in order
      */
-    static function multiGetClass($cls, $keyCol, array $keyVals, $skipNulls=true)
+    static function multiGetClass($cls, $keyCol, array $keyVals)
     {
-        $result = self::pivotGetClass($cls, $keyCol, $keyVals);
+        $obj = new $cls;
 
-        $values = array_values($result);
+        // php-compatible, for settype(), datatype
+        $colType = $obj->columnType($keyCol);
 
-        if ($skipNulls) {
-            $tmp = array();
-            foreach ($values as $value) {
-                if (!empty($value)) {
-                    $tmp[] = $value;
-                }
-            }
-            $values = $tmp;
+        if (!in_array($colType, array('integer', 'int'))) {
+            // This is because I'm afraid to escape strings incorrectly
+            // in the way we use them below in FIND_IN_SET for MariaDB
+            throw new ServerException('Cannot do multiGet on anything but integer columns');
+        }
+
+        $obj->whereAddIn($keyCol, $keyVals, $colType);
+
+        // Since we're inputting straight to a query: format and escape
+        foreach ($keyVals as $key=>$val) {
+            settype($val, $colType);
+            $keyVals[$key] = $obj->escape($val);
         }
 
-        return new ArrayWrapper($values);
+        // FIND_IN_SET will make sure we keep the desired order
+        $obj->orderBy(sprintf("FIND_IN_SET(%s, '%s')", $keyCol, implode(',', $keyVals)));
+        $obj->find();
+
+        return $obj;
     }
 
     /**
index 25422aa332d5b8ef6b421075f8e505aeaeff3899..917095acab075b383e6a754aba3d9c0f1e5ea010 100644 (file)
@@ -141,14 +141,18 @@ class Notice extends Managed_DataObject
     const GROUP_SCOPE     = 4;
     const FOLLOWER_SCOPE  = 8;
 
-    protected $_profile = -1;
+    protected $_profile = array();
     
+    /**
+     * Will always return a profile, if anything fails it will
+     * (through _setProfile) throw a NoProfileException.
+     */
     public function getProfile()
     {
-        if ($this->_profile === -1) {
+        if (!isset($this->_profile[$this->profile_id])) {
             $this->_setProfile(Profile::getKV('id', $this->profile_id));
         }
-        return $this->_profile;
+        return $this->_profile[$this->profile_id];
     }
     
     public function _setProfile(Profile $profile=null)
@@ -156,7 +160,7 @@ class Notice extends Managed_DataObject
         if (!$profile instanceof Profile) {
             throw new NoProfileException($this->profile_id);
         }
-        $this->_profile = $profile;
+        $this->_profile[$this->profile_id] = $profile;
     }
 
     function delete($useWhere=false)
@@ -507,12 +511,22 @@ class Notice extends Managed_DataObject
                 throw new ClientException(_('You already repeated that notice.'));
             }
 
-            $notice->repeat_of = $repeat_of;
+            $notice->repeat_of = $repeat->id;
+            $notice->conversation = $repeat->conversation;
         } else {
-            $reply = self::getReplyTo($reply_to, $profile_id, $source, $final);
+            $reply = null;
 
-            if (!empty($reply)) {
+            // If $reply_to is specified, we check that it exists, and then
+            // return it if it does
+            if (!empty($reply_to)) {
+                $reply = Notice::getKV('id', $reply_to);
+            } elseif (in_array($source, array('xmpp', 'mail', 'sms'))) {
+                // If the source lacks capability of sending the "reply_to"
+                // metadata, let's try to find an inline replyto-reference.
+                $reply = self::getInlineReplyTo($profile, $final);
+            }
 
+            if ($reply instanceof Notice) {
                 if (!$reply->inScope($profile)) {
                     // TRANS: Client error displayed when trying to reply to a notice a the target has no access to.
                     // TRANS: %1$s is a user nickname, %2$d is a notice ID (number).
@@ -520,11 +534,17 @@ class Notice extends Managed_DataObject
                                                       $profile->nickname, $reply->id), 403);
                 }
 
-                $notice->reply_to     = $reply->id;
+                // If it's a repeat, the reply_to should be to the original
+                if (!empty($reply->repeat_of)) {
+                    $notice->reply_to = $reply->repeat_of;
+                } else {
+                    $notice->reply_to = $reply->id;
+                }
+                // But the conversation ought to be the same :)
                 $notice->conversation = $reply->conversation;
 
-                // If the original is private to a group, and notice has no group specified,
-                // make it to the same group(s)
+                // If the original is private to a group, and notice has
+                // no group specified, make it to the same group(s)
 
                 if (empty($groups) && ($reply->scope & Notice::GROUP_SCOPE)) {
                     $groups = array();
@@ -822,7 +842,11 @@ class Notice extends Managed_DataObject
         if (common_config('attachments', 'process_links')) {
             // @fixme validation?
             foreach (array_unique($urls) as $url) {
-                File::processNew($url, $this->id);
+                try {
+                    File::processNew($url, $this->id);
+                } catch (ServerException $e) {
+                    // Could not save URL. Log it?
+                }
             }
         }
     }
@@ -831,7 +855,11 @@ class Notice extends Managed_DataObject
      * @private callback
      */
     function saveUrl($url, $notice_id) {
-        File::processNew($url, $notice_id);
+        try {
+            File::processNew($url, $notice_id);
+        } catch (ServerException $e) {
+            // Could not save URL. Log it?
+        }
     }
 
     static function checkDupes($profile_id, $content) {
@@ -880,12 +908,11 @@ class Notice extends Managed_DataObject
         return true;
     }
 
-       protected $_attachments = -1;
+       protected $_attachments = array();
        
     function attachments() {
-
-               if ($this->_attachments != -1)  {
-            return $this->_attachments;
+               if (isset($this->_attachments[$this->id])) {
+            return $this->_attachments[$this->id];
         }
                
         $f2ps = File_to_post::listGet('post_id', array($this->id));
@@ -898,14 +925,14 @@ class Notice extends Managed_DataObject
                
                $files = File::multiGet('id', $ids);
 
-               $this->_attachments = $files->fetchAll();
+               $this->_attachments[$this->id] = $files->fetchAll();
                
-        return $this->_attachments;
+        return $this->_attachments[$this->id];
     }
 
        function _setAttachments($attachments)
        {
-           $this->_attachments = $attachments;
+           $this->_attachments[$this->id] = $attachments;
        }
 
     function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0)
@@ -1293,19 +1320,17 @@ class Notice extends Managed_DataObject
             return array();
         }
 
-        $sender = Profile::getKV($this->profile_id);
+        $sender = $this->getProfile();
 
         $replied = array();
 
         // If it's a reply, save for the replied-to author
         try {
             $parent = $this->getParent();
-            $author = $parent->getProfile();
-            if ($author instanceof Profile) {
-                $this->saveReply($author->id);
-                $replied[$author->id] = 1;
-                self::blow('reply:stream:%d', $author->id);
-            }
+            $parentauthor = $parent->getProfile();
+            $this->saveReply($parentauthor->id);
+            $replied[$parentauthor->id] = 1;
+            self::blow('reply:stream:%d', $parentauthor->id);
         } catch (Exception $e) {
             // Not a reply, since it has no parent!
         }
@@ -1359,7 +1384,7 @@ class Notice extends Managed_DataObject
         return $reply;
     }
 
-    protected $_replies = -1;
+    protected $_replies = array();
 
     /**
      * Pull the complete list of @-reply targets for this notice.
@@ -1368,8 +1393,8 @@ class Notice extends Managed_DataObject
      */
     function getReplies()
     {
-        if ($this->_replies != -1) {
-            return $this->_replies;
+        if (isset($this->_replies[$this->id])) {
+            return $this->_replies[$this->id];
         }
 
         $replyMap = Reply::listGet('notice_id', array($this->id));
@@ -1380,14 +1405,14 @@ class Notice extends Managed_DataObject
             $ids[] = $reply->profile_id;
         }
 
-        $this->_replies = $ids;
+        $this->_replies[$this->id] = $ids;
 
         return $ids;
     }
 
     function _setReplies($replies)
     {
-        $this->_replies = $replies;
+        $this->_replies[$this->id] = $replies;
     }
 
     /**
@@ -1435,7 +1460,7 @@ class Notice extends Managed_DataObject
      * @return array of Group objects
      */
     
-    protected $_groups = -1;
+    protected $_groups = array();
     
     function getGroups()
     {
@@ -1445,9 +1470,8 @@ class Notice extends Managed_DataObject
             return array();
         }
         
-        if ($this->_groups != -1)
-        {
-            return $this->_groups;
+        if (isset($this->_groups[$this->id])) {
+            return $this->_groups[$this->id];
         }
         
         $gis = Group_inbox::listGet('notice_id', array($this->id));
@@ -1461,14 +1485,14 @@ class Notice extends Managed_DataObject
                
                $groups = User_group::multiGet('id', $ids);
                
-               $this->_groups = $groups->fetchAll();
+               $this->_groups[$this->id] = $groups->fetchAll();
                
-               return $this->_groups;
+               return $this->_groups[$this->id];
     }
     
     function _setGroups($groups)
     {
-        $this->_groups = $groups;
+        $this->_groups[$this->id] = $groups;
     }
 
     /**
@@ -1726,75 +1750,37 @@ class Notice extends Managed_DataObject
      * Determine which notice, if any, a new notice is in reply to.
      *
      * For conversation tracking, we try to see where this notice fits
-     * in the tree. Rough algorithm is:
-     *
-     * if (reply_to is set and valid) {
-     *     return reply_to;
-     * } else if ((source not API or Web) and (content starts with "T NAME" or "@name ")) {
-     *     return ID of last notice by initial @name in content;
-     * }
+     * in the tree. Beware that this may very well give false positives
+     * and add replies to wrong threads (if there have been newer posts
+     * by the same user as we're replying to).
      *
-     * Note that all @nickname instances will still be used to save "reply" records,
-     * so the notice shows up in the mentioned users' "replies" tab.
-     *
-     * @param integer $reply_to   ID passed in by Web or API
-     * @param integer $profile_id ID of author
-     * @param string  $source     Source tag, like 'web' or 'gwibber'
+     * @param Profile $sender     Author profile
      * @param string  $content    Final notice content
      *
      * @return integer ID of replied-to notice, or null for not a reply.
      */
 
-    static function getReplyTo($reply_to, $profile_id, $source, $content)
+    static function getInlineReplyTo(Profile $sender, $content)
     {
-        static $lb = array('xmpp', 'mail', 'sms', 'omb');
-
-        // If $reply_to is specified, we check that it exists, and then
-        // return it if it does
-
-        if (!empty($reply_to)) {
-            $reply_notice = Notice::getKV('id', $reply_to);
-            if ($reply_notice instanceof Notice) {
-                return $reply_notice;
-            }
-        }
-
-        // If it's not a "low bandwidth" source (one where you can't set
-        // a reply_to argument), we return. This is mostly web and API
-        // clients.
-
-        if (!in_array($source, $lb)) {
-            return null;
-        }
-
         // Is there an initial @ or T?
-
-        if (preg_match('/^T ([A-Z0-9]{1,64}) /', $content, $match) ||
-            preg_match('/^@([a-z0-9]{1,64})\s+/', $content, $match)) {
+        if (preg_match('/^T ([A-Z0-9]{1,64}) /', $content, $match)
+                || preg_match('/^@([a-z0-9]{1,64})\s+/', $content, $match)) {
             $nickname = common_canonical_nickname($match[1]);
         } else {
             return null;
         }
 
         // Figure out who that is.
-
-        $sender = Profile::getKV('id', $profile_id);
-        if (!$sender instanceof Profile) {
-            return null;
-        }
-
         $recipient = common_relative_profile($sender, $nickname, common_sql_now());
 
-        if (!$recipient instanceof Profile) {
-            return null;
-        }
-
-        // Get their last notice
-
-        $last = $recipient->getCurrentNotice();
-
-        if ($last instanceof Notice) {
-            return $last;
+        if ($recipient instanceof Profile) {
+            // Get their last notice
+            $last = $recipient->getCurrentNotice();
+            if ($last instanceof Notice) {
+                return $last;
+            }
+            // Maybe in the future we want to handle something else below
+            // so don't return getCurrentNotice() immediately.
         }
 
         return null;
@@ -2610,7 +2596,7 @@ class Notice extends Managed_DataObject
                }
     }
 
-    protected $_faves;
+    protected $_faves = array();
 
     /**
      * All faves of this notice
@@ -2620,17 +2606,17 @@ class Notice extends Managed_DataObject
 
     function getFaves()
     {
-        if (isset($this->_faves) && is_array($this->_faves)) {
-            return $this->_faves;
+        if (isset($this->_faves[$this->id])) {
+            return $this->_faves[$this->id];
         }
         $faveMap = Fave::listGet('notice_id', array($this->id));
-        $this->_faves = $faveMap[$this->id];
-        return $this->_faves;
+        $this->_faves[$this->id] = $faveMap[$this->id];
+        return $this->_faves[$this->id];
     }
 
     function _setFaves($faves)
     {
-        $this->_faves = $faves;
+        $this->_faves[$this->id] = $faves;
     }
 
     static function fillFaves(&$notices)
@@ -2665,21 +2651,21 @@ class Notice extends Managed_DataObject
         }
     }
 
-    protected $_repeats;
+    protected $_repeats = array();
 
     function getRepeats()
     {
-        if (isset($this->_repeats) && is_array($this->_repeats)) {
-            return $this->_repeats;
+        if (isset($this->_repeats[$this->id])) {
+            return $this->_repeats[$this->id];
         }
         $repeatMap = Notice::listGet('repeat_of', array($this->id));
-        $this->_repeats = $repeatMap[$this->id];
-        return $this->_repeats;
+        $this->_repeats[$this->id] = $repeatMap[$this->id];
+        return $this->_repeats[$this->id];
     }
 
     function _setRepeats($repeats)
     {
-        $this->_repeats = $repeats;
+        $this->_repeats[$this->id] = $repeats;
     }
 
     static function fillRepeats(&$notices)
index 961187bb93cb436135eac7159798f74914e5ecde..e48092cd8382f06c00defe93fb39429ec97b5848 100644 (file)
@@ -168,17 +168,6 @@ class Profile extends Managed_DataObject
             return null;
         }
 
-        foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) {
-            // We don't do a scaled one if original is our scaled size
-            if (!($avatar->width == $size && $avatar->height == $size)) {
-                try {
-                    Avatar::newSize($this, $size);
-                } catch (Exception $e) {
-                    // should we abort the generation and live without smaller avatars?
-                }
-            }
-        }
-
         return $avatar;
     }
 
@@ -237,9 +226,9 @@ class Profile extends Managed_DataObject
                 return $notice->_items[0];
             }
             return $notice;
-        } else {
-            return null;
         }
+        
+        return null;
     }
 
     function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
index c506b17e857a64596bfc1300a35df40c1e886cf6..fc47485c1892199fd84a92fe54383c7532e41189 100644 (file)
@@ -32,6 +32,8 @@ if (!defined('STATUSNET')) {
     exit(1);
 }
 
+require_once(INSTALLDIR.'/lib/activitystreamjsondocument.php');
+
 /**
  * A noun-ish thing in the activity universe
  *
index 9f26b3b304135dec609a0794e39c778b45f3ccb3..55860efa554286d900191ff25076916f6261771d 100644 (file)
@@ -384,13 +384,15 @@ class ApiAction extends Action
             $twitter_status['attachments'] = array();
 
             foreach ($attachments as $attachment) {
-                $enclosure_o=$attachment->getEnclosure();
-                if ($enclosure_o) {
+                try {
+                    $enclosure_o = $attachment->getEnclosure();
                     $enclosure = array();
                     $enclosure['url'] = $enclosure_o->url;
                     $enclosure['mimetype'] = $enclosure_o->mimetype;
                     $enclosure['size'] = $enclosure_o->size;
                     $twitter_status['attachments'][] = $enclosure;
+                } catch (ServerException $e) {
+                    // There was not enough metadata available
                 }
             }
         }
@@ -510,13 +512,15 @@ class ApiAction extends Action
             $enclosures = array();
 
             foreach ($attachments as $attachment) {
-                $enclosure_o=$attachment->getEnclosure();
-                if ($enclosure_o) {
+                try {
+                    $enclosure_o = $attachment->getEnclosure();
                     $enclosure = array();
                     $enclosure['url'] = $enclosure_o->url;
                     $enclosure['mimetype'] = $enclosure_o->mimetype;
                     $enclosure['size'] = $enclosure_o->size;
                     $enclosures[] = $enclosure;
+                } catch (ServerException $e) {
+                    // There was not enough metadata available
                 }
             }
 
index b00e476d92a1e11f231fea7b40977f7e096e97c8..c21d73cff5665ff04460ee8c6906ab10884408c2 100644 (file)
@@ -58,7 +58,7 @@ class NoticeList extends Widget
      *
      * @param Notice $notice stream of notices from DB_DataObject
      */
-    function __construct($notice, $out=null)
+    function __construct(Notice $notice, $out=null)
     {
         parent::__construct($out);
         $this->notice = $notice;
@@ -111,7 +111,7 @@ class NoticeList extends Widget
      *
      * @return NoticeListItem a list item for displaying the notice
      */
-    function newListItem($notice)
+    function newListItem(Notice $notice)
     {
         return new NoticeListItem($notice, $this->out);
     }
index b8ebe3bede27a85264136246cdc99bc38cac1489..75a1bdaab5469ccd539554dc89c3c8a539431b6d 100644 (file)
@@ -65,12 +65,12 @@ class NoticeListItem extends Widget
      *
      * @param Notice $notice The notice we'll display
      */
-    function __construct($notice, $out=null)
+    function __construct(Notice $notice, HTMLOutputter $out=null)
     {
         parent::__construct($out);
         if (!empty($notice->repeat_of)) {
             $original = Notice::getKV('id', $notice->repeat_of);
-            if (empty($original)) { // could have been deleted
+            if (!$original instanceof Notice) { // could have been deleted
                 $this->notice = $notice;
             } else {
                 $this->notice = $original;
index 4a4eac344a3b95e955e5bcd22fa26eeb19113a50..c1fcbbf8cd1fcf904d875b882e89c04f45ad174c 100644 (file)
@@ -70,7 +70,7 @@ class NoticeSection extends Section
         return null;
     }
 
-    function showNotice($notice)
+    function showNotice(Notice $notice)
     {
         $profile = $notice->getProfile();
         if (empty($profile)) {
index dac7fa4606e8d4fa5563f4229b10fc8a22b5a275..684ecd6d8c1f1370e088e83e3f60d03bbc377882 100644 (file)
@@ -278,8 +278,8 @@ class Rss10Action extends Action
         $attachments = $notice->attachments();
         if($attachments){
             foreach($attachments as $attachment){
-                $enclosure=$attachment->getEnclosure();
-                if ($enclosure) {
+                try {
+                    $enclosure = $attachment->getEnclosure();
                     $attribs = array('rdf:resource' => $enclosure->url);
                     if ($enclosure->title) {
                         $attribs['dc:title'] = $enclosure->title;
@@ -294,6 +294,8 @@ class Rss10Action extends Action
                         $attribs['enc:type'] = $enclosure->mimetype;
                     }
                     $this->element('enc:enclosure', $attribs);
+                } catch (ServerException $e) {
+                    // There was not enough metadata available
                 }
                 $this->element('sioc:links_to', array('rdf:resource'=>$attachment->url));
             }
index e8abeb74fc2048ae0d28abf10f0375e5e87e7b5b..92d9fbc571d88e6872a6e66c187242187f122fb3 100644 (file)
@@ -50,7 +50,7 @@ class ThreadedNoticeList extends NoticeList
 {
     protected $userProfile;
 
-    function __construct($notice, $out=null, $profile=-1)
+    function __construct(Notice $notice, HTMLOutputter $out=null, $profile=-1)
     {
         parent::__construct($notice, $out);
         if (is_int($profile) && $profile == -1) {
@@ -89,7 +89,7 @@ class ThreadedNoticeList extends NoticeList
             
             if ($notice->repeat_of) {
                 $orig = Notice::getKV('id', $notice->repeat_of);
-                if ($orig) {
+                if ($orig instanceof Notice) {
                     $notice = $orig;
                 }
             }
@@ -102,7 +102,7 @@ class ThreadedNoticeList extends NoticeList
 
             // Get the convo's root notice
             $root = $notice->conversationRoot($this->userProfile);
-            if ($root) {
+            if ($root instanceof Notice) {
                 $notice = $root;
             }
 
@@ -147,7 +147,7 @@ class ThreadedNoticeList extends NoticeList
      *
      * @return NoticeListItem a list item for displaying the notice
      */
-    function newListItem($notice)
+    function newListItem(Notice $notice)
     {
         return new ThreadedNoticeListItem($notice, $this->out, $this->userProfile);
     }
@@ -174,7 +174,7 @@ class ThreadedNoticeListItem extends NoticeListItem
 {
     protected $userProfile = null;
 
-    function __construct($notice, $out=null, $profile=null)
+    function __construct(Notice $notice, HTMLOutputter $out=null, $profile=null)
     {
         parent::__construct($notice, $out);
         $this->userProfile = $profile;
@@ -208,6 +208,11 @@ class ThreadedNoticeListItem extends NoticeListItem
             $moreCutoff = null;
             while ($notice->fetch()) {
                 if (Event::handle('StartAddNoticeReply', array($this, $this->notice, $notice))) {
+                    // Don't list repeats as separate notices in a conversation
+                    if (!empty($notice->repeat_of)) {
+                        continue;
+                    }
+
                     if ($notice->id == $this->notice->id) {
                         // Skip!
                         continue;
@@ -269,7 +274,7 @@ class ThreadedNoticeListSubItem extends NoticeListItem
 {
     protected $root = null;
 
-    function __construct($notice, $root, $out)
+    function __construct(Notice $notice, $root, $out)
     {
         $this->root = $root;
         parent::__construct($notice, $out);
@@ -316,6 +321,8 @@ class ThreadedNoticeListSubItem extends NoticeListItem
     {
         $item = new ThreadedNoticeListInlineFavesItem($this->notice, $this->out);
         $hasFaves = $item->show();
+        $item = new ThreadedNoticeListInlineRepeatsItem($this->notice, $this->out);
+        $hasRepeats = $item->show();
         parent::showEnd();
     }
 }
@@ -327,7 +334,7 @@ class ThreadedNoticeListMoreItem extends NoticeListItem
 {
     protected $cnt;
 
-    function __construct($notice, $out, $cnt)
+    function __construct(Notice $notice, HTMLOutputter $out, $cnt)
     {
         parent::__construct($notice, $out);
         $this->cnt = $cnt;
@@ -551,7 +558,7 @@ class ThreadedNoticeListInlineFavesItem extends ThreadedNoticeListFavesItem
 }
 
 /**
- * Placeholder for showing faves...
+ * Placeholder for showing repeats...
  */
 class ThreadedNoticeListRepeatsItem extends NoticeListActorsItem
 {
@@ -612,3 +619,17 @@ class ThreadedNoticeListRepeatsItem extends NoticeListActorsItem
         $this->out->elementEnd('li');
     }
 }
+
+// @todo FIXME: needs documentation.
+class ThreadedNoticeListInlineRepeatsItem extends ThreadedNoticeListRepeatsItem
+{
+    function showStart()
+    {
+        $this->out->elementStart('div', array('class' => 'entry-content notice-repeats'));
+    }
+
+    function showEnd()
+    {
+        $this->out->elementEnd('div');
+    }
+}
index c76f7b276268615467bc208e8c9314184f7330d2..ec0d7c6a251913a26cd7dbea53760c5024578fd7 100644 (file)
@@ -989,22 +989,27 @@ function common_linkify($url) {
 
     $f = File::getKV('url', $longurl);
 
-    if (empty($f)) {
+    if (!$f instanceof File) {
         if (common_config('attachments', 'process_links')) {
             // XXX: this writes to the database. :<
-            $f = File::processNew($longurl);
+            try {
+                $f = File::processNew($longurl);
+            } catch (ServerException $e) {
+                $f = null;
+            }
         }
     }
 
-    if (!empty($f)) {
-        if ($f->getEnclosure()) {
+    if ($f instanceof File) {
+        try {
+            $enclosure = $f->getEnclosure();
             $is_attachment = true;
             $attachment_id = $f->id;
 
             $thumb = File_thumbnail::getKV('file_id', $f->id);
-            if (!empty($thumb)) {
-                $has_thumb = true;
-            }
+            $has_thumb = ($thumb instanceof File_thumbnail);
+        } catch (ServerException $e) {
+            // There was not enough metadata available
         }
     }
 
@@ -2169,17 +2174,16 @@ function common_shorten_url($long_url, User $user=null, $force = false)
     if (Event::handle('StartShortenUrl',
                       array($long_url, $shortenerName, &$shortenedUrl))) {
         if ($shortenerName == 'internal') {
-            $f = File::processNew($long_url);
-            if (empty($f)) {
-                return $long_url;
-            } else {
-                $shortenedUrl = common_local_url('redirecturl',
-                                                 array('id' => $f->id));
+            try {
+                $f = File::processNew($long_url);
+                $shortenedUrl = common_local_url('redirecturl', array('id' => $f->id));
                 if ((mb_strlen($shortenedUrl) < mb_strlen($long_url)) || $force) {
                     return $shortenedUrl;
                 } else {
                     return $long_url;
                 }
+            } catch (ServerException $e) {
+                return $long_url;
             }
         } else {
             return $long_url;
index d068dea0f031b295a85fa73232c223abd437fb4e..11f4d08cb8ed1b484d8ad1c0bbe1bdabfdf2e8fe 100644 (file)
@@ -63,7 +63,7 @@ class Widget
      * @param HTMLOutputter $out output helper, defaults to null
      */
 
-    function __construct($out=null)
+    function __construct(HTMLOutputter $out=null)
     {
         $this->out = $out;
     }
index 381dc4dfc936ba715b6e99328b4274c43b199442..499445abef65c4245708e2e1b9cd9ef2e644be7c 100644 (file)
@@ -94,7 +94,7 @@ class BookmarkPlugin extends MicroAppPlugin
      */
     function onEndShowStyles($action)
     {
-        $action->cssLink($this->path('bookmark.css'));
+        $action->cssLink($this->path('css/bookmark.css'));
         return true;
     }
 
index 5eac33b11b73b7f32530c06812386093694ca135..c4cc4a8487cd81ed8c00f73036ca1b24c9d527ed 100644 (file)
@@ -78,17 +78,19 @@ class BookmarkforurlAction extends Action
             throw new ClientException(_('Invalid URL.'), 400);
         }
 
-        $f = File::getKV('url', $this->url);
-
-        if (empty($url)) { 
-           $f = File::processNew($this->url);
+        try {
+            // processNew will first try to fetch a locally stored File entry
+            $f = File::processNew($this->url);
+        } catch (ServerException $e) {
+            $f = null;
         }
 
         // How about now?
 
-        if (!empty($f)) {
+        if ($f instanceof File) {
+            // FIXME: Use some File metadata Event instead
             $this->oembed    = File_oembed::getKV('file_id', $f->id);
-            if (!empty($this->oembed)) {
+            if ($this->oembed instanceof File_oembed) {
                 $this->title = $this->oembed->title;
             }
             $this->thumbnail = File_thumbnail::getKV('file_id', $f->id);
index 168a6e07b69d1383cf23301097f6d806f362cb16..bc043f41ed51e01eafdde5878efde1fb4e07f4ae 100644 (file)
@@ -191,7 +191,7 @@ class NewbookmarkAction extends Action
      *
      * @return void
      */
-    function showNotice($notice)
+    function showNotice(Notice $notice)
     {
         class_exists('NoticeList'); // @fixme hack for autoloader
         $nli = new NoticeListItem($notice, $this);
diff --git a/plugins/Bookmark/bookmark.css b/plugins/Bookmark/bookmark.css
deleted file mode 100644 (file)
index 966adbc..0000000
+++ /dev/null
@@ -1 +0,0 @@
-.bookmark-tags li { display: inline; }
diff --git a/plugins/Bookmark/css/bookmark.css b/plugins/Bookmark/css/bookmark.css
new file mode 100644 (file)
index 0000000..77b7ab3
--- /dev/null
@@ -0,0 +1,135 @@
+/* Bookmark specific styles */
+
+.bookmark-tags li { display: inline; }
+
+.bookmark h3 {
+    margin: 0px 0px 8px 0px;
+    line-height: 3em;
+}
+
+.bookmark-notice-count {
+    border-radius: 4px;
+       -moz-border-radius: 4px;
+       -webkit-border-radius: 4px;
+    padding: 1px 6px;
+    font-size: 1.2em;
+    line-height: 1.2em;
+    background: #fff;
+    border: 1px solid #7b8dbb;
+    color: #3e3e8c !important;
+    position: relative;
+    right: 4px;
+    margin-left: 10px;
+}
+
+.bookmark-notice-count:hover {
+    text-decoration: none;
+    background: #f2f2f2;
+    border: 1px solid #7b8dbb;
+    text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.5);
+}
+
+.notice .bookmark-description {
+    clear: both;
+    margin-left: 0px;
+    margin-bottom: 0px;
+}
+
+.notice .bookmark-author {
+    margin-left: 0px;
+    float: left;
+}
+
+.bookmark-tags {
+    clear: both;
+    margin-bottom: 8px;
+    line-height: 1.6em;
+}
+
+ul.bookmark-tags a {
+    border-radius: 4px;
+       -moz-border-radius: 4px;
+       -webkit-border-radius: 4px;
+    padding: 1px 6px;
+    background: #f2f2f2;
+    color: #3e3e8c !important;
+    text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.5);
+    font-size: 0.88em;
+}
+
+ul.bookmark-tags a:hover {
+    background-color: #cdd1dd;
+    text-decoration: none;
+}
+
+.bookmark-avatar {
+    float: none !important;
+    position: relative;
+    top: 2px;
+}
+
+.bookmark div.entry-content {
+    font-size: 0.88em;
+    line-height: 1.2em;
+    margin-top: 6px;
+    opacity: 0.6;
+    margin-bottom: 0px;
+}
+
+.bookmark:hover div.entry-content {
+    opacity: 1;
+}
+
+#bookmarkpopup {
+    min-width: 600px;
+    margin-top: 0px;
+    height: 100%;
+    border: 10px solid #364A84;
+    background: #364A84;
+}
+
+#bookmarkpopup #wrap {
+    width: auto;
+    min-width: 560px;
+    padding: 40px 0px 25px 0px;
+    margin-right: 2px;
+    background: #fff url(../mobilelogo.png) no-repeat 6px 6px;
+}
+
+#bookmarkpopup #header {
+    width: auto;
+    padding: 0px 10px;
+}
+
+#bookmarkpopup .form_settings label {
+    margin-top: 2px;
+    text-align: right;
+    width: 24%;
+    font-size: 1.2em;
+}
+
+#bookmarkpopup .form_settings .form_data input {
+    width: 60%;
+}
+
+#bookmarkpopup .form_guide {
+    color: #777;
+}
+
+#bookmarkpopup #bookmark-submit {
+    min-width: 100px;
+}
+
+#bookmarkpopup fieldset fieldset {
+    margin-bottom: 10px;
+}
+
+#form_initial_bookmark.form_settings .form_data li {
+    margin-bottom: 0px;
+}
+
+#form_new_bookmark.form_settings .bookmarkform-thumbnail {
+    position: absolute;
+    top: 50px;
+    right: 0px;
+}
index 965eef7744f006df74fe6e75411523496ebdc4e6..c1c8ec77c3365da82e24c1aac72f58e82708dfce 100644 (file)
@@ -316,7 +316,7 @@ class EventPlugin extends MicroappPlugin
 
     function onEndShowStyles($action)
     {
-        $action->cssLink($this->path('event.css'));
+        $action->cssLink($this->path('css/event.css'));
         return true;
     }
 
index d05b5af8d3a5bd0ad4690267d266a925b1eac62e..9483db7b4e55ab5360410560bcbfef389ff407b6 100644 (file)
@@ -320,7 +320,7 @@ class NeweventAction extends Action
      *
      * @return void
      */
-    function showNotice($notice)
+    function showNotice(Notice $notice)
     {
         $nli = new NoticeListItem($notice, $this);
         $nli->show();
diff --git a/plugins/Event/css/event.css b/plugins/Event/css/event.css
new file mode 100644 (file)
index 0000000..6c69cf1
--- /dev/null
@@ -0,0 +1,131 @@
+/* Event specific styles */
+
+.event-tags li { display: inline; }
+.event-mentions li { display: inline; }
+.event-avatar { float: left; }
+.event-notice-count { float: right; }
+.event-info { float: left; }
+.event-title { margin-left: 0px; }
+#content .event .entry-title { margin-left: 0px; }
+#content .event .entry-content { margin-left: 0px; }
+.ui-autocomplete {
+    max-height: 100px;
+    overflow-y: auto;
+    /* prevent horizontal scrollbar */
+    overflow-x: hidden;
+    /* add padding to account for vertical scrollbar */
+    padding-right: 20px;
+}
+
+.attending-list { list-style-type: none; float: left; width: 100%; }
+
+#form_event_rsvp { clear: left; }
+
+li.rsvp-list { float: left; clear: left; }
+
+li.rsvp-list ul.entities {
+    display:inline;
+}
+li.rsvp-list .entities li {
+    list-style-type: none;
+    margin-right: 3px;
+    margin-bottom: 8px;
+    display: inline;
+}
+li.rsvp-list .entities li .photo {
+    margin: 0 !important;
+    float: none !important;
+}
+li.rsvp-list .entities li .fn {
+    display: none;
+}
+
+.notice .vevent div {
+    margin-bottom: 8px;
+}
+
+.event-info {
+    margin-left: 0px !important;
+    margin-top: 2px !important;
+}
+
+.notice .event-info + .notice-options {
+    margin-top: 14px;
+}
+
+.notice .threaded-replies .event-info + .notice-options {
+    margin-top: 20px;
+}
+
+#form_event_rsvp #new_rsvp_data {
+    display: inline;
+    margin: 10px 0px;
+}
+
+#form_event_rsvp input.submit {
+    height: auto;
+    padding: 0px 10px;
+    margin-left: 10px;
+    color:#fff;
+    font-weight: bold;
+    text-transform: uppercase;
+    font-size: 1.1em;
+    text-shadow: 0px -1px 0px rgba(0, 0, 0, 0.2);
+    border: 1px solid #d7621c;
+    border-radius: 4px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+    background: #FB6104;
+    background: -moz-linear-gradient(top, #ff9d63 0%, #fb6104 100%);
+    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ff9d63), color-stop(100%,#fb6104));
+    background: -webkit-linear-gradient(top, #ff9d63 0%,#fb6104 100%);
+    background: -o-linear-gradient(top, #ff9d63 0%,#fb6104 100%);
+    background: -ms-linear-gradient(top, #ff9d63 0%,#fb6104 100%);
+    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff9d63', endColorstr='#fb6104',GradientType=0 );
+    background: linear-gradient(top, #ff9d63 0%,#fb6104 100%);
+}
+
+#form_event_rsvp input.submit:hover {
+    text-shadow: 0px -1px 0px rgba(0, 0, 0, 0.6);
+    background: #ff9d63;
+    background: -moz-linear-gradient(top, #fb6104 0%, #fc8035 100%);
+    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fb6104), color-stop(100%,#fc8035));
+    background: -webkit-linear-gradient(top, #fb6104 0%,#fc8035 100%);
+    background: -o-linear-gradient(top, #fb6104 0%,#fc8035 100%);
+    background: -ms-linear-gradient(top, #fb6104 0%,#fc8035 100%);
+    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fb6104', endColorstr='#fc8035',GradientType=0 );
+    background: linear-gradient(top, #fb6104 0%,#fc8035 100%);
+}
+
+#wrap .vevent form.processing input.submit {
+    text-indent: 0;
+    background: #ff9d63;
+}
+
+#input_form_event .form_settings .form_data {
+    float: left;
+}
+
+#input_form_event .form_settings .form_data li {
+    float: left;
+    width: auto;
+}
+
+#input_form_event .form_settings .form_data label {
+    width: auto;
+}
+
+label[for=event-starttime], label[for=event-endtime] {
+    display: none !important;
+}
+
+#event-starttime, #event-endtime {
+    margin-top:  -1px;
+    margin-bottom:  -1px;
+    height: 2em;
+}
+
+#event-startdate, #event-enddate {
+    margin-right: 20px;
+    width: 120px;
+}
diff --git a/plugins/Event/event.css b/plugins/Event/event.css
deleted file mode 100644 (file)
index e5af5cf..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-.event-tags li { display: inline; }
-.event-mentions li { display: inline; }
-.event-avatar { float: left; }
-.event-notice-count { float: right; }
-.event-info { float: left; }
-.event-title { margin-left: 0px; }
-#content .event .entry-title { margin-left: 0px; }
-#content .event .entry-content { margin-left: 0px; }
-.ui-autocomplete {
-    max-height: 100px;
-    overflow-y: auto;
-    /* prevent horizontal scrollbar */
-    overflow-x: hidden;
-    /* add padding to account for vertical scrollbar */
-    padding-right: 20px;
-}
-
-.attending-list { list-style-type: none; float: left; width: 100%; }
-
-#form_event_rsvp { clear: left; }
-
-li.rsvp-list { float: left; clear: left; }
-
-li.rsvp-list ul.entities {
-    display:inline;
-}
-li.rsvp-list .entities li {
-    list-style-type: none;
-    margin-right: 3px;
-    margin-bottom: 8px;
-    display: inline;
-}
-li.rsvp-list .entities li .photo {
-    margin: 0 !important;
-    float: none !important;
-}
-li.rsvp-list .entities li .fn {
-    display: none;
-}
index deb92e93131a57041e47223cfea0cc9824eea769..5b512dfdff6cb5ca70ea660601cb38582dab08ac 100644 (file)
@@ -651,9 +651,10 @@ class Facebookclient
 
         foreach($attachments as $attachment)
         {
-            if($enclosure = $attachment->getEnclosure()){
+            try {
+                $enclosure = $attachment->getEnclosure();
                 $fbmedia = $this->getFacebookMedia($enclosure);
-            }else{
+            } catch (ServerException $e) {
                 $fbmedia = $this->getFacebookMedia($attachment);
             }
             if($fbmedia){
index 37099c24524bf8812fa0b98730324da282af6e97..42ee9a43efb86a5299adfff1dfd5249d1e0f4be8 100644 (file)
@@ -1341,16 +1341,16 @@ class OStatusPlugin extends Plugin
         $xrd->links[] = new XML_XRD_Element_Link(Salmon::NS_REPLIES, $salmon_url);
         $xrd->links[] = new XML_XRD_Element_Link(Salmon::NS_MENTIONS, $salmon_url);
 
-        // Get this user's keypair
-        $magickey = Magicsig::getKV('user_id', $target->id);
-        if (!($magickey instanceof Magicsig)) {
-            // No keypair yet, let's generate one.
-            $magickey = new Magicsig();
-            $magickey->generate($target->id);
+        // Get this profile's keypair
+        $magicsig = Magicsig::getKV('user_id', $target->id);
+        if (!$magicsig instanceof Magicsig && $target->isLocal()) {
+            $magicsig = Magicsig::generate($target->getUser());
         }
 
-        $xrd->links[] = new XML_XRD_Element_Link(Magicsig::PUBLICKEYREL,
-                            'data:application/magic-public-key,'. $magickey->toString(false));
+        if ($magicsig instanceof Magicsig) {
+            $xrd->links[] = new XML_XRD_Element_Link(Magicsig::PUBLICKEYREL,
+                                'data:application/magic-public-key,'. $magicsig->toString());
+        }
 
         // TODO - finalize where the redirect should go on the publisher
         $xrd->links[] = new XML_XRD_Element_Link('http://ostatus.org/schema/1.0/subscribe',
index b0b7cd1e904383d57a656db5cb937fe323697707..93e768d8e7b40d4f38f9c81de714b1dc4da1ecf1 100644 (file)
@@ -113,8 +113,7 @@ class UsersalmonAction extends SalmonAction
         $oprofile = $this->ensureProfile();
         if ($oprofile instanceof Ostatus_profile) {
             common_log(LOG_INFO, sprintf('Setting up subscription from remote %s to local %s', $oprofile->getUri(), $this->target->getNickname()));
-            Subscription::start($oprofile->localProfile(),
-                                $this->target);
+            Subscription::start($oprofile->localProfile(), $this->target);
         } else {
             common_log(LOG_INFO, "Can't set up subscription from remote; missing profile.");
         }
@@ -135,8 +134,6 @@ class UsersalmonAction extends SalmonAction
                 Subscription::cancel($oprofile->localProfile(), $this->target);
             } catch (NoProfileException $e) {
                 common_debug('Could not find profile for Subscription: '.$e->getMessage());
-            } catch (AlreadyFulfilledException $e) {
-                common_debug('Subscription did not exist, so there was nothing to cancel');
             }
         } else {
             common_log(LOG_ERR, "Can't cancel subscription from remote, didn't find the profile");
@@ -158,7 +155,7 @@ class UsersalmonAction extends SalmonAction
 
         if ($old instanceof Fave) {
             // TRANS: Client exception.
-            throw new ClientException(_m('This is already a favorite.'));
+            throw new AlreadyFulfilledException(_m('This is already a favorite.'));
         }
 
         if (!Fave::addNew($profile, $notice)) {
@@ -180,7 +177,7 @@ class UsersalmonAction extends SalmonAction
                                    'notice_id' => $notice->id));
         if (!$fave instanceof Fave) {
             // TRANS: Client exception.
-            throw new ClientException(_m('Notice was not favorited!'));
+            throw new AlreadyFulfilledException(_m('Notice was not favorited!'));
         }
 
         $fave->delete();
index e36c1552f86681f9b2f33f8c72896daf8528f403..289865a97f692eaad1fc4951e85302304cc9e4bb 100644 (file)
@@ -99,7 +99,7 @@ class Magicsig extends Managed_DataObject
             // legacy very-old-StatusNet generated keypairs.
             if (strlen($obj->publicKey->modulus->toBits()) < 1024) {
                 common_log(LOG_WARNING, sprintf('Salmon key with <1024 bits (%d) belongs to profile with id==%d',
-                                            strlen($this->publicKey->modulus->toBits()),
+                                            strlen($obj->publicKey->modulus->toBits()),
                                             $obj->user_id));
             }
         }
@@ -132,7 +132,7 @@ class Magicsig extends Managed_DataObject
      */
     function insert()
     {
-        $this->keypair = $this->toString();
+        $this->keypair = $this->toString(true);
 
         return parent::insert();
     }
@@ -143,71 +143,59 @@ class Magicsig extends Managed_DataObject
      * Warning: this can be very slow on systems without the GMP module.
      * Runtimes of 20-30 seconds are not unheard-of.
      *
-     * @param int $user_id id of local user we're creating a key for
+     * @param User $user the local user (since we don't have remote private keys)
      */
-    public function generate($user_id, $bits=1024)
+    public static function generate(User $user, $bits=1024, $alg='RSA-SHA256')
     {
+        $magicsig = new Magicsig($alg);
+        $magicsig->user_id = $user->id;
+
         $rsa = new Crypt_RSA();
 
         $keypair = $rsa->createKey($bits);
 
-        $rsa->loadKey($keypair['privatekey']);
+        $magicsig->privateKey = new Crypt_RSA();
+        $magicsig->privateKey->loadKey($keypair['privatekey']);
 
-        $this->privateKey = new Crypt_RSA();
-        $this->privateKey->loadKey($keypair['privatekey']);
+        $magicsig->publicKey = new Crypt_RSA();
+        $magicsig->publicKey->loadKey($keypair['publickey']);
 
-        $this->publicKey = new Crypt_RSA();
-        $this->publicKey->loadKey($keypair['publickey']);
+        $magicsig->insert();        // will do $this->keypair = $this->toString(true);
+        $magicsig->importKeys();    // seems it's necessary to re-read keys from text keypair
 
-        $this->user_id = $user_id;
-        $this->insert();
+        return $magicsig;
     }
 
     /**
      * Encode the keypair or public key as a string.
      *
-     * @param boolean $full_pair set to false to leave out the private key.
+     * @param boolean $full_pair set to true to include the private key.
      * @return string
      */
-    public function toString($full_pair = true)
+    public function toString($full_pair=false)
     {
         $mod = Magicsig::base64_url_encode($this->publicKey->modulus->toBytes());
         $exp = Magicsig::base64_url_encode($this->publicKey->exponent->toBytes());
         $private_exp = '';
-        if ($full_pair && $this->privateKey->exponent->toBytes()) {
+        if ($full_pair && $this->privateKey instanceof Crypt_RSA && $this->privateKey->exponent->toBytes()) {
             $private_exp = '.' . Magicsig::base64_url_encode($this->privateKey->exponent->toBytes());
         }
 
         return 'RSA.' . $mod . '.' . $exp . $private_exp;
     }
 
-    /**
-     * Decode a string representation of an RSA public key or keypair
-     * as a Magicsig object which can be used to sign or verify.
-     *
-     * @param string $text
-     * @return Magicsig
-     */
-    public static function fromString($text)
-    {
-        $magic_sig = new Magicsig();
-
-        // remove whitespace
-        $magic_sig->keypair = preg_replace('/\s+/', '', $text);
-        $magic_sig->importKeys();
-
-        // Please note this object will be missing the user_id field
-        return $magic_sig;
-    }
-
     /**
      * importKeys will load the object's keypair string, which initiates
      * loadKey() and configures Crypt_RSA objects.
+     *
+     * @param string $keypair optional, otherwise the object's "keypair" property will be used
      */
-    public function importKeys()
+    public function importKeys($keypair=null)
     {
+        $this->keypair = $keypair===null ? $this->keypair : preg_replace('/\s+/', '', $keypair);
+
         // parse components
-        if (!preg_match('/RSA\.([^\.]+)\.([^\.]+)(.([^\.]+))?/', $this->keypair, $matches)) {
+        if (!preg_match('/RSA\.([^\.]+)\.([^\.]+)(\.([^\.]+))?/', $this->keypair, $matches)) {
             common_debug('Magicsig error: RSA key not found in provided string.');
             throw new ServerException('RSA key not found in keypair string.');
         }
@@ -236,11 +224,9 @@ class Magicsig extends Managed_DataObject
      */
     public function loadKey($mod, $exp, $type = 'public')
     {
-        common_log(LOG_DEBUG, "Adding ".$type." key: (".$mod .', '. $exp .")");
-
         $rsa = new Crypt_RSA();
-        $rsa->signatureMode = CRYPT_RSA_SIGNATURE_PKCS1;
-        $rsa->setHash('sha256');
+        $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
+        $rsa->setHash($this->getHash());
         $rsa->modulus = new Math_BigInteger(Magicsig::base64_url_decode($mod), 256);
         $rsa->k = strlen($rsa->modulus->toBytes());
         $rsa->exponent = new Math_BigInteger(Magicsig::base64_url_decode($exp), 256);
@@ -266,15 +252,14 @@ class Magicsig extends Managed_DataObject
      * Returns the name of a hash function to use for signing with this key.
      *
      * @return string
-     * @fixme is this used? doesn't seem to be called by name.
      */
     public function getHash()
     {
         switch ($this->alg) {
-
         case 'RSA-SHA256':
             return 'sha256';
         }
+        throw new ServerException('Unknown or unsupported hash algorithm for Salmon');
     }
 
     /**
@@ -282,7 +267,7 @@ class Magicsig extends Managed_DataObject
      * using our private key.
      *
      * @param string $bytes as raw byte string
-     * @return string base64-encoded signature
+     * @return string base64url-encoded signature
      */
     public function sign($bytes)
     {
@@ -296,12 +281,12 @@ class Magicsig extends Managed_DataObject
     /**
      *
      * @param string $signed_bytes as raw byte string
-     * @param string $signature as base64
+     * @param string $signature as base64url encoded
      * @return boolean
      */
     public function verify($signed_bytes, $signature)
     {
-        $signature = Magicsig::base64_url_decode($signature);
+        $signature = self::base64_url_decode($signature);
         return $this->publicKey->verify($signed_bytes, $signature);
     }
 
index 19fa20f805f11616cd60263519d9fc8f7a5cbbd5..bead2b068e8765508e8bbd50f84f330e76e1b0ac 100644 (file)
@@ -238,7 +238,11 @@ class Ostatus_profile extends Managed_DataObject
     public function garbageCollect()
     {
         $feedsub = FeedSub::getKV('uri', $this->feeduri);
-        return $feedsub->garbageCollect();
+        if ($feedsub instanceof FeedSub) {
+            return $feedsub->garbageCollect();
+        }
+        // Since there's no FeedSub we can assume it's already garbage collected
+        return true;
     }
 
     /**
@@ -285,59 +289,49 @@ class Ostatus_profile extends Managed_DataObject
      * @param string  $verb   Activity::SUBSCRIBE or Activity::JOIN
      * @param Object  $object object of the action; must define asActivityNoun($tag)
      */
-    public function notify($actor, $verb, $object=null, $target=null)
+    public function notify(Profile $actor, $verb, $object=null, $target=null)
     {
-        if (!($actor instanceof Profile)) {
-            $type = gettype($actor);
-            if ($type == 'object') {
-                $type = get_class($actor);
-            }
-            // TRANS: Server exception.
-            // TRANS: %1$s is the method name the exception occured in, %2$s is the actor type.
-            throw new ServerException(sprintf(_m('Invalid actor passed to %1$s: %2$s.'),__METHOD__,$type));
-        }
         if ($object == null) {
             $object = $this;
         }
-        if ($this->salmonuri) {
-            $text = 'update';
-            $id = TagURI::mint('%s:%s:%s',
-                               $verb,
-                               $actor->getURI(),
-                               common_date_iso8601(time()));
-
-            // @todo FIXME: Consolidate all these NS settings somewhere.
-            $attributes = array('xmlns' => Activity::ATOM,
-                                'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/',
-                                'xmlns:thr' => 'http://purl.org/syndication/thread/1.0',
-                                'xmlns:georss' => 'http://www.georss.org/georss',
-                                'xmlns:ostatus' => 'http://ostatus.org/schema/1.0',
-                                'xmlns:poco' => 'http://portablecontacts.net/spec/1.0',
-                                'xmlns:media' => 'http://purl.org/syndication/atommedia');
-
-            $entry = new XMLStringer();
-            $entry->elementStart('entry', $attributes);
-            $entry->element('id', null, $id);
-            $entry->element('title', null, $text);
-            $entry->element('summary', null, $text);
-            $entry->element('published', null, common_date_w3dtf(common_sql_now()));
-
-            $entry->element('activity:verb', null, $verb);
-            $entry->raw($actor->asAtomAuthor());
-            $entry->raw($actor->asActivityActor());
-            $entry->raw($object->asActivityNoun('object'));
-            if ($target != null) {
-                $entry->raw($target->asActivityNoun('target'));
-            }
-            $entry->elementEnd('entry');
-
-            $xml = $entry->getString();
-            common_log(LOG_INFO, "Posting to Salmon endpoint $this->salmonuri: $xml");
-
-            $salmon = new Salmon(); // ?
-            return $salmon->post($this->salmonuri, $xml, $actor);
+        if (empty($this->salmonuri)) {
+            return false;
         }
-        return false;
+        $text = 'update';
+        $id = TagURI::mint('%s:%s:%s',
+                           $verb,
+                           $actor->getURI(),
+                           common_date_iso8601(time()));
+
+        // @todo FIXME: Consolidate all these NS settings somewhere.
+        $attributes = array('xmlns' => Activity::ATOM,
+                            'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/',
+                            'xmlns:thr' => 'http://purl.org/syndication/thread/1.0',
+                            'xmlns:georss' => 'http://www.georss.org/georss',
+                            'xmlns:ostatus' => 'http://ostatus.org/schema/1.0',
+                            'xmlns:poco' => 'http://portablecontacts.net/spec/1.0',
+                            'xmlns:media' => 'http://purl.org/syndication/atommedia');
+
+        $entry = new XMLStringer();
+        $entry->elementStart('entry', $attributes);
+        $entry->element('id', null, $id);
+        $entry->element('title', null, $text);
+        $entry->element('summary', null, $text);
+        $entry->element('published', null, common_date_w3dtf(common_sql_now()));
+
+        $entry->element('activity:verb', null, $verb);
+        $entry->raw($actor->asAtomAuthor());
+        $entry->raw($actor->asActivityActor());
+        $entry->raw($object->asActivityNoun('object'));
+        if ($target != null) {
+            $entry->raw($target->asActivityNoun('target'));
+        }
+        $entry->elementEnd('entry');
+
+        $xml = $entry->getString();
+        common_log(LOG_INFO, "Posting to Salmon endpoint $this->salmonuri: $xml");
+
+        Salmon::post($this->salmonuri, $xml, $actor->getUser());
     }
 
     /**
@@ -351,8 +345,7 @@ class Ostatus_profile extends Managed_DataObject
     public function notifyActivity($entry, Profile $actor)
     {
         if ($this->salmonuri) {
-            $salmon = new Salmon();
-            return $salmon->post($this->salmonuri, $this->notifyPrepXml($entry), $actor);
+            return Salmon::post($this->salmonuri, $this->notifyPrepXml($entry), $actor->getUser());
         }
         common_debug(__CLASS__.' error: No salmonuri for Ostatus_profile uri: '.$this->uri);
 
index a257656762c739d81483d6dcac389574ba966bb2..4f240cc9b8c57070490b089faa6ea459e7a9057e 100644 (file)
@@ -68,27 +68,39 @@ class MagicEnvelope
      */
     public function getKeyPair(Profile $profile, $discovery=false) {
         $magicsig = Magicsig::getKV('user_id', $profile->id);
+
         if ($discovery && !$magicsig instanceof Magicsig) {
-            $signer_uri = $profile->getUri();
-            if (empty($signer_uri)) {
-                throw new ServerException(sprintf('Profile missing URI (id==%d)', $profile->id));
-            }
-            $magicsig = $this->discoverKeyPair($signer_uri);
-            // discoverKeyPair should've thrown exception if it failed
-            assert($magicsig instanceof Magicsig);
+            // Throws exception on failure, but does not try to _load_ the keypair string.
+            $keypair = $this->discoverKeyPair($profile);
+
+            $magicsig = new Magicsig();
+            $magicsig->user_id = $profile->id;
+            $magicsig->importKeys($keypair);
         } elseif (!$magicsig instanceof Magicsig) { // No discovery request, so we'll give up.
             throw new ServerException(sprintf('No public key found for profile (id==%d)', $profile->id));
         }
+
+        assert($magicsig->publicKey instanceof Crypt_RSA);
+
         return $magicsig;
     }
 
     /**
-     * Get the Salmon keypair from a URI, uses XRD Discovery etc.
+     * Get the Salmon keypair from a URI, uses XRD Discovery etc. Reasonably
+     * you'll only get the public key ;)
+     *
+     * The string will (hopefully) be formatted as described in Magicsig specification:
+     * https://salmon-protocol.googlecode.com/svn/trunk/draft-panzer-magicsig-01.html#anchor13
      *
-     * @return Magicsig with loaded keypair
+     * @return string formatted as Magicsig keypair
      */
-    public function discoverKeyPair($signer_uri)
+    public function discoverKeyPair(Profile $profile)
     {
+        $signer_uri = $profile->getUri();
+        if (empty($signer_uri)) {
+            throw new ServerException(sprintf('Profile missing URI (id==%d)', $profile->id));
+        }
+
         $disco = new Discovery();
 
         // Throws exception on lookup problems
@@ -119,14 +131,7 @@ class MagicEnvelope
             throw new Exception(_m('Incorrectly formatted public key element.'));
         }
 
-        $magicsig = Magicsig::fromString($keypair);
-        if (!$magicsig instanceof Magicsig) {
-            common_debug('Salmon error: unable to parse keypair: '.var_export($keypair,true));
-            // TRANS: Exception when public key was properly formatted but not parsable.
-            throw new ServerException(_m('Retrieved Salmon keypair could not be parsed.'));
-        }
-
-        return $magicsig;
+        return $keypair;
     }
 
     /**
@@ -148,21 +153,23 @@ class MagicEnvelope
      * @param <type> $text
      * @param <type> $mimetype
      * @param Magicsig $magicsig    Magicsig with private key available.
+     *
      * @return MagicEnvelope object with all properties set
+     *
+     * @throws Exception of various kinds on signing failure
      */
-    public static function signMessage($text, $mimetype, Magicsig $magicsig)
+    public function signMessage($text, $mimetype, Magicsig $magicsig)
     {
-        $magic_env = new MagicEnvelope();
+        assert($magicsig->privateKey instanceof Crypt_RSA);
 
         // Prepare text and metadata for signing
-        $magic_env->data      = Magicsig::base64_url_encode($text);
-        $magic_env->data_type = $mimetype;
-        $magic_env->encoding  = self::ENCODING;
-        $magic_env->alg       = $magicsig->getName();
-        // Get the actual signature
-        $magic_env->sig = $magicsig->sign($magic_env->signingText());
+        $this->data = Magicsig::base64_url_encode($text);
+        $this->data_type = $mimetype;
+        $this->encoding  = self::ENCODING;
+        $this->alg       = $magicsig->getName();
 
-        return $magic_env;
+        // Get the actual signature
+        $this->sig = $magicsig->sign($this->signingText());
     }
 
     /**
@@ -177,11 +184,10 @@ class MagicEnvelope
         $xs->element('me:data', array('type' => $this->data_type), $this->data);
         $xs->element('me:encoding', null, $this->encoding);
         $xs->element('me:alg', null, $this->alg);
-        $xs->element('me:sig', null, $this->sig);
+        $xs->element('me:sig', null, $this->getSignature());
         $xs->elementEnd('me:env');
 
         $string =  $xs->getString();
-        common_debug('MagicEnvelope XML: ' . $string);
         return $string;
     }
 
@@ -215,7 +221,7 @@ class MagicEnvelope
             $prov->appendChild($enc);
             $alg = $dom->createElementNS(self::NS, 'me:alg', $this->alg);
             $prov->appendChild($alg);
-            $sig = $dom->createElementNS(self::NS, 'me:sig', $this->sig);
+            $sig = $dom->createElementNS(self::NS, 'me:sig', $this->getSignature());
             $prov->appendChild($sig);
     
             $dom->documentElement->appendChild($prov);
@@ -226,6 +232,11 @@ class MagicEnvelope
         return $dom;
     }
 
+    public function getSignature()
+    {
+        return $this->sig;
+    }
+
     /**
      * Find the author URI referenced in the payload Atom entry.
      *
@@ -251,11 +262,12 @@ class MagicEnvelope
      *
      * Details of failure conditions are dumped to output log and not exposed to caller.
      *
-     * @param Profile $profile optional profile used to get locally cached public signature key.
+     * @param Profile $profile profile used to get locally cached public signature key
+     *                         or if necessary perform discovery on.
      *
      * @return boolean
      */
-    public function verify(Profile $profile=null)
+    public function verify(Profile $profile)
     {
         if ($this->alg != 'RSA-SHA256') {
             common_log(LOG_DEBUG, "Salmon error: bad algorithm");
@@ -268,18 +280,13 @@ class MagicEnvelope
         }
 
         try {
-            if ($profile instanceof Profile) {
-                $magicsig = $this->getKeyPair($profile, true);    // Do discovery too if necessary
-            } else {
-                $signer_uri = $this->getAuthorUri();
-                $magicsig = $this->discoverKeyPair($signer_uri);
-            }
+            $magicsig = $this->getKeyPair($profile, true);    // Do discovery too if necessary
         } catch (Exception $e) {
             common_log(LOG_DEBUG, "Salmon error: ".$e->getMessage());
             return false;
         }
 
-        return $magicsig->verify($this->signingText(), $this->sig);
+        return $magicsig->verify($this->signingText(), $this->getSignature());
     }
 
     /**
@@ -323,30 +330,24 @@ class MagicEnvelope
      * on some systems.
      *
      * @param string $text XML fragment to sign, assumed to be Atom
-     * @param Profile $actor Profile of a local user to use as signer
+     * @param User $user User who cryptographically signs $text
      *
-     * @return string XML string representation of magic envelope
+     * @return MagicEnvelope object complete with signature
      *
      * @throws Exception on bad profile input or key generation problems
-     * @fixme if signing fails, this seems to return the original text without warning. Is there a reason for this?
      */
-    public static function signForProfile($text, Profile $actor)
+    public static function signAsUser($text, User $user)
     {
-        // We only generate keys for our local users of course, so let
-        // getUser throw an exception if the profile is not local.
-        $user = $actor->getUser();
-
         // Find already stored key
         $magicsig = Magicsig::getKV('user_id', $user->id);
         if (!$magicsig instanceof Magicsig) {
-            // No keypair yet, let's generate one.
-            $magicsig = new Magicsig();
-            $magicsig->generate($user->id);
+            $magicsig = Magicsig::generate($user);
         }
+        assert($magicsig instanceof Magicsig);
+        assert($magicsig->privateKey instanceof Crypt_RSA);
 
-        $magic_env = self::signMessage($text, 'application/atom+xml', $magicsig);
-
-        assert($magic_env instanceof MagicEnvelope);
+        $magic_env = new MagicEnvelope();
+        $magic_env->signMessage($text, 'application/atom+xml', $magicsig);
 
         return $magic_env;
     }
index 4e02c56c71bcf8644441871968c5499c1534a555..7f685665fa9a8268c3acf48c34ea37da0fa1eb43 100644 (file)
@@ -43,18 +43,18 @@ class Salmon
      *
      * @param string $endpoint_uri
      * @param string $xml string representation of payload
-     * @param Profile $actor local user profile whose keys to sign with
+     * @param User $user local user profile whose keys we sign with
      * @return boolean success
      */
-    public function post($endpoint_uri, $xml, Profile $actor)
+    public static function post($endpoint_uri, $xml, User $user)
     {
         if (empty($endpoint_uri)) {
-            common_debug('No endpoint URI for Salmon post to '.$actor->getUri());
+            common_debug('No endpoint URI for Salmon post to '.$user->getUri());
             return false;
         }
 
         try {
-            $magic_env = MagicEnvelope::signForProfile($xml, $actor);
+            $magic_env = MagicEnvelope::signAsUser($xml, $user);
             $envxml = $magic_env->toXML();
         } catch (Exception $e) {
             common_log(LOG_ERR, "Salmon unable to sign: " . $e->getMessage());
@@ -73,7 +73,7 @@ class Salmon
         }
         if ($response->getStatus() != 200) {
             common_log(LOG_ERR, sprintf('Salmon (from profile %d) endpoint %s returned status %s: %s',
-                                $actor->id, $endpoint_uri, $response->getStatus(), $response->getBody()));
+                                $user->id, $endpoint_uri, $response->getStatus(), $response->getBody()));
             return false;
         }
 
index 6e44ff2eb606bd219a41967b522c0673089a367f..b0365d161dcd7cfae9c3daf37010a6a613e441d7 100644 (file)
@@ -28,8 +28,6 @@ class SalmonAction extends Action
 {
     protected $needPost = true;
 
-    protected $verified = false;
-
     var $xml      = null;
     var $activity = null;
     var $target   = null;
@@ -45,21 +43,21 @@ class SalmonAction extends Action
             $this->clientError(_m('Salmon requires "application/magic-envelope+xml".'));
         }
 
-        $envxml = file_get_contents('php://input');
-        $magic_env = new MagicEnvelope($envxml);   // parse incoming XML as a MagicEnvelope
-
-        $entry = $magic_env->getPayload();  // Not cryptographically verified yet!
-        $this->activity = new Activity($entry->documentElement);
-
         try {
+            $envxml = file_get_contents('php://input');
+            $magic_env = new MagicEnvelope($envxml);   // parse incoming XML as a MagicEnvelope
+
+            $entry = $magic_env->getPayload();  // Not cryptographically verified yet!
+            $this->activity = new Activity($entry->documentElement);
             $profile = Profile::fromUri($this->activity->actor->id);
-            $this->verified = $magic_env->verify($profile);
-        } catch (UnknownUriException $e) {
-            // If we don't know the profile, perform some discovery instead
-            $this->verified = $magic_env->verify();
+            assert($profile instanceof Profile);
+        } catch (Exception $e) {
+            common_debug('Salmon envelope parsing failed with: '.$e->getMessage());
+            $this->clientError($e->getMessage());
         }
 
-        if (!$this->verified) {
+        // Cryptographic verification test
+        if (!$magic_env->verify($profile)) {
             common_log(LOG_DEBUG, "Salmon signature verification failed.");
             // TRANS: Client error.
             $this->clientError(_m('Salmon signature verification failed.'));
@@ -77,50 +75,56 @@ class SalmonAction extends Action
         parent::handle();
 
         common_log(LOG_DEBUG, "Got a " . $this->activity->verb);
-        if (Event::handle('StartHandleSalmonTarget', array($this->activity, $this->target)) &&
-            Event::handle('StartHandleSalmon', array($this->activity))) {
-            switch ($this->activity->verb)
-            {
-            case ActivityVerb::POST:
-                $this->handlePost();
-                break;
-            case ActivityVerb::SHARE:
-                $this->handleShare();
-                break;
-            case ActivityVerb::FAVORITE:
-                $this->handleFavorite();
-                break;
-            case ActivityVerb::UNFAVORITE:
-                $this->handleUnfavorite();
-                break;
-            case ActivityVerb::FOLLOW:
-            case ActivityVerb::FRIEND:
-                $this->handleFollow();
-                break;
-            case ActivityVerb::UNFOLLOW:
-                $this->handleUnfollow();
-                break;
-            case ActivityVerb::JOIN:
-                $this->handleJoin();
-                break;
-            case ActivityVerb::LEAVE:
-                $this->handleLeave();
-                break;
-            case ActivityVerb::TAG:
-                $this->handleTag();
-                break;
-            case ActivityVerb::UNTAG:
-                $this->handleUntag();
-                break;
-            case ActivityVerb::UPDATE_PROFILE:
-                $this->handleUpdateProfile();
-                break;
-            default:
-                // TRANS: Client exception.
-                throw new ClientException(_m('Unrecognized activity type.'));
+        try {
+            if (Event::handle('StartHandleSalmonTarget', array($this->activity, $this->target)) &&
+                    Event::handle('StartHandleSalmon', array($this->activity))) {
+                switch ($this->activity->verb) {
+                case ActivityVerb::POST:
+                    $this->handlePost();
+                    break;
+                case ActivityVerb::SHARE:
+                    $this->handleShare();
+                    break;
+                case ActivityVerb::FAVORITE:
+                    $this->handleFavorite();
+                    break;
+                case ActivityVerb::UNFAVORITE:
+                    $this->handleUnfavorite();
+                    break;
+                case ActivityVerb::FOLLOW:
+                case ActivityVerb::FRIEND:
+                    $this->handleFollow();
+                    break;
+                case ActivityVerb::UNFOLLOW:
+                    $this->handleUnfollow();
+                    break;
+                case ActivityVerb::JOIN:
+                    $this->handleJoin();
+                    break;
+                case ActivityVerb::LEAVE:
+                    $this->handleLeave();
+                    break;
+                case ActivityVerb::TAG:
+                    $this->handleTag();
+                    break;
+                case ActivityVerb::UNTAG:
+                    $this->handleUntag();
+                    break;
+                case ActivityVerb::UPDATE_PROFILE:
+                    $this->handleUpdateProfile();
+                    break;
+                default:
+                    // TRANS: Client exception.
+                    throw new ClientException(_m('Unrecognized activity type.'));
+                }
+                Event::handle('EndHandleSalmon', array($this->activity));
+                Event::handle('EndHandleSalmonTarget', array($this->activity, $this->target));
             }
-            Event::handle('EndHandleSalmon', array($this->activity));
-            Event::handle('EndHandleSalmonTarget', array($this->activity, $this->target));
+        } catch (AlreadyFulfilledException $e) {
+            // The action's results are already fulfilled. Maybe it was a
+            // duplicate? Maybe someone's database is out of sync?
+            // Let's just accept it and move on.
+            common_log(LOG_INFO, 'Salmon slap carried an event which had already been fulfilled.');
         }
     }
 
index 3b98a34a87c09c82e520da012670092191aed053..334dc14549c4fe101571fd4c0a30d4904ec1958b 100644 (file)
@@ -41,8 +41,7 @@ class SalmonQueueHandler extends QueueHandler
 
         $actor = Profile::getKV($data['actor']);
 
-        $salmon = new Salmon();
-        $salmon->post($data['salmonuri'], $data['entry'], $actor);
+        Salmon::post($data['salmonuri'], $data['entry'], $actor->getUser());
 
         // @fixme detect failure and attempt to resend
         return true;
index 99fb6c631caa19fcba389cacc847ac5f606f04ba..e3f5439aed69bd239da22149538dfbbade619819 100644 (file)
@@ -51,7 +51,7 @@ echo "== Original entry ==\n\n";
 print $entry;
 print "\n\n";
 
-$magic_env = MagicEnvelope::signForProfile($entry, $profile);
+$magic_env = MagicEnvelope::signAsUser($entry, $profile->getUser());
 $envxml = $magic_env->toXML();
 
 echo "== Signed envelope ==\n\n";
@@ -60,7 +60,9 @@ print "\n\n";
 
 echo "== Testing local verification ==\n\n";
 $magic_env = new MagicEnvelope($envxml);
-$ok = $magic_env->verify();
+$activity = new Activity($magic_env->getPayload()->documentElement);
+$actprofile = Profile::fromUri($activity->actor->id);
+$ok = $magic_env->verify($actprofile);
 if ($ok) {
     print "OK\n\n";
 } else {
@@ -84,7 +86,7 @@ if (have_option('--slap')) {
     echo "== Remote salmon slap ==\n\n";
     print "Sending signed Salmon slap to $url ...\n";
 
-    $ok = $salmon->post($url, $entry, $profile);
+    $ok = Salmon::post($url, $entry, $profile->getUser());
     if ($ok) {
         print "OK\n\n";
     } else {
index c6b4099756c16b55556ac57ceb7c6f2831c656de..76f46171cc589e68c5e2afeb7c3cb0ba742fd8c7 100644 (file)
@@ -77,7 +77,7 @@ class PollPlugin extends MicroAppPlugin
      */
     function onEndShowStyles($action)
     {
-        $action->cssLink($this->path('poll.css'));
+        $action->cssLink($this->path('css/poll.css'));
         return true;
     }
 
@@ -379,7 +379,7 @@ class PollPlugin extends MicroAppPlugin
      * open here, but we probably shouldn't open it here. Check parent class
      * and Bookmark plugin for if that's right.
      */
-    function showNotice($notice, $out)
+    function showNotice(Notice $notice, $out)
     {
         switch ($notice->object_type) {
         case self::POLL_OBJECT:
@@ -393,7 +393,7 @@ class PollPlugin extends MicroAppPlugin
         }
     }
 
-    function showNoticePoll($notice, $out)
+    function showNoticePoll(Notice $notice, $out)
     {
         $user = common_current_user();
 
@@ -425,7 +425,7 @@ class PollPlugin extends MicroAppPlugin
         $out->elementStart('div', array('class' => 'entry-content'));
     }
 
-    function showNoticePollResponse($notice, $out)
+    function showNoticePollResponse(Notice $notice, $out)
     {
         $user = common_current_user();
 
index 8a1155ba857646cb9e9f51fc931380ddd57a71e1..071778aaa2109486618b515a3ce8d4b091a20a47 100644 (file)
@@ -181,7 +181,7 @@ class NewPollAction extends Action
      *
      * @return void
      */
-    function showNotice($notice)
+    function showNotice(Notice $notice)
     {
         class_exists('NoticeList'); // @fixme hack for autoloader
         $nli = new NoticeListItem($notice, $this);
diff --git a/plugins/Poll/css/poll.css b/plugins/Poll/css/poll.css
new file mode 100644 (file)
index 0000000..975a52f
--- /dev/null
@@ -0,0 +1,20 @@
+/* Poll specific styles */
+
+.poll-block {
+    float: left;
+    height: 16px;
+    background: #8aa;
+    margin-right: 8px;
+}
+
+.poll-winner {
+    background: #4af;
+}
+
+.notice div.poll-content {
+    opacity: 1;
+}
+
+#poll-response-submit {
+    min-width: 100px;
+}
diff --git a/plugins/Poll/poll.css b/plugins/Poll/poll.css
deleted file mode 100644 (file)
index 5ba9c15..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-.poll-block {
-    float: left;
-    height: 16px;
-    background: #8aa;
-    margin-right: 8px;
-}
-
-.poll-winner {
-    background: #4af;
-}
index d874441d85a691f87aa13a2ce00bcc112564117b..8aa3247f9ae9ba4b52535d765b583ecef80db9dd 100644 (file)
@@ -319,7 +319,7 @@ class QnAPlugin extends MicroAppPlugin
      * @param Notice $notice
      * @param HTMLOutputter $out
      */
-    function showNotice($notice, $out)
+    function showNotice(Notice $notice, $out)
     {
         switch ($notice->object_type) {
         case QnA_Question::OBJECT_TYPE:
@@ -337,7 +337,7 @@ class QnAPlugin extends MicroAppPlugin
         }
     }
 
-    function showNoticeQuestion($notice, $out)
+    function showNoticeQuestion(Notice $notice, $out)
     {
         $user = common_current_user();
 
@@ -427,7 +427,7 @@ class QnAPlugin extends MicroAppPlugin
         return false;
     }
 
-    function showNoticeAnswer($notice, $out)
+    function showNoticeAnswer(Notice $notice, $out)
     {
         $user = common_current_user();
 
index dd56d8ccf03b873dbc7998a76e6f3acbdb91fe5e..83da6355201b91e2efdd6640c0ed79ebc3251d3e 100644 (file)
@@ -1,3 +1,5 @@
+/* QnA */
+
 .question .answer-count, .qna-full-question .answer-count {
     display: block;
     clear: left;
@@ -76,3 +78,76 @@ ul.qna-dummy + ul.threaded-replies li.notice:first-child, li.notice-answer + li.
     display: block;
     font-style: italic;
 }
+
+.question div.question-description {
+    font-size: 1em;
+    line-height: 1.36em;
+    margin-top: 0px;
+    opacity: 1;
+}
+
+.question div.answer-content, .qna-full-question div.answer-content {
+    font-size: 1em;
+    opacity: 1;
+}
+
+.qna-dummy-placeholder input, .question #qna-answer, .qna-full-question #qna-answer {
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    border-radius: 4px;
+    box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2);
+    -moz-box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2);
+    -webkit-box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2);
+}
+
+.question-description input.submit, .answer-content input.submit {
+    height: auto;
+    padding: 0px 10px;
+    margin: 6px 0px 10px 0px;
+    color:#fff;
+    font-weight: bold;
+    text-transform: uppercase;
+    font-size: 1.1em;
+    text-shadow: 0px -1px 0px rgba(0, 0, 0, 0.2);
+    border: 1px solid #d7621c;
+    border-radius: 4px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+    background: #FB6104;
+    background: -moz-linear-gradient(top, #ff9d63 0%, #fb6104 100%);
+    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ff9d63), color-stop(100%,#fb6104));
+    background: -webkit-linear-gradient(top, #ff9d63 0%,#fb6104 100%);
+    background: -o-linear-gradient(top, #ff9d63 0%,#fb6104 100%);
+    background: -ms-linear-gradient(top, #ff9d63 0%,#fb6104 100%);
+    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff9d63', endColorstr='#fb6104',GradientType=0 );
+    background: linear-gradient(top, #ff9d63 0%,#fb6104 100%);
+}
+
+#qna-answer-submit {
+    min-width: 100px;
+}
+
+.question .question-description input.submit:hover, .question .answer-content input.submit:hover {
+    text-shadow: 0px -1px 0px rgba(0, 0, 0, 0.6);
+    background: #ff9d63;
+    background: -moz-linear-gradient(top, #fb6104 0%, #fc8035 100%);
+    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fb6104), color-stop(100%,#fc8035));
+    background: -webkit-linear-gradient(top, #fb6104 0%,#fc8035 100%);
+    background: -o-linear-gradient(top, #fb6104 0%,#fc8035 100%);
+    background: -ms-linear-gradient(top, #fb6104 0%,#fc8035 100%);
+    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fb6104', endColorstr='#fc8035',GradientType=0 );
+    background: linear-gradient(top, #fb6104 0%,#fc8035 100%);
+}
+
+.question .question-description #answer-form input.submit {
+    margin-top: 0px;
+}
+
+.question p.best, .answer p.best {
+    background: url(../images/rosette.png) no-repeat top left;
+    padding-left: 20px;
+}
+
+.question p.best:before, .answer p.best:before {
+    content: none !important;
+}
index 124d9e2cfaebcd59a63a5ac13273a697bb2e86a2..41d8ac9d469831490498fd1b00af399928027f59 100644 (file)
@@ -569,7 +569,11 @@ class TwitterImport
         if (common_config('attachments', 'process_links')) {
             if (!empty($status->entities) && !empty($status->entities->urls)) {
                 foreach ($status->entities->urls as $url) {
-                    File::processNew($url->url, $notice->id);
+                    try {
+                        File::processNew($url->url, $notice->id);
+                    } catch (ServerException $e) {
+                        // Could not process attached URL
+                    }
                 }
             }
         }
index 92ab1241473d6c4709a4fb535ed04f2c5d4cdb17..534f70bf8fbae4364025b06b4ac7156a2800780c 100644 (file)
@@ -36,8 +36,17 @@ class WebfingerAction extends XrdAction
         // throws exception if resource is empty
         $this->resource = Discovery::normalize($this->trimmed('resource'));
 
-        if (Event::handle('StartGetWebFingerResource', array($this->resource, &$this->target, $this->args))) {
-            Event::handle('EndGetWebFingerResource', array($this->resource, &$this->target, $this->args));
+        try {
+            if (Event::handle('StartGetWebFingerResource', array($this->resource, &$this->target, $this->args))) {
+                Event::handle('EndGetWebFingerResource', array($this->resource, &$this->target, $this->args));
+            }
+        } catch (NoSuchUserException $e) {
+            throw new ServerException($e->getMessage(), 404);
+        }
+
+        if (!$this->target instanceof WebFingerResource) {
+            // TRANS: Error message when an object URI which we cannot find was requested
+            throw new ServerException(_m('Resource not found in local database.'), 404);
         }
 
         return true;
@@ -45,10 +54,6 @@ class WebfingerAction extends XrdAction
 
     protected function setXRD()
     {
-        if (!($this->target instanceof WebFingerResource)) {
-            throw new Exception(_('Target not set for resource descriptor'));
-        }
-
         $this->xrd->subject = $this->resource;
 
         foreach ($this->target->getAliases() as $alias) {
index c5da9e7d8cb3d1ceafab26564ab7501be6294ac8..f16d6aab577f25d97c654ee97e2bc9ed9f05e2d7 100644 (file)
@@ -847,143 +847,6 @@ h4.blog-entry-title {
     line-height: 1.2em;
 }
 
-/* Bookmark specific styles */
-/* TODO separate base styles and move to plugin */
-
-.bookmark h3 {
-    margin: 0px 0px 8px 0px;
-    float: left;
-    line-height: 1.2em;
-    max-width: 92%;
-}
-
-.bookmark-notice-count {
-    border-radius: 4px;
-       -moz-border-radius: 4px;
-       -webkit-border-radius: 4px;
-    padding: 1px 6px;
-    font-size: 1.2em;
-    line-height: 1.2em;
-    background: #fff;
-    border: 1px solid #7b8dbb;
-    color: #3e3e8c !important;
-    position: relative;
-    right: 4px;
-    margin-left: 10px;
-}
-
-.bookmark-notice-count:hover {
-    text-decoration: none;
-    background: #f2f2f2;
-    border: 1px solid #7b8dbb;
-    text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.5);
-}
-
-.notice .bookmark-description {
-    clear: both;
-    margin-left: 0px;
-    margin-bottom: 0px;
-}
-
-.notice .bookmark-author {
-    margin-left: 0px;
-    float: left;
-}
-
-.bookmark-tags {
-    clear: both;
-    margin-bottom: 8px;
-    line-height: 1.6em;
-}
-
-ul.bookmark-tags a {
-    border-radius: 4px;
-       -moz-border-radius: 4px;
-       -webkit-border-radius: 4px;
-    padding: 1px 6px;
-    background: #f2f2f2;
-    color: #3e3e8c !important;
-    text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.5);
-    font-size: 0.88em;
-}
-
-ul.bookmark-tags a:hover {
-    background-color: #cdd1dd;
-    text-decoration: none;
-}
-
-.bookmark-avatar {
-    float: none !important;
-    position: relative;
-    top: 2px;
-}
-
-.bookmark div.entry-content {
-    font-size: 0.88em;
-    line-height: 1.2em;
-    margin-top: 6px;
-    opacity: 0.6;
-    margin-bottom: 0px;
-}
-
-.bookmark:hover div.entry-content {
-    opacity: 1;
-}
-
-#bookmarkpopup {
-    min-width: 600px;
-    margin-top: 0px;
-    height: 100%;
-    border: 10px solid #364A84;
-    background: #364A84;
-}
-
-#bookmarkpopup #wrap {
-    width: auto;
-    min-width: 560px;
-    padding: 40px 0px 25px 0px;
-    margin-right: 2px;
-    background: #fff url(../mobilelogo.png) no-repeat 6px 6px;
-}
-
-#bookmarkpopup #header {
-    width: auto;
-    padding: 0px 10px;
-}
-
-#bookmarkpopup .form_settings label {
-    margin-top: 2px;
-    text-align: right;
-    width: 24%;
-    font-size: 1.2em;
-}
-
-#bookmarkpopup .form_settings .form_data input {
-    width: 60%;
-}
-
-#bookmarkpopup .form_guide {
-    color: #777;
-}
-
-#bookmarkpopup #bookmark-submit {
-    min-width: 100px;
-}
-
-#bookmarkpopup fieldset fieldset {
-    margin-bottom: 10px;
-}
-
-#form_initial_bookmark.form_settings .form_data li {
-    margin-bottom: 0px;
-}
-
-#form_new_bookmark.form_settings .bookmarkform-thumbnail {
-    position: absolute;
-    top: 50px;
-    right: 0px;
-}
-
 /* Onboard specific styles */
 /* TODO move to plugin */
 
@@ -1240,184 +1103,6 @@ td.entity_profile {
     -webkit-box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2);
 }
 
-/* Event specific styles */
-/* TODO separate base styles and move to plugin */
-
-.notice .vevent div {
-    margin-bottom: 8px;
-}
-
-.event-info {
-    margin-left: 0px !important;
-    margin-top: 2px !important;
-}
-
-.notice .event-info + .notice-options {
-    margin-top: 14px;
-}
-
-.notice .threaded-replies .event-info + .notice-options {
-    margin-top: 20px;
-}
-
-#form_event_rsvp #new_rsvp_data {
-    display: inline;
-    margin: 10px 0px;
-}
-
-#form_event_rsvp input.submit {
-    height: auto;
-    padding: 0px 10px;
-    margin-left: 10px;
-    color:#fff;
-    font-weight: bold;
-    text-transform: uppercase;
-    font-size: 1.1em;
-    text-shadow: 0px -1px 0px rgba(0, 0, 0, 0.2);
-    border: 1px solid #d7621c;
-    border-radius: 4px;
-    -moz-border-radius: 4px;
-    -webkit-border-radius: 4px;
-    background: #FB6104;
-    background: -moz-linear-gradient(top, #ff9d63 0%, #fb6104 100%);
-    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ff9d63), color-stop(100%,#fb6104));
-    background: -webkit-linear-gradient(top, #ff9d63 0%,#fb6104 100%);
-    background: -o-linear-gradient(top, #ff9d63 0%,#fb6104 100%);
-    background: -ms-linear-gradient(top, #ff9d63 0%,#fb6104 100%);
-    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff9d63', endColorstr='#fb6104',GradientType=0 );
-    background: linear-gradient(top, #ff9d63 0%,#fb6104 100%);
-}
-
-#form_event_rsvp input.submit:hover {
-    text-shadow: 0px -1px 0px rgba(0, 0, 0, 0.6);
-    background: #ff9d63;
-    background: -moz-linear-gradient(top, #fb6104 0%, #fc8035 100%);
-    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fb6104), color-stop(100%,#fc8035));
-    background: -webkit-linear-gradient(top, #fb6104 0%,#fc8035 100%);
-    background: -o-linear-gradient(top, #fb6104 0%,#fc8035 100%);
-    background: -ms-linear-gradient(top, #fb6104 0%,#fc8035 100%);
-    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fb6104', endColorstr='#fc8035',GradientType=0 );
-    background: linear-gradient(top, #fb6104 0%,#fc8035 100%);
-}
-
-#wrap .vevent form.processing input.submit {
-    text-indent: 0;
-    background: #ff9d63;
-}
-
-#input_form_event .form_settings .form_data {
-    float: left;
-}
-
-#input_form_event .form_settings .form_data li {
-    float: left;
-    width: auto;
-}
-
-#input_form_event .form_settings .form_data label {
-    width: auto;
-}
-
-label[for=event-starttime], label[for=event-endtime] {
-    display: none !important;
-}
-
-#event-starttime, #event-endtime {
-    margin-top:  -1px;
-    margin-bottom:  -1px;
-    height: 2em;
-}
-
-#event-startdate, #event-enddate {
-    margin-right: 20px;
-    width: 120px;
-}
-
-/* QnA */
-
-.question div.question-description {
-    font-size: 1em;
-    line-height: 1.36em;
-    margin-top: 0px;
-    opacity: 1;
-}
-
-.question div.answer-content, .qna-full-question div.answer-content {
-    font-size: 1em;
-    opacity: 1;
-}
-
-.qna-dummy-placeholder input, .question #qna-answer, .qna-full-question #qna-answer {
-    -webkit-border-radius: 4px;
-    -moz-border-radius: 4px;
-    border-radius: 4px;
-    box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2);
-    -moz-box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2);
-    -webkit-box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.2);
-}
-
-.question-description input.submit, .answer-content input.submit {
-    height: auto;
-    padding: 0px 10px;
-    margin: 6px 0px 10px 0px;
-    color:#fff;
-    font-weight: bold;
-    text-transform: uppercase;
-    font-size: 1.1em;
-    text-shadow: 0px -1px 0px rgba(0, 0, 0, 0.2);
-    border: 1px solid #d7621c;
-    border-radius: 4px;
-    -moz-border-radius: 4px;
-    -webkit-border-radius: 4px;
-    background: #FB6104;
-    background: -moz-linear-gradient(top, #ff9d63 0%, #fb6104 100%);
-    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ff9d63), color-stop(100%,#fb6104));
-    background: -webkit-linear-gradient(top, #ff9d63 0%,#fb6104 100%);
-    background: -o-linear-gradient(top, #ff9d63 0%,#fb6104 100%);
-    background: -ms-linear-gradient(top, #ff9d63 0%,#fb6104 100%);
-    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff9d63', endColorstr='#fb6104',GradientType=0 );
-    background: linear-gradient(top, #ff9d63 0%,#fb6104 100%);
-}
-
-#qna-answer-submit {
-    min-width: 100px;
-}
-
-.question .question-description input.submit:hover, .question .answer-content input.submit:hover {
-    text-shadow: 0px -1px 0px rgba(0, 0, 0, 0.6);
-    background: #ff9d63;
-    background: -moz-linear-gradient(top, #fb6104 0%, #fc8035 100%);
-    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fb6104), color-stop(100%,#fc8035));
-    background: -webkit-linear-gradient(top, #fb6104 0%,#fc8035 100%);
-    background: -o-linear-gradient(top, #fb6104 0%,#fc8035 100%);
-    background: -ms-linear-gradient(top, #fb6104 0%,#fc8035 100%);
-    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fb6104', endColorstr='#fc8035',GradientType=0 );
-    background: linear-gradient(top, #fb6104 0%,#fc8035 100%);
-}
-
-.question .question-description #answer-form input.submit {
-    margin-top: 0px;
-}
-
-.question p.best, .answer p.best {
-    background: url(../images/rosette.png) no-repeat top left;
-    padding-left: 20px;
-}
-
-.question p.best:before, .answer p.best:before {
-    content: none !important;
-}
-
-/* Poll specific styles */
-
-.notice div.poll-content {
-    opacity: 1;
-}
-
-#poll-response-submit {
-    min-width: 100px;
-}
-
 /* SNOD CompanyLogo styling */
 /* TODO move to plugin */