aboutsummaryrefslogtreecommitdiffstats
path: root/Zotlabs
diff options
context:
space:
mode:
Diffstat (limited to 'Zotlabs')
-rw-r--r--Zotlabs/Lib/ThreadItem.php18
-rw-r--r--Zotlabs/Module/Channel.php7
-rw-r--r--Zotlabs/Module/Pin.php68
-rw-r--r--Zotlabs/Widget/Pinned.php279
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);
+ }
+}