diff options
Diffstat (limited to 'Zotlabs')
-rw-r--r-- | Zotlabs/Lib/ThreadItem.php | 18 | ||||
-rw-r--r-- | Zotlabs/Module/Channel.php | 7 | ||||
-rw-r--r-- | Zotlabs/Module/Pin.php | 68 | ||||
-rw-r--r-- | Zotlabs/Widget/Pinned.php | 279 |
4 files changed, 366 insertions, 6 deletions
diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php index 32cd52751..301ce1a18 100644 --- a/Zotlabs/Lib/ThreadItem.php +++ b/Zotlabs/Lib/ThreadItem.php @@ -95,7 +95,7 @@ class ThreadItem { $total_children = $this->count_descendants(); $unseen_comments = (($item['real_uid']) ? 0 : $this->count_unseen_descendants()); - $conv = $this->get_conversation(); + $conv = $this->get_conversation(); $observer = $conv->get_observer(); $lock = (((intval($item['item_private'])) || (($item['uid'] == local_channel()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) @@ -356,7 +356,8 @@ class ThreadItem { call_hooks('dropdown_extras',$dropdown_extras_arr); $dropdown_extras = $dropdown_extras_arr['dropdown_extras']; - $mids = ['b64.' . base64url_encode($item['mid'])]; + $midb64 = 'b64.' . base64url_encode($item['mid']); + $mids = [ $midb64 ]; $response_mids = []; foreach($response_verbs as $v) { if(isset($conv_responses[$v]['mids'][$item['mid']])) { @@ -367,6 +368,11 @@ class ThreadItem { $mids = array_merge($mids, $response_mids); $json_mids = json_encode($mids); + // Pinned item processing + $allowed_type = (in_array($item['item_type'], get_config('system', 'pin_types', [ ITEM_TYPE_POST ])) ? true : false); + $pinned_items = ($allowed_type ? get_pconfig($item['uid'], 'pinned', $item['item_type'], []) : []); + $pinned = ((!empty($pinned_items) && in_array($midb64, $pinned_items)) ? true : false); + $tmp_item = array( 'template' => $this->get_template(), 'mode' => $mode, @@ -380,7 +386,7 @@ class ThreadItem { 'folders' => $body['folders'], 'text' => strip_tags($body['html']), 'id' => $this->get_id(), - 'mid' => 'b64.' . base64url_encode($item['mid']), + 'mid' => $midb64, 'mids' => $json_mids, 'parent' => $item['parent'], 'author_id' => (($item['author']['xchan_addr']) ? $item['author']['xchan_addr'] : $item['author']['xchan_url']), @@ -449,6 +455,9 @@ class ThreadItem { 'star' => ((feature_enabled($conv->get_profile_owner(),'star_posts') && ($item['item_type'] == ITEM_TYPE_POST)) ? $star : ''), 'tagger' => ((feature_enabled($conv->get_profile_owner(),'commtag')) ? $tagger : ''), 'filer' => ((feature_enabled($conv->get_profile_owner(),'filing') && ($item['item_type'] == ITEM_TYPE_POST)) ? $filer : ''), + 'pinned' => ($pinned ? t('Pinned post') : ''), + 'pinnable' => (($this->is_toplevel() && local_channel() && $item['owner_xchan'] == $observer['xchan_hash'] && $allowed_type && $item['item_private'] == 0) ? '1' : ''), + 'pinme' => ($pinned ? t('Unpin from the top') : t('Pin to the top')), 'bookmark' => (($conv->get_profile_owner() == local_channel() && local_channel() && $has_bookmarks) ? t('Save Bookmarks') : ''), 'addtocal' => (($has_event) ? t('Add to Calendar') : ''), 'drop' => $drop, @@ -874,7 +883,4 @@ class ThreadItem { return $this->visiting; } - - - } diff --git a/Zotlabs/Module/Channel.php b/Zotlabs/Module/Channel.php index d975ac1bf..20a5418c2 100644 --- a/Zotlabs/Module/Channel.php +++ b/Zotlabs/Module/Channel.php @@ -468,6 +468,13 @@ class Channel extends Controller { ); } } + + // Add pinned content + if(! $decoded && ! $search) { + $pinned = new \Zotlabs\Widget\Pinned; + $r = $pinned->widget(intval(App::$profile['profile_uid']), [ITEM_TYPE_POST]); + $o .= $r['html']; + } $mode = (($search) ? 'search' : 'channel'); diff --git a/Zotlabs/Module/Pin.php b/Zotlabs/Module/Pin.php new file mode 100644 index 000000000..74297aa7c --- /dev/null +++ b/Zotlabs/Module/Pin.php @@ -0,0 +1,68 @@ +<?php +namespace Zotlabs\Module; + +/* + * Pinned post processing + */ + +use App; + +class Pin extends \Zotlabs\Web\Controller { + + + function init() { + + if(argc() !== 2) + http_status_exit(400, 'Bad request'); + } + + + function post() { + + $item_id = intval($_POST['id']); + + if ($item_id <= 0) + http_status_exit(404, 'Not found'); + + $observer = \App::get_observer(); + if(! $observer) + http_status_exit(403, 'Forbidden'); + + $r = q("SELECT * FROM item WHERE id = %d AND id = parent AND item_private = 0 LIMIT 1", + $item_id + ); + if(! $r) { + notice(t('Unable to locate original post.')); + http_status_exit(404, 'Not found'); + } + + $midb64 = 'b64.' . base64url_encode($r[0]['mid']); + $pinned = (in_array($midb64, get_pconfig($r[0]['uid'], 'pinned', $r[0]['item_type'], [])) ? true : false); + + switch(argv(1)) { + + case 'pin': + if(! local_channel() || local_channel() != $r[0]['uid']) + http_status_exit(403, 'Forbidden'); + // Currently allow only one pinned item for each type + set_pconfig($r[0]['uid'], 'pinned', $r[0]['item_type'], ($pinned ? [] : [ $midb64 ])); + if($pinned) + del_pconfig($r[0]['uid'], 'pinned_hide', $midb64); + break; + + case 'hide': + if($pinned) { + $hidden = get_pconfig($r[0]['uid'], 'pinned_hide', $midb64, []); + if(! in_array($observer['xchan_hash'], $hidden)) { + $hidden[] = $observer['xchan_hash']; + set_pconfig($r[0]['uid'], 'pinned_hide', $midb64, $hidden); + } + } + + default: + http_status_exit(404, 'Not found'); + } + + build_sync_packet($r[0]['uid'], [ 'config' ]); + } +} diff --git a/Zotlabs/Widget/Pinned.php b/Zotlabs/Widget/Pinned.php new file mode 100644 index 000000000..0ef724102 --- /dev/null +++ b/Zotlabs/Widget/Pinned.php @@ -0,0 +1,279 @@ +<?php +namespace Zotlabs\Widget; + +/* + * Show pinned content + * + */ + +class Pinned { + + private $allowed_types = 0; + private $uid = 0; + + + /* + * @brief Displays pinned items + * + * @param $uid + * @param $types + * @return array of results: 'html' string, 'ids' array + * + */ + function widget($uid, $types) { + + $ret = [ 'html' => EMPTY_STR, 'ids' => [] ]; + + $this->uid = intval($uid); + if(! $this->uid) + return $ret; + + $this->allowed_types = get_config('system', 'pin_types', [ ITEM_TYPE_POST ]); + + $items = $this->list($types); + + if(empty($items)) + return $ret; + + $ret['ids'] = array_column($items, 'id'); + + $observer = \App::get_observer(); + + foreach($items as $item) { + + $midb64 = 'b64.' . base64url_encode($item['mid']); + + if(in_array($observer['xchan_hash'], get_pconfig($item['uid'], 'pinned_hide', $midb64, []))) + continue; + + $author = channelx_by_hash($item['author_xchan']); + $owner = channelx_by_hash($item['owner_xchan']); + + $profile_avatar = $author['xchan_photo_m']; + $profile_link = chanlink_hash($item['author_xchan']); + $profile_name = $author['xchan_name']; + + $commentable = ($item['item_nocomment'] == 0 && $item['comments_closed'] == NULL_DATE ? true : false); + + $location = format_location($item); + $isevent = false; + $attend = null; + $canvote = false; + + $conv_responses = []; + + if($item['obj_type'] === ACTIVITY_OBJ_EVENT) { + $conv_responses['attendyes'] = [ 'title' => t('Attending','title') ]; + $conv_responses['attendno'] = [ 'title' => t('Not attending','title') ]; + $conv_responses['attendmaybe'] = [ 'title' => t('Might attend','title') ]; + if($commentable && $observer) { + $attend = array( t('I will attend'), t('I will not attend'), t('I might attend')); + $isevent = true; + } + } + + $consensus = (intval($item['item_consensus']) ? true : false); + if($consensus) { + $conv_responses['agree'] = [ 'title' => t('Agree','title') ]; + $conv_responses['disagree'] = [ 'title' => t('Disagree','title') ]; + $conv_responses['abstain'] = [ 'title' => t('Abstain','title') ]; + if($commentable && $observer) { + $conlabels = array( t('I agree'), t('I disagree'), t('I abstain')); + $canvote = true; + } + } + + $this->activity($item, $conv_responses); + + $verified = (intval($item['item_verified']) ? t('Message signature validated') : ''); + $forged = ((! intval($item['item_verified']) && $item['sig']) ? t('Message signature incorrect') : ''); + + $shareable = ((local_channel() && \App::$profile_uid == local_channel() && $item['item_private'] != 1) ? true : false); + if ($shareable) { + // This actually turns out not to be possible in some protocol stacks without opening up hundreds of new issues. + // Will allow it only for uri resolvable sources. + if(strpos($item['mid'],'http') === 0) { + $share = []; //Not yet ready for primetime + //$share = array( t('Repeat This'), t('repeat')); + } + $embed = array( t('Share This'), t('share')); + } + + if(strcmp(datetime_convert('UTC','UTC',$item['created']),datetime_convert('UTC','UTC','now - 12 hours')) > 0) + $is_new = true; + + $body = prepare_body($item,true); + + $str = [ + 'item_type' => intval($item['item_type']), + 'body' => $body['html'], + 'tags' => $body['tags'], + 'categories' => $body['categories'], + 'mentions' => $body['mentions'], + 'attachments' => $body['attachments'], + 'folders' => $body['folders'], + 'text' => strip_tags($body['html']), + 'id' => $item['id'], + 'mids' => json_encode([ $midb64 ]), + 'isevent' => $isevent, + 'attend' => $attend, + 'consensus' => $consensus, + 'conlabels' => $conlabels, + 'canvote' => $canvote, + 'linktitle' => sprintf( t('View %s\'s profile - %s'), $profile_name, ($author['xchan_addr'] ? $author['xchan_addr'] : $author['xchan_url']) ), + 'olinktitle' => sprintf( t('View %s\'s profile - %s'), $owner['xchan_name'], ($owner['xchan_addr'] ? $owner['xchan_addr'] : $owner['xchan_url']) ), + 'profile_url' => $profile_link, + 'name' => $profile_name, + 'thumb' => $profile_avatar, + 'via' => t('via'), + 'title' => $item['title'], + 'title_tosource' => get_pconfig($item['uid'],'system','title_tosource'), + 'ago' => relative_date($item['created']), + 'app' => $item['app'], + 'str_app' => sprintf( t('from %s'), $item['app'] ), + 'isotime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'c'), + 'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'r'), + 'editedtime' => (($item['edited'] != $item['created']) ? sprintf( t('last edited: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['edited'], 'r') ) : ''), + 'expiretime' => ($item['expires'] > NULL_DATE ? sprintf( t('Expires: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['expires'], 'r') ) : ''), + 'lock' => $lock, + 'verified' => $verified, + 'forged' => $forged, + 'location' => $location, + 'divider' => get_pconfig($item['uid'],'system','item_divider'), + 'attend_title' => t('Attendance Options'), + 'vote_title' => t('Voting Options'), + 'is_new' => $is_new, + 'owner_url' => ($owner['xchan_addr'] != $author['xchan_addr'] ? chanlink_hash($owner['xchan_hash']) : ''), + 'owner_photo'=> $owner['xchan_photo_m'], + 'owner_name' => $owner['xchan_name'], + 'photo' => $body['photo'], + 'event' => $body['event'], + 'has_tags' => (($body['tags'] || $body['categories'] || $body['mentions'] || $body['attachments'] || $body['folders']) ? true : false), + // Item toolbar buttons + 'share' => $share, + 'embed' => $embed, + 'plink' => get_plink($item), + 'pinned' => t('Pinned post'), + 'hide' => (! $is_new && $observer && ($observer['xchan_hash'] != $owner['xchan_hash']) ? t("Don't show") : ''), + // end toolbar buttons + 'modal_dismiss' => t('Close'), + 'responses' => $conv_responses + ]; + + $tpl = get_markup_template('pinned_item.tpl'); + $ret['html'] .= replace_macros($tpl, $str); + } + + return $ret; + } + + + /* + * @brief List pinned items depend on type + * + * @param $types + * @return array of pinned items + * + */ + private function list($types) { + + if(empty($types) || (! is_array($types))) + return []; + + $item_types = array_intersect($this->allowed_types, $types); + if(empty($item_types)) + return []; + + $mids_list = []; + + foreach($item_types as $type) { + + $mids = get_pconfig($this->uid, 'pinned', $type, []); + foreach($mids as $mid) { + if(! empty($mid) && strpos($mid,'b64.') === 0) + $mids_list[] = @base64url_decode(substr($mid,4)); + } + } + if(empty($mids_list)) + return []; + + $r = q("SELECT * FROM item WHERE mid IN ( '%s' ) AND uid = %d AND id = parent AND item_private = 0 ORDER BY created DESC", + dbesc(implode(",", $mids_list)), + intval($this->uid) + ); + if($r) + return $r; + + return []; + } + + + /* + * @brief List activities on item + * + * @param array $item + * @param array $conv_responses + * @return array + * + */ + private function activity($item, &$conv_responses) { + + foreach(array_keys($conv_responses) as $verb) { + + switch($verb) { + case 'like': + $v = ACTIVITY_LIKE; + break; + case 'dislike': + $v = ACTIVITY_DISLIKE; + break; + case 'agree': + $v = ACTIVITY_AGREE; + break; + case 'disagree': + $v = ACTIVITY_DISAGREE; + break; + case 'abstain': + $v = ACTIVITY_ABSTAIN; + break; + case 'attendyes': + $v = ACTIVITY_ATTEND; + break; + case 'attendno': + $v = ACTIVITY_ATTENDNO; + break; + case 'attendmaybe': + $v = ACTIVITY_ATTENDMAYBE; + break; + default: + break; + } + + $r = q("SELECT * FROM item WHERE parent = %d AND id <> parent AND verb = '%s' AND item_deleted = 0", + intval($item['id']), + dbesc($v) + ); + if(! $r) { + unset($conv_responses[$verb]); + continue; + } + + $conv_responses[$verb]['count'] = count($r); + $conv_responses[$verb]['button'] = get_response_button_text($verb, $conv_responses[$verb]['count']); + + foreach($r as $rr) { + + $author = q("SELECT * FROM xchan WHERE xchan_hash = '%s' LIMIT 1", + dbesc($rr['author_xchan']) + ); + $name = ($author[0]['xchan_name'] ? $author[0]['xchan_name'] : t('Unknown')); + $conv_responses[$verb]['list'][] = (($rr['author_xchan'] && $author[0]['xchan_photo_s']) ? + '<a class="dropdown-item" href="' . chanlink_hash($rr['author_xchan']) . '">' . '<img class="menu-img-1" src="' . zid($author[0]['xchan_photo_s']) . '" alt="' . urlencode($name) . '" /> ' . $name . '</a>' : + '<a class="dropdown-item" href="#" class="disabled">' . $name . '</a>' + ); + } + } + + $conv_responses['count'] = count($conv_responses); + } +} |