X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=classes%2FNotice.php;h=b828678d87c92f338ee3538404a900f9b2324f39;hb=3d6e25ee5f78d4fc3e00335d39724694264bbd53;hp=86eec8e3adebbec2d838eaaeb82fc77630798d54;hpb=519e3308ab33048fdf51127560c67e7572e48d49;p=quix0rs-gnu-social.git diff --git a/classes/Notice.php b/classes/Notice.php index 86eec8e3ad..b828678d87 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -110,7 +110,10 @@ class Notice extends Managed_DataObject 'notice_profile_id_idx' => array('profile_id', 'created', 'id'), 'notice_repeat_of_created_id_idx' => array('repeat_of', 'created', 'id'), 'notice_conversation_created_id_idx' => array('conversation', 'created', 'id'), + 'notice_object_type_idx' => array('object_type'), 'notice_verb_idx' => array('verb'), + 'notice_profile_id_verb_idx' => array('profile_id', 'verb'), + 'notice_url_idx' => array('url'), // Qvitter wants this 'notice_replyto_idx' => array('reply_to') ) ); @@ -165,6 +168,18 @@ class Notice extends Managed_DataObject throw new AuthorizationException(_('You are not allowed to delete another user\'s notice.')); } + $result = null; + if (!$delete_event || Event::handle('DeleteNoticeAsProfile', array($this, $actor, &$result))) { + // If $delete_event is true, we run the event. If the Event then + // returns false it is assumed everything was handled properly + // and the notice was deleted. + $result = $this->delete(); + } + return $result; + } + + protected function deleteRelated() + { if (Event::handle('NoticeDeleteRelated', array($this))) { // Clear related records $this->clearReplies(); @@ -176,19 +191,12 @@ class Notice extends Managed_DataObject $this->clearAttentions(); // NOTE: we don't clear queue items } - - $result = null; - if (!$delete_event || Event::handle('DeleteNoticeAsProfile', array($this, $actor, &$result))) { - // If $delete_event is true, we run the event. If the Event then - // returns false it is assumed everything was handled properly - // and the notice was deleted. - $result = $this->delete(); - } - return $result; } public function delete($useWhere=false) { + $this->deleteRelated(); + $result = parent::delete($useWhere); $this->blowOnDelete(); @@ -240,10 +248,10 @@ class Notice extends Managed_DataObject return common_local_url('shownotice', array('notice' => $this->id), null, null, false); } - public function getTitle() + public function getTitle($imply=true) { $title = null; - if (Event::handle('GetNoticeTitle', array($this, &$title))) { + if (Event::handle('GetNoticeTitle', array($this, &$title)) && $imply) { // TRANS: Title of a notice posted without a title value. // TRANS: %1$s is a user name, %2$s is the notice creation date/time. $title = sprintf(_('%1$s\'s status on %2$s'), @@ -308,7 +316,6 @@ class Notice extends Managed_DataObject // let's generate a valid link to our locally available notice on demand return common_local_url('shownotice', array('notice' => $this->getID()), null, null, false); default: - common_debug('No URL available for notice: id='.$this->getID()); throw new InvalidUrlException($this->url); } } @@ -329,16 +336,6 @@ class Notice extends Managed_DataObject } } - public static function getByUri($uri) - { - $notice = new Notice(); - $notice->uri = $uri; - if (!$notice->find(true)) { - throw new NoResultException($notice); - } - return $notice; - } - /** * Extract #hashtags from this notice's content and save them to the database. */ @@ -507,12 +504,7 @@ class Notice extends Managed_DataObject $notice = new Notice(); $notice->profile_id = $profile_id; - $autosource = common_config('public', 'autosource'); - - // Sandboxed are non-false, but not 1, either - - if (!$profile->hasRight(Right::PUBLICNOTICE) || - ($source && $autosource && in_array($source, $autosource))) { + if ($source && in_array($source, common_config('public', 'autosource'))) { $notice->is_local = Notice::LOCAL_NONPUBLIC; } else { $notice->is_local = $is_local; @@ -526,9 +518,6 @@ class Notice extends Managed_DataObject if (!$notice->isLocal()) { // Only do these checks for non-local notices. Local notices will generate these values later. - if (!common_valid_http_url($url)) { - common_debug('Bad notice URL: ['.$url.'], URI: ['.$uri.']. Cannot link back to original! This is normal for shared notices etc.'); - } if (empty($uri)) { throw new ServerException('No URI for remote notice. Cannot accept that.'); } @@ -822,12 +811,12 @@ class Notice extends Managed_DataObject } } - $autosource = common_config('public', 'autosource'); + // NOTE: Sandboxed users previously got all the notices _during_ + // sandbox period set to to is_local=Notice::LOCAL_NONPUBLIC here. + // Since then we have started just filtering _when_ it gets shown + // instead of creating a mixed jumble of differently scoped notices. - // Sandboxed are non-false, but not 1, either - if (!$actor->hasRight(Right::PUBLICNOTICE) || - ($source && $autosource && in_array($source, $autosource))) { - // FIXME: ...what about remote nonpublic? Hmmm. That is, if we sandbox remote profiles... + if ($source && in_array($source, common_config('public', 'autosource'))) { $stored->is_local = Notice::LOCAL_NONPUBLIC; } else { $stored->is_local = intval($is_local); @@ -843,7 +832,7 @@ class Notice extends Managed_DataObject } } - $stored->profile_id = $actor->id; + $stored->profile_id = $actor->getID(); $stored->source = $source; $stored->uri = $uri; $stored->url = $url; @@ -854,13 +843,14 @@ class Notice extends Managed_DataObject if (mb_strlen($content)===0 && !is_null($actobj)) { $content = mb_strlen($actobj->content) ? $actobj->content : $actobj->summary; } - // Strip out any bad HTML from $content - $stored->rendered = common_purify($content); + // Strip out any bad HTML from $content. URI.Base is used to sort out relative URLs. + $stored->rendered = common_purify($content, ['URI.Base' => $stored->url ?: null]); $stored->content = common_strip_html($stored->getRendered(), true, true); if (trim($stored->content) === '') { // TRANS: Error message when the plain text content of a notice has zero length. throw new ClientException(_('Empty notice content, will not save this.')); } + unset($content); // garbage collect // Maybe a missing act-time should be fatal if the actor is not local? if (!empty($act->time)) { @@ -869,13 +859,31 @@ class Notice extends Managed_DataObject $stored->created = common_sql_now(); } - $reply = null; + $reply = null; // this will store the in-reply-to Notice if found + $replyUris = []; // this keeps a list of possible URIs to look up if ($act->context instanceof ActivityContext && !empty($act->context->replyToID)) { - $reply = self::getKV('uri', $act->context->replyToID); + $replyUris[] = $act->context->replyToID; } - if (!$reply instanceof Notice && $act->target instanceof ActivityObject) { - $reply = self::getKV('uri', $act->target->id); + if ($act->target instanceof ActivityObject && !empty($act->target->id)) { + $replyUris[] = $act->target->id; } + foreach (array_unique($replyUris) as $replyUri) { + $reply = self::getKV('uri', $replyUri); + // Only do remote fetching if we're not a private site + if (!common_config('site', 'private') && !$reply instanceof Notice) { + // the URI is the object we're looking for, $actor is a + // Profile that surely knows of it and &$reply where it + // will be stored when fetched + Event::handle('FetchRemoteNotice', array($replyUri, $actor, &$reply)); + } + // we got what we're in-reply-to now, so let's move on + if ($reply instanceof Notice) { + break; + } + // otherwise reset whatever we might've gotten from the event + $reply = null; + } + unset($replyUris); // garbage collect if ($reply instanceof Notice) { if (!$reply->inScope($actor)) { @@ -919,6 +927,7 @@ class Notice extends Managed_DataObject unset($conv); } } + unset($reply); // garbage collect // If it's not part of a conversation, it's the beginning of a new conversation. if (empty($stored->conversation)) { @@ -936,7 +945,13 @@ class Notice extends Managed_DataObject $act->context = new ActivityContext(); } - $stored->scope = self::figureOutScope($actor, $groups, $scope); + if (array_key_exists(ActivityContext::ATTN_PUBLIC, $act->context->attention)) { + $stored->scope = Notice::PUBLIC_SCOPE; + // TODO: maybe we should actually keep this? if the saveAttentions thing wants to use it... + unset($act->context->attention[ActivityContext::ATTN_PUBLIC]); + } else { + $stored->scope = self::figureOutScope($actor, $groups, $scope); + } foreach ($act->categories as $cat) { if ($cat->term) { @@ -983,6 +998,7 @@ class Notice extends Managed_DataObject if (empty($object)) { throw new NoticeSaveException('Unsuccessful call to StoreActivityObject '._ve($stored->getUri()) . ': '._ve($act->asString())); } + unset($object); // If something changed in the Notice during StoreActivityObject $stored->update($orig); @@ -996,8 +1012,12 @@ class Notice extends Managed_DataObject throw $e; } } + unset($notloc); // garbage collect + if (!$stored instanceof Notice) { - throw new ServerException('StartNoticeSave did not give back a Notice'); + throw new ServerException('StartNoticeSave did not give back a Notice.'); + } elseif (empty($stored->id)) { + throw new ServerException('Supposedly saved Notice has no ID.'); } // Only save 'attention' and metadata stuff (URLs, tags...) stuff if @@ -1262,14 +1282,12 @@ class Notice extends Managed_DataObject $ids[] = $f2p->file_id; } - $files = File::multiGet('id', $ids); - $this->_attachments[$this->id] = $files->fetchAll(); - return $this->_attachments[$this->id]; + return $this->_setAttachments(File::multiGet('id', $ids)->fetchAll()); } - function _setAttachments($attachments) + public function _setAttachments(array $attachments) { - $this->_attachments[$this->id] = $attachments; + return $this->_attachments[$this->id] = $attachments; } static function publicStream($offset=0, $limit=20, $since_id=null, $max_id=null) @@ -1278,9 +1296,9 @@ class Notice extends Managed_DataObject return $stream->getNotices($offset, $limit, $since_id, $max_id); } - static function conversationStream($id, $offset=0, $limit=20, $since_id=null, $max_id=null) + static function conversationStream($id, $offset=0, $limit=20, $since_id=null, $max_id=null, Profile $scoped=null) { - $stream = new ConversationNoticeStream($id); + $stream = new ConversationNoticeStream($id, $scoped); return $stream->getNotices($offset, $limit, $since_id, $max_id); } @@ -1298,8 +1316,9 @@ class Notice extends Managed_DataObject return false; } - $stream = new ConversationNoticeStream($this->conversation); - $notice = $stream->getNotices(/*offset*/ 1, /*limit*/ 1); + //FIXME: Get the Profile::current() stuff some other way + // to avoid confusion between queue processing and session. + $notice = self::conversationStream($this->conversation, 1, 1, null, null, Profile::current()); // if our "offset 1, limit 1" query got a result, return true else false return $notice->N > 0; @@ -1508,12 +1527,16 @@ class Notice extends Managed_DataObject function getProfileTags() { - $profile = $this->getProfile(); - $list = $profile->getOtherTags($profile); $ptags = array(); + try { + $profile = $this->getProfile(); + $list = $profile->getOtherTags($profile); - while($list->fetch()) { - $ptags[] = clone($list); + while($list->fetch()) { + $ptags[] = clone($list); + } + } catch (Exception $e) { + common_log(LOG_ERR, "Error during Notice->getProfileTags() for id=={$this->getID()}: {$e->getMessage()}"); } return $ptags; @@ -1644,8 +1667,6 @@ class Notice extends Managed_DataObject } $att = Attention::saveNew($this, $target, $reason); - - self::blow('reply:stream:%d', $target->getID()); return true; } @@ -1833,7 +1854,7 @@ class Notice extends Managed_DataObject foreach ($recipientIds as $recipientId) { try { $user = User::getByID($recipientId); - mail_notify_attn($user, $this); + mail_notify_attn($user->getProfile(), $this); } catch (NoResultException $e) { // No such user } @@ -2652,7 +2673,7 @@ class Notice extends Managed_DataObject public static function getAsTimestamp($id) { if (empty($id)) { - throw new EmptyIdException('Notice'); + throw new EmptyPkeyValueException('Notice', 'id'); } $timestamp = null; @@ -2748,10 +2769,10 @@ class Notice extends Managed_DataObject } } - function isPublic() + public function isPublic() { - return (($this->is_local != Notice::LOCAL_NONPUBLIC) && - ($this->is_local != Notice::GATEWAY)); + $is_local = intval($this->is_local); + return !($is_local === Notice::LOCAL_NONPUBLIC || $is_local === Notice::GATEWAY); } /** @@ -3021,6 +3042,19 @@ class Notice extends Managed_DataObject $files = array(); $f2ps = $f2pMap[$notice->id]; foreach ($f2ps as $f2p) { + if (!isset($fileMap[$f2p->file_id])) { + // We have probably deleted value from fileMap since + // it as a NULL entry (see the following elseif). + continue; + } elseif (is_null($fileMap[$f2p->file_id])) { + // If the file id lookup returned a NULL value, it doesn't + // exist in our file table! So this is a remnant file_to_post + // entry that is no longer valid and should be removed. + common_debug('ATTACHMENT deleting f2p for post_id='.$f2p->post_id.' file_id='.$f2p->file_id); + $f2p->delete(); + unset($fileMap[$f2p->file_id]); + continue; + } $files[] = $fileMap[$f2p->file_id]; } $notice->_setAttachments($files); @@ -3049,42 +3083,113 @@ class Notice extends Managed_DataObject // 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; - } - // Then we make sure the Notice_location table is created! - $schema->ensureTable('notice_location', Notice_location::schemaDef()); - - // 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)"; - - $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; + if (isset($schemadef['fields']['lat']) + && isset($schemadef['fields']['lon']) + && isset($schemadef['fields']['location_id']) + && isset($schemadef['fields']['location_ns'])) { + // Then we make sure the Notice_location table is created! + $schema->ensureTable('notice_location', Notice_location::schemaDef()); + + // 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)"; + + $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"; + } + + /** + * Make sure constraints are met before upgrading, if foreign keys + * are not already in use. + * 2016-03-31 + */ + if (!isset($schemadef['foreign keys'])) { + $newschemadef = self::schemaDef(); + printfnq("\nConstraint checking Notice table...\n"); + /** + * Improve typing and make sure no NULL values in any id-related columns are 0 + * 2016-03-31 + */ + foreach (['reply_to', 'repeat_of'] as $field) { + $notice = new Notice(); // reset the object + $notice->query(sprintf('UPDATE %1$s SET %2$s=NULL WHERE %2$s=0', $notice->escapedTableName(), $field)); + // Now we're sure that no Notice entries have repeat_of=0, only an id > 0 or NULL + unset($notice); + } + + /** + * This Will find foreign keys which do not fulfill the constraints and fix + * where appropriate, such as delete when "repeat_of" ID not found in notice.id + * or set to NULL for "reply_to" in the same case. + * 2016-03-31 + * + * XXX: How does this work if we would use multicolumn foreign keys? + */ + foreach (['reply_to' => 'reset', 'repeat_of' => 'delete', 'profile_id' => 'delete'] as $field=>$action) { + $notice = new Notice(); + + $fkeyname = $notice->tableName().'_'.$field.'_fkey'; + assert(isset($newschemadef['foreign keys'][$fkeyname])); + assert($newschemadef['foreign keys'][$fkeyname]); + + $foreign_key = $newschemadef['foreign keys'][$fkeyname]; + $fkeytable = $foreign_key[0]; + assert(isset($foreign_key[1][$field])); + $fkeycol = $foreign_key[1][$field]; + + printfnq("* {$fkeyname} ({$field} => {$fkeytable}.{$fkeycol})\n"); + + // NOTE: Above we set all repeat_of to NULL if they were 0, so this really gets them all. + $notice->whereAdd(sprintf('%1$s NOT IN (SELECT %2$s FROM %3$s)', $field, $fkeycol, $fkeytable)); + if ($notice->find()) { + printfnq("\tFound {$notice->N} notices with {$field} NOT IN notice.id, {$action}ing..."); + switch ($action) { + case 'delete': // since it's a directly dependant notice for an unknown ID we don't want it in our DB + while ($notice->fetch()) { + $notice->delete(); + } + break; + case 'reset': // just set it to NULL to be compatible with our constraints, if it was related to an unknown ID + $ids = []; + foreach ($notice->fetchAll('id') as $id) { + settype($id, 'int'); + $ids[] = $id; + } + unset($notice); + $notice = new Notice(); + $notice->query(sprintf('UPDATE %1$s SET %2$s=NULL WHERE id IN (%3$s)', + $notice->escapedTableName(), + $field, + implode(',', $ids))); + break; + default: + throw new ServerException('The programmer sucks, invalid action name when fixing table.'); + } + printfnq("DONE.\n"); + } + unset($notice); } - $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"; } }