/** * Add a thread to the conversation * * Returns: * _ The inserted item on success * _ false on failure */ public function add_thread($item) { $item_id = $item->get_id(); if (!$item_id) { logger('[ERROR] Conversation::add_thread : Item has no ID!!', LOGGER_DEBUG); return false; } if ($this->get_thread($item->get_id())) { logger('[WARN] Conversation::add_thread : Thread already exists (' . $item->get_id() . ').', LOGGER_DEBUG); return false; } /* * Only add things that will be displayed */ if ($item->get_data_value('id') != $item->get_data_value('parent') && (activity_match($item->get_data_value('verb'), ACTIVITY_LIKE) || activity_match($item->get_data_value('verb'), ACTIVITY_DISLIKE))) { return false; } $item->set_commentable(false); $ob_hash = $this->observer ? $this->observer['xchan_hash'] : ''; if (!comments_are_now_closed($item->get_data())) { if ($item->get_data_value('author_xchan') === $ob_hash || $item->get_data_value('owner_xchan') === $ob_hash) { $item->set_commentable(true); } if ($item->get_data_value('item_flags') & ITEM_NOCOMMENT) { $item->set_commentable(false); } elseif ($this->observer && !$item->is_commentable()) { if (array_key_exists('owner', $item->data) && $item->data['owner']['abook_flags'] & ABOOK_FLAG_SELF) { $item->set_commentable(perm_is_allowed($this->profile_owner, $this->observer['xchan_hash'], 'post_comments')); } else { $item->set_commentable(can_comment_on_post($this->observer['xchan_hash'], $item->data)); } } } require_once 'include/identity.php'; // $sys = get_sys_channel(); // if($sys && $item->get_data_value('uid') == $sys['channel_id']) { // $item->set_commentable(false); // } $item->set_conversation($this); $this->threads[] = $item; return end($this->threads); }
/** * @brief * * @param array $arr * @param boolean $allow_exec (optional) default false * @return array * * \e boolean \b success * * \e int \b item_id */ function item_store($arr, $allow_exec = false) { $d = array('item' => $arr, 'allow_exec' => $allow_exec); call_hooks('item_store', $d); $arr = $d['item']; $allow_exec = $d['allow_exec']; $ret = array('success' => false, 'item_id' => 0); if (!$arr['uid']) { logger('item_store: no uid'); $ret['message'] = 'No uid.'; return $ret; } //$uplinked_comment = false; // If a page layout is provided, ensure it exists and belongs to us. if (array_key_exists('layout_mid', $arr) && $arr['layout_mid']) { $l = q("select item_restrict from item where mid = '%s' and uid = %d limit 1", dbesc($arr['layout_mid']), intval($arr['uid'])); if (!$l || !($l[0]['item_restrict'] & ITEM_PDL)) { unset($arr['layout_mid']); } } // Don't let anybody set these, either intentionally or accidentally if (array_key_exists('id', $arr)) { unset($arr['id']); } if (array_key_exists('parent', $arr)) { unset($arr['parent']); } $arr['mimetype'] = x($arr, 'mimetype') ? notags(trim($arr['mimetype'])) : 'text/bbcode'; if ($arr['mimetype'] == 'application/x-php' && !$allow_exec) { logger('item_store: php mimetype but allow_exec is denied.'); $ret['message'] = 'exec denied.'; return $ret; } $arr['title'] = array_key_exists('title', $arr) && strlen($arr['title']) ? trim($arr['title']) : ''; $arr['body'] = array_key_exists('body', $arr) && strlen($arr['body']) ? trim($arr['body']) : ''; $arr['diaspora_meta'] = x($arr, 'diaspora_meta') ? $arr['diaspora_meta'] : ''; $arr['allow_cid'] = x($arr, 'allow_cid') ? trim($arr['allow_cid']) : ''; $arr['allow_gid'] = x($arr, 'allow_gid') ? trim($arr['allow_gid']) : ''; $arr['deny_cid'] = x($arr, 'deny_cid') ? trim($arr['deny_cid']) : ''; $arr['deny_gid'] = x($arr, 'deny_gid') ? trim($arr['deny_gid']) : ''; $arr['item_private'] = x($arr, 'item_private') ? intval($arr['item_private']) : 0; $arr['item_flags'] = x($arr, 'item_flags') ? intval($arr['item_flags']) : 0; // only detect language if we have text content, and if the post is private but not yet // obscured, make it so. if (!($arr['item_flags'] & ITEM_OBSCURED)) { $arr['lang'] = detect_language($arr['body']); // apply the input filter here - if it is obscured it has been filtered already $arr['body'] = trim(z_input_filter($arr['uid'], $arr['body'], $arr['mimetype'])); if (local_channel() && !$arr['sig']) { $channel = get_app()->get_channel(); if ($channel['channel_hash'] === $arr['author_xchan']) { $arr['sig'] = base64url_encode(rsa_sign($arr['body'], $channel['channel_prvkey'])); $arr['item_flags'] |= ITEM_VERIFIED; } } $allowed_languages = get_pconfig($arr['uid'], 'system', 'allowed_languages'); if (is_array($allowed_languages) && $arr['lang'] && !array_key_exists($arr['lang'], $allowed_languages)) { $translate = array('item' => $arr, 'from' => $arr['lang'], 'to' => $allowed_languages, 'translated' => false); call_hooks('item_translate', $translate); if (!$translate['translated'] && intval(get_pconfig($arr['uid'], 'system', 'reject_disallowed_languages'))) { logger('item_store: language ' . $arr['lang'] . ' not accepted for uid ' . $arr['uid']); $ret['message'] = 'language not accepted'; return $ret; } $arr = $translate['item']; } if ($arr['item_private']) { $key = get_config('system', 'pubkey'); $arr['item_flags'] = $arr['item_flags'] | ITEM_OBSCURED; if ($arr['title']) { $arr['title'] = json_encode(crypto_encapsulate($arr['title'], $key)); } if ($arr['body']) { $arr['body'] = json_encode(crypto_encapsulate($arr['body'], $key)); } } } if (x($arr, 'object') && is_array($arr['object'])) { activity_sanitise($arr['object']); $arr['object'] = json_encode($arr['object']); } if (x($arr, 'target') && is_array($arr['target'])) { activity_sanitise($arr['target']); $arr['target'] = json_encode($arr['target']); } if (x($arr, 'attach') && is_array($arr['attach'])) { activity_sanitise($arr['attach']); $arr['attach'] = json_encode($arr['attach']); } $arr['aid'] = x($arr, 'aid') ? intval($arr['aid']) : 0; $arr['mid'] = x($arr, 'mid') ? notags(trim($arr['mid'])) : random_string(); $arr['author_xchan'] = x($arr, 'author_xchan') ? notags(trim($arr['author_xchan'])) : ''; $arr['owner_xchan'] = x($arr, 'owner_xchan') ? notags(trim($arr['owner_xchan'])) : ''; $arr['created'] = x($arr, 'created') !== false ? datetime_convert('UTC', 'UTC', $arr['created']) : datetime_convert(); $arr['edited'] = x($arr, 'edited') !== false ? datetime_convert('UTC', 'UTC', $arr['edited']) : datetime_convert(); $arr['expires'] = x($arr, 'expires') !== false ? datetime_convert('UTC', 'UTC', $arr['expires']) : NULL_DATE; $arr['commented'] = x($arr, 'commented') !== false ? datetime_convert('UTC', 'UTC', $arr['commented']) : datetime_convert(); $arr['comments_closed'] = x($arr, 'comments_closed') !== false ? datetime_convert('UTC', 'UTC', $arr['comments_closed']) : NULL_DATE; $arr['received'] = datetime_convert(); $arr['changed'] = datetime_convert(); $arr['location'] = x($arr, 'location') ? notags(trim($arr['location'])) : ''; $arr['coord'] = x($arr, 'coord') ? notags(trim($arr['coord'])) : ''; $arr['parent_mid'] = x($arr, 'parent_mid') ? notags(trim($arr['parent_mid'])) : ''; $arr['thr_parent'] = x($arr, 'thr_parent') ? notags(trim($arr['thr_parent'])) : $arr['parent_mid']; $arr['verb'] = x($arr, 'verb') ? notags(trim($arr['verb'])) : ACTIVITY_POST; $arr['obj_type'] = x($arr, 'obj_type') ? notags(trim($arr['obj_type'])) : ACTIVITY_OBJ_NOTE; $arr['object'] = x($arr, 'object') ? trim($arr['object']) : ''; $arr['tgt_type'] = x($arr, 'tgt_type') ? notags(trim($arr['tgt_type'])) : ''; $arr['target'] = x($arr, 'target') ? trim($arr['target']) : ''; $arr['plink'] = x($arr, 'plink') ? notags(trim($arr['plink'])) : ''; $arr['attach'] = x($arr, 'attach') ? notags(trim($arr['attach'])) : ''; $arr['app'] = x($arr, 'app') ? notags(trim($arr['app'])) : ''; $arr['item_restrict'] = x($arr, 'item_restrict') ? intval($arr['item_restrict']) : 0; $arr['public_policy'] = x($arr, 'public_policy') ? notags(trim($arr['public_policy'])) : ''; $arr['comment_policy'] = x($arr, 'comment_policy') ? notags(trim($arr['comment_policy'])) : 'contacts'; $arr['item_unseen'] = array_key_exists('item_unseen', $arr) ? intval($arr['item_unseen']) : 1; if ($arr['comment_policy'] == 'none') { $arr['item_flags'] = $arr['item_flags'] | ITEM_NOCOMMENT; } // handle time travelers // Allow a bit of fudge in case somebody just has a slightly slow/fast clock $d1 = new DateTime('now +10 minutes', new DateTimeZone('UTC')); $d2 = new DateTime($arr['created'] . '+00:00'); if ($d2 > $d1) { $arr['item_restrict'] = $arr['item_restrict'] | ITEM_DELAYED_PUBLISH; } $arr['llink'] = z_root() . '/display/' . $arr['mid']; if (!$arr['plink']) { $arr['plink'] = $arr['llink']; } if ($arr['parent_mid'] === $arr['mid']) { $parent_id = 0; $parent_deleted = 0; $allow_cid = $arr['allow_cid']; $allow_gid = $arr['allow_gid']; $deny_cid = $arr['deny_cid']; $deny_gid = $arr['deny_gid']; $public_policy = $arr['public_policy']; $comments_closed = $arr['comments_closed']; $arr['item_flags'] = $arr['item_flags'] | ITEM_THREAD_TOP; } else { // find the parent and snarf the item id and ACL's // and anything else we need to inherit $r = q("SELECT * FROM `item` WHERE `mid` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1", dbesc($arr['parent_mid']), intval($arr['uid'])); if ($r) { // in case item_store was killed before the parent's parent attribute got set, // set it now. This happens with some regularity on Dreamhost. This will keep // us from getting notifications for threads that exist but which we can't see. if ($r[0]['mid'] === $r[0]['parent_mid'] && !intval($r[0]['parent'])) { q("update item set parent = id where id = %d", intval($r[0]['id'])); } if (comments_are_now_closed($r[0])) { logger('item_store: comments closed'); $ret['message'] = 'Comments closed.'; return $ret; } if ($arr['obj_type'] == ACTIVITY_OBJ_NOTE) { $arr['obj_type'] = ACTIVITY_OBJ_COMMENT; } // is the new message multi-level threaded? // even though we don't support it now, preserve the info // and re-attach to the conversation parent. if ($r[0]['mid'] != $r[0]['parent_mid']) { $arr['parent_mid'] = $r[0]['parent_mid']; $z = q("SELECT * FROM `item` WHERE `mid` = '%s' AND `parent_mid` = '%s' AND `uid` = %d\n\t\t\t\t\tORDER BY `id` ASC LIMIT 1", dbesc($r[0]['parent_mid']), dbesc($r[0]['parent_mid']), intval($arr['uid'])); if ($z && count($z)) { $r = $z; } } $parent_id = $r[0]['id']; $parent_deleted = $r[0]['item_restrict'] & ITEM_DELETED; $allow_cid = $r[0]['allow_cid']; $allow_gid = $r[0]['allow_gid']; $deny_cid = $r[0]['deny_cid']; $deny_gid = $r[0]['deny_gid']; $public_policy = $r[0]['public_policy']; $comments_closed = $r[0]['comments_closed']; if ($r[0]['item_flags'] & ITEM_WALL) { $arr['item_flags'] = $arr['item_flags'] | ITEM_WALL; } // An uplinked comment might arrive with a downstream owner. // Fix it. if ($r[0]['owner_xchan'] !== $arr['owner_xchan']) { $arr['owner_xchan'] = $r[0]['owner_xchan']; // $uplinked_comment = true; } // if the parent is private, force privacy for the entire conversation if ($r[0]['item_private']) { $arr['item_private'] = $r[0]['item_private']; } // Edge case. We host a public forum that was originally posted to privately. // The original author commented, but as this is a comment, the permissions // weren't fixed up so it will still show the comment as private unless we fix it here. if (intval($r[0]['item_flags']) & ITEM_UPLINK && !$r[0]['item_private']) { $arr['item_private'] = 0; } } else { logger('item_store: item parent was not found - ignoring item'); $ret['message'] = 'parent not found.'; return $ret; } } if ($parent_deleted) { $arr['item_restrict'] = $arr['item_restrict'] | ITEM_DELETED; } $r = q("SELECT `id` FROM `item` WHERE `mid` = '%s' AND `uid` = %d LIMIT 1", dbesc($arr['mid']), intval($arr['uid'])); if ($r) { logger('item_store: duplicate item ignored. ' . print_r($arr, true)); $ret['message'] = 'duplicate post.'; return $ret; } call_hooks('item_store', $arr); // This hook remains for backward compatibility. call_hooks('post_remote', $arr); if (x($arr, 'cancel')) { logger('item_store: post cancelled by plugin.'); $ret['message'] = 'cancelled.'; return $ret; } // pull out all the taxonomy stuff for separate storage $terms = null; if (array_key_exists('term', $arr)) { $terms = $arr['term']; unset($arr['term']); } if (strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid) || strlen($public_policy)) { $private = 1; } else { $private = $arr['item_private']; } $arr['parent'] = $parent_id; $arr['allow_cid'] = $allow_cid; $arr['allow_gid'] = $allow_gid; $arr['deny_cid'] = $deny_cid; $arr['deny_gid'] = $deny_gid; $arr['public_policy'] = $public_policy; $arr['item_private'] = $private; $arr['comments_closed'] = $comments_closed; logger('item_store: ' . print_r($arr, true), LOGGER_DATA); dbesc_array($arr); $r = dbq("INSERT INTO `item` (`" . implode("`, `", array_keys($arr)) . "`) VALUES ('" . implode("', '", array_values($arr)) . "')"); // find the item we just created $r = q("SELECT * FROM `item` WHERE `mid` = '%s' AND `uid` = %d ORDER BY `id` ASC ", $arr['mid'], intval($arr['uid'])); if ($r && count($r)) { $current_post = $r[0]['id']; $arr = $r[0]; // This will gives us a fresh copy of what's now in the DB and undo the db escaping, which really messes up the notifications logger('item_store: created item ' . $current_post, LOGGER_DEBUG); } else { logger('item_store: could not locate stored item'); $ret['message'] = 'unable to retrieve.'; return $ret; } if (count($r) > 1) { logger('item_store: duplicated post occurred. Removing duplicates.'); q("DELETE FROM `item` WHERE `mid` = '%s' AND `uid` = %d AND `id` != %d ", $arr['mid'], intval($arr['uid']), intval($current_post)); } $arr['id'] = $current_post; if (!intval($r[0]['parent'])) { $x = q("update item set parent = id where id = %d", intval($r[0]['id'])); } // Store taxonomy if ($terms && is_array($terms)) { foreach ($terms as $t) { q("insert into term (uid,oid,otype,type,term,url)\n\t\t\t\tvalues(%d,%d,%d,%d,'%s','%s') ", intval($arr['uid']), intval($current_post), intval(TERM_OBJ_POST), intval($t['type']), dbesc($t['term']), dbesc($t['url'])); } $arr['term'] = $terms; } call_hooks('post_remote_end', $arr); // update the commented timestamp on the parent $z = q("select max(created) as commented from item where parent_mid = '%s' and uid = %d and not ( item_restrict & %d )>0 ", dbesc($arr['parent_mid']), intval($arr['uid']), intval(ITEM_DELAYED_PUBLISH)); q("UPDATE item set commented = '%s', changed = '%s' WHERE id = %d", dbesc($z ? $z[0]['commented'] : datetime_convert()), dbesc(datetime_convert()), intval($parent_id)); // If _creating_ a deleted item, don't propagate it further or send out notifications. // We need to store the item details just in case the delete came in before the original post, // so that we have an item in the DB that's marked deleted and won't store a fresh post // that isn't aware that we were already told to delete it. if (!($arr['item_restrict'] & ITEM_DELETED)) { send_status_notifications($current_post, $arr); tag_deliver($arr['uid'], $current_post); } $ret['success'] = true; $ret['item_id'] = $current_post; return $ret; }
/** * Add a thread to the conversation * * Returns: * _ The inserted item on success * _ false on failure */ public function add_thread($item) { $item_id = $item->get_id(); if (!$item_id) { logger('Item has no ID!!', LOGGER_DEBUG, LOG_ERR); return false; } if ($this->get_thread($item->get_id())) { logger('Thread already exists (' . $item->get_id() . ').', LOGGER_DEBUG, LOG_WARNING); return false; } /* * Only add things that will be displayed */ if ($item->get_data_value('id') != $item->get_data_value('parent') && (activity_match($item->get_data_value('verb'), ACTIVITY_LIKE) || activity_match($item->get_data_value('verb'), ACTIVITY_DISLIKE))) { return false; } $item->set_commentable(false); $ob_hash = $this->observer ? $this->observer['xchan_hash'] : ''; if (!comments_are_now_closed($item->get_data())) { if ($item->get_data_value('author_xchan') === $ob_hash || $item->get_data_value('owner_xchan') === $ob_hash) { $item->set_commentable(true); } if (intval($item->get_data_value('item_nocomment'))) { $item->set_commentable(false); } elseif ($this->observer && !$item->is_commentable()) { if (array_key_exists('owner', $item->data) && intval($item->data['owner']['abook_self'])) { $item->set_commentable(perm_is_allowed($this->profile_owner, $this->observer['xchan_hash'], 'post_comments')); } else { $item->set_commentable(can_comment_on_post($this->observer['xchan_hash'], $item->data)); } } } require_once 'include/identity.php'; $item->set_conversation($this); $this->threads[] = $item; return end($this->threads); }