]> git.mxchange.org Git - friendica.git/blobdiff - src/Model/Item.php
User-Item table is replaced
[friendica.git] / src / Model / Item.php
index 7e5ae10e864f4b4b9436cd4b1b1dd89c64f5c2bf..5041b7ef5b6544c28cc44cdacf0a03a2207fd632 100644 (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 "item-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 "item-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', 'item-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
         *
@@ -196,84 +160,44 @@ class Item
                        return false;
                }
 
-               $data_fields = $fields;
-
-               // To ensure the data integrity we do it in an transaction
-               DBA::transaction();
-
-               // We cannot simply expand the condition to check for origin entries
-               // The condition needn't to be a simple array but could be a complex condition.
-               // And we have to execute this query before the update to ensure to fetch the same data.
-               $items = DBA::select('item', ['id', 'origin', 'uri', 'uri-id', 'uid'], $condition);
-
-               $content_fields = [];
-               foreach (array_merge(self::CONTENT_FIELDLIST, self::MIXED_CONTENT_FIELDLIST) as $field) {
-                       if (isset($fields[$field])) {
-                               $content_fields[$field] = $fields[$field];
-                               unset($fields[$field]);
-                       }
+               if (!empty($fields['verb'])) {
+                       $fields['vid'] = Verb::getID($fields['verb']);
                }
 
-               $delivery_data = Post\DeliveryData::extractFields($fields);
-
-               $clear_fields = ['bookmark', 'type', 'author-name', 'author-avatar', 'author-link', 'owner-name', 'owner-avatar', 'owner-link', 'postopts', 'inform'];
-               foreach ($clear_fields as $field) {
-                       unset($fields[$field]);
+               $rows = Post::update($fields, $condition);
+               if (is_bool($rows)) {
+                       return $rows;
                }
 
-               if (array_key_exists('file', $fields)) {
-                       $files = $fields['file'];
-                       unset($fields['file']);
-               } else {
-                       $files = null;
+               // We only need to call the line by line update for specific fields
+               if (empty($fields['body']) && empty($fields['file']) &&
+                       empty($fields['attach']) && empty($fields['edited'])) {
+                       return $rows;
                }
 
-               if (!empty($content_fields['verb'])) {
-                       $fields['vid'] = Verb::getID($content_fields['verb']);
-               }
+               Logger::info('Updating per single row method', ['fields' => $fields, 'condition' => $condition]);
 
-               if (!empty($fields)) {
-                       $success = DBA::update('item', $fields, $condition);
-
-                       if (!$success) {
-                               DBA::close($items);
-                               DBA::rollback();
-                               return false;
-                       }
-               }
-
-               // When there is no content for the "old" item table, this will count the fetched items
-               $rows = DBA::affectedRows();
+               $items = Post::select(['id', 'origin', 'uri-id', 'uid'], $condition);
 
                $notify_items = [];
 
                while ($item = DBA::fetch($items)) {
-                       Post\User::update($item['uri-id'], $item['uid'], $data_fields);
-
-                       if (empty($content_fields['verb']) || !in_array($content_fields['verb'], self::ACTIVITIES)) {
-                               if (!empty($content_fields['body'])) {
-                                       $content_fields['raw-body'] = trim($content_fields['raw-body'] ?? $content_fields['body']);
-               
-                                       // Remove all media attachments from the body and store them in the post-media table
-                                       $content_fields['raw-body'] = Post\Media::insertFromBody($item['uri-id'], $content_fields['raw-body']);
-                                       $content_fields['raw-body'] = self::setHashtags($content_fields['raw-body']);
-                               }
-               
-                               self::updateContent($content_fields, ['uri-id' => $item['uri-id']]);
+                       if (!empty($fields['body'])) {
+                               $content_fields = ['raw-body' => trim($fields['raw-body'] ?? $fields['body'])];
+       
+                               // Remove all media attachments from the body and store them in the post-media table
+                               $content_fields['raw-body'] = Post\Media::insertFromBody($item['uri-id'], $content_fields['raw-body']);
+                               $content_fields['raw-body'] = self::setHashtags($content_fields['raw-body']);
                        }
 
-                       if (!is_null($files)) {
-                               Post\Category::storeTextByURIId($item['uri-id'], $item['uid'], $files);
+                       if (!empty($fields['file'])) {
+                               Post\Category::storeTextByURIId($item['uri-id'], $item['uid'], $fields['file']);
                        }
 
                        if (!empty($fields['attach'])) {
                                Post\Media::insertFromAttachment($item['uri-id'], $fields['attach']);
                        }
 
-                       Post\DeliveryData::update($item['uri-id'], $delivery_data);
-
-                       self::updateThread($item['id']);
-
                        // We only need to notfiy others when it is an original entry from us.
                        // Only call the notifier when the item has some content relevant change.
                        if ($item['origin'] && in_array('edited', array_keys($fields))) {
@@ -282,7 +206,6 @@ class Item
                }
 
                DBA::close($items);
-               DBA::commit();
 
                foreach ($notify_items as $notify_item) {
                        Worker::add(PRIORITY_HIGH, "Notifier", Delivery::POST, $notify_item);
@@ -326,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']);
                        }
                }
@@ -434,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]);
@@ -796,8 +710,6 @@ class Item
 
        public static function insert($item, $notify = false, $dontcache = false)
        {
-               $structure = self::getItemFields();
-
                $orig_item = $item;
 
                $priority = PRIORITY_HIGH;
@@ -993,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']);
 
@@ -1035,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;
@@ -1061,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);
                }
@@ -1081,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);
 
@@ -1094,42 +974,27 @@ class Item
                        $notify_type = Delivery::POST;
                }
 
-               if (!in_array($item['verb'], self::ACTIVITIES) && !self::insertContent($item)) {
-                       // This shouldn't happen
-                       Logger::warning('No content stored, quitting', ['guid' => $item['guid'], 'uri-id' => $item['uri-id'], 'causer-id' => ($item['causer-id'] ?? 0), 'post-type' => $item['post-type'], 'network' => $item['network']]);
-                       return 0;
+               // Filling item related side tables
+               if (!empty($item['attach'])) {
+                       Post\Media::insertFromAttachment($item['uri-id'], $item['attach']);
                }
 
-               $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]);
+               if (!in_array($item['verb'], self::ACTIVITIES)) {
+                       Post\Content::insert($item['uri-id'], $item);
                }
 
-               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);
@@ -1137,32 +1002,38 @@ 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']);
                }
 
-               if (Post\User::insert($item['uri-id'], $item['uid'], $item)) {
-                       // 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]);
-                               }
-                       }
-
-                       $condition = ['uri-id' => $item['uri-id'], 'uid' => $item['uid'], 'network' => $item['network']];
-                       if (Post::exists($condition)) {
-                               Logger::notice('Item is already inserted - aborting', $condition);
-                               return 0;
-                       }
+               $id = Post\User::insert($item['uri-id'], $item['uid'], $item);
+               if (!$id) {
+                       Logger::notice('Post-User is already inserted - aborting', ['uid' => $item['uid'], 'uri-id' => $item['uri-id']]);
+                       return 0;
+               }
 
-                       $result = DBA::insert('item', $item);
+               if ($item['gravity'] == GRAVITY_PARENT) {
+                       Post\ThreadUser::insert($item['uri-id'], $item['uid'], $item);
+               }
 
-                       // 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']]);
+               $condition = ['uri-id' => $item['uri-id'], 'uid' => $item['uid'], 'network' => $item['network']];
+               if (Post::exists($condition)) {
+                       Logger::notice('Item is already inserted - aborting', $condition);
                        return 0;
                }
 
+               // 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();
+
                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.
                        Logger::warning('Could not store item. it will be spooled', ['result' => $result, 'id' => $current_post]);
@@ -1188,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) {
@@ -1233,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);
@@ -1336,67 +1208,6 @@ class Item
                }
        }
 
-       /**
-        * Insert a new item content entry
-        *
-        * @param array $item The item fields that are to be inserted
-        * @return bool "true" if content was inserted or already existed
-        * @throws \Exception
-        */
-       private static function insertContent(array $item)
-       {
-               $fields = ['uri-plink-hash' => (string)$item['uri-id'], 'uri-id' => $item['uri-id']];
-
-               foreach (array_merge(self::CONTENT_FIELDLIST, self::MIXED_CONTENT_FIELDLIST) as $field) {
-                       if (isset($item[$field])) {
-                               $fields[$field] = $item[$field];
-                       }
-               }
-
-               $found = DBA::exists('item-content', ['uri-id' => $item['uri-id']]);
-               if ($found) {
-                       Logger::info('Existing content found', ['uri-id' => $item['uri-id'], 'uri' => $item['uri']]);
-                       return true;
-               }
-
-               DBA::insert('item-content', $fields, Database::INSERT_IGNORE);
-
-               $found = DBA::exists('item-content', ['uri-id' => $item['uri-id']]);
-               if ($found) {
-                       Logger::notice('Content inserted', ['uri-id' => $item['uri-id'], 'uri' => $item['uri']]);
-                       return true;
-               }
-
-               // This shouldn't happen.
-               Logger::error("Content wasn't inserted", $item);
-               return false;
-       }
-
-       /**
-        * Update existing item content entries
-        *
-        * @param array $item      The item fields that are to be changed
-        * @param array $condition The condition for finding the item content entries
-        * @throws \Exception
-        */
-       private static function updateContent($item, $condition)
-       {
-               // We have to select only the fields from the "item-content" table
-               $fields = [];
-               foreach (array_merge(self::CONTENT_FIELDLIST, self::MIXED_CONTENT_FIELDLIST) as $field) {
-                       if (isset($item[$field])) {
-                               $fields[$field] = $item[$field];
-                       }
-               }
-
-               if (empty($fields)) {
-                       return;
-               }
-
-               DBA::update('item-content', $fields, $condition, true);
-               Logger::info('Updated content', ['condition' => $condition]);
-       }
-
        /**
         * Distributes public items to the receivers
         *
@@ -1991,7 +1802,8 @@ class Item
                        if (($community_page || $prvgroup) &&
                                  !$item['wall'] && !$item['origin'] && ($item['gravity'] == GRAVITY_PARENT)) {
                                Logger::info('Delete private group/communiy top-level item without mention', ['id' => $item_id, 'guid'=> $item['guid']]);
-                               DBA::delete('item', ['id' => $item_id]);
+                               DBA::delete('item', ['uri-id' => $item['uri-id'], 'uid' => $item['uid']]);
+                               Post\User::delete(['uri-id' => $item['uri-id'], 'uid' => $item['uid']]);
                                return true;
                        }
                        return false;
@@ -2670,6 +2482,7 @@ class Item
                $condition = ["`uri-id` = ? AND NOT `deleted` AND NOT (`uid` IN (?, 0))", $uri_id, $item["uid"]];
                if (!Post::exists($condition)) {
                        DBA::delete('item', ['uri-id' => $uri_id, 'uid' => 0]);
+                       Post\User::delete(['uri-id' => $uri_id, 'uid' => 0]);
                        Logger::debug('Deleted shadow item', ['id' => $itemid, 'uri-id' => $uri_id]);
                }
        }