X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=classes%2FNotice.php;h=41c4544cf2fc69efc5ce84c43fbbea11729b2f5a;hb=0e2470998936872dca00f7819408933a1e72b4b7;hp=89f41ad79b1335c5334ede77a45d5029f839b3ce;hpb=c96f0aa30fce7995faac5a359a23e897fc656a93;p=quix0rs-gnu-social.git diff --git a/classes/Notice.php b/classes/Notice.php index 89f41ad79b..41c4544cf2 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -55,23 +55,19 @@ class Notice extends Managed_DataObject public $__table = 'notice'; // table name public $id; // int(4) primary_key not_null public $profile_id; // int(4) multiple_key not_null - public $uri; // varchar(255) unique_key + public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space public $content; // text public $rendered; // text - public $url; // varchar(255) + public $url; // varchar(191) not 255 because utf8mb4 takes more space public $created; // datetime multiple_key not_null default_0000-00-00%2000%3A00%3A00 public $modified; // timestamp not_null default_CURRENT_TIMESTAMP public $reply_to; // int(4) public $is_local; // int(4) public $source; // varchar(32) public $conversation; // int(4) - public $lat; // decimal(10,7) - public $lon; // decimal(10,7) - public $location_id; // int(4) - public $location_ns; // int(4) public $repeat_of; // int(4) - public $verb; // varchar(255) - public $object_type; // varchar(255) + public $verb; // varchar(191) not 255 because utf8mb4 takes more space + public $object_type; // varchar(191) not 255 because utf8mb4 takes more space public $scope; // int(4) /* the code above is auto generated do not remove the tag below */ @@ -83,23 +79,19 @@ class Notice extends Managed_DataObject 'fields' => array( 'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'), 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'who made the update'), - 'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universally unique identifier, usually a tag URI'), - 'content' => array('type' => 'text', 'description' => 'update content', 'collate' => 'utf8_general_ci'), + 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universally unique identifier, usually a tag URI'), + 'content' => array('type' => 'text', 'description' => 'update content', 'collate' => 'utf8mb4_general_ci'), 'rendered' => array('type' => 'text', 'description' => 'HTML version of the content'), - 'url' => array('type' => 'varchar', 'length' => 255, 'description' => 'URL of any attachment (image, video, bookmark, whatever)'), + 'url' => array('type' => 'varchar', 'length' => 191, 'description' => 'URL of any attachment (image, video, bookmark, whatever)'), 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'), 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), 'reply_to' => array('type' => 'int', 'description' => 'notice replied to (usually a guess)'), 'is_local' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'notice was generated by a user'), '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'), - 'lat' => array('type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'latitude'), - 'lon' => array('type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'longitude'), - 'location_id' => array('type' => 'int', 'description' => 'location id if possible'), - 'location_ns' => array('type' => 'int', 'description' => 'namespace for location'), 'repeat_of' => array('type' => 'int', 'description' => 'notice this is a repeat of'), - 'object_type' => array('type' => 'varchar', 'length' => 255, 'description' => 'URI representing activity streams object type', 'default' => 'http://activitystrea.ms/schema/1.0/note'), - 'verb' => array('type' => 'varchar', 'length' => 255, 'description' => 'URI representing activity streams verb', 'default' => 'http://activitystrea.ms/schema/1.0/post'), + 'object_type' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams object type', 'default' => 'http://activitystrea.ms/schema/1.0/note'), + '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'), ), @@ -128,7 +120,7 @@ class Notice extends Managed_DataObject return $def; } - + /* Notice types */ const LOCAL_PUBLIC = 1; const REMOTE = 0; @@ -142,7 +134,7 @@ class Notice extends Managed_DataObject const FOLLOWER_SCOPE = 8; protected $_profile = array(); - + /** * Will always return a profile, if anything fails it will * (through _setProfile) throw a NoProfileException. @@ -157,7 +149,7 @@ class Notice extends Managed_DataObject } return $this->_profile[$this->profile_id]; } - + public function _setProfile(Profile $profile=null) { if (!$profile instanceof Profile) { @@ -166,6 +158,14 @@ class Notice extends Managed_DataObject $this->_profile[$this->profile_id] = $profile; } + public function deleteAs(Profile $actor) + { + if ($this->getProfile()->sameAs($actor) || $actor->hasRight(Right::DELETEOTHERSNOTICE)) { + return $this->delete(); + } + throw new AuthorizationException('You are not allowed to delete other user\'s notices'); + } + function delete($useWhere=false) { // For auditing purposes, save a record that the notice @@ -196,6 +196,7 @@ class Notice extends Managed_DataObject // Clear related records $this->clearReplies(); + $this->clearLocation(); $this->clearRepeats(); $this->clearTags(); $this->clearGroupInboxes(); @@ -268,7 +269,7 @@ class Notice extends Managed_DataObject } return $title; } - + public function getContent() { return $this->content; @@ -276,19 +277,21 @@ class Notice extends Managed_DataObject /* * Get the original representation URL of this notice. + * + * @param boolean $fallback Whether to fall back to generate a local URL or throw InvalidUrlException */ - public function getUrl() + public function getUrl($fallback=false) { // The risk is we start having empty urls and non-http uris... // and we can't really handle any other protocol right now. switch (true) { case common_valid_http_url($this->url): // should we allow non-http/https URLs? return $this->url; - case $this->isLocal(): + case !$this->isLocal() && common_valid_http_url($this->uri): // Sometimes we only have the URI for remote posts. + return $this->uri; + case $this->isLocal() || $fallback: // let's generate a valid link to our locally available notice on demand return common_local_url('shownotice', array('notice' => $this->id), null, null, false); - case common_valid_http_url($this->uri): - return $this->uri; default: common_debug('No URL available for notice: id='.$this->id); throw new InvalidUrlException($this->url); @@ -417,8 +420,9 @@ class Notice extends Managed_DataObject static function saveNew($profile_id, $content, $source, array $options=null) { $defaults = array('uri' => null, 'url' => null, - 'reply_to' => null, - 'repeat_of' => null, + 'conversation' => null, // URI of conversation + 'reply_to' => null, // This will override convo URI if the parent is known + 'repeat_of' => null, // This will override convo URI if the repeated notice is known 'scope' => null, 'distribute' => true, 'object_type' => null, @@ -520,18 +524,13 @@ class Notice extends Managed_DataObject // Handle repeat case - if (isset($repeat_of)) { + if (!empty($options['repeat_of'])) { // Check for a private one - $repeat = Notice::getKV('id', $repeat_of); - - if (!($repeat instanceof Notice)) { - // TRANS: Client exception thrown in notice when trying to repeat a missing or deleted notice. - throw new ClientException(_('Cannot repeat; original notice is missing or deleted.')); - } + $repeat = Notice::getByID($options['repeat_of']); - if ($profile->id == $repeat->profile_id) { + if ($profile->sameAs($repeat->getProfile())) { // TRANS: Client error displayed when trying to repeat an own notice. throw new ClientException(_('You cannot repeat your own notice.')); } @@ -600,16 +599,38 @@ class Notice extends Managed_DataObject // Scope set below } + + // If we don't know the reply, we might know the conversation! + // This will happen if a known remote user replies to an + // unknown remote user - within a known conversation. + if (empty($notice->conversation) and !empty($options['conversation'])) { + $conv = Conversation::getKV('uri', $options['conversation']); + if ($conv instanceof Conversation) { + common_debug('Conversation stitched together from (probably) a reply to unknown remote user. Activity creation time ('.$notice->created.') should maybe be compared to conversation creation time ('.$conv->created.').'); + $notice->conversation = $conv->id; + } else { + // Conversation URI was not found, so we must create it. But we can't create it + // until we have a Notice ID because of the database layout... + // $options['conversation'] will be used later after the $notice->insert(); + common_debug('Conversation URI not found, so we have to create it after inserting this Notice: '.$options['conversation']); + } + } else { + // If we're not using the attached conversation URI let's remove it + // so we don't mistake ourselves later, when creating our own Conversation. + // This implies that the notice knows which conversation it belongs to. + $options['conversation'] = null; + } } + $notloc = new Notice_location(); if (!empty($lat) && !empty($lon)) { - $notice->lat = $lat; - $notice->lon = $lon; + $notloc->lat = $lat; + $notloc->lon = $lon; } if (!empty($location_ns) && !empty($location_id)) { - $notice->location_id = $location_id; - $notice->location_ns = $location_ns; + $notloc->location_id = $location_id; + $notloc->location_ns = $location_ns; } if (!empty($rendered)) { @@ -648,7 +669,23 @@ class Notice extends Managed_DataObject // XXX: some of these functions write to the DB try { - $notice->insert(); // throws exception on failure + $notice->insert(); // throws exception on failure, if successful we have an ->id + + if (($notloc->lat && $notloc->lon) || ($notloc->location_id && $notloc->location_ns)) { + $notloc->notice_id = $notice->getID(); + $notloc->insert(); // store the notice location if it had any information + } + + // If it's not part of a conversation, it's + // the beginning of a new conversation. + if (empty($notice->conversation)) { + $orig = clone($notice); + // $act->context->conversation will be null if it was not provided + + $conv = Conversation::create($notice, $options['conversation']); + $notice->conversation = $conv->id; + $notice->update($orig); + } } catch (Exception $e) { // Let's test if we managed initial insert, which would imply // failing on some update-part (check 'insert()'). Delete if @@ -656,6 +693,7 @@ class Notice extends Managed_DataObject if (!empty($notice->id)) { $notice->delete(); } + throw $e; } } @@ -744,7 +782,7 @@ class Notice extends Managed_DataObject 'distribute' => true); // options will have default values when nothing has been supplied - $options = array_merge($defaults, $options); + $options = array_merge($defaults, $options); foreach (array_keys($defaults) as $key) { // Only convert the keynames we specify ourselves from 'defaults' array into variables $$key = $options[$key]; @@ -820,17 +858,28 @@ class Notice extends Managed_DataObject if (is_null($scope)) { $scope = $reply->scope; } + } else { + // If we don't know the reply, we might know the conversation! + // This will happen if a known remote user replies to an + // unknown remote user - within a known conversation. + if (empty($stored->conversation) and !empty($act->context->conversation)) { + $conv = Conversation::getKV('uri', $act->context->conversation); + if ($conv instanceof Conversation) { + common_debug('Conversation stitched together from (probably) a reply activity to unknown remote user. Activity creation time ('.$stored->created.') should maybe be compared to conversation creation time ('.$conv->created.').'); + $stored->conversation = $conv->id; + } else { + // Conversation URI was not found, so we must create it. But we can't create it + // until we have a Notice ID because of the database layout... + // $options['conversation'] will be used later after the $stored->insert(); + common_debug('Conversation URI from activity context not found, so we have to create it after inserting this Notice: '.$act->context->conversation); + } + } } + $notloc = null; if ($act->context instanceof ActivityContext) { - $location = $act->context->location; - if ($location) { - $stored->lat = $location->lat; - $stored->lon = $location->lon; - if ($location->location_id) { - $stored->location_ns = $location->location_ns; - $stored->location_id = $location->location_id; - } + if ($act->context->location instanceof Location) { + $notloc = Notice_location::fromLocation($act->context->location); } } else { $act->context = new ActivityContext(); @@ -858,13 +907,28 @@ class Notice extends Managed_DataObject try { $stored->insert(); // throws exception on error + if ($notloc instanceof Notice_location) { + $notloc->notice_id = $stored->getID(); + $notloc->insert(); + } + + $orig = clone($stored); // for updating later in this try clause + $object = null; Event::handle('StoreActivityObject', array($act, $stored, $options, &$object)); if (empty($object)) { - throw new ServerException('No object from StoreActivityObject '.$stored->uri . ': '.$act->asString()); + throw new ServerException('Unsuccessful call to StoreActivityObject '.$stored->uri . ': '.$act->asString()); } - $orig = clone($stored); - $stored->object_type = ActivityUtils::resolveUri($object->getObjectType(), true); + + // If it's not part of a conversation, it's the beginning + // of a new one (or belongs to a previously unknown URI). + if (empty($stored->conversation)) { + // $act->context->conversation will be null if it was not provided + common_debug('Creating a new conversation for stored notice ID='.$stored->getID().' with URI: '.$act->context->conversation); + $conv = Conversation::create($stored, $act->context->conversation); + $stored->conversation = $conv->id; + } + $stored->update($orig); } catch (Exception $e) { if (empty($stored->id)) { @@ -916,7 +980,7 @@ class Notice extends Managed_DataObject // Prepare inbox delivery, may be queued to background. $stored->distribute(); } - + return $stored; } @@ -938,12 +1002,14 @@ class Notice extends Managed_DataObject // Force the scope for private groups foreach ($groups as $group_id) { - $group = User_group::staticGet('id', $group_id); - if ($group instanceof User_group) { + try { + $group = User_group::getByID($group_id); if ($group->force_scope) { $scope |= Notice::GROUP_SCOPE; break; } + } catch (Exception $e) { + common_log(LOG_ERR, 'Notice figureOutScope threw exception: '.$e->getMessage()); } } @@ -956,6 +1022,7 @@ class Notice extends Managed_DataObject if ($this->isPublic()) { $this->blowStream('public'); + $this->blowStream('networkpublic'); } self::blow('notice:list-ids:conversation:%s', $this->conversation); @@ -1000,6 +1067,7 @@ class Notice extends Managed_DataObject if ($this->isPublic()) { self::blow('public;last'); + self::blow('networkpublic;last'); } self::blow('fave:by_notice', $this->id); @@ -1024,13 +1092,9 @@ class Notice extends Managed_DataObject } $args = func_get_args(); - $format = array_shift($args); - $keyPart = vsprintf($format, $args); - $cacheKey = Cache::key($keyPart); - $c->delete($cacheKey); // delete the "last" stream, too, if this notice is @@ -1060,7 +1124,7 @@ class Notice extends Managed_DataObject */ function saveUrls() { if (common_config('attachments', 'process_links')) { - common_replace_urls_callback($this->content, array($this, 'saveUrl'), $this->id); + common_replace_urls_callback($this->content, array($this, 'saveUrl'), $this); } } @@ -1077,11 +1141,7 @@ class Notice extends Managed_DataObject if (common_config('attachments', 'process_links')) { // @fixme validation? foreach (array_unique($urls) as $url) { - try { - File::processNew($url, $this->id); - } catch (ServerException $e) { - // Could not save URL. Log it? - } + $this->saveUrl($url, $this); } } } @@ -1089,9 +1149,9 @@ class Notice extends Managed_DataObject /** * @private callback */ - function saveUrl($url, $notice_id) { + function saveUrl($url, Notice $notice) { try { - File::processNew($url, $notice_id); + File::processNew($url, $notice); } catch (ServerException $e) { // Could not save URL. Log it? } @@ -1144,24 +1204,20 @@ class Notice extends Managed_DataObject } protected $_attachments = array(); - + function attachments() { if (isset($this->_attachments[$this->id])) { return $this->_attachments[$this->id]; } - + $f2ps = File_to_post::listGet('post_id', array($this->id)); - $ids = array(); - foreach ($f2ps[$this->id] as $f2p) { - $ids[] = $f2p->file_id; + $ids[] = $f2p->file_id; } - - $files = File::multiGet('id', $ids); + $files = File::multiGet('id', $ids); $this->_attachments[$this->id] = $files->fetchAll(); - return $this->_attachments[$this->id]; } @@ -1170,17 +1226,15 @@ class Notice extends Managed_DataObject $this->_attachments[$this->id] = $attachments; } - function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0) + static function publicStream($offset=0, $limit=20, $since_id=null, $max_id=null) { $stream = new PublicNoticeStream(); return $stream->getNotices($offset, $limit, $since_id, $max_id); } - - function conversationStream($id, $offset=0, $limit=20, $since_id=0, $max_id=0) + static function conversationStream($id, $offset=0, $limit=20, $since_id=null, $max_id=null) { $stream = new ConversationNoticeStream($id); - return $stream->getNotices($offset, $limit, $since_id, $max_id); } @@ -1192,18 +1246,17 @@ class Notice extends Managed_DataObject */ function hasConversation() { - if (!empty($this->conversation)) { - $conversation = Notice::conversationStream( - $this->conversation, - 1, - 1 - ); - - if ($conversation->N > 0) { - return true; - } + if (empty($this->conversation)) { + // this notice is not part of a conversation apparently + // FIXME: all notices should have a conversation value, right? + return false; } - return false; + + $stream = new ConversationNoticeStream($this->conversation); + $notice = $stream->getNotices(/*offset*/ 1, /*limit*/ 1); + + // if our "offset 1, limit 1" query got a result, return true else false + return $notice->N > 0; } /** @@ -1232,12 +1285,17 @@ class Notice extends Managed_DataObject } // If this isn't a reply to anything, then it's its own - // root. + // root if it's the earliest notice in the conversation: if (empty($this->reply_to)) { - return $this; + $root = new Notice; + $root->conversation = $this->conversation; + $root->orderBy('notice.created ASC'); + $root->find(true); // true means "fetch first result" + $root->free(); + return $root; } - + if (is_null($profile)) { $keypart = sprintf('notice:conversation_root:%d:null', $this->id); } else { @@ -1245,7 +1303,7 @@ class Notice extends Managed_DataObject $this->id, $profile->id); } - + $root = self::cacheGet($keypart); if ($root !== false && $root->inScope($profile)) { @@ -1260,7 +1318,7 @@ class Notice extends Managed_DataObject $last = $parent; continue; } - } catch (Exception $e) { + } catch (NoParentNoticeException $e) { // Latest notice has no parent } // No parent, or parent out of scope @@ -1444,7 +1502,7 @@ class Notice extends Managed_DataObject foreach (array_unique($group_ids) as $id) { $group = User_group::getKV('id', $id); if ($group instanceof User_group) { - common_log(LOG_ERR, "Local delivery to group id $id, $group->nickname"); + common_log(LOG_DEBUG, "Local delivery to group id $id, $group->nickname"); $result = $this->addToGroupInbox($group); if (!$result) { common_log_db_error($gi, 'INSERT', __FILE__); @@ -1566,7 +1624,7 @@ class Notice extends Managed_DataObject $this->saveReply($parentauthor->id); $replied[$parentauthor->id] = 1; self::blow('reply:stream:%d', $parentauthor->id); - } catch (Exception $e) { + } catch (NoParentNoticeException $e) { // Not a reply, since it has no parent! } @@ -1583,8 +1641,7 @@ class Notice extends Managed_DataObject foreach ($mention['mentioned'] as $mentioned) { // skip if they're already covered - - if (!empty($replied[$mentioned->id])) { + if (array_key_exists($mentioned->id, $replied)) { continue; } @@ -1622,32 +1679,22 @@ class Notice extends Managed_DataObject protected $_replies = array(); /** - * Pull the complete list of @-reply targets for this notice. + * Pull the complete list of @-mentioned profile IDs for this notice. * * @return array of integer profile ids */ function getReplies() { - if (isset($this->_replies[$this->id])) { - return $this->_replies[$this->id]; - } - - $replyMap = Reply::listGet('notice_id', array($this->id)); - - $ids = array(); - - foreach ($replyMap[$this->id] as $reply) { - $ids[] = $reply->profile_id; + if (!isset($this->_replies[$this->getID()])) { + $mentions = Reply::multiGet('notice_id', array($this->getID())); + $this->_replies[$this->getID()] = $mentions->fetchAll('profile_id'); } - - $this->_replies[$this->id] = $ids; - - return $ids; + return $this->_replies[$this->getID()]; } function _setReplies($replies) { - $this->_replies[$this->id] = $replies; + $this->_replies[$this->getID()] = $replies; } /** @@ -1658,9 +1705,9 @@ class Notice extends Managed_DataObject function getReplyProfiles() { $ids = $this->getReplies(); - + $profiles = Profile::multiGet('id', $ids); - + return $profiles->fetchAll(); } @@ -1698,9 +1745,9 @@ class Notice extends Managed_DataObject * * @return array of Group objects */ - + protected $_groups = array(); - + function getGroups() { // Don't save groups for repeats @@ -1708,27 +1755,24 @@ class Notice extends Managed_DataObject if (!empty($this->repeat_of)) { return array(); } - + if (isset($this->_groups[$this->id])) { return $this->_groups[$this->id]; } - + $gis = Group_inbox::listGet('notice_id', array($this->id)); $ids = array(); - foreach ($gis[$this->id] as $gi) - { + foreach ($gis[$this->id] as $gi) { $ids[] = $gi->group_id; } - + $groups = User_group::multiGet('id', $ids); - $this->_groups[$this->id] = $groups->fetchAll(); - return $this->_groups[$this->id]; } - + function _setGroups($groups) { $this->_groups[$this->id] = $groups; @@ -1737,12 +1781,12 @@ class Notice extends Managed_DataObject /** * Convert a notice into an activity for export. * - * @param User $cur Current user + * @param Profile $scoped The currently logged in/scoped profile * * @return Activity activity object representing this Notice. */ - function asActivity($cur=null) + function asActivity(Profile $scoped=null) { $act = self::cacheGet(Cache::codeKey('notice:as-activity:'.$this->id)); @@ -1751,7 +1795,7 @@ class Notice extends Managed_DataObject } $act = new Activity(); - if (Event::handle('StartNoticeAsActivity', array($this, &$act))) { + if (Event::handle('StartNoticeAsActivity', array($this, $act, $scoped))) { $act->id = $this->uri; $act->time = strtotime($this->created); @@ -1766,16 +1810,11 @@ class Notice extends Managed_DataObject $profile = $this->getProfile(); $act->actor = $profile->asActivityObject(); - $act->actor->extra[] = $profile->profileInfo($cur); + $act->actor->extra[] = $profile->profileInfo($scoped); $act->verb = $this->verb; - if ($this->repeat_of) { - $repeated = Notice::getKV('id', $this->repeat_of); - if ($repeated instanceof Notice) { - $act->objects[] = $repeated->asActivity($cur); - } - } else { + if (!$this->repeat_of) { $act->objects[] = $this->asActivityObject(); } @@ -1798,9 +1837,9 @@ class Notice extends Managed_DataObject $attachments = $this->attachments(); foreach ($attachments as $attachment) { - // Save local attachments + // Include local attachments in Activity if (!empty($attachment->filename)) { - $act->attachments[] = ActivityObject::fromFile($attachment); + $act->enclosures[] = $attachment->getEnclosure(); } } @@ -1809,12 +1848,16 @@ class Notice extends Managed_DataObject try { $reply = $this->getParent(); $ctx->replyToID = $reply->getUri(); - $ctx->replyToUrl = $reply->getUrl(); - } catch (Exception $e) { + $ctx->replyToUrl = $reply->getUrl(true); // true for fallback to local URL, less messy + } catch (NoParentNoticeException $e) { // This is not a reply to something } - $ctx->location = $this->getLocation(); + try { + $ctx->location = Notice_location::locFromStored($this); + } catch (ServerException $e) { + $ctx->location = null; + } $conv = null; @@ -1898,7 +1941,7 @@ class Notice extends Managed_DataObject $act->editLink = $act->selfLink; } - Event::handle('EndNoticeAsActivity', array($this, &$act)); + Event::handle('EndNoticeAsActivity', array($this, $act, $scoped)); } self::cacheSet(Cache::codeKey('notice:as-activity:'.$this->id), $act); @@ -1912,10 +1955,10 @@ class Notice extends Managed_DataObject function asAtomEntry($namespace=false, $source=false, $author=true, - $cur=null) + Profile $scoped=null) { - $act = $this->asActivity($cur); - $act->extra[] = $this->noticeInfo($cur); + $act = $this->asActivity($scoped); + $act->extra[] = $this->noticeInfo($scoped); return $act->asString($namespace, $author, $source); } @@ -1925,12 +1968,12 @@ class Notice extends Managed_DataObject * Clients use some extra notice info in the atom stream. * This gives it to them. * - * @param User $cur Current user + * @param Profile $scoped The currently logged in/scoped profile * * @return array representation of element */ - function noticeInfo($cur) + function noticeInfo(Profile $scoped=null) { // local notice ID (useful to clients for ordering) @@ -1956,9 +1999,7 @@ class Notice extends Managed_DataObject // favorite and repeated - $scoped = null; - if (!empty($cur)) { - $scoped = $cur->getProfile(); + if ($scoped instanceof Profile) { $noticeInfoAttr['repeated'] = ($scoped->hasRepeated($this)) ? "true" : "false"; } @@ -2062,23 +2103,6 @@ class Notice extends Managed_DataObject return ($contentlimit > 0 && !empty($content) && (mb_strlen($content) > $contentlimit)); } - function getLocation() - { - $location = null; - - if (!empty($this->location_id) && !empty($this->location_ns)) { - $location = Location::fromId($this->location_id, $this->location_ns); - } - - if (is_null($location)) { // no ID, or Location::fromId() failed - if (!empty($this->lat) && !empty($this->lon)) { - $location = Location::fromLatLon($this->lat, $this->lon); - } - } - - return $location; - } - /** * Convenience function for posting a repeat of an existing message. * @@ -2098,6 +2122,18 @@ class Notice extends Managed_DataObject $author->getNickname(), $this->content); + $maxlen = self::maxContent(); + if ($maxlen > 0 && mb_strlen($content) > $maxlen) { + // Web interface and current Twitter API clients will + // pull the original notice's text, but some older + // clients and RSS/Atom feeds will see this trimmed text. + // + // Unfortunately this is likely to lose tags or URLs + // at the end of long notices. + $content = mb_substr($content, 0, $maxlen - 4) . ' ...'; + } + + // Scope is same as this one's return self::saveNew($repeater->id, $content, @@ -2153,7 +2189,7 @@ class Notice extends Managed_DataObject return $notice->fetchAll('id'); } - function locationOptions($lat, $lon, $location_id, $location_ns, $profile = null) + static function locationOptions($lat, $lon, $location_id, $location_ns, $profile = null) { $options = array(); @@ -2237,6 +2273,16 @@ class Notice extends Managed_DataObject $reply->free(); } + function clearLocation() + { + $loc = new Notice_location(); + $loc->notice_id = $this->id; + + if ($loc->find()) { + $loc->delete(); + } + } + function clearFiles() { $f2p = new File_to_post(); @@ -2365,14 +2411,6 @@ class Notice extends Managed_DataObject $changed = true; } - // If it's not part of a conversation, it's - // the beginning of a new conversation. - if (empty($this->conversation)) { - $conv = Conversation::create($this); - $this->conversation = $conv->id; - $changed = true; - } - if ($changed && $this->update($orig) === false) { common_log_db_error($notice, 'UPDATE', __FILE__); // TRANS: Server exception thrown when a notice cannot be updated. @@ -2392,31 +2430,34 @@ class Notice extends Managed_DataObject */ function getSource() { + if (empty($this->source)) { + return false; + } + $ns = new Notice_source(); - if (!empty($this->source)) { - switch ($this->source) { - case 'web': - case 'xmpp': - case 'mail': - case 'omb': - case 'system': - case 'api': + switch ($this->source) { + case 'web': + case 'xmpp': + case 'mail': + case 'omb': + case 'system': + case 'api': + $ns->code = $this->source; + break; + default: + $ns = Notice_source::getKV($this->source); + if (!$ns) { + $ns = new Notice_source(); $ns->code = $this->source; - break; - default: - $ns = Notice_source::getKV($this->source); - if (!$ns) { - $ns = new Notice_source(); - $ns->code = $this->source; - $app = Oauth_application::getKV('name', $this->source); - if ($app) { - $ns->name = $app->name; - $ns->url = $app->source_url; - } + $app = Oauth_application::getKV('name', $this->source); + if ($app) { + $ns->name = $app->name; + $ns->url = $app->source_url; } - break; } + break; } + return $ns; } @@ -2579,12 +2620,8 @@ class Notice extends Managed_DataObject function isPublic() { - if (common_config('public', 'localonly')) { - return ($this->is_local == Notice::LOCAL_PUBLIC); - } else { - return (($this->is_local != Notice::LOCAL_NONPUBLIC) && - ($this->is_local != Notice::GATEWAY)); - } + return (($this->is_local != Notice::LOCAL_NONPUBLIC) && + ($this->is_local != Notice::GATEWAY)); } /** @@ -2632,89 +2669,69 @@ class Notice extends Managed_DataObject $scope = self::defaultScope(); } - // If there's no scope, anyone (even anon) is in scope. - - if ($scope == 0) { // Not private - + if ($scope == 0 && !$this->getProfile()->isPrivateStream()) { // Not scoping, so it is public. return !$this->isHiddenSpam($profile); + } - } else { // Private, somehow - - // If there's scope, anon cannot be in scope + // If there's scope, anon cannot be in scope + if (empty($profile)) { + return false; + } - if (empty($profile)) { - return false; - } + // Author is always in scope + if ($this->profile_id == $profile->id) { + return true; + } - // Author is always in scope + // Only for users on this site + if (($scope & Notice::SITE_SCOPE) && !$profile->isLocal()) { + return false; + } - if ($this->profile_id == $profile->id) { - return true; - } + // Only for users mentioned in the notice + if ($scope & Notice::ADDRESSEE_SCOPE) { - // Only for users on this site + $reply = Reply::pkeyGet(array('notice_id' => $this->id, + 'profile_id' => $profile->id)); - if (($scope & Notice::SITE_SCOPE) && !$profile->isLocal()) { + if (!$reply instanceof Reply) { return false; } + } - // Only for users mentioned in the notice - - if ($scope & Notice::ADDRESSEE_SCOPE) { - - $reply = Reply::pkeyGet(array('notice_id' => $this->id, - 'profile_id' => $profile->id)); - - if (!$reply instanceof Reply) { - return false; - } - } - - // Only for members of the given group - - if ($scope & Notice::GROUP_SCOPE) { - - // XXX: just query for the single membership + // Only for members of the given group + if ($scope & Notice::GROUP_SCOPE) { - $groups = $this->getGroups(); + // XXX: just query for the single membership - $foundOne = false; + $groups = $this->getGroups(); - foreach ($groups as $group) { - if ($profile->isMember($group)) { - $foundOne = true; - break; - } - } + $foundOne = false; - if (!$foundOne) { - return false; + foreach ($groups as $group) { + if ($profile->isMember($group)) { + $foundOne = true; + break; } } - // Only for followers of the author - - $author = null; + if (!$foundOne) { + return false; + } + } - if ($scope & Notice::FOLLOWER_SCOPE) { + if ($scope & Notice::FOLLOWER_SCOPE || $this->getProfile()->isPrivateStream()) { - try { - $author = $this->getProfile(); - } catch (Exception $e) { - return false; - } - - if (!Subscription::exists($profile, $author)) { - return false; - } + if (!Subscription::exists($profile, $this->getProfile())) { + return false; } - - return !$this->isHiddenSpam($profile); } + + return !$this->isHiddenSpam($profile); } function isHiddenSpam($profile) { - + // Hide posts by silenced users from everyone but moderators. if (common_config('notice', 'hidespam')) { @@ -2739,13 +2756,10 @@ class Notice extends Managed_DataObject public function getParent() { - $parent = Notice::getKV('id', $this->reply_to); - - if (!$parent instanceof Notice) { - throw new ServerException('Notice has no parent'); + if (empty($this->reply_to)) { + throw new NoParentNoticeException($this); } - - return $parent; + return self::getByID($this->reply_to); } /** @@ -2764,7 +2778,7 @@ class Notice extends Managed_DataObject $skip = array('_profile', '_groups', '_attachments', '_faves', '_replies', '_repeats'); return array_diff($vars, $skip); } - + static function defaultScope() { $scope = common_config('notice', 'defaultscope'); @@ -2781,7 +2795,6 @@ class Notice extends Managed_DataObject static function fillProfiles($notices) { $map = self::getProfiles($notices); - foreach ($notices as $entry=>$notice) { try { if (array_key_exists($notice->profile_id, $map)) { @@ -2792,42 +2805,35 @@ class Notice extends Managed_DataObject unset($notices[$entry]); } } - + return array_values($map); } - + static function getProfiles(&$notices) { $ids = array(); foreach ($notices as $notice) { $ids[] = $notice->profile_id; } - $ids = array_unique($ids); - - return Profile::pivotGet('id', $ids); + return Profile::pivotGet('id', $ids); } - + static function fillGroups(&$notices) { $ids = self::_idsOf($notices); - $gis = Group_inbox::listGet('notice_id', $ids); - $gids = array(); - foreach ($gis as $id => $gi) - { + foreach ($gis as $id => $gi) { foreach ($gi as $g) { $gids[] = $g->group_id; } } - + $gids = array_unique($gids); - $group = User_group::pivotGet('id', $gids); - foreach ($notices as $notice) { $grps = array(); @@ -2851,21 +2857,16 @@ class Notice extends Managed_DataObject static function fillAttachments(&$notices) { $ids = self::_idsOf($notices); - $f2pMap = File_to_post::listGet('post_id', $ids); - $fileIds = array(); - foreach ($f2pMap as $noticeId => $f2ps) { foreach ($f2ps as $f2p) { - $fileIds[] = $f2p->file_id; + $fileIds[] = $f2p->file_id; } } $fileIds = array_unique($fileIds); - $fileMap = File::pivotGet('id', $fileIds); - foreach ($notices as $notice) { $files = array(); @@ -2891,30 +2892,50 @@ class Notice extends Managed_DataObject } } - protected $_repeats = array(); - - function getRepeats() + static public function beforeSchemaUpdate() { - if (isset($this->_repeats[$this->id])) { - return $this->_repeats[$this->id]; + $table = strtolower(get_called_class()); + $schema = Schema::get(); + $schemadef = $schema->getTableDef($table); + + // 2015-09-04 We move Notice location data to Notice_location + // First we see if we have to do this at all + if (!isset($schemadef['fields']['lat']) + && !isset($schemadef['fields']['lon']) + && !isset($schemadef['fields']['location_id']) + && !isset($schemadef['fields']['location_ns'])) { + // We have already removed the location fields, so no need to migrate. + return; } - $repeatMap = Notice::listGet('repeat_of', array($this->id)); - $this->_repeats[$this->id] = $repeatMap[$this->id]; - return $this->_repeats[$this->id]; - } + // Then we make sure the Notice_location table is created! + $schema->ensureTable('notice_location', Notice_location::schemaDef()); - function _setRepeats($repeats) - { - $this->_repeats[$this->id] = $repeats; - } + // Then we continue on our road to migration! + echo "\nFound old $table table, moving location data to 'notice_location' table... (this will probably take a LONG time, but can be aborted and continued)"; - static function fillRepeats(&$notices) - { - $ids = self::_idsOf($notices); - $repeatMap = Notice::listGet('repeat_of', $ids); - foreach ($notices as $notice) { - $repeats = $repeatMap[$notice->id]; - $notice->_setRepeats($repeats); + $notice = new Notice(); + $notice->query(sprintf('SELECT id, lat, lon, location_id, location_ns FROM %1$s ' . + 'WHERE lat IS NOT NULL ' . + 'OR lon IS NOT NULL ' . + 'OR location_id IS NOT NULL ' . + 'OR location_ns IS NOT NULL', + $schema->quoteIdentifier($table))); + print "\nFound {$notice->N} notices with location data, inserting"; + while ($notice->fetch()) { + $notloc = Notice_location::getKV('notice_id', $notice->id); + if ($notloc instanceof Notice_location) { + print "-"; + continue; + } + $notloc = new Notice_location(); + $notloc->notice_id = $notice->id; + $notloc->lat= $notice->lat; + $notloc->lon= $notice->lon; + $notloc->location_id= $notice->location_id; + $notloc->location_ns= $notice->location_ns; + $notloc->insert(); + print "."; } + print "\n"; } }