]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge remote-tracking branch 'upstream/master' into social-master
authorRoland Haeder <roland@mxchange.org>
Tue, 5 Jan 2016 13:27:33 +0000 (14:27 +0100)
committerRoland Haeder <roland@mxchange.org>
Tue, 5 Jan 2016 13:27:33 +0000 (14:27 +0100)
Signed-off-by: Roland Haeder <roland@mxchange.org>
1  2 
classes/Notice.php
lib/activityutils.php
lib/framework.php
lib/noticelistitem.php
plugins/Activity/ActivityPlugin.php
plugins/Event/EventPlugin.php
plugins/Event/classes/RSVP.php
plugins/Favorite/classes/Fave.php

diff --combined classes/Notice.php
index 757205761a3e48ec43eca2eb7a3683ecdb43662b,1afc6d09772d583df574ec4395f0053e97bc16d5..47f03099a0f009d74f51cdf0713e13914ac80f20
@@@ -90,7 -90,7 +90,7 @@@ class Notice extends Managed_DataObjec
                  'source' => array('type' => 'varchar', 'length' => 32, 'description' => 'source of comment, like "web", "im", or "clientname"'),
                  'conversation' => array('type' => 'int', 'description' => 'id of root notice in this conversation'),
                  'repeat_of' => array('type' => 'int', 'description' => 'notice this is a repeat of'),
-                 'object_type' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams object type', 'default' => 'http://activitystrea.ms/schema/1.0/note'),
+                 'object_type' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams object type', 'default' => null),
                  'verb' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams verb', 'default' => 'http://activitystrea.ms/schema/1.0/post'),
                  'scope' => array('type' => 'int',
                                   'description' => 'bit map for distribution scope; 0 = everywhere; 1 = this server only; 2 = addressees; 4 = followers; null = default'),
       * Record the given set of hash tags in the db for this notice.
       * Given tag strings will be normalized and checked for dupes.
       */
 -    function saveKnownTags($hashtags)
 +    function saveKnownTags(array $hashtags)
      {
          //turn each into their canonical tag
          //this is needed to remove dupes before saving e.g. #hash.tag = #hashtag
       * @return Notice
       * @throws ClientException
       */
 -    static function saveNew($profile_id, $content, $source, array $options=null) {
 +    static function saveNew($profile_id, $content, $source, array $options=array()) {
          $defaults = array('uri' => null,
                            'url' => null,
                            'conversation' => null,   // URI of conversation
                            'object_type' => null,
                            'verb' => null);
  
 -        if (!empty($options) && is_array($options)) {
 +        /*
 +         * Above type-hint is already array, so simply count it, this saves
 +         * "some" CPU cycles.
 +         */
 +        if (count($options) > 0) {
              $options = array_merge($defaults, $options);
 -            extract($options);
 -        } else {
 -            extract($defaults);
          }
  
 +        extract($options);
 +
          if (!isset($is_local)) {
              $is_local = Notice::LOCAL_PUBLIC;
          }
                  throw new ClientException(_('You cannot repeat your own notice.'));
              }
  
 -            if ($repeat->scope != Notice::SITE_SCOPE &&
 -                $repeat->scope != Notice::PUBLIC_SCOPE) {
 +            if ($repeat->isPrivateScope()) {
                  // TRANS: Client error displayed when trying to repeat a non-public notice.
                  throw new ClientException(_('Cannot repeat a private notice.'), 403);
              }
       *
       * @return void
       */
 -    function saveKnownUrls($urls)
 +    function saveKnownUrls(array $urls)
      {
          if (common_config('attachments', 'process_links')) {
              // @fixme validation?
          return $this->_replies[$this->getID()];
      }
  
 -    function _setReplies($replies)
 +    function _setReplies(array $replies)
      {
          $this->_replies[$this->getID()] = $replies;
      }
                }
  
                $groups = User_group::multiGet('id', $ids);
 -              $this->_groups[$this->id] = $groups->fetchAll();
 +              $this->_setGroups($groups->fetchAll());
                return $this->_groups[$this->id];
      }
  
 -    function _setGroups($groups)
 +    function _setGroups(array $groups)
      {
          $this->_groups[$this->id] = $groups;
      }
       */
      public function getTags()
      {
 +        // Check default scope (non-private notices)
 +        $inScope = (!$this->isPrivateScope());
 +
 +        // Get current profile
 +        $profile = Profile::current();
 +
 +        // Is the general scope check okay and the user in logged in?
 +        //* NOISY-DEBUG: */ common_debug('[' . __METHOD__ . ':' . __LINE__ . ']: inScope=' . intval($inScope) . ',profile[]=' . gettype($profile));
 +        if (($inScope === TRUE) && ($profile instanceof Profile)) {
 +            /*
 +             * Check scope, else a privacy leaks happens this way:
 +             *
 +             * 1) Bob and Alice follow each other and write private notices
 +             *    (this->scope=2) to each other.
 +             * 2) Bob uses tags in his private notice to alice (which she can
 +             *    read from him).
 +             * 3) Alice adds that notice (with tags) to her favorites
 +             *    ("faving") it.
 +             * 4) The tags from Bob's private notice becomes visible in Alice's
 +             *    profile.
 +             *
 +             * This has the simple background that the scope is not being
 +             * re-checked. This has to be done here at this point because given
 +             * above scenario is a privacy leak as the tags may be *really*
 +             * private (nobody else shall see them) such as initmate words or
 +             * very political words.
 +             */
 +            $inScope = $this->inScope($profile);
 +            //* NOISY-DEBUG: */ common_debug('[' . __METHOD__ . ':' . __LINE__ . ']: inScope=' . intval($inScope) . ' - After inScope() has been called.');
 +        }
 +
          $tags = array();
  
          $keypart = sprintf('notice:tags:%d', $this->id);
          } else {
              $tag = new Notice_tag();
              $tag->notice_id = $this->id;
 -            if ($tag->find()) {
 +
 +            // Check scope for privacy-leak protection (see some lines above why)
 +            if (($inScope === TRUE) && ($tag->find())) {
                  while ($tag->fetch()) {
                      $tags[] = $tag->tag;
                  }
                  ($this->is_local != Notice::GATEWAY));
      }
  
 +    public function isPrivateScope () {
 +        return ($this->scope != Notice::SITE_SCOPE &&
 +                $this->scope != Notice::PUBLIC_SCOPE);
 +    }
 +
      /**
       * Check that the given profile is allowed to read, respond to, or otherwise
       * act on this notice.
       *
       * @return boolean whether the profile is in the notice's scope
       */
 -    function inScope($profile)
 +    function inScope(Profile $profile=null)
      {
          if (is_null($profile)) {
              $keypart = sprintf('notice:in-scope-for:%d:null', $this->id);
          return ($result == 1) ? true : false;
      }
  
 -    protected function _inScope($profile)
 +    protected function _inScope(Profile $profile=null)
      {
          $scope = is_null($this->scope) ? self::defaultScope() : $this->getScope();
  
          return !$this->isHiddenSpam($profile);
      }
  
 -    function isHiddenSpam($profile) {
 +    function isHiddenSpam(Profile $profile=null) {
  
          // Hide posts by silenced users from everyone but moderators.
  
        return $scope;
      }
  
 -      static function fillProfiles($notices)
 +      static function fillProfiles(array $notices)
        {
                $map = self::getProfiles($notices);
                foreach ($notices as $entry=>$notice) {
                return array_values($map);
        }
  
 -      static function getProfiles(&$notices)
 +      static function getProfiles(array &$notices)
        {
                $ids = array();
                foreach ($notices as $notice) {
                return Profile::pivotGet('id', $ids);
        }
  
 -      static function fillGroups(&$notices)
 +      static function fillGroups(array &$notices)
        {
          $ids = self::_idsOf($notices);
          $gis = Group_inbox::listGet('notice_id', $ids);
                return array_keys($ids);
      }
  
 -    static function fillAttachments(&$notices)
 +    static function fillAttachments(array &$notices)
      {
          $ids = self::_idsOf($notices);
          $f2pMap = File_to_post::listGet('post_id', $ids);
                }
      }
  
 -    static function fillReplies(&$notices)
 +    static function fillReplies(array &$notices)
      {
          $ids = self::_idsOf($notices);
          $replyMap = Reply::listGet('notice_id', $ids);
          }
      }
  
 +    /**
 +     * Checks whether the current profile is allowed (in scope) to see this notice.
 +     *
 +     * @return $inScope Whether the current profile is allowed to see this notice
 +     */
 +    function isCurrentProfileInScope () {
 +        // Check scope, default is allowed
 +        $inScope = TRUE;
 +
 +        //* NOISY-DEBUG: */ common_debug('[' . __METHOD__ . ':' . __LINE__ . '] this->tag=' . $this->tag . ',this->id=' . $this->id . ',this->scope=' . $this->scope);
 +
 +        // Is it private scope?
 +        if ($this->isPrivateScope()) {
 +            // 2) Get current profile
 +            $profile = Profile::current();
 +
 +            // Is the profile not set?
 +            if (!$profile instanceof Profile) {
 +                // Public viewer shall not see a tag from a private dent (privacy leak)
 +                //* NOISY-DEBUG: */ common_debug('[' . __METHOD__ . ':' . __LINE__ . '] Not logged in (public view).');
 +                $inScope = FALSE;
 +            } elseif (!$this->inScope($profile)) {
 +                // Current profile is not in scope (not allowed to see) of notice
 +                //* NOISY-DEBUG: */ common_debug('[' . __METHOD__ . ':' . __LINE__ . '] profile->id=' . $profile->id . ' is not allowed to see this notice.');
 +                $inScope = FALSE;
 +            }
 +        }
 +
 +        // Return result
 +        //* NOISY-DEBUG: */ common_debug('[' . __METHOD__ . ':' . __LINE__ . '] this->tag=' . $this->tag . ',this->weight=' . $this->weight . ',inScope=' . intval($inScope) . ' - EXIT!');
 +        return $inScope;
 +    }
 +
      static public function beforeSchemaUpdate()
      {
          $table = strtolower(get_called_class());
diff --combined lib/activityutils.php
index bb430c6f7849b8f0818b18933d190f1d7005aab9,8a2be350225140d6198128ad33d97a42a305c39b..76e4777deb5feb471dc69960ff1c1feee64c00f4
@@@ -301,7 -301,7 +301,7 @@@ class ActivityUtil
          return false;
      }
  
 -    static function getFeedAuthor($feedEl)
 +    static function getFeedAuthor(DOMElement $feedEl)
      {
          // Try old and deprecated activity:subject
  
          return null;
      }
  
 -    static function compareTypes($type, $objects)
 +    static function compareTypes($type, array $objects)
      {
          $type = self::resolveUri($type);
 -        foreach ((array)$objects as $object) {
 +        foreach ($objects as $object) {
              if ($type === self::resolveUri($object)) {
                  return true;
              }
      }
  
      static function findLocalObject(array $uris, $type=ActivityObject::NOTE) {
-         $object = null;
-         // TODO: Extend this in plugins etc.
-         if (Event::handle('StartFindLocalActivityObject', array($uris, $type, &$object))) {
+         $obj_class = null;
+         // TODO: Extend this in plugins etc. and describe in EVENTS.txt
+         if (Event::handle('StartFindLocalActivityObject', array($uris, $type, &$obj_class))) {
              switch (self::resolveUri($type)) {
              case ActivityObject::PERSON:
                  // GROUP will also be here in due time...
-                 $object = new Profile();
+                 $obj_class = 'Profile';
                  break;
              default:
-                 $object = new Notice();
+                 $obj_class = 'Notice';
              }
          }
-         foreach (array_unique($uris) as $uri) {
+         $object = null;
+         $uris = array_unique($uris);
+         foreach ($uris as $uri) {
              try {
                  // the exception thrown will cancel before reaching $object
-                 $object = call_user_func(array($object, 'fromUri'), $uri);
+                 $object = call_user_func("{$obj_class}::fromUri", $uri);
                  break;
-             } catch (Exception $e) {
-                 common_debug('Could not find local activity object from uri: '.$uri);
+             } catch (UnknownUriException $e) {
+                 common_debug('Could not find local activity object from uri: '.$e->object_uri);
              }
          }
-         if (!empty($object)) {
-             Event::handle('EndFindLocalActivityObject', array($object->getUri(), $type, $object));
-         } else {
-             throw new ServerException('Could not find any activityobject stored locally with given URI');
+         if (!$object instanceof Managed_DataObject) {
+             throw new ServerException('Could not find any activityobject stored locally with given URIs: '.var_export($uris,true));
          }
+         Event::handle('EndFindLocalActivityObject', array($object->getUri(), $object->getObjectType(), $object));
          return $object;
      }
  
diff --combined lib/framework.php
index 2ce4ed71f00f79bf04a094c8aac28424f76bf2a4,f8c877954f338274e28549da58d15ec9d314af3a..a159c662a527a46db102e6dcd2479d5dc13cbae4
@@@ -23,11 -23,11 +23,11 @@@ define('GNUSOCIAL_ENGINE', 'GNU social'
  define('GNUSOCIAL_ENGINE_URL', 'https://www.gnu.org/software/social/');
  
  define('GNUSOCIAL_BASE_VERSION', '1.2.0');
- define('GNUSOCIAL_LIFECYCLE', 'beta2'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
+ define('GNUSOCIAL_LIFECYCLE', 'beta3'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
  
  define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE);
  
 -define('GNUSOCIAL_CODENAME', 'Not decided yet');
 +define('GNUSOCIAL_CODENAME', 'Only a fixed bug is a good bug.');
  
  define('AVATAR_PROFILE_SIZE', 96);
  define('AVATAR_STREAM_SIZE', 48);
@@@ -110,28 -110,6 +110,28 @@@ function _have_config(
      return GNUsocial::haveConfig();
  }
  
 +function common_get_temp_dir()
 +{
 +    // Try to get it from php.ini first
 +    $temp_path = trim(ini_get('upload_tmp_dir'));
 +
 +    // Is it empty?
 +    if (strlen($temp_path) == 0) {
 +        // Then try sys_get_temp_dir()
 +        $temp_path = trim(sys_get_temp_dir());
 +
 +        // Still empty?
 +        if (strlen($temp_path) == 0) {
 +            // Then set it to /tmp (ugly)
 +            // @TODO Hard-coded non-Windows stuff!
 +            $temp_path = '/tmp';
 +        }
 +    }
 +
 +    // Return found path
 +    return $temp_path;
 +}
 +
  function GNUsocial_class_autoload($cls)
  {
      if (file_exists(INSTALLDIR.'/classes/' . $cls . '.php')) {
      }
  }
  
 +
  // Autoload function queue, starting with our own discovery method
  spl_autoload_register('GNUsocial_class_autoload');
  
diff --combined lib/noticelistitem.php
index 2d538c6e6a9aa6f249cef1878b901acc0993df61,a440c4dd6298bdaf470e439bbd6e163417097cea..eefde0d4a3fad5835fba8c525861d2b777c8784a
@@@ -158,6 -158,8 +158,8 @@@ class NoticeListItem extends Widge
                  $this->showParent();
              } catch (NoParentNoticeException $e) {
                  // no parent notice
+             } catch (InvalidUrlException $e) {
+                 // parent had an invalid URL so we can't show it
              }
              if ($this->addressees) { $this->showAddressees(); }
              $this->elementEnd('div');
      {
          if (Event::handle('StartShowNoticeOptions', array($this))) {
              $user = common_current_user();
 -            if ($user) {
 +
 +            if ($user instanceof User) {
                  $this->out->elementStart('div', 'notice-options');
                  if (Event::handle('StartShowNoticeOptionItems', array($this))) {
                      $this->showReplyLink();
                  }
                  $this->out->elementEnd('div');
              }
 +
              Event::handle('EndShowNoticeOptions', array($this));
          }
      }
index 0dc67cda637706b5cb47ebf4738db9e2f3a5ad43,6805b4fe07d30b546578b2db44367dfc59803c4d..7a2d9517302458d1b01c5e186e94d732dc79c9d1
@@@ -183,7 -183,7 +183,7 @@@ class ActivityPlugin extends Plugi
                                          'uri' => $uri,
                                          'verb' => ActivityVerb::UNFAVORITE,
                                          'object_type' => (($notice->verb == ActivityVerb::POST) ?
-                                                          $notice->object_type : ActivityObject::ACTIVITY)));
+                                                          $notice->object_type : null)));
  
          return true;
      }
          return true;
      }
  
 -    function onStartShowNoticeItem($nli)
 +    function onStartShowNoticeItem(NoticeListItem $nli)
      {
          $notice = $nli->notice;
  
index a1d0761a4a03ce9042c23eab590b811b765e17da,6a00907aa996f929b158a1fbac172af564c96006..8470e5bafb7ba4004e331f0ac9792b60ad34f20b
   * @link      http://status.net/
   */
  
- if (!defined('STATUSNET')) {
-     // This check helps protect against security problems;
-     // your code file can't be executed directly from the web.
-     exit(1);
- }
+ if (!defined('GNUSOCIAL')) { exit(1); }
  
  /**
   * Event plugin
@@@ -125,7 -121,11 +121,11 @@@ class EventPlugin extends MicroAppPlugi
      }
  
      function types() {
-         return array(Happening::OBJECT_TYPE,
+         return array(Happening::OBJECT_TYPE);
+     }
+     function verbs() {
+         return array(ActivityVerb::POST,
                       RSVP::POSITIVE,
                       RSVP::NEGATIVE,
                       RSVP::POSSIBLE);
              $url = $url_object->item(0)->nodeValue;
          }
  
-         $notice = null;
          switch ($activity->verb) {
          case ActivityVerb::POST:
                // FIXME: get startTime, endTime, location and URL
              break;
          case RSVP::POSITIVE:
          case RSVP::NEGATIVE:
+         case RSVP::POSSIBLE:
+             return Notice::saveActivity($activity, $actor, $options);
+             break;
+         default:
+             // TRANS: Exception thrown when event plugin comes across a undefined verb.
+             throw new Exception(_m('Unknown verb for events.'));
+         }
+     }
+     protected function saveObjectFromActivity(Activity $activity, Notice $stored, array $options=array())
+     {
+         $happeningObj = $activity->objects[0];
+         switch ($activity->verb) {
+         case RSVP::POSITIVE:
+         case RSVP::NEGATIVE:
          case RSVP::POSSIBLE:
              $happening = Happening::getKV('uri', $happeningObj->id);
              if (empty($happening)) {
                  // TRANS: Exception thrown when trying to RSVP for an unknown event.
                  throw new Exception(_m('RSVP for unknown event.'));
              }
-             $notice = RSVP::saveNew($actor, $happening, $activity->verb, $options);
+             $object = RSVP::saveNewFromNotice($stored, $happening, $activity->verb);
+             // Our data model expects this
+             $stored->object_type = $activity->verb;
+             return $object;
              break;
          default:
-             // TRANS: Exception thrown when event plugin comes across a undefined verb.
-             throw new Exception(_m('Unknown verb for events.'));
+             common_log(LOG_ERR, 'Unknown verb for events.');
+             return NULL;
          }
-         return $notice;
      }
  
      /**
      {
          switch ($notice->object_type) {
          case Happening::OBJECT_TYPE:
 -            common_log(LOG_DEBUG, "Deleting event from notice...");
 +            common_debug("Deleting event from notice...");
              $happening = Happening::fromNotice($notice);
              $happening->delete();
              break;
          case RSVP::POSITIVE:
          case RSVP::NEGATIVE:
          case RSVP::POSSIBLE:
 -            common_log(LOG_DEBUG, "Deleting rsvp from notice...");
 +            common_debug("Deleting rsvp from notice...");
              $rsvp = RSVP::fromNotice($notice);
 -            common_log(LOG_DEBUG, "to delete: $rsvp->id");
 +            common_debug("to delete: $rsvp->id");
              $rsvp->delete();
              break;
          default:
 -            common_log(LOG_DEBUG, "Not deleting related, wtf...");
 +            common_debug("Not deleting related, wtf...");
          }
      }
  
 -    function onEndShowScripts($action)
 +    function onEndShowScripts(Action $action)
      {
          $action->script($this->path('js/event.js'));
      }
  
 -    function onEndShowStyles($action)
 +    function onEndShowStyles(Action $action)
      {
          $action->cssLink($this->path('css/event.css'));
          return true;
index c4ffc3ee340bb74ba09be17eac457161f10167e7,5c74bfee4f2e2a552f48f8faacd8690323d9ce48..0c39022b140433ccfba4a2cba5b940386a0f8bb4
@@@ -124,16 -124,39 +124,39 @@@ class RSVP extends Managed_DataObjec
          print "Resuming core schema upgrade...";
      }
  
 -    function saveNew($profile, $event, $verb, $options=array())
 +    function saveNew(Profile $profile, $event, $verb, array $options = array())
      {
-         if (array_key_exists('uri', $options)) {
-             $other = RSVP::getKV('uri', $options['uri']);
-             if (!empty($other)) {
-                 // TRANS: Client exception thrown when trying to save an already existing RSVP ("please respond").
-                 throw new ClientException(_m('RSVP already exists.'));
-             }
+         $eventNotice = $event->getNotice();
+         $options = array_merge(array('source' => 'web'), $options);
+         $act = new Activity();
+         $act->type    = ActivityObject::ACTIVITY;
+         $act->verb    = $verb;
+         $act->time    = $options['created'] ? strtotime($options['created']) : time();
+         $act->title   = _m("RSVP");
+         $act->actor   = $profile->asActivityObject();
+         $act->target  = $eventNotice->asActivityObject();
+         $act->objects = array(clone($act->target));
+         $act->content = RSVP::toHTML($profile, $event, self::codeFor($verb));
+         $act->id = common_local_url('showrsvp', array('id' => UUID::gen()));
+         $act->link = $act->id;
+         $saved = Notice::saveActivity($act, $profile, $options);
+         return $saved;
+     }
+     function saveNewFromNotice($notice, $event, $verb)
+     {
+         $other = RSVP::getKV('uri', $notice->uri);
+         if (!empty($other)) {
+             // TRANS: Client exception thrown when trying to save an already existing RSVP ("please respond").
+             throw new ClientException(_m('RSVP already exists.'));
          }
  
+         $profile = $notice->getProfile();
          try {
              $other = RSVP::getByKeys( [ 'profile_id' => $profile->getID(),
                                          'event_uri' => $event->getUri(),
  
          $rsvp = new RSVP();
  
-         $rsvp->id          = UUID::gen();
-         $rsvp->profile_id  = $profile->getID();
-         $rsvp->event_uri    = $event->getUri();
-         $rsvp->response      = self::codeFor($verb);
-         if (array_key_exists('created', $options)) {
-             $rsvp->created = $options['created'];
-         } else {
-             $rsvp->created = common_sql_now();
-         }
-         if (array_key_exists('uri', $options)) {
-             $rsvp->uri = $options['uri'];
-         } else {
-             $rsvp->uri = common_local_url('showrsvp',
-                                         array('id' => $rsvp->id));
-         }
+         preg_match('/\/([^\/]+)\/*/', $notice->uri, $match);
+         $rsvp->id          = $match[1] ? $match[1] : UUID::gen();
+         $rsvp->profile_id  = $profile->id;
+         $rsvp->event_id    = $event->id;
+         $rsvp->response    = self::codeFor($verb);
+         $rsvp->created     = $notice->created;
+         $rsvp->uri         = $notice->uri;
  
          $rsvp->insert();
  
          self::blow('rsvp:for-event:%s', $event->getUri());
  
-         // XXX: come up with something sexier
-         $content = $rsvp->asString();
-         $rendered = $rsvp->asHTML();
-         $options = array_merge(array('object_type' => $verb),
-                                $options);
-         if (!array_key_exists('uri', $options)) {
-             $options['uri'] = $rsvp->uri;
-         }
-         $eventNotice = $event->getNotice();
-         if (!empty($eventNotice)) {
-             $options['reply_to'] = $eventNotice->getID();
-         }
-         $saved = Notice::saveNew($profile->getID(),
-                                  $content,
-                                  array_key_exists('source', $options) ?
-                                  $options['source'] : 'web',
-                                  $options);
-         return $saved;
+         return $rsvp;
      }
  
      function codeFor($verb)
                                $this->response);
      }
  
 -    static function toHTML($profile, $event, $response)
 +    static function toHTML(Profile $profile, Event $event, $response)
      {
          $fmt = null;
  
index eca4412b23068c0d5a114203269d77bd29c26bce,66d5a186d8e2056f8a631c2609a174a71b7fc18b..bad8d42b8c5e747d4434b762b77208574a7349ab
@@@ -114,24 -114,41 +114,41 @@@ class Fave extends Managed_DataObjec
  
      public function delete($useWhere=false)
      {
-         $profile = Profile::getKV('id', $this->user_id);
-         $notice  = Notice::getKV('id', $this->notice_id);
          $result = null;
  
-         if (Event::handle('StartDisfavorNotice', array($profile, $notice, &$result))) {
+         try {
+             $profile = $this->getActor();
+             $notice  = $this->getTarget();
  
-             $result = parent::delete($useWhere);
+             if (Event::handle('StartDisfavorNotice', array($profile, $notice, &$result))) {
  
-             self::blowCacheForProfileId($this->user_id);
-             self::blowCacheForNoticeId($this->notice_id);
-             self::blow('popular');
+                 $result = parent::delete($useWhere);
  
-             if ($result) {
-                 Event::handle('EndDisfavorNotice', array($profile, $notice));
+                 if ($result !== false) {
+                     Event::handle('EndDisfavorNotice', array($profile, $notice));
+                 }
              }
+         } catch (NoResultException $e) {
+             // In case there's some inconsistency where the profile or notice was deleted without losing the fave db entry
+             common_log(LOG_INFO, '"'.get_class($e->obj).'" with id=='.var_export($e->obj->id, true).' object not found when deleting favorite, ignoring...');
+         } catch (EmptyIdException $e) {
+             // Some buggy instances of GNU social have had favroites with notice id==0 stored in the database
+             common_log(LOG_INFO, '"'.get_class($e->obj).'"object had empty id deleting favorite, ignoring...');
+         }
+         // If we catch an exception above, then $result===null because parent::delete only returns an int>=0 or boolean false
+         if (is_null($result)) {
+             // Delete it without the event, as something is wrong and we don't want it anyway.
+             $result = parent::delete($useWhere);
          }
  
+         // Err, apparently we can reference $this->user_id after parent::delete,
+         // I guess it's safe because this is the order it was before!
+         self::blowCacheForProfileId($this->user_id);
+         self::blowCacheForNoticeId($this->notice_id);
+         self::blow('popular');
          return $result;
      }
  
       *
       * @return array Array of Fave objects
       */
 -    static public function byNotice($notice)
 +    static public function byNotice(Notice $notice)
      {
          if (!isset(self::$_faves[$notice->id])) {
              self::fillFaves(array($notice->id));
  
      public function getTarget()
      {
-         // throws exception on failure
-         $target = new Notice();
-         $target->id = $this->notice_id;
-         if (!$target->find(true)) {
-             throw new NoResultException($target);
-         }
-         return $target;
+         return Notice::getByID($this->notice_id);
      }
  
      public function getTargetObject()