class Item extends BaseObject
{
// Field list that is used to display the items
- const DISPLAY_FIELDLIST = ['uid', 'id', 'parent', 'uri', 'thr-parent', 'parent-uri', 'guid',
+ const DISPLAY_FIELDLIST = ['uid', 'id', 'parent', 'uri', 'thr-parent', 'parent-uri', 'guid', 'network',
'commented', 'created', 'edited', 'received', 'verb', 'object-type', 'postopts', 'plink',
'wall', 'private', 'starred', 'origin', 'title', 'body', 'file', 'attach',
'content-warning', 'location', 'coord', 'app', 'rendered-hash', 'rendered-html', 'object',
- 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'item_id', 'item_network',
+ 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'item_id',
'author-id', 'author-link', 'author-name', 'author-avatar',
'owner-id', 'owner-link', 'owner-name', 'owner-avatar',
'contact-id', 'contact-link', 'contact-name', 'contact-avatar',
- 'network', 'url', 'name', 'writable', 'self', 'cid', 'alias',
+ 'writable', 'self', 'cid', 'alias',
'event-id', 'event-created', 'event-edited', 'event-start', 'event-finish',
'event-summary', 'event-desc', 'event-location', 'event-type',
'event-nofinish', 'event-adjust', 'event-ignore', 'event-id'];
'author-id', 'author-link', 'owner-link', 'contact-uid',
'signed_text', 'signature', 'signer'];
+ // Field list for "item-content" table that is mixed with the item table
+ const CONTENT_FIELDLIST = ['title', 'content-warning', 'body', 'location',
+ 'coord', 'app', 'rendered-hash', 'rendered-html',
+ 'object-type', 'object', 'target-type', 'target'];
+
+ // All fields in the item table
+ const ITEM_FIELDLIST = ['id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent', 'guid',
+ 'contact-id', 'type', 'wall', 'gravity', 'extid', 'icid',
+ 'created', 'edited', 'commented', 'received', 'changed', 'verb',
+ 'postopts', 'plink', 'resource-id', 'event-id', 'tag', 'attach', 'inform',
+ 'file', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
+ 'private', 'pubmail', 'moderated', 'visible', 'starred', 'bookmark',
+ 'unseen', 'deleted', 'origin', 'forum_mode', 'mention', 'global', 'network',
+ 'title', 'content-warning', 'body', 'location', 'coord', 'app',
+ 'rendered-hash', 'rendered-html', 'object-type', 'object', 'target-type', 'target',
+ 'author-id', 'author-link', 'author-name', 'author-avatar',
+ 'owner-id', 'owner-link', 'owner-name', 'owner-avatar'];
+
+ /**
+ * @brief Fetch a single item row
+ *
+ * @param mixed $stmt statement object
+ * @return array current row
+ */
+ public static function fetch($stmt)
+ {
+ $row = dba::fetch($stmt);
+
+ // Fetch data from the item-content table whenever there is content there
+ foreach (self::CONTENT_FIELDLIST as $field) {
+ if (is_null($row[$field]) && !is_null($row['item-' . $field])) {
+ $row[$field] = $row['item-' . $field];
+ }
+ unset($row['item-' . $field]);
+ }
+
+ // We prefer the data from the user's contact over the public one
+ if (!empty($row['author-link']) && !empty($row['contact-link']) &&
+ ($row['author-link'] == $row['contact-link'])) {
+ if (isset($row['author-avatar']) && !empty($row['contact-avatar'])) {
+ $row['author-avatar'] = $row['contact-avatar'];
+ }
+ if (isset($row['author-name']) && !empty($row['contact-name'])) {
+ $row['author-name'] = $row['contact-name'];
+ }
+ }
+
+ if (!empty($row['owner-link']) && !empty($row['contact-link']) &&
+ ($row['owner-link'] == $row['contact-link'])) {
+ if (isset($row['owner-avatar']) && !empty($row['contact-avatar'])) {
+ $row['owner-avatar'] = $row['contact-avatar'];
+ }
+ if (isset($row['owner-name']) && !empty($row['contact-name'])) {
+ $row['owner-name'] = $row['contact-name'];
+ }
+ }
+
+ // We can always comment on posts from these networks
+ if (isset($row['writable']) && !empty($row['network']) &&
+ in_array($row['network'], [NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS])) {
+ $row['writable'] = true;
+ }
+
+ return $row;
+ }
+
+ /**
+ * @brief Fills an array with data from an item query
+ *
+ * @param object $stmt statement object
+ * @return array Data array
+ */
+ public static function inArray($stmt, $do_close = true) {
+ if (is_bool($stmt)) {
+ return $stmt;
+ }
+
+ $data = [];
+ while ($row = self::fetch($stmt)) {
+ $data[] = $row;
+ }
+ if ($do_close) {
+ dba::close($stmt);
+ }
+ return $data;
+ }
+
/**
* Retrieve a single record from the item table for a given user and returns it in an associative array
*
if (is_bool($result)) {
return $result;
} else {
- $row = dba::fetch($result);
+ $row = self::fetch($result);
dba::close($result);
return $row;
}
return dba::p($sql, $condition);
}
+ /**
+ * @brief Select rows from the starting post in the item table
+ *
+ * @param integer $uid User ID
+ * @param array $fields Array of selected fields, empty for all
+ * @param array $condition Array of fields for condition
+ * @param array $params Array of several parameters
+ *
+ * @return boolean|object
+ */
+ public static function selectThreadForUser($uid, array $selected = [], array $condition = [], $params = [])
+ {
+ $params['uid'] = $uid;
+
+ if (empty($selected)) {
+ $selected = Item::DISPLAY_FIELDLIST;
+ }
+
+ return self::selectThread($selected, $condition, $params);
+ }
+
/**
* Retrieve a single record from the starting post in the item table and returns it in an associative array
*
* @brief Retrieve a single record from a table
* @param integer $uid User ID
+ * @param array $selected
+ * @param array $condition
+ * @param array $params
+ * @return bool|array
+ * @see dba::select
+ */
+ public static function selectFirstThreadForUser($uid, array $selected = [], array $condition = [], $params = [])
+ {
+ $params['uid'] = $uid;
+
+ if (empty($selected)) {
+ $selected = Item::DISPLAY_FIELDLIST;
+ }
+
+ return self::selectFirstThread($selected, $condition, $params);
+ }
+
+ /**
+ * Retrieve a single record from the starting post in the item table and returns it in an associative array
+ *
+ * @brief Retrieve a single record from a table
* @param array $fields
* @param array $condition
* @param array $params
* @return bool|array
* @see dba::select
*/
- public static function selectFirstThreadForUser($uid, array $fields = [], array $condition = [], $params = [])
+ public static function selectFirstThread(array $fields = [], array $condition = [], $params = [])
{
$params['limit'] = 1;
- $result = self::selectThreadForUser($uid, $fields, $condition, $params);
+ $result = self::selectThread($fields, $condition, $params);
if (is_bool($result)) {
return $result;
} else {
- $row = dba::fetch($result);
+ $row = self::fetch($result);
dba::close($result);
return $row;
}
/**
* @brief Select rows from the starting post in the item table
*
- * @param integer $uid User ID
- * @param array $fields Array of selected fields, empty for all
+ * @param array $selected Array of selected fields, empty for all
* @param array $condition Array of fields for condition
* @param array $params Array of several parameters
*
* @return boolean|object
*/
- public static function selectThreadForUser($uid, array $selected = [], array $condition = [], $params = [])
+ public static function selectThread(array $selected = [], array $condition = [], $params = [])
{
- if (empty($selected)) {
- $selected = Item::DISPLAY_FIELDLIST;
+ $uid = 0;
+ $usermode = false;
+
+ if (isset($params['uid'])) {
+ $uid = $params['uid'];
+ $usermode = true;
}
$fields = self::fieldlist($selected);
$condition_string = self::addTablesToFields($condition_string, $threadfields);
$condition_string = self::addTablesToFields($condition_string, $fields);
- $condition_string = $condition_string . ' AND ' . self::condition(true);
+ if ($usermode) {
+ $condition_string = $condition_string . ' AND ' . self::condition(true);
+ }
$param_string = dba::buildParameter($params);
$param_string = self::addTablesToFields($param_string, $threadfields);
{
$fields = [];
- $fields['item'] = ['author-id', 'owner-id', 'contact-id', 'uid', 'id', 'parent',
- 'uri', 'thr-parent', 'parent-uri', 'content-warning',
- 'commented', 'created', 'edited', 'received', 'verb', 'object-type', 'postopts', 'plink',
- 'guid', 'wall', 'private', 'starred', 'origin', 'title', 'body', 'file', 'event-id',
- 'location', 'coord', 'app', 'attach', 'rendered-hash', 'rendered-html', 'object',
- 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
- 'id' => 'item_id', 'network' => 'item_network',
- 'type', 'extid', 'changed', 'moderated', 'target-type', 'target',
- 'resource-id', 'tag', 'inform', 'pubmail', 'visible', 'bookmark', 'unseen', 'deleted',
- 'forum_mode', 'mention', 'global', 'shadow'];
+ $fields['item'] = ['id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent', 'guid',
+ 'contact-id', 'owner-id', 'author-id', 'type', 'wall', 'gravity', 'extid',
+ 'created', 'edited', 'commented', 'received', 'changed', 'verb',
+ 'postopts', 'plink', 'resource-id', 'event-id', 'tag', 'attach', 'inform',
+ 'file', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
+ 'private', 'pubmail', 'moderated', 'visible', 'starred', 'bookmark',
+ 'unseen', 'deleted', 'origin', 'forum_mode', 'mention', 'global',
+ 'id' => 'item_id', 'network'];
- $fields['author'] = ['url' => 'author-link', 'name' => 'author-name', 'thumb' => 'author-avatar'];
+ $fields['item-content'] = self::CONTENT_FIELDLIST;
- $fields['owner'] = ['url' => 'owner-link', 'name' => 'owner-name', 'thumb' => 'owner-avatar'];
+ $fields['author'] = ['url' => 'author-link', 'name' => 'author-name',
+ 'thumb' => 'author-avatar', 'nick' => 'author-nick'];
+
+ $fields['owner'] = ['url' => 'owner-link', 'name' => 'owner-name',
+ 'thumb' => 'owner-avatar', 'nick' => 'owner-nick'];
$fields['contact'] = ['url' => 'contact-link', 'name' => 'contact-name', 'thumb' => 'contact-avatar',
- 'network', 'url', 'name', 'writable', 'self', 'id' => 'cid', 'alias', 'uid' => 'contact-uid',
+ 'writable', 'self', 'id' => 'cid', 'alias', 'uid' => 'contact-uid',
'photo', 'name-date', 'uri-date', 'avatar-date', 'thumb', 'dfrn-id'];
- $fields['parent-item'] = ['guid' => 'parent-guid'];
+ $fields['parent-item'] = ['guid' => 'parent-guid', 'network' => 'parent-network'];
$fields['parent-item-author'] = ['url' => 'parent-author-link', 'name' => 'parent-author-name'];
$joins .= " LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`";
}
+ if (strpos($sql_commands, "`item-content`.") !== false) {
+ $joins .= " LEFT JOIN `item-content` ON `item-content`.`id` = `item`.`icid`";
+ }
+
if ((strpos($sql_commands, "`parent-item`.") !== false) || (strpos($sql_commands, "`parent-author`.") !== false)) {
$joins .= " STRAIGHT_JOIN `item` AS `parent-item` ON `parent-item`.`id` = `item`.`parent`";
}
foreach ($fields as $table => $table_fields) {
foreach ($table_fields as $field => $select) {
if (empty($selected) || in_array($select, $selected)) {
+ if (in_array($select, self::CONTENT_FIELDLIST)) {
+ $selection[] = "`item`.`".$select."` AS `item-" . $select . "`";
+ }
if (is_int($field)) {
- $selection[] = "`" . $table . "`.`".$select."`";
+ $selection[] = "`" . $table . "`.`" . $select . "`";
} else {
- $selection[] = "`" . $table . "`.`" . $field . "` AS `".$select ."`";
+ $selection[] = "`" . $table . "`.`" . $field . "` AS `" . $select . "`";
}
}
}
// 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'], $condition);
+ $items = dba::select('item', ['id', 'origin', 'uri', 'plink'], $condition);
- $success = dba::update('item', $fields, $condition);
+ $content_fields = [];
+ foreach (self::CONTENT_FIELDLIST as $field) {
+ if (isset($fields[$field])) {
+ $content_fields[$field] = $fields[$field];
+ unset($fields[$field]);
+ }
+ }
- if (!$success) {
- dba::close($items);
- dba::rollback();
- return false;
+ 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::affected_rows();
while ($item = dba::fetch($items)) {
+ if (!empty($item['plink'])) {
+ $content_fields['plink'] = $item['plink'];
+ }
+ self::updateContent($content_fields, ['uri' => $item['uri']]);
Term::insertFromTagFieldByItemId($item['id']);
Term::insertFromFileFieldByItemId($item['id']);
self::updateThread($item['id']);
// When there is no content then we don't post it
if ($item['body'].$item['title'] == '') {
+ logger('No body, no title.');
return 0;
}
return 0;
}
- //unset($item['author-link']);
+ // These fields aren't stored anymore in the item table, they are fetched upon request
+ unset($item['author-link']);
unset($item['author-name']);
unset($item['author-avatar']);
- //unset($item['owner-link']);
+ unset($item['owner-link']);
unset($item['owner-name']);
unset($item['owner-avatar']);
$user = dba::selectFirst('user', ['nickname'], ['uid' => $item['uid']]);
if (DBM::is_result($user)) {
$self = normalise_link(System::baseUrl() . '/profile/' . $user['nickname']);
- logger("'myself' is ".$self." for parent ".$parent_id." checking against ".$item['author-link']." and ".$item['owner-link'], LOGGER_DEBUG);
- if ((normalise_link($item['author-link']) == $self) || (normalise_link($item['owner-link']) == $self)) {
+ $self_id = Contact::getIdForURL($self, 0, true);
+ logger("'myself' is ".$self_id." for parent ".$parent_id." checking against ".$item['author-id']." and ".$item['owner-id'], LOGGER_DEBUG);
+ if (($item['author-id'] == $self_id) || ($item['owner-id'] == $self_id)) {
dba::update('thread', ['mention' => true], ['iid' => $parent_id]);
logger("tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
}
logger('' . print_r($item,true), LOGGER_DATA);
dba::transaction();
+ self::insertContent($item);
$ret = dba::insert('item', $item);
// When the item was successfully stored we fetch the ID of the item.
return $current_post;
}
+ /**
+ * @brief Insert a new item content entry
+ *
+ * @param array $item The item fields that are to be inserted
+ */
+ private static function insertContent(&$item)
+ {
+ $fields = ['uri' => $item['uri'], 'plink' => $item['plink'],
+ 'uri-plink-hash' => hash('sha1', $item['plink']).hash('sha1', $item['uri'])];
+
+ unset($item['plink']);
+
+ foreach (self::CONTENT_FIELDLIST as $field) {
+ if (isset($item[$field])) {
+ $fields[$field] = $item[$field];
+ unset($item[$field]);
+ }
+ }
+
+ // Do we already have this content?
+ if (!dba::exists('item-content', ['uri' => $item['uri']])) {
+ dba::insert('item-content', $fields, true);
+ }
+
+ $item_content = dba::selectFirst('item-content', ['id'], ['uri' => $item['uri']]);
+ if (DBM::is_result($item_content)) {
+ $item['icid'] = $item_content['id'];
+ logger('Insert content for URI '.$item['uri'].' ('.$item['icid'].')');
+ }
+ }
+
+ /**
+ * @brief 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
+ */
+ private static function updateContent($item, $condition)
+ {
+ // We have to select only the fields from the "item-content" table
+ $fields = [];
+ foreach (self::CONTENT_FIELDLIST as $field) {
+ if (isset($item[$field])) {
+ $fields[$field] = $item[$field];
+ }
+ }
+
+ if (empty($fields)) {
+ return;
+ }
+
+ if (!empty($item['plink'])) {
+ $fields['plink'] = $item['plink'];
+ $fields['uri-plink-hash'] = hash('sha1', $item['plink']).hash('sha1', $condition['uri']);
+ }
+
+ logger('Update content for URI '.$condition['uri']);
+
+ dba::update('item-content', $fields, $condition, true);
+ }
+
/**
* @brief Distributes public items to the receivers
*
$condition = ['id' => $itemid, 'uid' => 0,
'network' => [NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS, ""],
'visible' => true, 'deleted' => false, 'moderated' => false, 'private' => false];
- $item = dba::selectFirst('item', [], ['id' => $itemid]);
+ $item = self::selectFirst(self::ITEM_FIELDLIST, ['id' => $itemid]);
if (!DBM::is_result($item)) {
return;
}
unset($item['wall']);
unset($item['origin']);
unset($item['starred']);
- unset($item['rendered-hash']);
- unset($item['rendered-html']);
$users = [];
return;
}
- $item = dba::selectFirst('item', [], ['id' => $itemid]);
+ $item = self::selectFirst(self::ITEM_FIELDLIST, ['id' => $itemid]);
if (DBM::is_result($item) && ($item["allow_cid"] == '') && ($item["allow_gid"] == '') &&
($item["deny_cid"] == '') && ($item["deny_gid"] == '')) {
unset($item['mention']);
unset($item['origin']);
unset($item['starred']);
- unset($item['rendered-hash']);
- unset($item['rendered-html']);
if ($item['uri'] == $item['parent-uri']) {
- $item['contact-id'] = Contact::getIdForURL($item['owner-link']);
+ $item['contact-id'] = $item['owner-id'];
} else {
- $item['contact-id'] = Contact::getIdForURL($item['author-link']);
+ $item['contact-id'] = $item['author-id'];
}
if (in_array($item['type'], ["net-comment", "wall-comment"])) {
unset($item['mention']);
unset($item['origin']);
unset($item['starred']);
- unset($item['rendered-hash']);
- unset($item['rendered-html']);
$item['contact-id'] = Contact::getIdForURL($item['author-link']);
if (in_array($item['type'], ["net-comment", "wall-comment"])) {
private static function updateThread($itemid, $setmention = false)
{
- $fields = ['uid', 'guid', 'title', 'body', 'created', 'edited', 'commented', 'received', 'changed',
+ $fields = ['uid', 'guid', 'created', 'edited', 'commented', 'received', 'changed',
'wall', 'private', 'pubmail', 'moderated', 'visible', 'starred', 'bookmark', 'contact-id',
- 'deleted', 'origin', 'forum_mode', 'network', 'author-id', 'owner-id', 'rendered-html', 'rendered-hash'];
+ 'deleted', 'origin', 'forum_mode', 'network', 'author-id', 'owner-id'];
$condition = ["`id` = ? AND (`parent` = ? OR `parent` = 0)", $itemid, $itemid];
$item = dba::selectFirst('item', $fields, $condition);
$fields = [];
foreach ($item as $field => $data) {
- if (!in_array($field, ["guid", "title", "body", "rendered-html", "rendered-hash"])) {
+ if (!in_array($field, ["guid"])) {
$fields[$field] = $data;
}
}
$result = dba::update('thread', $fields, ['iid' => $itemid]);
logger("Update thread for item ".$itemid." - guid ".$item["guid"]." - ".(int)$result, LOGGER_DEBUG);
-
- // Updating a shadow item entry
- $items = dba::selectFirst('item', ['id'], ['guid' => $item['guid'], 'uid' => 0]);
-
- if (!DBM::is_result($items)) {
- return;
- }
-
- $fields = ['title' => $item['title'], 'body' => $item['body'],
- 'rendered-html' => $item['rendered-html'], 'rendered-hash' => $item['rendered-hash']];
- $result = dba::update('item', $fields, ['id' => $items['id']]);
-
- logger("Updating public shadow for post ".$items["id"]." - guid ".$item["guid"]." Result: ".print_r($result, true), LOGGER_DEBUG);
}
private static function deleteThread($itemid, $itemuri = "")