diff options
-rw-r--r-- | Zotlabs/Lib/ThreadItem.php | 15 | ||||
-rw-r--r-- | Zotlabs/Lib/ThreadStream.php | 5 | ||||
-rw-r--r-- | Zotlabs/Module/Article_edit.php | 138 | ||||
-rw-r--r-- | Zotlabs/Module/Articles.php | 187 | ||||
-rw-r--r-- | Zotlabs/Module/Item.php | 15 | ||||
-rw-r--r-- | Zotlabs/Module/Oep.php | 85 | ||||
-rw-r--r-- | Zotlabs/Module/Update_articles.php | 39 | ||||
-rw-r--r-- | Zotlabs/Widget/Categories.php | 10 | ||||
-rw-r--r-- | app/articles.apd | 6 | ||||
-rwxr-xr-x | boot.php | 2 | ||||
-rw-r--r-- | include/attach.php | 2 | ||||
-rw-r--r-- | include/bbcode.php | 2 | ||||
-rw-r--r-- | include/contact_widgets.php | 48 | ||||
-rw-r--r-- | include/conversation.php | 39 | ||||
-rw-r--r-- | include/features.php | 10 | ||||
-rwxr-xr-x | include/items.php | 2 | ||||
-rw-r--r-- | include/nav.php | 11 | ||||
-rw-r--r-- | include/taxonomy.php | 71 | ||||
-rw-r--r-- | view/css/conversation.css | 4 | ||||
-rw-r--r-- | view/js/main.js | 3 | ||||
-rw-r--r-- | view/js/mod_articles.js | 9 | ||||
-rw-r--r-- | view/pdl/mod_articles.pdl | 8 | ||||
-rwxr-xr-x | view/tpl/conv_item.tpl | 4 | ||||
-rwxr-xr-x | view/tpl/search_item.tpl | 3 |
24 files changed, 699 insertions, 19 deletions
diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php index 67a507025..197657ab1 100644 --- a/Zotlabs/Lib/ThreadItem.php +++ b/Zotlabs/Lib/ThreadItem.php @@ -105,7 +105,17 @@ class ThreadItem { $mode = $conv->get_mode(); - $edlink = (($item['item_type'] == ITEM_TYPE_CARD) ? 'card_edit' : 'editpost'); + switch($item['item_type']) { + case ITEM_TYPE_CARD: + $edlink = 'card_edit'; + break; + case ITEM_TYPE_ARTICLE: + $edlink = 'article_edit'; + break; + default: + $edlink = 'editpost'; + break; + } if(local_channel() && $observer['xchan_hash'] === $item['author_xchan']) $edpost = array(z_root() . '/' . $edlink . '/' . $item['id'], t('Edit')); @@ -303,7 +313,7 @@ class ThreadItem { $comment_count_txt = sprintf( tt('%d comment','%d comments',$total_children),$total_children ); $list_unseen_txt = (($unseen_comments) ? sprintf('%d unseen',$unseen_comments) : ''); - + @@ -360,6 +370,7 @@ class ThreadItem { 'unverified' => $unverified, 'forged' => $forged, 'location' => $location, + 'divider' => get_pconfig($conv->get_profile_owner(),'system','item_divider'), 'attend_label' => t('Attend'), 'attend_title' => t('Attendance Options'), 'vote_label' => t('Vote'), diff --git a/Zotlabs/Lib/ThreadStream.php b/Zotlabs/Lib/ThreadStream.php index 436723f8c..9eebb929c 100644 --- a/Zotlabs/Lib/ThreadStream.php +++ b/Zotlabs/Lib/ThreadStream.php @@ -63,6 +63,11 @@ class ThreadStream { $this->writable = perm_is_allowed($this->profile_owner,$ob_hash,'post_comments'); $this->reload = $_SESSION['return_url']; break; + case 'articles': + $this->profile_owner = \App::$profile['profile_uid']; + $this->writable = perm_is_allowed($this->profile_owner,$ob_hash,'post_comments'); + $this->reload = $_SESSION['return_url']; + break; case 'display': // in this mode we set profile_owner after initialisation (from conversation()) and then // pull some trickery which allows us to re-invoke this function afterward diff --git a/Zotlabs/Module/Article_edit.php b/Zotlabs/Module/Article_edit.php new file mode 100644 index 000000000..758c1db2e --- /dev/null +++ b/Zotlabs/Module/Article_edit.php @@ -0,0 +1,138 @@ +<?php +namespace Zotlabs\Module; + +require_once('include/channel.php'); +require_once('include/acl_selectors.php'); +require_once('include/conversation.php'); + +class Article_edit extends \Zotlabs\Web\Controller { + + + function get() { + + // Figure out which post we're editing + $post_id = ((argc() > 1) ? intval(argv(1)) : 0); + + if(! $post_id) { + notice( t('Item not found') . EOL); + return; + } + + $itm = q("SELECT * FROM item WHERE id = %d and item_type = %d LIMIT 1", + intval($post_id), + intval(ITEM_TYPE_ARTICLE) + ); + if($itm) { + $item_id = q("select * from iconfig where cat = 'system' and k = 'ARTICLE' and iid = %d limit 1", + intval($itm[0]['id']) + ); + if($item_id) + $card_title = $item_id[0]['v']; + } + else { + notice( t('Item not found') . EOL); + return; + } + + $owner = $itm[0]['uid']; + $uid = local_channel(); + + $observer = \App::get_observer(); + + $channel = channelx_by_n($owner); + if(! $channel) { + notice( t('Channel not found.') . EOL); + return; + } + + $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + + if(! perm_is_allowed($owner,$ob_hash,'write_pages')) { + notice( t('Permission denied.') . EOL); + return; + } + + $is_owner = (($uid && $uid == $owner) ? true : false); + + $o = ''; + + + + $category = ''; + $catsenabled = ((feature_enabled($owner,'categories')) ? 'categories' : ''); + + if ($catsenabled){ + $itm = fetch_post_tags($itm); + + $cats = get_terms_oftype($itm[0]['term'], TERM_CATEGORY); + + foreach ($cats as $cat) { + if (strlen($category)) + $category .= ', '; + $category .= $cat['term']; + } + } + + if($itm[0]['attach']) { + $j = json_decode($itm[0]['attach'],true); + if($j) { + foreach($j as $jj) { + $itm[0]['body'] .= "\n" . '[attachment]' . basename($jj['href']) . ',' . $jj['revision'] . '[/attachment]' . "\n"; + } + } + } + + + $mimetype = $itm[0]['mimetype']; + + $content = $itm[0]['body']; + + + + $rp = 'articles/' . $channel['channel_address']; + + $x = array( + 'nickname' => $channel['channel_address'], + 'bbco_autocomplete'=> 'bbcode', + 'return_path' => $rp, + 'webpage' => ITEM_TYPE_ARTICLE, + 'button' => t('Edit'), + 'writefiles' => perm_is_allowed($owner, get_observer_hash(), 'write_pages'), + 'weblink' => t('Insert web link'), + 'hide_voting' => false, + 'hide_future' => false, + 'hide_location' => false, + 'hide_expire' => false, + 'showacl' => true, + 'acl' => populate_acl($itm[0],false,\Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_pages')), + 'permissions' => $itm[0], + 'lockstate' => (($itm[0]['allow_cid'] || $itm[0]['allow_gid'] || $itm[0]['deny_cid'] || $itm[0]['deny_gid']) ? 'lock' : 'unlock'), + 'ptyp' => $itm[0]['type'], + 'mimeselect' => false, + 'mimetype' => $itm[0]['mimetype'], + 'body' => undo_post_tagging($content), + 'post_id' => $post_id, + 'visitor' => true, + 'title' => htmlspecialchars($itm[0]['title'],ENT_COMPAT,'UTF-8'), + 'placeholdertitle' => t('Title (optional)'), + 'pagetitle' => $card_title, + 'profile_uid' => (intval($channel['channel_id'])), + 'catsenabled' => $catsenabled, + 'category' => $category, + 'bbcode' => (($mimetype == 'text/bbcode') ? true : false) + ); + + $editor = status_editor($a, $x); + + $o .= replace_macros(get_markup_template('edpost_head.tpl'), array( + '$title' => t('Edit Article'), + '$delete' => ((($itm[0]['author_xchan'] === $ob_hash) || ($itm[0]['owner_xchan'] === $ob_hash)) ? t('Delete') : false), + '$id' => $itm[0]['id'], + '$editor' => $editor + )); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Articles.php b/Zotlabs/Module/Articles.php new file mode 100644 index 000000000..25daca81d --- /dev/null +++ b/Zotlabs/Module/Articles.php @@ -0,0 +1,187 @@ +<?php +namespace Zotlabs\Module; + +require_once('include/channel.php'); +require_once('include/conversation.php'); +require_once('include/acl_selectors.php'); + + +class Articles extends \Zotlabs\Web\Controller { + + function init() { + + if(argc() > 1) + $which = argv(1); + else + return; + + profile_load($which); + + } + + function get($update = 0, $load = false) { + + if(observer_prohibited(true)) { + return login(); + } + + if(! \App::$profile) { + notice( t('Requested profile is not available.') . EOL ); + \App::$error = 404; + return; + } + + if(! feature_enabled(\App::$profile_uid,'articles')) { + return; + } + + nav_set_selected(t('Cards')); + + head_add_link([ + 'rel' => 'alternate', + 'type' => 'application/json+oembed', + 'href' => z_root() . '/oep?f=&url=' . urlencode(z_root() . '/' . \App::$query_string), + 'title' => 'oembed' + ]); + + + $category = (($_REQUEST['cat']) ? escape_tags(trim($_REQUEST['cat'])) : ''); + + if($category) { + $sql_extra2 .= protect_sprintf(term_item_parent_query(\App::$profile['profile_uid'],'item', $category, TERM_CATEGORY)); + } + + + $which = argv(1); + + $selected_card = ((argc() > 2) ? argv(2) : ''); + + $_SESSION['return_url'] = \App::$query_string; + + $uid = local_channel(); + $owner = \App::$profile_uid; + $observer = \App::get_observer(); + + $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + + if(! perm_is_allowed($owner,$ob_hash,'view_pages')) { + notice( t('Permission denied.') . EOL); + return; + } + + $is_owner = ($uid && $uid == $owner); + + $channel = channelx_by_n($owner); + + if($channel) { + $channel_acl = array( + 'allow_cid' => $channel['channel_allow_cid'], + 'allow_gid' => $channel['channel_allow_gid'], + 'deny_cid' => $channel['channel_deny_cid'], + 'deny_gid' => $channel['channel_deny_gid'] + ); + } + else { + $channel_acl = [ 'allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '' ]; + } + + + + if(perm_is_allowed($owner,$ob_hash,'write_pages')) { + + $x = [ + 'webpage' => ITEM_TYPE_ARTICLE, + 'is_owner' => true, + 'content_label' => t('Add Article'), + 'button' => t('Create'), + 'nickname' => $channel['channel_address'], + 'lockstate' => (($channel['channel_allow_cid'] || $channel['channel_allow_gid'] + || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 'lock' : 'unlock'), + 'acl' => (($is_owner) ? populate_acl($channel_acl, false, + \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_pages')) : ''), + 'permissions' => $channel_acl, + 'showacl' => (($is_owner) ? true : false), + 'visitor' => true, + 'hide_location' => false, + 'hide_voting' => false, + 'profile_uid' => intval($owner), + 'mimetype' => 'text/bbcode', + 'mimeselect' => false, + 'layoutselect' => false, + 'expanded' => false, + 'novoting' => false, + 'catsenabled' => feature_enabled($owner,'categories'), + 'bbco_autocomplete' => 'bbcode', + 'bbcode' => true + ]; + + if($_REQUEST['title']) + $x['title'] = $_REQUEST['title']; + if($_REQUEST['body']) + $x['body'] = $_REQUEST['body']; + $editor = status_editor($a,$x); + + } + else { + $editor = ''; + } + + + $sql_extra = item_permissions_sql($owner); + + if($selected_card) { + $r = q("select * from iconfig where iconfig.cat = 'system' and iconfig.k = 'ARTICLE' and iconfig.v = '%s' limit 1", + dbesc($selected_card) + ); + if($r) { + $sql_extra .= "and item.id = " . intval($r[0]['iid']) . " "; + } + } + + $r = q("select * from item + where item.uid = %d and item_type = %d + $sql_extra order by item.created desc", + intval($owner), + intval(ITEM_TYPE_ARTICLE) + ); + + $item_normal = " and item.item_hidden = 0 and item.item_type in (0,7) and item.item_deleted = 0 + and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_pending_remove = 0 + and item.item_blocked = 0 "; + + if($r) { + + $parents_str = ids_to_querystr($r,'id'); + + $items = q("SELECT item.*, item.id AS item_id + FROM item + WHERE item.uid = %d $item_normal + AND item.parent IN ( %s ) + $sql_extra $sql_extra2 ", + intval(\App::$profile['profile_uid']), + dbesc($parents_str) + ); + if($items) { + xchan_query($items); + $items = fetch_post_tags($items, true); + $items = conv_sort($items,'updated'); + } + else + $items = []; + } + + $mode = 'articles'; + + $content = conversation($items,$mode,false,'traditional'); + + $o = replace_macros(get_markup_template('cards.tpl'), [ + '$title' => t('Articles'), + '$editor' => $editor, + '$content' => $content, + '$pager' => alt_pager($a,count($items)) + ]); + + return $o; + } + +} diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php index f2b850ffc..ecbefa1c2 100644 --- a/Zotlabs/Module/Item.php +++ b/Zotlabs/Module/Item.php @@ -629,6 +629,9 @@ class Item extends \Zotlabs\Web\Controller { if($webpage == ITEM_TYPE_CARD) { $catlink = z_root() . '/cards/' . $channel['channel_address'] . '?f=&cat=' . urlencode(trim($cat)); } + elseif($webpage == ITEM_TYPE_ARTICLE) { + $catlink = z_root() . '/articles/' . $channel['channel_address'] . '?f=&cat=' . urlencode(trim($cat)); + } else { $catlink = $owner_xchan['xchan_url'] . '?f=&cat=' . urlencode(trim($cat)); } @@ -733,6 +736,18 @@ class Item extends \Zotlabs\Web\Controller { } } + if($webpage == ITEM_TYPE_ARTICLE) { + $plink = z_root() . '/articles/' . $channel['channel_address'] . '/' . (($pagetitle) ? $pagetitle : substr($mid,0,16)); + } + if(($parent_item) && ($parent_item['item_type'] == ITEM_TYPE_ARTICLE)) { + $r = q("select v from iconfig where iconfig.cat = 'system' and iconfig.k = 'ARTICLE' and iconfig.iid = %d limit 1", + intval($parent_item['id']) + ); + if($r) { + $plink = z_root() . '/articles/' . $channel['channel_address'] . '/' . $r[0]['v']; + } + } + if ((! $plink) && ($item_thread_top)) { $plink = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . $mid; } diff --git a/Zotlabs/Module/Oep.php b/Zotlabs/Module/Oep.php index 5e06d3540..bb3a13b56 100644 --- a/Zotlabs/Module/Oep.php +++ b/Zotlabs/Module/Oep.php @@ -45,6 +45,8 @@ class Oep extends \Zotlabs\Web\Controller { $arr = $this->oep_profile_reply($_REQUEST); elseif(fnmatch('*/cards/*',$url)) $arr = $this->oep_cards_reply($_REQUEST); + elseif(fnmatch('*/articles/*',$url)) + $arr = $this->oep_articles_reply($_REQUEST); if($arr) { if($html) { @@ -232,6 +234,89 @@ class Oep extends \Zotlabs\Web\Controller { } + function oep_articles_reply($args) { + + $ret = []; + $url = $args['url']; + $maxwidth = intval($args['maxwidth']); + $maxheight = intval($args['maxheight']); + + if(preg_match('#//(.*?)/articles/(.*?)/(.*?)(&|\?|$)#',$url,$matches)) { + $nick = $matches[2]; + $res = $matches[3]; + } + if(! ($nick && $res)) + return $ret; + + $channel = channelx_by_nick($nick); + + if(! $channel) + return $ret; + + + if(! perm_is_allowed($channel['channel_id'],get_observer_hash(),'view_pages')) + return $ret; + + $sql_extra = item_permissions_sql($channel['channel_id'],get_observer_hash()); + + $r = q("select * from iconfig where iconfig.cat = 'system' and iconfig.k = 'ARTICLE' and iconfig.v = '%s' limit 1", + dbesc($res) + ); + if($r) { + $sql_extra = "and item.id = " . intval($r[0]['iid']) . " "; + } + else { + return $ret; + } + + $r = q("select * from item + where item.uid = %d and item_type = %d + $sql_extra order by item.created desc", + intval($channel['channel_id']), + intval(ITEM_TYPE_ARTICLE) + ); + + $item_normal = " and item.item_hidden = 0 and item.item_type in (0,7) and item.item_deleted = 0 + and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_pending_remove = 0 + and item.item_blocked = 0 "; + + if($r) { + xchan_query($r); + $p = fetch_post_tags($r, true); + } + + $x = '2eGriplW^*Jmf4'; + + + $o = "[share author='".urlencode($p[0]['author']['xchan_name']). + "' profile='".$p[0]['author']['xchan_url'] . + "' avatar='".$p[0]['author']['xchan_photo_s']. + "' link='".$p[0]['plink']. + "' posted='".$p[0]['created']. + "' message_id='".$p[0]['mid']."']"; + if($p[0]['title']) + $o .= '[b]'.$p[0]['title'].'[/b]'."\r\n"; + + $o .= $x; + $o .= "[/share]"; + $o = bbcode($o); + + $o = str_replace($x,bbcode($p[0]['body']),$o); + + $ret['type'] = 'rich'; + + $w = (($maxwidth) ? $maxwidth : 640); + $h = (($maxheight) ? $maxheight : intval($w * 2 / 3)); + + $ret['html'] = '<div style="width: ' . $w . '; height: ' . $h . '; font-family: sans-serif,arial,freesans;" >' . $o . '</div>'; + + $ret['width'] = $w; + $ret['height'] = $h; + + return $ret; + + } + function oep_mid_reply($args) { diff --git a/Zotlabs/Module/Update_articles.php b/Zotlabs/Module/Update_articles.php new file mode 100644 index 000000000..280904f67 --- /dev/null +++ b/Zotlabs/Module/Update_articles.php @@ -0,0 +1,39 @@ +<?php + +namespace Zotlabs\Module; + +/** + * Module: update_profile + * Purpose: AJAX synchronisation of profile page + * + */ + + +class Update_articles extends \Zotlabs\Web\Controller { + +function get() { + + $profile_uid = intval($_GET['p']); + $load = (((argc() > 1) && (argv(1) == 'load')) ? 1 : 0); + + header("Content-type: text/html"); + echo "<!DOCTYPE html><html><body><section></section></body></html>\r\n"; + + killme(); + + + $mod = new Articles(); + + $text = $mod->get($profile_uid,$load); + + /** + * reportedly some versions of MSIE don't handle tabs in XMLHttpRequest documents very well + */ + + echo str_replace("\t",' ',$text); + echo (($_GET['msie'] == 1) ? '</div>' : '</section>'); + echo "</body></html>\r\n"; + killme(); + +} +} diff --git a/Zotlabs/Widget/Categories.php b/Zotlabs/Widget/Categories.php index 305869706..9bfa9742a 100644 --- a/Zotlabs/Widget/Categories.php +++ b/Zotlabs/Widget/Categories.php @@ -13,8 +13,14 @@ class Categories { if(($cards) && (! feature_enabled(\App::$profile['profile_uid'],'cards'))) return ''; + $articles = ((array_key_exists('articles',$arr) && $arr['articles']) ? true : false); + + if(($articles) && (! feature_enabled(\App::$profile['profile_uid'],'articles'))) + return ''; + + if((! \App::$profile['profile_uid']) - || (! perm_is_allowed(\App::$profile['profile_uid'],get_observer_hash(),(($cards) ? 'view_pages' : 'view_stream')))) { + || (! perm_is_allowed(\App::$profile['profile_uid'],get_observer_hash(),(($cards || $articles) ? 'view_pages' : 'view_stream')))) { return ''; } @@ -25,6 +31,8 @@ class Categories { if($cards) return cardcategories_widget($srchurl, $cat); + elseif($articles) + return articlecategories_widget($srchurl, $cat); else return categories_widget($srchurl, $cat); diff --git a/app/articles.apd b/app/articles.apd new file mode 100644 index 000000000..eec9cda0a --- /dev/null +++ b/app/articles.apd @@ -0,0 +1,6 @@ +version: 1.1 +url: $baseurl/articles/$nick +name: Articles +requires: local_channel, articles +photo: icon:file-text-o +categories: Productivity @@ -560,6 +560,7 @@ define ( 'ITEM_BUG', 0x0400); // Is a bug, can be used by the internal bug tr define ( 'ITEM_PENDING_REMOVE', 0x0800); // deleted, notification period has lapsed define ( 'ITEM_DOC', 0x1000); // hubzilla only, define here so that item import does the right thing define ( 'ITEM_CARD', 0x2000); +define ( 'ITEM_ARTICLE', 0x4000); define ( 'ITEM_TYPE_POST', 0 ); @@ -569,6 +570,7 @@ define ( 'ITEM_TYPE_WEBPAGE', 3 ); define ( 'ITEM_TYPE_BUG', 4 ); define ( 'ITEM_TYPE_DOC', 5 ); define ( 'ITEM_TYPE_CARD', 6 ); +define ( 'ITEM_TYPE_ARTICLE', 7 ); define ( 'ITEM_IS_STICKY', 1000 ); diff --git a/include/attach.php b/include/attach.php index 7988286c9..5b4c24f6f 100644 --- a/include/attach.php +++ b/include/attach.php @@ -1639,7 +1639,7 @@ function find_filename_by_hash($channel_id, $attachHash) { * @param int $bufsize size of chunk, default 16384 * @return number with the size */ -function pipe_streams($in, $out, $bufize = 16384) { +function pipe_streams($in, $out, $bufsize = 16384) { $size = 0; while (!feof($in)) $size += fwrite($out, fread($in, $bufsize)); diff --git a/include/bbcode.php b/include/bbcode.php index 050ab2d29..7ebc8d37c 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -329,6 +329,8 @@ function bb_ShareAttributes($match) { if(strpos($link,'/cards/')) $type = t('card'); + elseif(strpos($link,'/articles/')) + $type = t('article'); else $type = t('post'); diff --git a/include/contact_widgets.php b/include/contact_widgets.php index 9cc9f0baf..b36471924 100644 --- a/include/contact_widgets.php +++ b/include/contact_widgets.php @@ -150,6 +150,54 @@ function cardcategories_widget($baseurl,$selected = '') { } +function articlecategories_widget($baseurl,$selected = '') { + + if(! feature_enabled(App::$profile['profile_uid'],'categories')) + return ''; + + $sql_extra = item_permissions_sql(App::$profile['profile_uid']); + + $item_normal = "and item.item_hidden = 0 and item.item_type = 7 and item.item_deleted = 0 + and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_pending_remove = 0 + and item.item_blocked = 0 "; + + $terms = array(); + $r = q("select distinct(term.term) + from term join item on term.oid = item.id + where item.uid = %d + and term.uid = item.uid + and term.ttype = %d + and term.otype = %d + and item.owner_xchan = '%s' + $item_normal + $sql_extra + order by term.term asc", + intval(App::$profile['profile_uid']), + intval(TERM_CATEGORY), + intval(TERM_OBJ_POST), + dbesc(App::$profile['channel_hash']) + ); + if($r && count($r)) { + foreach($r as $rr) + $terms[] = array('name' => $rr['term'], 'selected' => (($selected == $rr['term']) ? 'selected' : '')); + + return replace_macros(get_markup_template('categories_widget.tpl'),array( + '$title' => t('Categories'), + '$desc' => '', + '$sel_all' => (($selected == '') ? 'selected' : ''), + '$all' => t('Everything'), + '$terms' => $terms, + '$base' => $baseurl, + + )); + } + return ''; +} + + + + + function common_friends_visitor_widget($profile_uid,$cnt = 25) { diff --git a/include/conversation.php b/include/conversation.php index 1cbd9116c..63d503177 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -539,6 +539,15 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa $jsreload = $_SESSION['return_url']; } + elseif ($mode === 'articles') { + $profile_owner = App::$profile['profile_uid']; + $page_writeable = ($profile_owner == local_channel()); + $live_update_div = '<div id="live-articles"></div>' . "\r\n" + . "<script> var profile_uid = " . App::$profile['profile_uid'] + . "; var netargs = '?f='; var profile_page = " . App::$pager['page'] . "; </script>\r\n"; + $jsreload = $_SESSION['return_url']; + } + elseif ($mode === 'display') { $profile_owner = local_channel(); @@ -619,13 +628,14 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa // "New Item View" on network page or search page results // - just loop through the items and format them minimally for display - - //$tpl = get_markup_template('search_item.tpl'); $tpl = 'search_item.tpl'; foreach($items as $item) { - $x = [ 'mode' => $mode, 'item' => $item ]; + $x = [ + 'mode' => $mode, + 'item' => $item + ]; call_hooks('stream_item',$x); if($x['item']['blocked']) @@ -646,14 +656,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa if(((activity_match($item['verb'],ACTIVITY_LIKE)) || (activity_match($item['verb'],ACTIVITY_DISLIKE))) && ($item['id'] != $item['parent'])) continue; -// $nickname = $item['nickname']; } -// else -// $nickname = App::$user['nickname']; - -// $profile_name = ((strlen($item['author-name'])) ? $item['author-name'] : $item['name']); -// if($item['author-link'] && (! $item['author-name'])) -// $profile_name = $item['author-link']; $sp = false; $profile_link = best_link_url($item,$sp); @@ -662,8 +665,6 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa else $profile_link = zid($profile_link); -// $normalised = normalise_link((strlen($item['author-link'])) ? $item['author-link'] : $item['url']); - $profile_name = $item['author']['xchan_name']; $profile_link = $item['author']['xchan_url']; $profile_avatar = $item['author']['xchan_photo_m']; @@ -714,7 +715,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa $conv_link_mid = (($mode == 'moderate') ? $item['parent_mid'] : $item['mid']); - $conv_link = (($item['item_type'] == ITEM_TYPE_CARD) ? $item['plink'] : z_root() . '/display/' . gen_link_id($conv_link_mid)); + $conv_link = ((in_array($item['item_type'],[ ITEM_TYPE_CARD, ITEM_TYPE_ARTICLE] )) ? $item['plink'] : z_root() . '/display/' . gen_link_id($conv_link_mid)); $tmp_item = array( @@ -759,6 +760,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa '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')):''), 'location' => $location, + 'divider' => false, 'indent' => '', 'owner_name' => $owner_name, 'owner_url' => $owner_url, @@ -1887,6 +1889,17 @@ function profile_tabs($a, $is_owner = false, $nickname = null){ 'icon' => 'list' ); } + + if(feature_enabled($uid,'articles')) { + $tabs[] = array( + 'label' => t('articles'), + 'url' => z_root() . '/articles/' . $nickname, + 'sel' => ((argv(0) == 'articles') ? 'active' : ''), + 'title' => t('View Articles'), + 'id' => 'articles-tab', + 'icon' => 'file-text-o' + ); + } if($has_webpages && feature_enabled($uid,'webpages')) { $tabs[] = array( diff --git a/include/features.php b/include/features.php index d8d98dbaa..8601ff79e 100644 --- a/include/features.php +++ b/include/features.php @@ -126,6 +126,16 @@ function get_features($filtered = true) { feature_level('cards',1), ], +/* reserved, work in progress + [ + 'articles', + t('Articles'), + t('Create interactive articles'), + false, + get_config('feature_lock','articles'), + feature_level('articles',1), + ], +*/ [ 'nav_channel_select', t('Navigation Channel Select'), diff --git a/include/items.php b/include/items.php index b1b40e977..654cc5de3 100755 --- a/include/items.php +++ b/include/items.php @@ -4309,6 +4309,8 @@ function webpage_to_namespace($webpage) { $page_type = 'PDL'; elseif($webpage == ITEM_TYPE_CARD) $page_type = 'CARD'; + elseif($webpage == ITEM_TYPE_ARTICLE) + $page_type = 'ARTICLE'; elseif($webpage == ITEM_TYPE_DOC) $page_type = 'docfile'; else diff --git a/include/nav.php b/include/nav.php index 2dcf68bc8..8ab401c76 100644 --- a/include/nav.php +++ b/include/nav.php @@ -491,6 +491,17 @@ function channel_apps($is_owner = false, $nickname = null) { ]; } + if($p['view_pages'] && feature_enabled($uid,'articles')) { + $tabs[] = [ + 'label' => t('Articles'), + 'url' => z_root() . '/articles/' . $nickname , + 'sel' => ((argv(0) == 'articles') ? 'active' : ''), + 'title' => t('View Articles'), + 'id' => 'articles-tab', + 'icon' => 'file-text-o' + ]; + } + if($has_webpages && feature_enabled($uid,'webpages')) { $tabs[] = [ diff --git a/include/taxonomy.php b/include/taxonomy.php index 23acaa24d..fbd2a18db 100644 --- a/include/taxonomy.php +++ b/include/taxonomy.php @@ -253,6 +253,56 @@ function card_tagadelic($uid, $count = 0, $authors = '', $owner = '', $flags = 0 } +function article_tagadelic($uid, $count = 0, $authors = '', $owner = '', $flags = 0, $restrict = 0, $type = TERM_CATEGORY) { + + require_once('include/security.php'); + + if(! perm_is_allowed($uid,get_observer_hash(),'view_pages')) + return array(); + + + $item_normal = item_normal(); + $sql_options = item_permissions_sql($uid); + $count = intval($count); + + if($flags) { + if($flags === 'wall') + $sql_options .= " and item_wall = 1 "; + } + + if($authors) { + if(! is_array($authors)) + $authors = array($authors); + + stringify_array_elms($authors,true); + $sql_options .= " and author_xchan in (" . implode(',',$authors) . ") "; + } + + if($owner) { + $sql_options .= " and owner_xchan = '" . dbesc($owner) . "' "; + } + + + // Fetch tags + $r = q("select term, count(term) as total from term left join item on term.oid = item.id + where term.uid = %d and term.ttype = %d + and otype = %d and item_type = %d and item_private = 0 + $sql_options $item_normal + group by term order by total desc %s", + intval($uid), + intval($type), + intval(TERM_OBJ_POST), + intval($restrict), + ((intval($count)) ? "limit $count" : '') + ); + + if(! $r) + return array(); + + return Zotlabs\Text\Tagadelic::calc($r); + +} + @@ -395,6 +445,27 @@ function card_catblock($uid,$count = 0,$authors = '',$owner = '', $flags = 0,$re } +function article_catblock($uid,$count = 0,$authors = '',$owner = '', $flags = 0,$restrict = 0,$type = TERM_CATEGORY) { + $o = ''; + + $r = article_tagadelic($uid,$count,$authors,$owner,$flags,$restrict,$type); + + if($r) { + $c = q("select channel_address from channel where channel_id = %d limit 1", + intval($uid) + ); + + $o = '<div class="tagblock widget"><h3>' . t('Categories') . '</h3><div class="tags" align="center">'; + foreach($r as $rr) { + $o .= '<a href="articles/' . $c[0]['channel_address']. '?f=&cat=' . urlencode($rr[0]).'" class="tag'.$rr[2].'">'.$rr[0].'</a> ' . "\r\n"; + } + $o .= '</div></div>'; + } + + return $o; +} + + function dir_tagblock($link,$r) { $o = ''; diff --git a/view/css/conversation.css b/view/css/conversation.css index d2736acaa..77a600deb 100644 --- a/view/css/conversation.css +++ b/view/css/conversation.css @@ -87,6 +87,10 @@ margin-left:10px; } +.wall-item-divider { + margin: 5px; +} + .wall-item-lock { float: left; } diff --git a/view/js/main.js b/view/js/main.js index ddd876689..c39d6c84c 100644 --- a/view/js/main.js +++ b/view/js/main.js @@ -439,8 +439,9 @@ function NavUpdate() { if($('#live-display').length) { src = 'display'; liveUpdate(); } if($('#live-search').length) { src = 'search'; liveUpdate(); } // if($('#live-cards').length) { src = 'cards'; liveUpdate(); } + // if($('#live-articles').length) { src = 'articles'; liveUpdate(); } - if($('#live-photos').length || $('#live-cards').length) { + if($('#live-photos').length || $('#live-cards').length || $('#live-articles').length ) { if(liking) { liking = 0; window.location.href=window.location.href; diff --git a/view/js/mod_articles.js b/view/js/mod_articles.js new file mode 100644 index 000000000..8b31c0f52 --- /dev/null +++ b/view/js/mod_articles.js @@ -0,0 +1,9 @@ +$(document).ready( function() { + $(".autotime").timeago(); + + /* autocomplete @nicknames */ + $(".comment-edit-form textarea").editor_autocomplete(baseurl+"/acl?f=&n=1"); + /* autocomplete bbcode */ + $(".comment-edit-form textarea").bbco_autocomplete('bbcode'); + +});
\ No newline at end of file diff --git a/view/pdl/mod_articles.pdl b/view/pdl/mod_articles.pdl new file mode 100644 index 000000000..c7599a008 --- /dev/null +++ b/view/pdl/mod_articles.pdl @@ -0,0 +1,8 @@ +[region=aside] +[widget=categories][var=articles]1[/var][/widget] +[widget=tasklist][/widget] +[widget=notes][/widget] +[/region] +[region=right_aside] +[widget=notifications][/widget] +[/region] diff --git a/view/tpl/conv_item.tpl b/view/tpl/conv_item.tpl index b0e1cb6fb..e9f507f1e 100755 --- a/view/tpl/conv_item.tpl +++ b/view/tpl/conv_item.tpl @@ -46,7 +46,9 @@ {{if $item.verified}}<i class="fa fa-check item-verified" title="{{$item.verified}}"></i> {{elseif $item.forged}}<i class="fa fa-exclamation item-forged" title="{{$item.forged}}"></i> {{/if}}{{if $item.location}}<span class="wall-item-location p-location" id="wall-item-location-{{$item.id}}">{{$item.location}}, </span>{{/if}}<span class="autotime" title="{{$item.isotime}}"><time class="dt-published" datetime="{{$item.isotime}}">{{$item.localtime}}</time>{{if $item.editedtime}} {{$item.editedtime}}{{/if}}{{if $item.expiretime}} {{$item.expiretime}}{{/if}}</span>{{if $item.editedtime}} <i class="fa fa-pencil"></i>{{/if}} {{if $item.app}}<span class="item.app">{{$item.str_app}}</span>{{/if}} </div> </div> - + {{if $item.divider}} + <hr class="wall-item-divider"> + {{/if}} {{if $item.body}} <div class="p-2 wall-item-content clearfix" id="wall-item-content-{{$item.id}}"> <div class="wall-item-body e-content" id="wall-item-body-{{$item.id}}" > diff --git a/view/tpl/search_item.tpl b/view/tpl/search_item.tpl index 0a8f12d4f..ebfb6a256 100755 --- a/view/tpl/search_item.tpl +++ b/view/tpl/search_item.tpl @@ -39,6 +39,9 @@ {{if $item.verified}}<i class="fa fa-check item-verified" title="{{$item.verified}}"></i> {{elseif $item.forged}}<i class="fa fa-exclamation item-forged" title="{{$item.forged}}"></i> {{/if}}{{if $item.location}}<span class="wall-item-location" id="wall-item-location-{{$item.id}}">{{$item.location}}, </span>{{/if}}<span class="autotime" title="{{$item.isotime}}">{{$item.localtime}}{{if $item.editedtime}} {{$item.editedtime}}{{/if}}{{if $item.expiretime}} {{$item.expiretime}}{{/if}}</span>{{if $item.editedtime}} <i class="fa fa-pencil"></i>{{/if}} {{if $item.app}}<span class="item.app">{{$item.str_app}}</span>{{/if}} </div> </div> + {{if $item.divider}} + <hr class="wall-item-divider"> + {{/if}} {{if $item.body}} <div class="p-2 clrearfix {{if $item.is_photo}} wall-photo-item{{else}} wall-item-content{{/if}}" id="wall-item-content-{{$item.id}}"> <div class="wall-item-body" id="wall-item-body-{{$item.id}}" > |