1
0
Fork 0

Merge pull request #9901 from annando/post-thread-user

New table "post-thread-user", removed tabled "user-item"
This commit is contained in:
Hypolite Petovan 2021-02-02 08:41:58 -05:00 committed by GitHub
commit 0ef5bf29d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 542 additions and 401 deletions

View file

@ -1,6 +1,6 @@
-- ------------------------------------------
-- Friendica 2021.03-dev (Red Hot Poker)
-- DB_UPDATE_VERSION 1396
-- DB_UPDATE_VERSION 1397
-- ------------------------------------------
@ -684,7 +684,6 @@ CREATE TABLE IF NOT EXISTS `item` (
`guid` varchar(255) NOT NULL DEFAULT '' COMMENT 'A unique identifier for this item',
`uri` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`uri-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the item uri',
`uri-hash` varchar(80) NOT NULL DEFAULT '' COMMENT 'RIPEMD-128 hash from uri',
`parent` int unsigned COMMENT 'item.id of the parent to this item if it is a reply of some form; otherwise this must be set to the id of this item',
`parent-uri` varchar(255) NOT NULL DEFAULT '' COMMENT 'uri of the top-level parent to this item',
`parent-uri-id` int unsigned COMMENT 'Id of the item-uri table that contains the top-level parent uri',
@ -710,16 +709,19 @@ CREATE TABLE IF NOT EXISTS `item` (
`deleted` boolean NOT NULL DEFAULT '0' COMMENT 'item has been deleted',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner id which owns this copy of the item',
`contact-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'contact.id',
`wall` boolean NOT NULL DEFAULT '0' COMMENT 'This item was posted to the wall of uid',
`origin` boolean NOT NULL DEFAULT '0' COMMENT 'item originated at this site',
`pubmail` boolean NOT NULL DEFAULT '0' COMMENT '',
`starred` boolean NOT NULL DEFAULT '0' COMMENT 'item has been favourited',
`unseen` boolean NOT NULL DEFAULT '1' COMMENT 'item has not been seen',
`mention` boolean NOT NULL DEFAULT '0' COMMENT 'The owner of this item was mentioned in it',
`forum_mode` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`origin` boolean NOT NULL DEFAULT '0' COMMENT 'item originated at this site',
`psid` int unsigned COMMENT 'ID of the permission set of this post',
`resource-id` varchar(32) NOT NULL DEFAULT '' COMMENT 'Used to link other tables to items, it identifies the linked resource (e.g. photo) and if set must also set resource_type',
`starred` boolean NOT NULL DEFAULT '0' COMMENT 'item has been favourited',
`wall` boolean NOT NULL DEFAULT '0' COMMENT 'This item was posted to the wall of uid',
`pubmail` boolean NOT NULL DEFAULT '0' COMMENT '',
`forum_mode` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`event-id` int unsigned COMMENT 'Used to link to the event.id',
`type` varchar(20) COMMENT '',
`bookmark` boolean COMMENT '',
`mention` boolean NOT NULL DEFAULT '0' COMMENT 'The owner of this item was mentioned in it',
`resource-id` varchar(32) COMMENT 'Deprecated',
`uri-hash` varchar(80) COMMENT 'Deprecated',
`iaid` int unsigned COMMENT 'Deprecated',
`icid` int unsigned COMMENT 'Deprecated',
`attach` mediumtext COMMENT 'Deprecated',
@ -729,8 +731,6 @@ CREATE TABLE IF NOT EXISTS `item` (
`deny_gid` mediumtext COMMENT 'Deprecated',
`postopts` text COMMENT 'Deprecated',
`inform` mediumtext COMMENT 'Deprecated',
`type` varchar(20) COMMENT 'Deprecated',
`bookmark` boolean COMMENT 'Deprecated',
`file` mediumtext COMMENT 'Deprecated',
`location` varchar(255) COMMENT 'Deprecated',
`coord` varchar(255) COMMENT 'Deprecated',
@ -970,22 +970,6 @@ CREATE TABLE IF NOT EXISTS `parsed_url` (
INDEX `created` (`created`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='cache for \'parse_url\' queries';
--
-- TABLE participation
--
CREATE TABLE IF NOT EXISTS `participation` (
`iid` int unsigned NOT NULL COMMENT '',
`server` varchar(60) NOT NULL COMMENT '',
`cid` int unsigned NOT NULL COMMENT '',
`fid` int unsigned NOT NULL COMMENT '',
PRIMARY KEY(`iid`,`server`),
INDEX `cid` (`cid`),
INDEX `fid` (`fid`),
FOREIGN KEY (`iid`) REFERENCES `item` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`cid`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`fid`) REFERENCES `fcontact` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Storage for participation messages from Diaspora';
--
-- TABLE pconfig
--
@ -1141,6 +1125,26 @@ CREATE TABLE IF NOT EXISTS `post-tag` (
FOREIGN KEY (`cid`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='post relation to tags';
--
-- TABLE post-thread-user
--
CREATE TABLE IF NOT EXISTS `post-thread-user` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner id which owns this copy of the item',
`pinned` boolean NOT NULL DEFAULT '0' COMMENT 'The thread is pinned on the profile page',
`starred` boolean NOT NULL DEFAULT '0' COMMENT '',
`ignored` boolean NOT NULL DEFAULT '0' COMMENT 'Ignore updates for this thread',
`wall` boolean NOT NULL DEFAULT '0' COMMENT 'This item was posted to the wall of uid',
`pubmail` boolean NOT NULL DEFAULT '0' COMMENT '',
`forum_mode` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
PRIMARY KEY(`uid`,`uri-id`),
INDEX `uid_wall` (`uid`,`wall`),
INDEX `uid_pinned` (`uid`,`pinned`),
INDEX `uri-id` (`uri-id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Thread related data per user';
--
-- TABLE post-user
--
@ -1166,6 +1170,19 @@ CREATE TABLE IF NOT EXISTS `post-user` (
FOREIGN KEY (`psid`) REFERENCES `permissionset` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='User specific post data';
--
-- TABLE post-user-notification
--
CREATE TABLE IF NOT EXISTS `post-user-notification` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`uid` mediumint unsigned NOT NULL COMMENT 'Owner id which owns this copy of the item',
`notification-type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
PRIMARY KEY(`uid`,`uri-id`),
INDEX `uri-id` (`uri-id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='User post notifications';
--
-- TABLE process
--
@ -1429,23 +1446,6 @@ CREATE TABLE IF NOT EXISTS `user-contact` (
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='User specific public contact data';
--
-- TABLE user-item
--
CREATE TABLE IF NOT EXISTS `user-item` (
`iid` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item id',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'User id',
`hidden` boolean NOT NULL DEFAULT '0' COMMENT 'Marker to hide an item from the user',
`ignored` boolean COMMENT 'Ignore this thread if set',
`pinned` boolean COMMENT 'The item is pinned on the profile page',
`notification-type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
PRIMARY KEY(`uid`,`iid`),
INDEX `uid_pinned` (`uid`,`pinned`),
INDEX `iid_uid` (`iid`,`uid`),
FOREIGN KEY (`iid`) REFERENCES `item` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='User specific item data';
--
-- TABLE worker-ipc
--
@ -1848,13 +1848,13 @@ CREATE VIEW `network-item-view` AS SELECT
FROM `item`
INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `thread`.`contact-id`
LEFT JOIN `user-item` ON `user-item`.`iid` = `item`.`id` AND `user-item`.`uid` = `thread`.`uid`
LEFT JOIN `post-user` ON `post-user`.`uri-id` = `item`.`uri-id` AND `post-user`.`uid` = `thread`.`uid`
LEFT JOIN `user-contact` AS `author` ON `author`.`uid` = `thread`.`uid` AND `author`.`cid` = `thread`.`author-id`
LEFT JOIN `user-contact` AS `owner` ON `owner`.`uid` = `thread`.`uid` AND `owner`.`cid` = `thread`.`owner-id`
LEFT JOIN `contact` AS `ownercontact` ON `ownercontact`.`id` = `thread`.`owner-id`
WHERE `thread`.`visible` AND NOT `thread`.`deleted` AND NOT `thread`.`moderated`
AND (NOT `contact`.`readonly` AND NOT `contact`.`blocked` AND NOT `contact`.`pending`)
AND (`user-item`.`hidden` IS NULL OR NOT `user-item`.`hidden`)
AND (`post-user`.`hidden` IS NULL OR NOT `post-user`.`hidden`)
AND (`author`.`blocked` IS NULL OR NOT `author`.`blocked`)
AND (`owner`.`blocked` IS NULL OR NOT `owner`.`blocked`);
@ -1879,13 +1879,13 @@ CREATE VIEW `network-thread-view` AS SELECT
FROM `thread`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `thread`.`contact-id`
STRAIGHT_JOIN `item` ON `item`.`id` = `thread`.`iid`
LEFT JOIN `user-item` ON `user-item`.`iid` = `item`.`id` AND `user-item`.`uid` = `thread`.`uid`
LEFT JOIN `post-user` ON `post-user`.`uri-id` = `item`.`uri-id` AND `post-user`.`uid` = `thread`.`uid`
LEFT JOIN `user-contact` AS `author` ON `author`.`uid` = `thread`.`uid` AND `author`.`cid` = `thread`.`author-id`
LEFT JOIN `user-contact` AS `owner` ON `owner`.`uid` = `thread`.`uid` AND `owner`.`cid` = `thread`.`owner-id`
LEFT JOIN `contact` AS `ownercontact` ON `ownercontact`.`id` = `thread`.`owner-id`
WHERE `thread`.`visible` AND NOT `thread`.`deleted` AND NOT `thread`.`moderated`
AND (NOT `contact`.`readonly` AND NOT `contact`.`blocked` AND NOT `contact`.`pending`)
AND (`user-item`.`hidden` IS NULL OR NOT `user-item`.`hidden`)
AND (`post-user`.`hidden` IS NULL OR NOT `post-user`.`hidden`)
AND (`author`.`blocked` IS NULL OR NOT `author`.`blocked`)
AND (`owner`.`blocked` IS NULL OR NOT `owner`.`blocked`);

View file

@ -32,7 +32,6 @@ Database Tables
| [notify-threads](help/database/db_notify-threads) | |
| [oembed](help/database/db_oembed) | cache for OEmbed queries |
| [parsed_url](help/database/db_parsed_url) | cache for "parse_url" queries |
| [participation](help/database/db_participation) | Storage for participation messages from Diaspora |
| [pconfig](help/database/db_pconfig) | personal (per user) configuration storage |
| [photo](help/database/db_photo) | photo storage |
| [poll](help/database/db_poll) | data for polls |

View file

@ -1,10 +0,0 @@
Table participation
===================
| Field | Description | Type | Null | Key | Default | Extra |
|-------------|------------------|------------------|------|-----|---------------------|-------|
| iid | item id | int(10) unsigned | NO | PRI | | |
| server | Name of server | varchar(60) | NO | PRI | | |
| cid | contact id | int(10) unsigned | NO | | | |
Return to [database documentation](help/database)

View file

@ -43,7 +43,6 @@ use Friendica\Model\Notification;
use Friendica\Model\Photo;
use Friendica\Model\Post;
use Friendica\Model\User;
use Friendica\Model\UserItem;
use Friendica\Model\Verb;
use Friendica\Network\HTTPException;
use Friendica\Network\HTTPException\BadRequestException;
@ -2170,24 +2169,21 @@ function api_statuses_mentions($type)
$start = max(0, ($page - 1) * $count);
$query = "`gravity` IN (?, ?) AND `id` IN (SELECT `iid` FROM `user-item`
WHERE (`hidden` IS NULL OR NOT `hidden`) AND
`uid` = ? AND `notification-type` & ? != 0
AND `iid` > ?";
$query = "`gravity` IN (?, ?) AND `uri-id` IN
(SELECT `uri-id` FROM `post-user-notification` WHERE `uid` = ? AND `notification-type` & ? != 0 ORDER BY `uri-id`)
AND (`uid` = 0 OR (`uid` = ? AND NOT `global`)) AND `id` > ?";
$condition = [GRAVITY_PARENT, GRAVITY_COMMENT, api_user(),
UserItem::NOTIF_EXPLICIT_TAGGED | UserItem::NOTIF_IMPLICIT_TAGGED |
UserItem::NOTIF_THREAD_COMMENT | UserItem::NOTIF_DIRECT_COMMENT |
UserItem::NOTIF_DIRECT_THREAD_COMMENT,
$since_id];
Post\UserNotification::NOTIF_EXPLICIT_TAGGED | Post\UserNotification::NOTIF_IMPLICIT_TAGGED |
Post\UserNotification::NOTIF_THREAD_COMMENT | Post\UserNotification::NOTIF_DIRECT_COMMENT |
Post\UserNotification::NOTIF_DIRECT_THREAD_COMMENT,
api_user(), $since_id];
if ($max_id > 0) {
$query .= " AND `iid` <= ?";
$query .= " AND `id` <= ?";
$condition[] = $max_id;
}
$query .= ")";
array_unshift($condition, $query);
$params = ['order' => ['id' => true], 'limit' => [$start, $count]];

View file

@ -32,7 +32,6 @@ use Friendica\Model\Item;
use Friendica\Model\Notification;
use Friendica\Model\Post;
use Friendica\Model\User;
use Friendica\Model\UserItem;
use Friendica\Protocol\Activity;
/**
@ -149,9 +148,8 @@ function notification($params)
}
if ($params['type'] == Notification\Type::COMMENT || $params['type'] == Notification\Type::TAG_SELF) {
$thread = Post::selectFirstThreadForUser($params['uid'], ['ignored'], ['iid' => $parent_id, 'deleted' => false]);
if (DBA::isResult($thread) && $thread['ignored']) {
Logger::log('Thread ' . $parent_id . ' will be ignored', Logger::DEBUG);
if (Post\ThreadUser::getIgnored($parent_uri_id, $params['uid'])) {
Logger::info('Thread is ignored', ['parent' => $parent_id, 'parent-uri-id' => $parent_uri_id]);
return false;
}
@ -593,32 +591,39 @@ function notification($params)
/**
* Checks for users who should be notified
*
* @param int $itemid ID of the item for which the check should be done
* @param int $uri_id URI ID of the item for which the check should be done
* @param int $uid User ID of the item
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
function check_user_notification($itemid) {
// fetch all users with notifications
$useritems = DBA::select('user-item', ['uid', 'notification-type'], ['iid' => $itemid]);
while ($useritem = DBA::fetch($useritems)) {
check_item_notification($itemid, $useritem['uid'], $useritem['notification-type']);
function check_user_notification(int $uri_id, int $uid) {
$condition = ['uri-id' => $uri_id];
// fetch all users with notifications on public posts
if ($uid != 0) {
$condition['uid'] = $uid;
}
DBA::close($useritems);
$usernotifications = DBA::select('post-user-notification', ['uri-id', 'uid', 'notification-type'], $condition);
while ($usernotification = DBA::fetch($usernotifications)) {
check_item_notification($usernotification['uri-id'], $usernotification['uid'], $usernotification['notification-type']);
}
DBA::close($usernotifications);
}
/**
* Checks for item related notifications and sends them
*
* @param int $itemid ID of the item for which the check should be done
* @param int $uri_id URI ID of the item for which the check should be done
* @param int $uid User ID
* @param int $notification_type Notification bits
* @return bool
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
function check_item_notification($itemid, $uid, $notification_type) {
function check_item_notification(int $uri_id, int $uid, int $notification_type) {
$fields = ['id', 'uri-id', 'mention', 'parent', 'parent-uri-id', 'thr-parent-id',
'title', 'body', 'author-link', 'author-name', 'author-avatar', 'author-id',
'gravity', 'guid', 'parent-uri', 'uri', 'contact-id', 'network'];
$condition = ['id' => $itemid, 'deleted' => false];
$condition = ['uri-id' => $uri_id, 'uid' => $uid, 'deleted' => false];
$item = Post::selectFirstForUser($uid, $fields, $condition);
if (!DBA::isResult($item)) {
return false;
@ -638,19 +643,19 @@ function check_item_notification($itemid, $uid, $notification_type) {
$params['link'] = DI::baseUrl() . '/display/' . urlencode($item['guid']);
// Set the activity flags
$params['activity']['explicit_tagged'] = ($notification_type & UserItem::NOTIF_EXPLICIT_TAGGED);
$params['activity']['implicit_tagged'] = ($notification_type & UserItem::NOTIF_IMPLICIT_TAGGED);
$params['activity']['origin_comment'] = ($notification_type & UserItem::NOTIF_DIRECT_COMMENT);
$params['activity']['origin_thread'] = ($notification_type & UserItem::NOTIF_THREAD_COMMENT);
$params['activity']['thread_comment'] = ($notification_type & UserItem::NOTIF_COMMENT_PARTICIPATION);
$params['activity']['thread_activity'] = ($notification_type & UserItem::NOTIF_ACTIVITY_PARTICIPATION);
$params['activity']['explicit_tagged'] = ($notification_type & Post\UserNotification::NOTIF_EXPLICIT_TAGGED);
$params['activity']['implicit_tagged'] = ($notification_type & Post\UserNotification::NOTIF_IMPLICIT_TAGGED);
$params['activity']['origin_comment'] = ($notification_type & Post\UserNotification::NOTIF_DIRECT_COMMENT);
$params['activity']['origin_thread'] = ($notification_type & Post\UserNotification::NOTIF_THREAD_COMMENT);
$params['activity']['thread_comment'] = ($notification_type & Post\UserNotification::NOTIF_COMMENT_PARTICIPATION);
$params['activity']['thread_activity'] = ($notification_type & Post\UserNotification::NOTIF_ACTIVITY_PARTICIPATION);
// Tagging a user in a direct post (first comment level) means a direct comment
if ($params['activity']['explicit_tagged'] && ($notification_type & UserItem::NOTIF_DIRECT_THREAD_COMMENT)) {
if ($params['activity']['explicit_tagged'] && ($notification_type & Post\UserNotification::NOTIF_DIRECT_THREAD_COMMENT)) {
$params['activity']['origin_comment'] = true;
}
if ($notification_type & UserItem::NOTIF_SHARED) {
if ($notification_type & Post\UserNotification::NOTIF_SHARED) {
$params['type'] = Notification\Type::SHARE;
$params['verb'] = Activity::POST;
@ -667,22 +672,22 @@ function check_item_notification($itemid, $uid, $notification_type) {
$params['item'] = $parent_item;
}
}
} elseif ($notification_type & UserItem::NOTIF_EXPLICIT_TAGGED) {
} elseif ($notification_type & Post\UserNotification::NOTIF_EXPLICIT_TAGGED) {
$params['type'] = Notification\Type::TAG_SELF;
$params['verb'] = Activity::TAG;
} elseif ($notification_type & UserItem::NOTIF_IMPLICIT_TAGGED) {
} elseif ($notification_type & Post\UserNotification::NOTIF_IMPLICIT_TAGGED) {
$params['type'] = Notification\Type::COMMENT;
$params['verb'] = Activity::POST;
} elseif ($notification_type & UserItem::NOTIF_THREAD_COMMENT) {
} elseif ($notification_type & Post\UserNotification::NOTIF_THREAD_COMMENT) {
$params['type'] = Notification\Type::COMMENT;
$params['verb'] = Activity::POST;
} elseif ($notification_type & UserItem::NOTIF_DIRECT_COMMENT) {
} elseif ($notification_type & Post\UserNotification::NOTIF_DIRECT_COMMENT) {
$params['type'] = Notification\Type::COMMENT;
$params['verb'] = Activity::POST;
} elseif ($notification_type & UserItem::NOTIF_COMMENT_PARTICIPATION) {
} elseif ($notification_type & Post\UserNotification::NOTIF_COMMENT_PARTICIPATION) {
$params['type'] = Notification\Type::COMMENT;
$params['verb'] = Activity::POST;
} elseif ($notification_type & UserItem::NOTIF_ACTIVITY_PARTICIPATION) {
} elseif ($notification_type & Post\UserNotification::NOTIF_ACTIVITY_PARTICIPATION) {
$params['type'] = Notification\Type::COMMENT;
$params['verb'] = Activity::POST;
} else {

View file

@ -79,7 +79,8 @@ class DBStructure
}
$old_tables = ['fserver', 'gcign', 'gcontact', 'gcontact-relation', 'gfollower' ,'glink', 'item-delivery-data',
'item-activity', 'item-content', 'item_id', 'poll', 'poll_result', 'queue', 'retriever_rule', 'sign', 'spam', 'term'];
'item-activity', 'item-content', 'item_id', 'participation', 'poll', 'poll_result', 'queue', 'retriever_rule',
'sign', 'spam', 'term', 'user-item'];
$tables = DBA::selectToArray(['INFORMATION_SCHEMA' => 'TABLES'], ['TABLE_NAME'],
['TABLE_SCHEMA' => DBA::databaseName(), 'TABLE_TYPE' => 'BASE TABLE']);

View file

@ -31,7 +31,6 @@ use Friendica\Model\Photo;
use Friendica\Model\Post;
use Friendica\Model\Post\Category;
use Friendica\Model\Tag;
use Friendica\Model\UserItem;
use Friendica\Model\Verb;
use Friendica\Util\Strings;
@ -168,7 +167,7 @@ class PostUpdate
}
/**
* update user-item data with notifications
* update user notification data
*
* @return bool "true" when the job is done
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
@ -188,7 +187,7 @@ class PostUpdate
$rows = 0;
$condition = ["`id` > ?", $id];
$params = ['order' => ['id'], 'limit' => 10000];
$items = DBA::select('item', ['id'], $condition, $params);
$items = DBA::select('item', ['id', 'uri-id', 'uid'], $condition, $params);
if (DBA::errorNo() != 0) {
Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
@ -198,7 +197,7 @@ class PostUpdate
while ($item = DBA::fetch($items)) {
$id = $item['id'];
UserItem::setNotification($item['id']);
Post\UserNotification::setNotification($item['uri-id'], $item['uid']);
++$rows;
}

View file

@ -77,9 +77,9 @@ class Status extends BaseFactory
$userAttributes = new \Friendica\Object\Api\Mastodon\Status\UserAttributes(
Post::exists(['thr-parent-id' => $uriId, 'uid' => $uid, 'origin' => true, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::LIKE)]),
Post::exists(['thr-parent-id' => $uriId, 'uid' => $uid, 'origin' => true, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::ANNOUNCE)]),
DBA::exists('thread', ['iid' => $item['id'], 'uid' => $item['uid'], 'ignored' => true]),
Post\ThreadUser::getIgnored($uriId, $item['uid']),
(bool)$item['starred'],
DBA::exists('user-item', ['iid' => $item['id'], 'uid' => $item['uid'], 'pinned' => true])
Post\ThreadUser::getPinned($uriId, $item['uid'])
);
$sensitive = DBA::exists('tag-view', ['uri-id' => $uriId, 'name' => 'nsfw']);

View file

@ -31,7 +31,6 @@ use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Model\Tag;
use Friendica\Core\Worker;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\DI;
@ -99,14 +98,6 @@ class Item
'author-id', 'author-link', 'owner-link', 'contact-uid',
'signed_text', 'network'];
// Field list for "post-content" table that is mixed with the item table
const MIXED_CONTENT_FIELDLIST = ['title', 'content-warning', 'body', 'location',
'coord', 'app', 'rendered-hash', 'rendered-html', 'verb',
'object-type', 'object', 'target-type', 'target', 'plink'];
// Field list for "post-content" table that is not present in the "item" table
const CONTENT_FIELDLIST = ['language', 'raw-body'];
// All fields in the item table
const ITEM_FIELDLIST = ['id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent',
'guid', 'uri-id', 'parent-uri-id', 'thr-parent-id', 'vid',
@ -121,6 +112,22 @@ class Item
'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network',
'owner-id', 'owner-link', 'owner-name', 'owner-avatar', 'causer-id'];
// Item fiels that still are in use
const USED_FIELDLIST = ['id', 'parent', 'guid', 'uri', 'uri-id', 'parent-uri', 'parent-uri-id',
'thr-parent', 'thr-parent-id', 'created', 'edited', 'commented', 'received', 'changed',
'gravity', 'network', 'owner-id', 'author-id', 'causer-id', 'vid', 'extid', 'post-type',
'global', 'private', 'visible', 'moderated', 'deleted', 'uid', 'contact-id',
'wall', 'origin', 'pubmail', 'starred', 'unseen', 'mention', 'forum_mode', 'psid',
'event-id', 'type', 'bookmark'];
// Legacy item fields that aren't stored any more in the item table
const LEGACY_FIELDLIST = ['uri-hash', 'iaid', 'icid', 'attach',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'postopts',
'resource-id', 'inform', 'file', 'location', 'coord', 'tag', 'plink',
'title', 'content-warning', 'body', 'app', 'verb', 'object-type', 'object',
'target-type', 'target', 'author-name', 'author-link', 'author-avatar',
'owner-name', 'owner-link', 'owner-avatar', 'rendered-hash', 'rendered-html'];
// List of all verbs that don't need additional content data.
// Never reorder or remove entries from this list. Just add new ones at the end, if needed.
const ACTIVITIES = [
@ -133,49 +140,6 @@ class Item
const PRIVATE = 1;
const UNLISTED = 2;
const TABLES = ['item', 'user-item', 'post-content', 'post-delivery-data', 'diaspora-interaction'];
private static function getItemFields()
{
$definition = DBStructure::definition('', false);
$postfields = [];
foreach (self::TABLES as $table) {
$postfields[$table] = array_keys($definition[$table]['fields']);
}
return $postfields;
}
/**
* Set the pinned state of an item
*
* @param integer $iid Item ID
* @param integer $uid User ID
* @param boolean $pinned Pinned state
*/
public static function setPinned(int $iid, int $uid, bool $pinned)
{
DBA::update('user-item', ['pinned' => $pinned], ['iid' => $iid, 'uid' => $uid], true);
}
/**
* Get the pinned state
*
* @param integer $iid Item ID
* @param integer $uid User ID
*
* @return boolean pinned state
*/
public static function getPinned(int $iid, int $uid)
{
$useritem = DBA::selectFirst('user-item', ['pinned'], ['iid' => $iid, 'uid' => $uid]);
if (!DBA::isResult($useritem)) {
return false;
}
return (bool)$useritem['pinned'];
}
/**
* Update existing item entries
*
@ -285,12 +249,9 @@ class Item
Post\User::update($item['uri-id'], $uid, ['hidden' => true], true);
}
// "Deleting" global items just means hiding them
if ($item['uid'] == 0) {
DBA::update('user-item', ['hidden' => true], ['iid' => $item['id'], 'uid' => $uid], true);
} elseif ($item['uid'] == $uid) {
if ($item['uid'] == $uid) {
self::markForDeletionById($item['id'], PRIORITY_HIGH);
} else {
} elseif ($item['uid'] != 0) {
Logger::log('Wrong ownership. Not deleting item ' . $item['id']);
}
}
@ -393,12 +354,6 @@ class Item
}
} elseif ($item['uid'] != 0) {
Post\User::update($item['uri-id'], $item['uid'], ['hidden' => true]);
// When we delete just our local user copy of an item, we have to set a marker to hide it
$global_item = Post::selectFirst(['id'], ['uri-id' => $item['uri-id'], 'uid' => 0, 'deleted' => false]);
if (DBA::isResult($global_item)) {
DBA::update('user-item', ['hidden' => true], ['iid' => $global_item['id'], 'uid' => $item['uid']], true);
}
}
Logger::info('Item has been marked for deletion.', ['id' => $item_id]);
@ -755,8 +710,6 @@ class Item
public static function insert($item, $notify = false, $dontcache = false)
{
$structure = self::getItemFields();
$orig_item = $item;
$priority = PRIORITY_HIGH;
@ -952,26 +905,11 @@ class Item
// Update the contact relations
Contact\Relation::store($toplevel_parent['author-id'], $item['author-id'], $item['created']);
unset($item['parent_origin']);
} else {
$parent_id = 0;
$parent_origin = $item['origin'];
}
// We don't store the causer link, only the id
unset($item['causer-link']);
// We don't store these fields anymore in the item table
unset($item['author-link']);
unset($item['author-name']);
unset($item['author-avatar']);
unset($item['author-network']);
unset($item['owner-link']);
unset($item['owner-name']);
unset($item['owner-avatar']);
$item['parent-uri-id'] = ItemURI::getIdByURI($item['parent-uri']);
$item['thr-parent-id'] = ItemURI::getIdByURI($item['thr-parent']);
@ -994,14 +932,10 @@ class Item
$item['edit'] = false;
$item['parent'] = $parent_id;
Hook::callAll('post_local', $item);
unset($item['edit']);
} else {
Hook::callAll('post_remote', $item);
}
// Set after the insert because top-level posts are self-referencing
unset($item['parent']);
if (!empty($item['cancel'])) {
Logger::log('post cancelled by addon.');
return 0;
@ -1020,15 +954,6 @@ class Item
$item['deny_gid']
);
unset($item['allow_cid']);
unset($item['allow_gid']);
unset($item['deny_cid']);
unset($item['deny_gid']);
// This array field is used to trigger some automatic reactions
// It is mainly used in the "post_local" hook.
unset($item['api_source']);
if ($item['verb'] == Activity::ANNOUNCE) {
self::setOwnerforResharedItem($item);
}
@ -1040,10 +965,6 @@ class Item
// Check for hashtags in the body and repair or add hashtag links
$item['body'] = self::setHashtags($item['body']);
if (!empty($item['attach'])) {
Post\Media::insertFromAttachment($item['uri-id'], $item['attach']);
}
// Fill the cache field
self::putInCache($item);
@ -1053,40 +974,27 @@ class Item
$notify_type = Delivery::POST;
}
// Filling item related side tables
if (!empty($item['attach'])) {
Post\Media::insertFromAttachment($item['uri-id'], $item['attach']);
}
if (!in_array($item['verb'], self::ACTIVITIES)) {
Post\Content::insert($item['uri-id'], $item);
}
$body = $item['body'];
$verb = $item['verb'];
// We just remove everything that is content
foreach (array_merge(self::CONTENT_FIELDLIST, self::MIXED_CONTENT_FIELDLIST) as $field) {
unset($item[$field]);
}
unset($item['activity']);
// Filling item related side tables
// Diaspora signature
if (!empty($item['diaspora_signed_text'])) {
DBA::replace('diaspora-interaction', ['uri-id' => $item['uri-id'], 'interaction' => $item['diaspora_signed_text']]);
}
unset($item['diaspora_signed_text']);
// Attached file links
if (!empty($item['file'])) {
Post\Category::storeTextByURIId($item['uri-id'], $item['uid'], $item['file']);
}
unset($item['file']);
// Delivery relevant data
$delivery_data = Post\DeliveryData::extractFields($item);
unset($item['postopts']);
unset($item['inform']);
if (!empty($item['origin']) || !empty($item['wall']) || !empty($delivery_data['postopts']) || !empty($delivery_data['inform'])) {
Post\DeliveryData::insert($item['uri-id'], $delivery_data);
@ -1094,16 +1002,17 @@ class Item
// Store tags from the body if this hadn't been handled previously in the protocol classes
if (!Tag::existsForPost($item['uri-id'])) {
Tag::storeFromBody($item['uri-id'], $body);
Tag::storeFromBody($item['uri-id'], $item['body']);
}
$id = Post\User::insert($item['uri-id'], $item['uid'], $item);
if ($id) {
// Remove all fields that aren't part of the item table
foreach ($item as $field => $value) {
if (!in_array($field, $structure['item'])) {
unset($item[$field]);
if (!$id) {
Logger::notice('Post-User is already inserted - aborting', ['uid' => $item['uid'], 'uri-id' => $item['uri-id']]);
return 0;
}
if ($item['gravity'] == GRAVITY_PARENT) {
Post\ThreadUser::insert($item['uri-id'], $item['uid'], $item);
}
$condition = ['uri-id' => $item['uri-id'], 'uid' => $item['uid'], 'network' => $item['network']];
@ -1112,14 +1021,18 @@ class Item
return 0;
}
$result = DBA::insert('item', $item);
// Remove all fields that aren't part of the item table
$table_fields = DBStructure::getFieldsForTable('item', $item);
// We remove all legacy fields that now are stored in other tables
foreach (self::LEGACY_FIELDLIST as $field) {
unset($table_fields[$field]);
}
$result = DBA::insert('item', $table_fields);
// When the item was successfully stored we fetch the ID of the item.
$current_post = DBA::lastInsertId();
} else {
Logger::notice('Post-User is already inserted - aborting', ['uid' => $item['uid'], 'uri-id' => $item['uri-id']]);
return 0;
}
if (empty($current_post) || !DBA::isResult($result)) {
// On failure store the data into a spool file so that the "SpoolPost" worker can try again later.
@ -1146,7 +1059,7 @@ class Item
$update_commented = in_array($item['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT]);
} else {
// Update when it isn't a follow or tag verb
$update_commented = !in_array($verb, [Activity::FOLLOW, Activity::TAG]);
$update_commented = !in_array($item['verb'], [Activity::FOLLOW, Activity::TAG]);
}
if ($update_commented) {
@ -1191,9 +1104,10 @@ class Item
self::updateContact($item);
UserItem::setNotification($current_post);
Post\UserNotification::setNotification($item['uri-id'], $item['uid']);
check_user_notification($current_post);
check_user_notification($item['uri-id'], $item['uid']);
//check_user_notification($current_post);
// Distribute items to users who subscribed to their tags
self::distributeByTags($item);

View file

@ -192,7 +192,7 @@ class Post
$selected = array_merge(['author-addr', 'author-nick', 'owner-addr', 'owner-nick', 'causer-addr', 'causer-nick',
'causer-network', 'photo', 'name-date', 'uri-date', 'avatar-date', 'thumb', 'dfrn-id',
'parent-guid', 'parent-network', 'parent-author-id', 'parent-author-link', 'parent-author-name',
'parent-author-network', 'signed_text'], Item::DISPLAY_FIELDLIST, Item::ITEM_FIELDLIST, Item::CONTENT_FIELDLIST);
'parent-author-network', 'signed_text', 'language', 'raw-body'], Item::DISPLAY_FIELDLIST, Item::ITEM_FIELDLIST);
if ($view == 'post-thread-view') {
$selected = array_merge($selected, ['ignored', 'iid']);
@ -258,7 +258,7 @@ class Post
AND (NOT `causer-blocked` OR `causer-id` = ?) AND NOT `contact-blocked`
AND ((NOT `contact-readonly` AND NOT `contact-pending` AND (`contact-rel` IN (?, ?)))
OR `self` OR `gravity` != ? OR `contact-uid` = ?)
AND NOT EXISTS (SELECT `iid` FROM `user-item` WHERE `hidden` AND `iid` = `id` AND `uid` = ?)
AND NOT EXISTS (SELECT `uri-id` FROM `post-user` WHERE `hidden` AND `uri-id` = `" . $view . "`.`uri-id` AND `uid` = ?)
AND NOT EXISTS (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `author-id` AND `blocked`)
AND NOT EXISTS (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `owner-id` AND `blocked`)
AND NOT EXISTS (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `author-id` AND `ignored` AND `gravity` = ?)
@ -272,7 +272,7 @@ class Post
unset($selected['pinned']);
$selected = array_flip($selected);
$select_string = "(SELECT `pinned` FROM `user-item` WHERE `iid` = `" . $view . "`.`id` AND uid=`" . $view . "`.`uid`) AS `pinned`, ";
$select_string = "(SELECT `pinned` FROM `post-thread-user` WHERE `uri-id` = `" . $view . "`.`uri-id` AND uid=`" . $view . "`.`uid`) AS `pinned`, ";
}
$select_string .= implode(', ', array_map([DBA::class, 'quoteIdentifier'], $selected));
@ -344,32 +344,6 @@ class Post
}
}
/**
* Retrieve a single record from the starting post in the item table and returns it in an associative array
*
* @param integer $uid User ID
* @param array $selected
* @param array $condition
* @param array $params
* @return bool|array
* @throws \Exception
* @see DBA::select
*/
public static function selectFirstThreadForUser($uid, array $selected = [], array $condition = [], $params = [])
{
$params['limit'] = 1;
$result = self::selectThreadForUser($uid, $selected, $condition, $params);
if (is_bool($result)) {
return $result;
} else {
$row = self::fetch($result);
DBA::close($result);
return $row;
}
}
/**
* Select pinned rows from the item table for a given user
*
@ -383,24 +357,24 @@ class Post
*/
public static function selectPinned(int $uid, array $selected = [], array $condition = [], $params = [])
{
$useritems = DBA::select('user-item', ['iid'], ['uid' => $uid, 'pinned' => true]);
if (!DBA::isResult($useritems)) {
return $useritems;
$postthreaduser = DBA::select('post-thread-user', ['uri-id'], ['uid' => $uid, 'pinned' => true]);
if (!DBA::isResult($postthreaduser)) {
return $postthreaduser;
}
$pinned = [];
while ($useritem = DBA::fetch($useritems)) {
$pinned[] = $useritem['iid'];
while ($useritem = DBA::fetch($postthreaduser)) {
$pinned[] = $useritem['uri-id'];
}
DBA::close($useritems);
DBA::close($postthreaduser);
if (empty($pinned)) {
return [];
}
$condition = DBA::mergeConditions(['iid' => $pinned], $condition);
$condition = DBA::mergeConditions(['uri-id' => $pinned, 'uid' => $uid, 'gravity' => GRAVITY_PARENT], $condition);
return self::selectThreadForUser($uid, $selected, $condition, $params);
return self::selectForUser($uid, $selected, $condition, $params);
}
/**
@ -431,6 +405,8 @@ class Post
unset($fields['parent-uri']);
unset($fields['parent-uri-id']);
$thread_condition = DBA::mergeConditions($condition, ['gravity' => GRAVITY_PARENT]);
// To ensure the data integrity we do it in an transaction
DBA::transaction();
@ -472,34 +448,44 @@ class Post
$affected = max($affected, DBA::affectedRows());
}
$update_fields = DBStructure::getFieldsForTable('thread', $fields);
$update_fields = DBStructure::getFieldsForTable('post-thread-user', $fields);
if (!empty($update_fields)) {
$rows = DBA::selectToArray('post-view', ['id'], $condition);
$ids = array_column($rows, 'id');
if (!DBA::update('thread', $update_fields, ['iid' => $ids])) {
$rows = DBA::selectToArray('post-view', ['post-user-id'], $thread_condition);
$thread_puids = array_column($rows, 'post-user-id');
$post_thread_condition = DBA::collapseCondition(['id' => $thread_puids]);
$post_thread_condition[0] = "EXISTS(SELECT `id` FROM `post-user` WHERE " .
$post_thread_condition[0] . " AND `uri-id` = `post-thread-user`.`uri-id` AND `uid` = `post-thread-user`.`uid`)";
if (!DBA::update('post-thread-user', $update_fields, $post_thread_condition)) {
DBA::rollback();
Logger::notice('Updating thread failed', ['fields' => $update_fields, 'condition' => $condition]);
Logger::notice('Updating post-thread-user failed', ['fields' => $update_fields, 'condition' => $condition]);
return false;
}
$affected = max($affected, DBA::affectedRows());
}
$item_fields = ['guid', 'type', 'wall', 'gravity', 'extid', 'created', 'edited', 'commented', 'received', 'changed',
'resource-id', 'post-type', 'private', 'pubmail', 'moderated', 'visible', 'starred', 'bookmark',
'unseen', 'deleted', 'origin', 'forum_mode', 'mention', 'global', 'network', 'vid', 'psid',
'contact-id', 'author-id', 'owner-id', 'causer-id', 'event-id'];
$update_fields = DBStructure::getFieldsForTable('thread', $fields);
if (!empty($update_fields)) {
$rows = DBA::selectToArray('post-view', ['id'], $thread_condition);
$ids = array_column($rows, 'id');
if (!DBA::update('thread', $update_fields, ['iid' => $ids])) {
DBA::rollback();
Logger::notice('Updating thread failed', ['fields' => $update_fields, 'condition' => $thread_condition]);
return false;
}
$affected = max($affected, DBA::affectedRows());
}
$update_fields = [];
foreach ($item_fields as $field) {
foreach (Item::USED_FIELDLIST as $field) {
if (array_key_exists($field, $fields)) {
$update_fields[$field] = $fields[$field];
}
}
if (!empty($update_fields)) {
if (empty($ids)) {
$rows = DBA::selectToArray('post-view', ['id'], $condition, []);
$ids = array_column($rows, 'id');
}
if (!DBA::update('item', $update_fields, ['id' => $ids])) {
DBA::rollback();
Logger::notice('Updating item failed', ['fields' => $update_fields, 'condition' => $condition]);

View file

@ -0,0 +1,153 @@
<?php
/**
* @copyright Copyright (C) 2020, Friendica
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Model\Post;
use \BadMethodCallException;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
class ThreadUser
{
/**
* Insert a new URI user entry
*
* @param integer $uri_id
* @param integer $uid
* @param array $fields
* @return bool success
* @throws \Exception
*/
public static function insert(int $uri_id, int $uid, array $data = [])
{
if (empty($uri_id)) {
throw new BadMethodCallException('Empty URI_id');
}
$fields = DBStructure::getFieldsForTable('post-thread-user', $data);
// Additionally assign the key fields
$fields['uri-id'] = $uri_id;
$fields['uid'] = $uid;
return DBA::insert('post-thread-user', $fields, Database::INSERT_IGNORE);
}
/**
* Update a URI user entry
*
* @param integer $uri_id
* @param integer $uid
* @param array $data
* @param bool $insert_if_missing
* @return bool
* @throws \Exception
*/
public static function update(int $uri_id, int $uid, array $data = [], bool $insert_if_missing = false)
{
if (empty($uri_id)) {
throw new BadMethodCallException('Empty URI_id');
}
$fields = DBStructure::getFieldsForTable('post-thread-user', $data);
// Remove the key fields
unset($fields['uri-id']);
unset($fields['uid']);
if (empty($fields)) {
return true;
}
return DBA::update('post-thread-user', $fields, ['uri-id' => $uri_id, 'uid' => $uid], $insert_if_missing ? true : []);
}
/**
* Delete a row from the post-thread-user table
*
* @param array $conditions Field condition(s)
* @param array $options
* - cascade: If true we delete records in other tables that depend on the one we're deleting through
* relations (default: true)
*
* @return boolean was the delete successful?
* @throws \Exception
*/
public static function delete(array $conditions, array $options = [])
{
return DBA::delete('post-thread-user', $conditions, $options);
}
/**
* @param int $uri_id
* @param int $uid
* @return bool
* @throws Exception
*/
public static function getIgnored(int $uri_id, int $uid)
{
$threaduser = DBA::selectFirst('post-thread-user', ['ignored'], ['uri-id' => $uri_id, 'uid' => $uid]);
if (empty($threaduser)) {
return false;
}
return (bool)$threaduser['ignored'];
}
/**
* @param int $uri_id
* @param int $uid
* @param int $ignored
* @return void
* @throws Exception
*/
public static function setIgnored(int $uri_id, int $uid, int $ignored)
{
DBA::update('post-thread-user', ['ignored' => $ignored], ['uri-id' => $uri_id, 'uid' => $uid], true);
}
/**
* @param int $uri_id
* @param int $uid
* @return bool
* @throws Exception
*/
public static function getPinned(int $uri_id, int $uid)
{
$threaduser = DBA::selectFirst('post-thread-user', ['pinned'], ['uri-id' => $uri_id, 'uid' => $uid]);
if (empty($threaduser)) {
return false;
}
return (bool)$threaduser['pinned'];
}
/**
* @param int $uri_id
* @param int $uid
* @param int $pinned
* @return void
* @throws Exception
*/
public static function setPinned(int $uri_id, int $uid, int $pinned)
{
DBA::update('post-thread-user', ['pinned' => $pinned], ['uri-id' => $uri_id, 'uid' => $uid], true);
}
}

View file

@ -29,7 +29,7 @@ use Friendica\Database\DBStructure;
class User
{
/**
* Insert a new URI user entry
* Insert a new post user entry
*
* @param integer $uri_id
* @param integer $uid
@ -66,7 +66,7 @@ class User
}
/**
* Update a URI user entry
* Update a post user entry
*
* @param integer $uri_id
* @param integer $uid

View file

@ -19,17 +19,23 @@
*
*/
namespace Friendica\Model;
namespace Friendica\Model\Post;
use \BadMethodCallException;
use Friendica\Core\Logger;
use Friendica\Core\Hook;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Post;
use Friendica\Util\Strings;
use Friendica\Model\Tag;
use Friendica\Protocol\Activity;
class UserItem
class UserNotification
{
// Notification types
const NOTIF_NONE = 0;
@ -42,18 +48,89 @@ class UserItem
const NOTIF_DIRECT_THREAD_COMMENT = 64;
const NOTIF_SHARED = 128;
/**
* Insert a new user notification entry
*
* @param integer $uri_id
* @param integer $uid
* @param array $fields
* @return bool success
* @throws \Exception
*/
public static function insert(int $uri_id, int $uid, array $data = [])
{
if (empty($uri_id)) {
throw new BadMethodCallException('Empty URI_id');
}
$fields = DBStructure::getFieldsForTable('post-user-notification', $data);
// Additionally assign the key fields
$fields['uri-id'] = $uri_id;
$fields['uid'] = $uid;
return DBA::insert('post-user-notification', $fields, Database::INSERT_IGNORE);
}
/**
* Update a user notification entry
*
* @param integer $uri_id
* @param integer $uid
* @param array $data
* @param bool $insert_if_missing
* @return bool
* @throws \Exception
*/
public static function update(int $uri_id, int $uid, array $data = [], bool $insert_if_missing = false)
{
if (empty($uri_id)) {
throw new BadMethodCallException('Empty URI_id');
}
$fields = DBStructure::getFieldsForTable('post-user-notification', $data);
// Remove the key fields
unset($fields['uri-id']);
unset($fields['uid']);
if (empty($fields)) {
return true;
}
return DBA::update('post-user-notification', $fields, ['uri-id' => $uri_id, 'uid' => $uid], $insert_if_missing ? true : []);
}
/**
* Delete a row from the post-user-notification table
*
* @param array $conditions Field condition(s)
* @param array $options
* - cascade: If true we delete records in other tables that depend on the one we're deleting through
* relations (default: true)
*
* @return boolean was the delete successful?
* @throws \Exception
*/
public static function delete(array $conditions, array $options = [])
{
return DBA::delete('post-user-notification', $conditions, $options);
}
/**
* Checks an item for notifications and sets the "notification-type" field
* @ToDo:
* - Check for mentions in posts with "uid=0" where the user hadn't interacted before
*
* @param int $iid Item ID
* @param int $uri_id URI ID
* @param int $uid user ID
*/
public static function setNotification(int $iid)
public static function setNotification(int $uri_id, int $uid)
{
$fields = ['id', 'uri-id', 'parent-uri-id', 'uid', 'body', 'parent', 'gravity',
'private', 'contact-id', 'thr-parent', 'parent-uri', 'author-id', 'verb'];
$item = Post::selectFirst($fields, ['id' => $iid, 'origin' => false]);
'private', 'contact-id', 'thr-parent', 'parent-uri-id', 'parent-uri', 'author-id', 'verb'];
$item = Post::selectFirst($fields, ['uri-id' => $uri_id, 'uid' => $uid, 'origin' => false]);
if (!DBA::isResult($item)) {
return;
}
@ -73,7 +150,7 @@ class UserItem
// Add every user who participated so far in this thread
// This can only happen with participations on global items. (means: uid = 0)
$users = DBA::p("SELECT DISTINCT(`contact-uid`) AS `uid` FROM `post-view`
WHERE `contact-uid` != 0 AND `parent` IN (SELECT `parent` FROM `post-view` WHERE `id` = ?)", $iid);
WHERE `contact-uid` != 0 AND `parent-uri-id` = ? AND `uid` = ?", $item['parent-uri-id'], $uid);
while ($user = DBA::fetch($users)) {
$uids[] = $user['uid'];
}
@ -92,8 +169,7 @@ class UserItem
*/
private static function setNotificationForUser(array $item, int $uid)
{
$thread = Post::selectFirstThreadForUser($uid, ['ignored'], ['iid' => $item['parent'], 'deleted' => false]);
if (!empty($thread['ignored'])) {
if (Post\ThreadUser::getIgnored($item['parent-uri-id'], $uid)) {
return;
}
@ -153,11 +229,11 @@ class UserItem
return;
}
Logger::info('Set notification', ['iid' => $item['id'], 'uid' => $uid, 'notification-type' => $notification_type]);
Logger::info('Set notification', ['iid' => $item['id'], 'uri-id' => $item['uri-id'], 'uid' => $uid, 'notification-type' => $notification_type]);
$fields = ['notification-type' => $notification_type];
Post\User::update($item['uri-id'], $uid, $fields);
DBA::update('user-item', $fields, ['iid' => $item['id'], 'uid' => $uid], true);
self::update($item['uri-id'], $uid, $fields, true);
}
/**

View file

@ -49,26 +49,16 @@ class Ignore extends BaseModule
$dba = DI::dba();
$thread = Post::selectFirstThreadForUser(local_user(), ['uid', 'ignored'], ['iid' => $itemId]);
$thread = Post::selectFirst(['uri-id', 'uid'], ['id' => $itemId, 'gravity' => GRAVITY_PARENT]);
if (!$dba->isResult($thread)) {
throw new HTTPException\NotFoundException();
}
// Numeric values are needed for the json output further below
$ignored = !empty($thread['ignored']) ? 0 : 1;
$ignored = !Post\ThreadUser::getIgnored($thread['uri-id'], local_user());
switch ($thread['uid'] ?? 0) {
// if the thread is from the current user
case local_user():
$dba->update('thread', ['ignored' => $ignored], ['iid' => $itemId]);
break;
// 0 (null will get transformed to 0) => it's a public post
case 0:
$dba->update('user-item', ['ignored' => $ignored], ['iid' => $itemId, 'uid' => local_user()], true);
break;
// Throws a BadRequestException and not a ForbiddenException on purpose
// Avoids harvesting existing, but forbidden IIDs (security issue)
default:
if (in_array($thread['uid'], [0, local_user()])) {
Post\ThreadUser::setIgnored($thread['uri-id'], local_user(), $ignored);
} else {
throw new HTTPException\BadRequestException();
}

View file

@ -24,8 +24,9 @@ namespace Friendica\Module\Item;
use Friendica\BaseModule;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Network\HTTPException;
/**
@ -47,9 +48,18 @@ class Pin extends BaseModule
$itemId = intval($parameters['id']);
$pinned = !Item::getPinned($itemId, local_user());
$item = Post::selectFirst(['uri-id', 'uid'], ['id' => $itemId]);
if (!DBA::isResult($item)) {
throw new HTTPException\NotFoundException();
}
Item::setPinned($itemId, local_user(), $pinned);
if (!in_array($item['uid'], [0, local_user()])) {
throw new HttpException\ForbiddenException($l10n->t('Access denied.'));
}
$pinned = !Post\ThreadUser::getPinned($item['uri-id'], local_user());
Post\ThreadUser::setPinned($item['uri-id'], local_user(), $pinned);
// See if we've been passed a return path to redirect to
$return_path = $_REQUEST['return'] ?? '';

View file

@ -307,17 +307,15 @@ class Post
if ($this->isToplevel()) {
if(local_user()) {
$thread = PostModel::selectFirstThreadForUser(local_user(), ['ignored'], ['iid' => $item['id']]);
if (DBA::isResult($thread)) {
$ignored = PostModel\ThreadUser::getIgnored($item['uri-id'], local_user());
$ignore = [
'do' => DI::l10n()->t("ignore thread"),
'undo' => DI::l10n()->t("unignore thread"),
'toggle' => DI::l10n()->t("toggle ignore status"),
'classdo' => $thread['ignored'] ? "hidden" : "",
'classundo' => $thread['ignored'] ? "" : "hidden",
'classdo' => $ignored ? "hidden" : "",
'classundo' => $ignored ? "" : "hidden",
'ignored' => DI::l10n()->t('ignored'),
];
}
if ($conv->getProfileOwner() == local_user() && ($item['uid'] != 0)) {
if ($origin) {

View file

@ -55,7 +55,7 @@
use Friendica\Database\DBA;
if (!defined('DB_UPDATE_VERSION')) {
define('DB_UPDATE_VERSION', 1396);
define('DB_UPDATE_VERSION', 1397);
}
return [
@ -750,7 +750,6 @@ return [
"guid" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "A unique identifier for this item"],
"uri" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
"uri-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
"uri-hash" => ["type" => "varchar(80)", "not null" => "1", "default" => "", "comment" => "RIPEMD-128 hash from uri"],
"parent" => ["type" => "int unsigned", "relation" => ["item" => "id"], "comment" => "item.id of the parent to this item if it is a reply of some form; otherwise this must be set to the id of this item"],
"parent-uri" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "uri of the top-level parent to this item"],
"parent-uri-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table that contains the top-level parent uri"],
@ -774,21 +773,26 @@ return [
"visible" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
"moderated" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
"deleted" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "item has been deleted"],
// User specific fields. Eventually they will move to user-item
// Part of "post-user". Will be deprecated in a later step
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "foreign" => ["user" => "uid"], "comment" => "Owner id which owns this copy of the item"],
"contact-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "foreign" => ["contact" => "id"], "comment" => "contact.id"],
"wall" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "This item was posted to the wall of uid"],
"origin" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "item originated at this site"],
"pubmail" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
"starred" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "item has been favourited"],
"unseen" => ["type" => "boolean", "not null" => "1", "default" => "1", "comment" => "item has not been seen"],
"mention" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "The owner of this item was mentioned in it"],
"forum_mode" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
"origin" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "item originated at this site"],
"psid" => ["type" => "int unsigned", "foreign" => ["permissionset" => "id", "on delete" => "restrict"], "comment" => "ID of the permission set of this post"],
// Part of "post-thread-user". Will be deprecated in a later step
"starred" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "item has been favourited"],
"wall" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "This item was posted to the wall of uid"],
"pubmail" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
"forum_mode" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
// It has to be decided whether these fields belong to the user or the structure
"resource-id" => ["type" => "varchar(32)", "not null" => "1", "default" => "", "comment" => "Used to link other tables to items, it identifies the linked resource (e.g. photo) and if set must also set resource_type"],
"event-id" => ["type" => "int unsigned", "relation" => ["event" => "id"], "comment" => "Used to link to the event.id"],
// Check deprecation status
"type" => ["type" => "varchar(20)", "comment" => ""],
"bookmark" => ["type" => "boolean", "comment" => ""],
"mention" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "The owner of this item was mentioned in it"],
// Deprecated fields. Will be removed in upcoming versions
"resource-id" => ["type" => "varchar(32)", "comment" => "Deprecated"],
"uri-hash" => ["type" => "varchar(80)", "comment" => "Deprecated"],
"iaid" => ["type" => "int unsigned", "comment" => "Deprecated"],
"icid" => ["type" => "int unsigned", "comment" => "Deprecated"],
"attach" => ["type" => "mediumtext", "comment" => "Deprecated"],
@ -798,8 +802,6 @@ return [
"deny_gid" => ["type" => "mediumtext", "comment" => "Deprecated"],
"postopts" => ["type" => "text", "comment" => "Deprecated"],
"inform" => ["type" => "mediumtext", "comment" => "Deprecated"],
"type" => ["type" => "varchar(20)", "comment" => "Deprecated"],
"bookmark" => ["type" => "boolean", "comment" => "Deprecated"],
"file" => ["type" => "mediumtext", "comment" => "Deprecated"],
"location" => ["type" => "varchar(255)", "comment" => "Deprecated"],
"coord" => ["type" => "varchar(255)", "comment" => "Deprecated"],
@ -1029,20 +1031,6 @@ return [
"created" => ["created"],
]
],
"participation" => [
"comment" => "Storage for participation messages from Diaspora",
"fields" => [
"iid" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["item" => "id"], "comment" => ""],
"server" => ["type" => "varchar(60)", "not null" => "1", "primary" => "1", "comment" => ""],
"cid" => ["type" => "int unsigned", "not null" => "1", "foreign" => ["contact" => "id"], "comment" => ""],
"fid" => ["type" => "int unsigned", "not null" => "1", "foreign" => ["fcontact" => "id"], "comment" => ""],
],
"indexes" => [
"PRIMARY" => ["iid", "server"],
"cid" => ["cid"],
"fid" => ["fid"]
]
],
"pconfig" => [
"comment" => "personal (per user) configuration storage",
"fields" => [
@ -1193,6 +1181,25 @@ return [
"cid" => ["cid"]
]
],
"post-thread-user" => [
"comment" => "Thread related data per user",
"fields" => [
"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "primary" => "1", "foreign" => ["user" => "uid"], "comment" => "Owner id which owns this copy of the item"],
"pinned" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "The thread is pinned on the profile page"],
"starred" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
"ignored" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Ignore updates for this thread"],
"wall" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "This item was posted to the wall of uid"],
"pubmail" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
"forum_mode" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""]
],
"indexes" => [
"PRIMARY" => ["uid", "uri-id"],
"uid_wall" => ["uid", "wall"],
"uid_pinned" => ["uid", "pinned"],
"uri-id" => ["uri-id"],
]
],
"post-user" => [
"comment" => "User specific post data",
"fields" => [
@ -1215,6 +1222,18 @@ return [
"psid" => ["psid"],
],
],
"post-user-notification" => [
"comment" => "User post notifications",
"fields" => [
"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "primary" => "1", "foreign" => ["user" => "uid"], "comment" => "Owner id which owns this copy of the item"],
"notification-type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
],
"indexes" => [
"PRIMARY" => ["uid", "uri-id"],
"uri-id" => ["uri-id"],
],
],
"process" => [
"comment" => "Currently running system processes",
"fields" => [
@ -1474,22 +1493,6 @@ return [
"cid" => ["cid"],
]
],
"user-item" => [
"comment" => "User specific item data",
"fields" => [
"iid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "primary" => "1", "foreign" => ["item" => "id"], "comment" => "Item id"],
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "primary" => "1", "foreign" => ["user" => "uid"], "comment" => "User id"],
"hidden" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Marker to hide an item from the user"],
"ignored" => ["type" => "boolean", "comment" => "Ignore this thread if set"],
"pinned" => ["type" => "boolean", "comment" => "The item is pinned on the profile page"],
"notification-type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
],
"indexes" => [
"PRIMARY" => ["uid", "iid"],
"uid_pinned" => ["uid", "pinned"],
"iid_uid" => ["iid", "uid"]
]
],
"worker-ipc" => [
"comment" => "Inter process communication between the frontend and the worker",
"fields" => [

View file

@ -395,13 +395,13 @@
"query" => "FROM `item`
INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `thread`.`contact-id`
LEFT JOIN `user-item` ON `user-item`.`iid` = `item`.`id` AND `user-item`.`uid` = `thread`.`uid`
LEFT JOIN `post-user` ON `post-user`.`uri-id` = `item`.`uri-id` AND `post-user`.`uid` = `thread`.`uid`
LEFT JOIN `user-contact` AS `author` ON `author`.`uid` = `thread`.`uid` AND `author`.`cid` = `thread`.`author-id`
LEFT JOIN `user-contact` AS `owner` ON `owner`.`uid` = `thread`.`uid` AND `owner`.`cid` = `thread`.`owner-id`
LEFT JOIN `contact` AS `ownercontact` ON `ownercontact`.`id` = `thread`.`owner-id`
WHERE `thread`.`visible` AND NOT `thread`.`deleted` AND NOT `thread`.`moderated`
AND (NOT `contact`.`readonly` AND NOT `contact`.`blocked` AND NOT `contact`.`pending`)
AND (`user-item`.`hidden` IS NULL OR NOT `user-item`.`hidden`)
AND (`post-user`.`hidden` IS NULL OR NOT `post-user`.`hidden`)
AND (`author`.`blocked` IS NULL OR NOT `author`.`blocked`)
AND (`owner`.`blocked` IS NULL OR NOT `owner`.`blocked`)"
],
@ -424,13 +424,13 @@
"query" => "FROM `thread`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `thread`.`contact-id`
STRAIGHT_JOIN `item` ON `item`.`id` = `thread`.`iid`
LEFT JOIN `user-item` ON `user-item`.`iid` = `item`.`id` AND `user-item`.`uid` = `thread`.`uid`
LEFT JOIN `post-user` ON `post-user`.`uri-id` = `item`.`uri-id` AND `post-user`.`uid` = `thread`.`uid`
LEFT JOIN `user-contact` AS `author` ON `author`.`uid` = `thread`.`uid` AND `author`.`cid` = `thread`.`author-id`
LEFT JOIN `user-contact` AS `owner` ON `owner`.`uid` = `thread`.`uid` AND `owner`.`cid` = `thread`.`owner-id`
LEFT JOIN `contact` AS `ownercontact` ON `ownercontact`.`id` = `thread`.`owner-id`
WHERE `thread`.`visible` AND NOT `thread`.`deleted` AND NOT `thread`.`moderated`
AND (NOT `contact`.`readonly` AND NOT `contact`.`blocked` AND NOT `contact`.`pending`)
AND (`user-item`.`hidden` IS NULL OR NOT `user-item`.`hidden`)
AND (`post-user`.`hidden` IS NULL OR NOT `post-user`.`hidden`)
AND (`author`.`blocked` IS NULL OR NOT `author`.`blocked`)
AND (`owner`.`blocked` IS NULL OR NOT `owner`.`blocked`)"
],

View file

@ -466,7 +466,7 @@ function pre_update_1364()
return Update::FAILED;
}
if (!DBA::e("DELETE FROM `user-item` WHERE NOT `uid` IN (SELECT `uid` FROM `user`)")) {
if (DBStructure::existsTable('user-item') && !DBA::e("DELETE FROM `user-item` WHERE NOT `uid` IN (SELECT `uid` FROM `user`)")) {
return Update::FAILED;
}
@ -490,10 +490,6 @@ function pre_update_1364()
return Update::FAILED;
}
if (!DBA::e("DELETE FROM `participation` WHERE NOT `cid` IN (SELECT `id` FROM `contact`)")) {
return Update::FAILED;
}
if (!DBA::e("DELETE FROM `profile_check` WHERE NOT `cid` IN (SELECT `id` FROM `contact`)")) {
return Update::FAILED;
}
@ -502,10 +498,6 @@ function pre_update_1364()
return Update::FAILED;
}
if (!DBA::e("DELETE FROM `participation` WHERE NOT `fid` IN (SELECT `id` FROM `fcontact`)")) {
return Update::FAILED;
}
if (!DBA::e("DELETE FROM `group_member` WHERE NOT `gid` IN (SELECT `id` FROM `group`)")) {
return Update::FAILED;
}
@ -514,11 +506,7 @@ function pre_update_1364()
return Update::FAILED;
}
if (!DBA::e("DELETE FROM `participation` WHERE NOT `iid` IN (SELECT `id` FROM `item`)")) {
return Update::FAILED;
}
if (!DBA::e("DELETE FROM `user-item` WHERE NOT `iid` IN (SELECT `id` FROM `item`)")) {
if (DBStructure::existsTable('user-item') && !DBA::e("DELETE FROM `user-item` WHERE NOT `iid` IN (SELECT `id` FROM `item`)")) {
return Update::FAILED;
}
@ -686,7 +674,7 @@ function update_1395()
return Update::FAILED;
}
if (!DBA::e("INSERT INTO `post-user`(`uri-id`, `uid`, `hidden`, `notification-type`)
if (DBStructure::existsTable('user-item') && !DBA::e("INSERT INTO `post-user`(`uri-id`, `uid`, `hidden`, `notification-type`)
SELECT `uri-id`, `user-item`.`uid`, `hidden`,`notification-type` FROM `user-item`
INNER JOIN `item` ON `item`.`id` = `user-item`.`iid`
ON DUPLICATE KEY UPDATE `hidden` = `user-item`.`hidden`, `notification-type` = `user-item`.`notification-type`")) {
@ -714,3 +702,36 @@ function update_1396()
}
return Update::SUCCESS;
}
function update_1397()
{
if (!DBA::e("INSERT INTO `post-user-notification`(`uri-id`, `uid`, `notification-type`)
SELECT `uri-id`, `uid`, `notification-type` FROM `post-user` WHERE `notification-type` != 0
ON DUPLICATE KEY UPDATE `uri-id` = `post-user`.`uri-id`, `uid` = `post-user`.`uid`, `notification-type` = `post-user`.`notification-type`")) {
return Update::FAILED;
}
if (!DBStructure::existsTable('user-item')) {
return Update::SUCCESS;
}
if (!DBA::e("INSERT INTO `post-user-notification`(`uri-id`, `uid`, `notification-type`)
SELECT `uri-id`, `user-item`.`uid`, `notification-type` FROM `user-item`
INNER JOIN `item` ON `item`.`id` = `user-item`.`iid` WHERE `notification-type` != 0
ON DUPLICATE KEY UPDATE `notification-type` = `user-item`.`notification-type`")) {
return Update::FAILED;
}
if (!DBStructure::existsTable('thread')) {
return Update::SUCCESS;
}
if (!DBA::e("INSERT IGNORE INTO `post-thread-user`(`uri-id`, `uid`, `pinned`, `starred`, `ignored`, `wall`, `pubmail`, `forum_mode`)
SELECT `thread`.`uri-id`, `thread`.`uid`, `user-item`.`pinned`, `thread`.`starred`,
`thread`.`ignored`, `thread`.`wall`, `thread`.`pubmail`, `thread`.`forum_mode`
FROM `thread` LEFT JOIN `user-item` ON `user-item`.`iid` = `thread`.`iid`")) {
return Update::FAILED;
}
return Update::SUCCESS;
}