diff options
53 files changed, 922 insertions, 99 deletions
diff --git a/Zotlabs/Daemon/Expire.php b/Zotlabs/Daemon/Expire.php index 215513e87..398425861 100644 --- a/Zotlabs/Daemon/Expire.php +++ b/Zotlabs/Daemon/Expire.php @@ -34,7 +34,8 @@ class Expire { logger('expire: start', LOGGER_DEBUG); - $site_expire = get_config('system', 'default_expire_days'); + $site_expire = intval(get_config('system', 'default_expire_days')); + $commented_days = intval(get_config('system','active_expire_days')); logger('site_expire: ' . $site_expire); @@ -64,7 +65,7 @@ class Expire { // if the site or service class expiration is non-zero and less than person expiration, use that logger('Expire: ' . $rr['channel_address'] . ' interval: ' . $expire_days, LOGGER_DEBUG); - item_expire($rr['channel_id'], $expire_days); + item_expire($rr['channel_id'], $expire_days, $commented_days); } } @@ -85,7 +86,7 @@ class Expire { logger('Expire: sys interval: ' . $expire_days, LOGGER_DEBUG); if ($expire_days) - item_expire($x['channel_id'], $expire_days); + item_expire($x['channel_id'], $expire_days, $commented_days); logger('Expire: sys: done', LOGGER_DEBUG); } diff --git a/Zotlabs/Lib/Enotify.php b/Zotlabs/Lib/Enotify.php index 61c98c881..e05d391f3 100644 --- a/Zotlabs/Lib/Enotify.php +++ b/Zotlabs/Lib/Enotify.php @@ -115,6 +115,7 @@ class Enotify { $always_show_in_notices = get_pconfig($recip['channel_id'],'system','always_show_in_notices'); + $vnotify = get_pconfig($recip['channel_id'],'system','vnotify'); // e.g. "your post", "David's photo", etc. $possess_desc = t('%s <!item_type!>'); @@ -142,7 +143,7 @@ class Enotify { if(array_key_exists('item',$params) && in_array($params['item']['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) { - if(! $always_show_in_notices) { + if(! $always_show_in_notices || !($vnotify & VNOTIFY_LIKE)) { logger('notification: not a visible activity. Ignoring.'); pop_lang(); return; @@ -249,7 +250,7 @@ class Enotify { $itemlink = $params['link']; if (array_key_exists('item',$params) && (! activity_match($params['item']['verb'],ACTIVITY_LIKE))) { - if(! $always_show_in_notices) { + if(! $always_show_in_notices || !($vnotify & VNOTIFY_LIKE)) { logger('notification: not a visible activity. Ignoring.'); pop_lang(); return; diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php index d35d4732a..c2aa920b9 100644 --- a/Zotlabs/Lib/ThreadItem.php +++ b/Zotlabs/Lib/ThreadItem.php @@ -733,6 +733,8 @@ class ThreadItem { $arr = array('comment_buttons' => '','id' => $this->get_id()); call_hooks('comment_buttons',$arr); $comment_buttons = $arr['comment_buttons']; + + $feature_auto_save_draft = ((feature_enabled($conv->get_profile_owner(), 'auto_save_draft')) ? "true" : "false"); $comment_box = replace_macros($template,array( '$return_path' => '', @@ -768,7 +770,8 @@ class ThreadItem { '$anoncomments' => ((($conv->get_mode() === 'channel' || $conv->get_mode() === 'display') && perm_is_allowed($conv->get_profile_owner(),'','post_comments')) ? true : false), '$anonname' => [ 'anonname', t('Your full name (required)') ], '$anonmail' => [ 'anonmail', t('Your email address (required)') ], - '$anonurl' => [ 'anonurl', t('Your website URL (optional)') ] + '$anonurl' => [ 'anonurl', t('Your website URL (optional)') ], + '$auto_save_draft' => $feature_auto_save_draft, )); return $comment_box; diff --git a/Zotlabs/Module/Acl.php b/Zotlabs/Module/Acl.php index ef901aef1..4c5883e88 100644 --- a/Zotlabs/Module/Acl.php +++ b/Zotlabs/Module/Acl.php @@ -24,7 +24,7 @@ class Acl extends \Zotlabs\Web\Controller { function init() { - logger('mod_acl: ' . print_r($_REQUEST,true)); + logger('mod_acl: ' . print_r($_REQUEST,true),LOGGER_DATA); $start = (x($_REQUEST,'start') ? $_REQUEST['start'] : 0); $count = (x($_REQUEST,'count') ? $_REQUEST['count'] : 500); diff --git a/Zotlabs/Module/Admin/Site.php b/Zotlabs/Module/Admin/Site.php index 95d44d754..656770ad9 100644 --- a/Zotlabs/Module/Admin/Site.php +++ b/Zotlabs/Module/Admin/Site.php @@ -56,6 +56,7 @@ class Site { $global_directory = ((x($_POST,'directory_submit_url')) ? notags(trim($_POST['directory_submit_url'])) : ''); $no_community_page = !((x($_POST,'no_community_page')) ? True : False); $default_expire_days = ((array_key_exists('default_expire_days',$_POST)) ? intval($_POST['default_expire_days']) : 0); + $active_expire_days = ((array_key_exists('active_expire_days',$_POST)) ? intval($_POST['active_expire_days']) : 7); $reply_address = ((array_key_exists('reply_address',$_POST) && trim($_POST['reply_address'])) ? trim($_POST['reply_address']) : 'noreply@' . \App::get_hostname()); $from_email = ((array_key_exists('from_email',$_POST) && trim($_POST['from_email'])) ? trim($_POST['from_email']) : 'Administrator@' . \App::get_hostname()); @@ -95,6 +96,7 @@ class Site { set_config('system', 'enable_context_help', $enable_context_help); set_config('system', 'verify_email', $verify_email); set_config('system', 'default_expire_days', $default_expire_days); + set_config('system', 'active_expire_days', $active_expire_days); set_config('system', 'reply_address', $reply_address); set_config('system', 'from_email', $from_email); set_config('system', 'from_email_name' , $from_email_name); @@ -348,6 +350,7 @@ class Site { '$thumbnail_security' => array('thumbnail_security', t("Allow SVG thumbnails in file browser"), get_config('system','thumbnail_security',0), t("WARNING: SVG images may contain malicious code.")), '$maxloadavg' => array('maxloadavg', t("Maximum Load Average"), ((intval(get_config('system','maxloadavg')) > 0)?get_config('system','maxloadavg'):50), t("Maximum system load before delivery and poll processes are deferred - default 50.")), '$default_expire_days' => array('default_expire_days', t('Expiration period in days for imported (grid/network) content'), intval(get_config('system','default_expire_days')), t('0 for no expiration of imported content')), + '$active_expire_days' => array('active_expire_days', t('Do not expire any posts which have comments less than this many days ago'), intval(get_config('system','active_expire_days',7)), ''), '$sellpage' => array('site_sellpage', t('Public servers: Optional landing (marketing) webpage for new registrants'), get_config('system','sellpage',''), sprintf( t('Create this page first. Default is %s/register'),z_root())), '$first_page' => array('first_page', t('Page to display after creating a new channel'), get_config('system','workflow_channel_next','profiles'), t('Recommend: profiles, go, or settings')), diff --git a/Zotlabs/Module/Channel.php b/Zotlabs/Module/Channel.php index 3d3eb2a85..6a334b59a 100644 --- a/Zotlabs/Module/Channel.php +++ b/Zotlabs/Module/Channel.php @@ -204,7 +204,7 @@ class Channel extends \Zotlabs\Web\Controller { $_SESSION['loadtime'] = datetime_convert(); } else { - $r = q("SELECT distinct parent AS item_id from item + $r = q("SELECT parent AS item_id from item left join abook on ( item.owner_xchan = abook.abook_xchan $abook_uids ) WHERE uid = %d $item_normal_update AND item_wall = 1 $simple_update diff --git a/Zotlabs/Module/Display.php b/Zotlabs/Module/Display.php index 30f2a7f5f..06c9479b2 100644 --- a/Zotlabs/Module/Display.php +++ b/Zotlabs/Module/Display.php @@ -132,7 +132,7 @@ class Display extends \Zotlabs\Web\Controller { $y = q("select * from iconfig left join item on iconfig.iid = item.id where item.uid = %d and iconfig.cat = 'system' and iconfig.k = 'WEBPAGE' and item.id = %d limit 1", intval($target_item['uid']), - intval($target_item['id']) + intval($target_item['parent']) ); if($x && $y) { goaway(z_root() . '/page/' . $x[0]['channel_address'] . '/' . $y[0]['v']); @@ -149,7 +149,7 @@ class Display extends \Zotlabs\Web\Controller { $y = q("select * from iconfig left join item on iconfig.iid = item.id where item.uid = %d and iconfig.cat = 'system' and iconfig.k = 'ARTICLE' and item.id = %d limit 1", intval($target_item['uid']), - intval($target_item['id']) + intval($target_item['parent']) ); if($x && $y) { goaway(z_root() . '/articles/' . $x[0]['channel_address'] . '/' . $y[0]['v']); @@ -166,7 +166,7 @@ class Display extends \Zotlabs\Web\Controller { $y = q("select * from iconfig left join item on iconfig.iid = item.id where item.uid = %d and iconfig.cat = 'system' and iconfig.k = 'CARD' and item.id = %d limit 1", intval($target_item['uid']), - intval($target_item['id']) + intval($target_item['parent']) ); if($x && $y) { goaway(z_root() . '/cards/' . $x[0]['channel_address'] . '/' . $y[0]['v']); diff --git a/Zotlabs/Module/Editpost.php b/Zotlabs/Module/Editpost.php index a54c42e7f..57a4cb97f 100644 --- a/Zotlabs/Module/Editpost.php +++ b/Zotlabs/Module/Editpost.php @@ -82,7 +82,7 @@ class Editpost extends \Zotlabs\Web\Controller { 'editor_autocomplete'=> true, 'bbco_autocomplete'=> 'bbcode', 'return_path' => $_SESSION['return_url'], - 'button' => t('Edit'), + 'button' => t('Submit'), 'hide_voting' => true, 'hide_future' => true, 'hide_location' => true, diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php index ad72d9ccd..bba1dc02d 100644 --- a/Zotlabs/Module/Item.php +++ b/Zotlabs/Module/Item.php @@ -468,6 +468,7 @@ class Item extends \Zotlabs\Web\Controller { $private = intval($acl->is_private() || $parent_item['item_private']); $public_policy = $parent_item['public_policy']; $owner_hash = $parent_item['owner_xchan']; + $webpage = $parent_item['item_type']; } if((! $allow_empty) && (! strlen($body))) { diff --git a/Zotlabs/Module/Like.php b/Zotlabs/Module/Like.php index 12de86e72..4e216f08b 100644 --- a/Zotlabs/Module/Like.php +++ b/Zotlabs/Module/Like.php @@ -419,6 +419,7 @@ class Like extends \Zotlabs\Web\Controller { $arr['item_origin'] = 1; $arr['item_notshown'] = 1; + $arr['item_type'] = $item['item_type']; if(intval($item['item_wall'])) $arr['item_wall'] = 1; diff --git a/Zotlabs/Module/Photos.php b/Zotlabs/Module/Photos.php index 81af607ec..fa22c3f26 100644 --- a/Zotlabs/Module/Photos.php +++ b/Zotlabs/Module/Photos.php @@ -695,8 +695,8 @@ class Photos extends \Zotlabs\Web\Controller { '$newalbum_label' => t('Enter an album name'), '$newalbum_placeholder' => t('or select an existing album (doubleclick)'), '$visible' => array('visible', t('Create a status post for this upload'), 0,'', array(t('No'), t('Yes')), 'onclick="showHideBodyTextarea();"'), - '$caption' => array('description', t('Caption (optional):')), - '$body' => array('body', t('Description (optional):'),'', 'Description will only appear in the status post'), + '$caption' => array('description', t('Title (optional)')), + '$body' => array('body', t('Description (optional)'),'', 'Description will only appear in the status post'), '$albums' => $albums['albums'], '$selname' => $selname, '$permissions' => t('Permissions'), @@ -841,7 +841,7 @@ class Photos extends \Zotlabs\Web\Controller { '$album_id' => $datum, '$album_edit' => array(t('Edit Album'), $album_edit), '$can_post' => $can_post, - '$upload' => array(t('Upload'), z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/upload/' . $datum), + '$upload' => array(t('Add Photos'), z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/upload/' . $datum), '$order' => $order, '$upload_form' => $upload_form, '$usage' => $usage_message @@ -1065,7 +1065,7 @@ class Photos extends \Zotlabs\Web\Controller { 'newalbum_placeholder' => t('or select an existing one (doubleclick)'), 'nickname' => \App::$data['channel']['channel_address'], 'resource_id' => $ph[0]['resource_id'], - 'capt_label' => t('Caption'), + 'capt_label' => t('Title (optional)'), 'caption' => $caption_e, 'tag_label' => t('Add a Tag'), 'permissions' => t('Permissions'), @@ -1378,7 +1378,7 @@ class Photos extends \Zotlabs\Web\Controller { '$title' => t('Recent Photos'), '$album_id' => bin2hex(t('Recent Photos')), '$can_post' => $can_post, - '$upload' => array(t('Upload'), z_root().'/photos/'.\App::$data['channel']['channel_address'].'/upload'), + '$upload' => array(t('Add Photos'), z_root().'/photos/'.\App::$data['channel']['channel_address'].'/upload'), '$photos' => $photos, '$upload_form' => $upload_form, '$usage' => $usage_message diff --git a/Zotlabs/Module/Ping.php b/Zotlabs/Module/Ping.php index eab49d69e..9372866d7 100644 --- a/Zotlabs/Module/Ping.php +++ b/Zotlabs/Module/Ping.php @@ -140,7 +140,13 @@ class Ping extends \Zotlabs\Web\Controller { db_utcnow(), db_quoteinterval('3 MINUTE') ); - $discover_tab_on = ((get_config('system','disable_discover_tab') || get_config('system','disable_discover_tab') === false) ? false : true); + + $sql_extra = ''; + if(! ($vnotify & VNOTIFY_LIKE)) + $sql_extra = ' AND verb NOT IN ("' . dbesc(ACTIVITY_LIKE) . '", "' . dbesc(ACTIVITY_DISLIKE) . '") '; + + $discover_tab_on = can_view_public_stream(); + $notify_pubs = ((local_channel()) ? ($vnotify & VNOTIFY_PUBS) && $discover_tab_on : $discover_tab_on); if($notify_pubs) { @@ -151,7 +157,8 @@ class Ping extends \Zotlabs\Web\Controller { AND item_unseen = 1 AND author_xchan != '%s' AND created > '" . datetime_convert('UTC','UTC',$_SESSION['static_loadtime']) . "' - $item_normal", + $item_normal + $sql_extra", intval($sys['channel_id']), dbesc(get_observer_hash()) ); @@ -160,6 +167,8 @@ class Ping extends \Zotlabs\Web\Controller { $result['pubs'] = intval($pubs[0]['total']); } + + if((argc() > 1) && (argv(1) === 'pubs') && ($notify_pubs)) { $sys = get_sys_channel(); $result = array(); @@ -170,6 +179,7 @@ class Ping extends \Zotlabs\Web\Controller { AND author_xchan != '%s' AND created > '" . datetime_convert('UTC','UTC',$_SESSION['static_loadtime']) . "' $item_normal + $sql_extra ORDER BY created DESC LIMIT 300", intval($sys['channel_id']), @@ -334,6 +344,7 @@ class Ping extends \Zotlabs\Web\Controller { AND item_unseen = 1 AND author_xchan != '%s' $item_normal + $sql_extra ORDER BY created DESC LIMIT 300", intval(local_channel()), @@ -508,6 +519,7 @@ class Ping extends \Zotlabs\Web\Controller { $r = q("SELECT id, item_wall FROM item WHERE uid = %d and item_unseen = 1 $item_normal + $sql_extra AND author_xchan != '%s'", intval(local_channel()), dbesc($ob_hash) diff --git a/Zotlabs/Module/Profiles.php b/Zotlabs/Module/Profiles.php index e02cb33db..202ee462a 100644 --- a/Zotlabs/Module/Profiles.php +++ b/Zotlabs/Module/Profiles.php @@ -732,7 +732,7 @@ class Profiles extends \Zotlabs\Web\Controller { '$addthing' => t('Add profile things'), '$personal' => t('Personal'), '$location' => t('Location'), - '$relation' => t('Relation'), + '$relation' => t('Relationship'), '$miscellaneous'=> t('Miscellaneous'), '$exportable' => feature_enabled(local_channel(),'profile_export'), '$lbl_import' => t('Import profile from file'), diff --git a/Zotlabs/Module/React.php b/Zotlabs/Module/React.php index 6473317c7..fbb760786 100644 --- a/Zotlabs/Module/React.php +++ b/Zotlabs/Module/React.php @@ -49,6 +49,7 @@ class React extends \Zotlabs\Web\Controller { $n['aid'] = $channel['channel_account_id']; $n['uid'] = $channel['channel_id']; $n['item_origin'] = true; + $n['item_type'] = $i[0]['item_type']; $n['parent'] = $postid; $n['parent_mid'] = $i[0]['mid']; $n['mid'] = item_message_id(); diff --git a/Zotlabs/Module/Search.php b/Zotlabs/Module/Search.php index 55e0e746f..43464ad8b 100644 --- a/Zotlabs/Module/Search.php +++ b/Zotlabs/Module/Search.php @@ -66,6 +66,10 @@ class Search extends \Zotlabs\Web\Controller { $search = substr($search,1); goaway(z_root() . '/directory' . '?f=1&navsearch=1&search=' . $search); } + if(strpos($search,'!') === 0) { + $search = substr($search,1); + goaway(z_root() . '/directory' . '?f=1&navsearch=1&search=' . $search); + } if(strpos($search,'?') === 0) { $search = substr($search,1); goaway(z_root() . '/help' . '?f=1&navsearch=1&search=' . $search); diff --git a/Zotlabs/Module/Settings/Channel.php b/Zotlabs/Module/Settings/Channel.php index a7dfdd790..e274c9786 100644 --- a/Zotlabs/Module/Settings/Channel.php +++ b/Zotlabs/Module/Settings/Channel.php @@ -208,6 +208,8 @@ class Channel { $vnotify += intval($_POST['vnotify12']); if(x($_POST,'vnotify13')) $vnotify += intval($_POST['vnotify13']); + if(x($_POST,'vnotify14')) + $vnotify += intval($_POST['vnotify14']); $always_show_in_notices = x($_POST,'always_show_in_notices') ? 1 : 0; @@ -484,7 +486,8 @@ class Channel { $plugin = [ 'basic' => '', 'security' => '', 'notify' => '', 'misc' => '' ]; call_hooks('channel_settings',$plugin); - $disable_discover_tab = get_config('system','disable_discover_tab') || get_config('system','disable_discover_tab') === false; + $disable_discover_tab = intval(get_config('system','disable_discover_tab',1)) == 1; + $site_firehose = intval(get_config('system','site_firehose',0)) == 1; $o .= replace_macros($stpl,array( '$ptitle' => t('Channel Settings'), @@ -575,9 +578,10 @@ class Channel { '$vnotify10' => array('vnotify10', t('New connections'), ($vnotify & VNOTIFY_INTRO), VNOTIFY_INTRO, t('Recommended'), $yes_no), '$vnotify11' => ((is_site_admin()) ? array('vnotify11', t('System Registrations'), ($vnotify & VNOTIFY_REGISTER), VNOTIFY_REGISTER, '', $yes_no) : array()), '$vnotify12' => array('vnotify12', t('Unseen shared files'), ($vnotify & VNOTIFY_FILES), VNOTIFY_FILES, '', $yes_no), - '$vnotify13' => (($disable_discover_tab) ? array() : array('vnotify13', t('Unseen public activity'), ($vnotify & VNOTIFY_PUBS), VNOTIFY_PUBS, '', $yes_no)), + '$vnotify13' => (($disable_discover_tab && !$site_firehose) ? array() : array('vnotify13', t('Unseen public activity'), ($vnotify & VNOTIFY_PUBS), VNOTIFY_PUBS, '', $yes_no)), + '$vnotify14' => array('vnotify14', t('Unseen likes and dislikes'), ($vnotify & VNOTIFY_LIKE), VNOTIFY_LIKE, '', $yes_no), '$mailhost' => [ 'mailhost', t('Email notification hub (hostname)'), get_pconfig(local_channel(),'system','email_notify_host',\App::get_hostname()), sprintf( t('If your channel is mirrored to multiple hubs, set this to your preferred location. This will prevent duplicate email notifications. Example: %s'),\App::get_hostname()) ], - '$always_show_in_notices' => array('always_show_in_notices', t('Also show new wall posts, private messages and connections under Notices'), $always_show_in_notices, 1, '', $yes_no), + '$always_show_in_notices' => array('always_show_in_notices', t('Show new wall posts, private messages and connections under Notices'), $always_show_in_notices, 1, '', $yes_no), '$evdays' => array('evdays', t('Notify me of events this many days in advance'), $evdays, t('Must be greater than 0')), '$basic_addon' => $plugin['basic'], diff --git a/Zotlabs/Module/Settings/Features.php b/Zotlabs/Module/Settings/Features.php index 5b642acc3..888032c28 100644 --- a/Zotlabs/Module/Settings/Features.php +++ b/Zotlabs/Module/Settings/Features.php @@ -8,43 +8,75 @@ class Features { function post() { check_form_security_token_redirectOnErr('/settings/features', 'settings_features'); - // Build list of features and check which are set - // We will not create any settings for features that are above our techlevel - - $features = get_features(); - $all_features = array(); - foreach($features as $k => $v) { - foreach($v as $f) - $all_features[] = $f[0]; - } - foreach($all_features as $k) { - if(x($_POST,"feature_$k")) - set_pconfig(local_channel(),'feature',$k, 1); - else - set_pconfig(local_channel(),'feature',$k, 0); + $features = get_features(false); + + foreach($features as $fname => $fdata) { + foreach(array_slice($fdata,1) as $f) { + $k = $f[0]; + if(array_key_exists("feature_$k",$_POST)) + set_pconfig(local_channel(),'feature',$k, (string) $_POST["feature_$k"]); + else + set_pconfig(local_channel(),'feature', $k, ''); + } } build_sync_packet(); return; } function get() { - $arr = array(); - $features = get_features(); - + + $arr = []; + $harr = []; + + if(intval($_REQUEST['techlevel'])) + $level = intval($_REQUEST['techlevel']); + else { + $level = get_account_techlevel(); + } + + if(! intval($level)) { + notice( t('Permission denied.') . EOL); + return; + } + + $techlevels = \Zotlabs\Lib\Techlevels::levels(); + + // This page isn't accessible at techlevel 0 + + unset($techlevels[0]); + + $def_techlevel = (($level > 0) ? $level : 1); + $techlock = get_config('system','techlevel_lock'); + + $all_features_raw = get_features(false); + + foreach($all_features_raw as $fname => $fdata) { + foreach(array_slice($fdata,1) as $f) { + $harr[$f[0]] = ((intval(feature_enabled(local_channel(),$f[0]))) ? "1" : ''); + } + } + + $features = get_features(true,$level); + foreach($features as $fname => $fdata) { $arr[$fname] = array(); $arr[$fname][0] = $fdata[0]; foreach(array_slice($fdata,1) as $f) { - $arr[$fname][1][] = array('feature_' .$f[0],$f[1],((intval(feature_enabled(local_channel(),$f[0]))) ? "1" : ''),$f[2],array(t('Off'),t('On'))); + $arr[$fname][1][] = array('feature_' . $f[0],$f[1],((intval(feature_enabled(local_channel(),$f[0]))) ? "1" : ''),$f[2],array(t('Off'),t('On'))); + unset($harr[$f[0]]); } } $tpl = get_markup_template("settings_features.tpl"); $o .= replace_macros($tpl, array( '$form_security_token' => get_form_security_token("settings_features"), - '$title' => t('Additional Features'), - '$features' => $arr, - '$submit' => t('Submit'), + '$title' => t('Additional Features'), + '$techlevel' => [ 'techlevel', t('Your technical skill level'), $def_techlevel, t('Used to provide a member experience and additional features consistent with your comfort level'), $techlevels ], + '$techlock' => $techlock, + '$features' => $arr, + '$hiddens' => $harr, + '$baseurl' => z_root(), + '$submit' => t('Submit'), )); return $o; diff --git a/Zotlabs/Module/Settings/Oauth.php b/Zotlabs/Module/Settings/Oauth.php index c612c7667..d6576c6de 100644 --- a/Zotlabs/Module/Settings/Oauth.php +++ b/Zotlabs/Module/Settings/Oauth.php @@ -23,11 +23,12 @@ class Oauth { check_form_security_token_redirectOnErr('/settings/oauth', 'settings_oauth'); - $name = ((x($_POST,'name')) ? $_POST['name'] : ''); - $key = ((x($_POST,'key')) ? $_POST['key'] : ''); - $secret = ((x($_POST,'secret')) ? $_POST['secret'] : ''); - $redirect = ((x($_POST,'redirect')) ? $_POST['redirect'] : ''); - $icon = ((x($_POST,'icon')) ? $_POST['icon'] : ''); + $name = ((x($_POST,'name')) ? escape_tags($_POST['name']) : ''); + $key = ((x($_POST,'key')) ? escape_tags($_POST['key']) : ''); + $secret = ((x($_POST,'secret')) ? escape_tags($_POST['secret']) : ''); + $redirect = ((x($_POST,'redirect')) ? escape_tags($_POST['redirect']) : ''); + $icon = ((x($_POST,'icon')) ? escape_tags($_POST['icon']) : ''); + $oauth2 = ((x($_POST,'oauth2')) ? intval($_POST['oauth2']) : 0); $ok = true; if($name == '') { $ok = false; diff --git a/Zotlabs/Module/Settings/Oauth2.php b/Zotlabs/Module/Settings/Oauth2.php new file mode 100644 index 000000000..88bbea3b8 --- /dev/null +++ b/Zotlabs/Module/Settings/Oauth2.php @@ -0,0 +1,160 @@ +<?php + +namespace Zotlabs\Module\Settings; + + +class Oauth2 { + + + function post() { + + if(x($_POST,'remove')){ + check_form_security_token_redirectOnErr('/settings/oauth2', 'settings_oauth2'); + + $key = $_POST['remove']; + q("DELETE FROM tokens WHERE id='%s' AND uid=%d", + dbesc($key), + local_channel()); + goaway(z_root()."/settings/oauth2/"); + return; + } + + if((argc() > 2) && (argv(2) === 'edit' || argv(2) === 'add') && x($_POST,'submit')) { + + check_form_security_token_redirectOnErr('/settings/oauth2', 'settings_oauth2'); + + $name = ((x($_POST,'name')) ? escape_tags(trim($_POST['name'])) : ''); + $secret = ((x($_POST,'secret')) ? escape_tags(trim($_POST['secret'])) : ''); + $redirect = ((x($_POST,'redirect')) ? escape_tags(trim($_POST['redirect'])) : ''); + $grant = ((x($_POST,'grant')) ? escape_tags(trim($_POST['grant'])) : ''); + $scope = ((x($_POST,'scope')) ? escape_tags(trim($_POST['scope'])) : ''); + + $ok = true; + if($name == '' || $secret == '') { + $ok = false; + notice( t('Name and Secret are required') . EOL); + } + + if($ok) { + if ($_POST['submit']==t("Update")){ + $r = q("UPDATE oauth_clients SET + client_id = '%s', + client_secret = '%s', + redirect_uri = '%s', + grant_types = '%s', + scope = '%s', + user_id = '%s' + WHERE client_id='%s'", + dbesc($name), + dbesc($secret), + dbesc($redirect), + dbesc($grant), + dbesc($scope), + dbesc(local_channel()), + dbesc($name)); + } else { + $r = q("INSERT INTO oauth_clients (client_id, client_secret, redirect_uri, grant_types, scope, user_id) + VALUES ('%s','%s','%s','%s','%s','%s')", + dbesc($name), + dbesc($secret), + dbesc($redirect), + dbesc($grant), + dbesc($scope), + dbesc(local_channel()) + ); + $r = q("INSERT INTO xperm (xp_client, xp_channel, xp_perm) VALUES ('%s', %d, '%s') ", + dbesc($name), + intval(local_channel()), + dbesc('all') + ); + } + } + goaway(z_root()."/settings/oauth2/"); + return; + } + } + + function get() { + + if((argc() > 2) && (argv(2) === 'add')) { + $tpl = get_markup_template("settings_oauth2_edit.tpl"); + $o .= replace_macros($tpl, array( + '$form_security_token' => get_form_security_token("settings_oauth2"), + '$title' => t('Add OAuth2 application'), + '$submit' => t('Submit'), + '$cancel' => t('Cancel'), + '$name' => array('name', t('Name'), '', t('Name of application')), + '$secret' => array('secret', t('Consumer Secret'), random_string(16), t('Automatically generated - change if desired. Max length 20')), + '$redirect' => array('redirect', t('Redirect'), '', t('Redirect URI - leave blank unless your application specifically requires this')), + '$grant' => array('grant', t('Grant Types'), '', t('leave blank unless your application sepcifically requires this')), + '$scope' => array('scope', t('Authorization scope'), '', t('leave blank unless your application sepcifically requires this')), + )); + return $o; + } + + if((argc() > 3) && (argv(2) === 'edit')) { + $r = q("SELECT * FROM oauth_clients WHERE client_id='%s' AND user_id= '%s'", + dbesc(argv(3)), + dbesc(local_channel()) + ); + + if (! $r){ + notice(t('OAuth2 Application not found.')); + return; + } + + $app = $r[0]; + + $tpl = get_markup_template("settings_oauth2_edit.tpl"); + $o .= replace_macros($tpl, array( + '$form_security_token' => get_form_security_token("settings_oauth2"), + '$title' => t('Add application'), + '$submit' => t('Update'), + '$cancel' => t('Cancel'), + '$name' => array('name', t('Name'), $app['client_id'], t('Name of application')), + '$secret' => array('secret', t('Consumer Secret'), $app['client_secret'], t('Automatically generated - change if desired. Max length 20')), + '$redirect' => array('redirect', t('Redirect'), $app['redirect_uri'], t('Redirect URI - leave blank unless your application specifically requires this')), + '$grant' => array('grant', t('Grant Types'), $app['grant_types'], t('leave blank unless your application sepcifically requires this')), + '$scope' => array('scope', t('Authorization scope'), $app['scope'], t('leave blank unless your application sepcifically requires this')), + )); + return $o; + } + + if((argc() > 3) && (argv(2) === 'delete')) { + check_form_security_token_redirectOnErr('/settings/oauth2', 'settings_oauth2', 't'); + + $r = q("DELETE FROM oauth_clients WHERE client_id = '%s' AND user_id = '%s'", + dbesc(argv(3)), + dbesc(local_channel()) + ); + goaway(z_root()."/settings/oauth2/"); + return; + } + + + $r = q("SELECT oauth_clients.*, oauth_access_tokens.access_token as oauth_token, (oauth_clients.user_id = '%s') AS my + FROM oauth_clients + LEFT JOIN oauth_access_tokens ON oauth_clients.client_id=oauth_access_tokens.client_id + WHERE oauth_clients.user_id IN ('%s',0)", + dbesc(local_channel()), + dbesc(local_channel()) + ); + + $tpl = get_markup_template("settings_oauth2.tpl"); + $o .= replace_macros($tpl, array( + '$form_security_token' => get_form_security_token("settings_oauth2"), + '$baseurl' => z_root(), + '$title' => t('Connected OAuth2 Apps'), + '$add' => t('Add application'), + '$edit' => t('Edit'), + '$delete' => t('Delete'), + '$consumerkey' => t('Client key starts with'), + '$noname' => t('No name'), + '$remove' => t('Remove authorization'), + '$apps' => $r, + )); + return $o; + + } + +}
\ No newline at end of file diff --git a/Zotlabs/Module/Well_known.php b/Zotlabs/Module/Well_known.php index b57666bff..177de2323 100644 --- a/Zotlabs/Module/Well_known.php +++ b/Zotlabs/Module/Well_known.php @@ -26,7 +26,6 @@ class Well_known extends \Zotlabs\Web\Controller { killme(); } - switch(argv(1)) { case 'zot-info': \App::$argc -= 1; @@ -52,6 +51,10 @@ class Well_known extends \Zotlabs\Web\Controller { $module->init(); break; + case 'dnt-policy.txt': + echo file_get_contents('doc/dnt-policy.txt'); + killme(); + default: if(file_exists(\App::$cmd)) { echo file_get_contents(\App::$cmd); diff --git a/Zotlabs/Storage/Browser.php b/Zotlabs/Storage/Browser.php index f1c95802b..508c39d22 100644 --- a/Zotlabs/Storage/Browser.php +++ b/Zotlabs/Storage/Browser.php @@ -274,7 +274,7 @@ class Browser extends DAV\Browser\Plugin { '$actionspanel' => $output, '$shared' => t('Shared'), '$create' => t('Create'), - '$upload' => t('Upload'), + '$upload' => t('Add Files'), '$is_owner' => $is_owner, '$parentpath' => $parentpath, '$cpath' => bin2hex(\App::$query_string), diff --git a/Zotlabs/Widget/Catcloud.php b/Zotlabs/Widget/Catcloud.php new file mode 100644 index 000000000..c53f9bbf6 --- /dev/null +++ b/Zotlabs/Widget/Catcloud.php @@ -0,0 +1,46 @@ +<?php + +namespace Zotlabs\Widget; + +class Catcloud { + + function widget($arr) { + + if((! \App::$profile['profile_uid']) || (! \App::$profile['channel_hash'])) + return ''; + + $limit = ((array_key_exists('limit',$arr)) ? intval($arr['limit']) : 50); + + if(array_key_exists('type',$arr)) { + switch($arr['type']) { + + case 'cards': + + if(! perm_is_allowed(\App::$profile['profile_uid'], get_observer_hash(), 'view_pages')) + return ''; + + return card_catblock(\App::$profile['profile_uid'], $limit, '', \App::$profile['channel_hash']); + + case 'articles': + + if(! perm_is_allowed(\App::$profile['profile_uid'], get_observer_hash(), 'view_pages')) + return ''; + + return article_catblock(\App::$profile['profile_uid'], $limit, '', \App::$profile['channel_hash']); + + + default: + break; + } + } + + + if(! perm_is_allowed(\App::$profile['profile_uid'], get_observer_hash(), 'view_stream')) + return ''; + + return catblock(\App::$profile['profile_uid'], $limit, '', \App::$profile['channel_hash']); + + + } + +} diff --git a/Zotlabs/Widget/Notifications.php b/Zotlabs/Widget/Notifications.php index f2619c5cf..fc78a2a39 100644 --- a/Zotlabs/Widget/Notifications.php +++ b/Zotlabs/Widget/Notifications.php @@ -123,7 +123,7 @@ class Notifications { ]; } - if(get_config('system', 'disable_discover_tab') != 1) { + if(can_view_public_stream()) { $notifications[] = [ 'type' => 'pubs', 'icon' => 'globe', diff --git a/Zotlabs/Widget/Settings_menu.php b/Zotlabs/Widget/Settings_menu.php index 455fdcb9b..9574becc3 100644 --- a/Zotlabs/Widget/Settings_menu.php +++ b/Zotlabs/Widget/Settings_menu.php @@ -81,12 +81,20 @@ class Settings_menu { if(feature_enabled(local_channel(),'oauth_clients')) { $tabs[] = array( - 'label' => t('Connected apps'), + 'label' => t('OAuth1 apps'), 'url' => z_root() . '/settings/oauth', 'selected' => ((argv(1) === 'oauth') ? 'active' : ''), ); } + if(feature_enabled(local_channel(),'oauth2_clients')) { + $tabs[] = array( + 'label' => t('OAuth2 apps'), + 'url' => z_root() . '/settings/oauth2', + 'selected' => ((argv(1) === 'oauth2') ? 'active' : ''), + ); + } + if(feature_enabled(local_channel(),'access_tokens')) { $tabs[] = array( 'label' => t('Guest Access Tokens'), diff --git a/Zotlabs/Widget/Tagcloud.php b/Zotlabs/Widget/Tagcloud.php index cf7a4932e..f79bd59ad 100644 --- a/Zotlabs/Widget/Tagcloud.php +++ b/Zotlabs/Widget/Tagcloud.php @@ -2,9 +2,6 @@ namespace Zotlabs\Widget; -// @FIXME The problem with this widget is that we don't have a search function for webpages -// that we can send the links to. Then we should also provide an option to search webpages -// and conversations. class Tagcloud { @@ -14,15 +11,15 @@ class Tagcloud { $uid = \App::$profile_uid; $count = ((x($args,'count')) ? intval($args['count']) : 24); $flags = 0; - $type = TERM_CATEGORY; + $type = TERM_HASHTAG; // @FIXME there exists no $authors variable - $r = tagadelic($uid, $count, $authors, $owner, $flags, ITEM_TYPE_WEBPAGE, $type); + $r = tagadelic($uid, $count, $authors, $owner, $flags, 0, $type); // @FIXME this should use a template if($r) { - $o = '<div class="tagblock widget"><h3>' . t('Categories') . '</h3><div class="tags" align="center">'; + $o = '<div class="tagblock widget"><h3>' . t('Tags') . '</h3><div class="tags" align="center">'; foreach($r as $rv) { $o .= '<span class="tag' . $rv[2] . '">' . $rv[0] .' </span> ' . "\r\n"; } @@ -50,7 +50,7 @@ require_once('include/attach.php'); require_once('include/bbcode.php'); define ( 'PLATFORM_NAME', 'hubzilla' ); -define ( 'STD_VERSION', '3.3.3' ); +define ( 'STD_VERSION', '3.3.4' ); define ( 'ZOT_REVISION', '6.0a' ); @@ -404,6 +404,7 @@ define ( 'VNOTIFY_INTRO', 0x0200 ); define ( 'VNOTIFY_REGISTER', 0x0400 ); define ( 'VNOTIFY_FILES', 0x0800 ); define ( 'VNOTIFY_PUBS', 0x1000 ); +define ( 'VNOTIFY_LIKE', 0x2000 ); diff --git a/doc/dnt-policy.txt b/doc/dnt-policy.txt new file mode 100644 index 000000000..ad946d1f8 --- /dev/null +++ b/doc/dnt-policy.txt @@ -0,0 +1,218 @@ +Do Not Track Compliance Policy + +Version 1.0 + +This domain complies with user opt-outs from tracking via the "Do Not Track" +or "DNT" header [http://www.w3.org/TR/tracking-dnt/]. This file will always +be posted via HTTPS at https://example-domain.com/.well-known/dnt-policy.txt +to indicate this fact. + +SCOPE + +This policy document allows an operator of a Fully Qualified Domain Name +("domain") to declare that it respects Do Not Track as a meaningful privacy +opt-out of tracking, so that privacy-protecting software can better determine +whether to block or anonymize communications with this domain. This policy is +intended first and foremost to be posted on domains that publish ads, widgets, +images, scripts and other third-party embedded hypertext (for instance on +widgets.example.com), but it can be posted on any domain, including those users +visit directly (such as www.example.com). The policy may be applied to some +domains used by a company, site, or service, and not to others. Do Not Track +may be sent by any client that uses the HTTP protocol, including websites, +mobile apps, and smart devices like TVs. Do Not Track also works with all +protocols able to read HTTP headers, including SPDY. + +NOTE: This policy contains both Requirements and Exceptions. Where possible +terms are defined in the text, but a few additional definitions are included +at the end. + +REQUIREMENTS + +When this domain receives Web requests from a user who enables DNT by actively +choosing an opt-out setting in their browser or by installing software that is +primarily designed to protect privacy ("DNT User"), we will take the following +measures with respect to those users' data, subject to the Exceptions, also +listed below: + +1. END USER IDENTIFIERS: + + a. If a DNT User has logged in to our service, all user identifiers, such as + unique or nearly unique cookies, "supercookies" and fingerprints are + discarded as soon as the HTTP(S) response is issued. + + Data structures which associate user identifiers with accounts may be + employed to recognize logged in users per Exception 4 below, but may not + be associated with records of the user's activities unless otherwise + excepted. + + b. If a DNT User is not logged in to our service, we will take steps to ensure + that no user identifiers are transmitted to us at all. + +2. LOG RETENTION: + + a. Logs with DNT Users' identifiers removed (but including IP addresses and + User Agent strings) may be retained for a period of 10 days or less, + unless an Exception (below) applies. This period of time balances privacy + concerns with the need to ensure that log processing systems have time to + operate; that operations engineers have time to monitor and fix technical + and performance problems; and that security and data aggregation systems + have time to operate. + + b. These logs will not be used for any other purposes. + +3. OTHER DOMAINS: + + a. If this domain transfers identifiable user data about DNT Users to + contractors, affiliates or other parties, or embeds from or posts data to + other domains, we will either: + + b. ensure that the operators of those domains abide by this policy overall + by posting it at /.well-known/dnt-policy.txt via HTTPS on the domains in + question, + + OR + + ensure that the recipient's policies and practices require the recipient + to respect the policy for our DNT Users' data. + + OR + + obtain a contractual commitment from the recipient to respect this policy + for our DNT Users' data. + + NOTE: if an “Other Domain” does not receive identifiable user information + from the domain because such information has been removed, because the + Other Domain does not log that information, or for some other reason, these + requirements do not apply. + + c. "Identifiable" means any records which are not Anonymized or otherwise + covered by the Exceptions below. + +4. PERIODIC REASSERTION OF COMPLIANCE: + + At least once every 12 months, we will take reasonable steps commensurate + with the size of our organization and the nature of our service to confirm + our ongoing compliance with this document, and we will publicly reassert our + compliance. + +5. USER NOTIFICATION: + + a. If we are required by law to retain or disclose user identifiers, we will + attempt to provide the users with notice (unless we are prohibited or it + would be futile) that a request for their information has been made in + order to give the users an opportunity to object to the retention or + disclosure. + + b. We will attempt to provide this notice by email, if the users have given + us an email address, and by postal mail if the users have provided a + postal address. + + c. If the users do not challenge the disclosure request, we may be legally + required to turn over their information. + + d. We may delay notice if we, in good faith, believe that an emergency + involving danger of death or serious physical injury to any person + requires disclosure without delay of information relating to the + emergency. + +EXCEPTIONS + +Data from DNT Users collected by this domain may be logged or retained only in +the following specific situations: + +1. CONSENT / "OPT BACK IN" + + a. DNT Users are opting out from tracking across the Web. It is possible + that for some feature or functionality, we will need to ask a DNT User to + "opt back in" to be tracked by us across the entire Web. + + b. If we do that, we will take reasonable steps to verify that the users who + select this option have genuinely intended to opt back in to tracking. + One way to do this is by performing scientifically reasonable user + studies with a representative sample of our users, but smaller + organizations can satisfy this requirement by other means. + + c. Where we believe that we have opt back in consent, our server will + send a tracking value status header "Tk: C" as described in section 6.2 + of the W3C Tracking Preference Expression draft: + + http://www.w3.org/TR/tracking-dnt/#tracking-status-value + +2. TRANSACTIONS + + If a DNT User actively and knowingly enters a transaction with our + services (for instance, clicking on a clearly-labeled advertisement, + posting content to a widget, or purchasing an item), we will retain + necessary data for as long as required to perform the transaction. This + may for example include keeping auditing information for clicks on + advertising links; keeping a copy of posted content and the name of the + posting user; keeping server-side session IDs to recognize logged in + users; or keeping a copy of the physical address to which a purchased + item will be shipped. By their nature, some transactions will require data + to be retained indefinitely. + +3. TECHNICAL AND SECURITY LOGGING: + + a. If, during the processing of the initial request (for unique identifiers) + or during the subsequent 10 days (for IP addresses and User Agent strings), + we obtain specific information that causes our employees or systems to + believe that a request is, or is likely to be, part of a security attack, + spam submission, or fraudulent transaction, then logs of those requests + are not subject to this policy. + + b. If we encounter technical problems with our site, then, in rare + circumstances, we may retain logs for longer than 10 days, if that is + necessary to diagnose and fix those problems, but this practice will not be + routinized and we will strive to delete such logs as soon as possible. + +4. AGGREGATION: + + a. We may retain and share anonymized datasets, such as aggregate records of + readership patterns; statistical models of user behavior; graphs of system + variables; data structures to count active users on monthly or yearly + bases; database tables mapping authentication cookies to logged in + accounts; non-unique data structures constructed within browsers for tasks + such as ad frequency capping or conversion tracking; or logs with truncated + and/or encrypted IP addresses and simplified User Agent strings. + + b. "Anonymized" means we have conducted risk mitigation to ensure + that the dataset, plus any additional information that is in our + possession or likely to be available to us, does not allow the + reconstruction of reading habits, online or offline activity of groups of + fewer than 5000 individuals or devices. + + c. If we generate anonymized datasets under this exception we will publicly + document our anonymization methods in sufficient detail to allow outside + experts to evaluate the effectiveness of those methods. + +5. ERRORS: + +From time to time, there may be errors by which user data is temporarily +logged or retained in violation of this policy. If such errors are +inadvertent, rare, and made in good faith, they do not constitute a breach +of this policy. We will delete such data as soon as practicable after we +become aware of any error and take steps to ensure that it is deleted by any +third-party who may have had access to the data. + +ADDITIONAL DEFINITIONS + +"Fully Qualified Domain Name" means a domain name that addresses a computer +connected to the Internet. For instance, example1.com; www.example1.com; +ads.example1.com; and widgets.example2.com are all distinct FQDNs. + +"Supercookie" means any technology other than an HTTP Cookie which can be used +by a server to associate identifiers with the clients that visit it. Examples +of supercookies include Flash LSO cookies, DOM storage, HTML5 storage, or +tricks to store information in caches or etags. + +"Risk mitigation" means an engineering process that evaluates the possibility +and likelihood of various adverse outcomes, considers the available methods of +making those adverse outcomes less likely, and deploys sufficient mitigations +to bring the probability and harm from adverse outcomes below an acceptable +threshold. + +"Reading habits" includes amongst other things lists of visited DNS names, if +those domains pertain to specific topics or activities, but records of visited +DNS names are not reading habits if those domain names serve content of a very +diverse and general nature, thereby revealing minimal information about the +opinions, interests or activities of the user. diff --git a/include/channel.php b/include/channel.php index 4a87ef602..5f87e587c 100644 --- a/include/channel.php +++ b/include/channel.php @@ -2725,7 +2725,7 @@ function anon_identity_init($reqvars) { $hash = hash('md5',$anon_email); - $x = q("select * from xchan where xchan_guid = '%s' and xchan_hash = '%s' and xchan_network = 'unknown' limit 1", + $x = q("select * from xchan where xchan_guid = '%s' and xchan_hash = '%s' and xchan_network = 'anon' limit 1", dbesc($anon_email), dbesc($hash) ); @@ -2736,19 +2736,19 @@ function anon_identity_init($reqvars) { 'xchan_hash' => $hash, 'xchan_name' => $anon_name, 'xchan_url' => $anon_url, - 'xchan_network' => 'unknown', + 'xchan_network' => 'anon', 'xchan_name_date' => datetime_convert() ]); - $x = q("select * from xchan where xchan_guid = '%s' and xchan_hash = '%s' and xchan_network = 'unknown' limit 1", + $x = q("select * from xchan where xchan_guid = '%s' and xchan_hash = '%s' and xchan_network = 'anon' limit 1", dbesc($anon_email), dbesc($hash) ); $photo = z_root() . '/' . get_default_profile_photo(300); $photos = import_xchan_photo($photo,$hash); - $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_guid = '%s' and xchan_hash = '%s' and xchan_network = 'unknown' ", + $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_guid = '%s' and xchan_hash = '%s' and xchan_network = 'anon' ", dbesc(datetime_convert()), dbesc($photos[0]), dbesc($photos[1]), diff --git a/include/connections.php b/include/connections.php index 8d1b9e07f..e5bf07d96 100644 --- a/include/connections.php +++ b/include/connections.php @@ -110,6 +110,12 @@ function vcard_from_xchan($xchan, $observer = null, $mode = '') { $connect = t('Connect'); } + // don't provide a connect button for transient or one-way identities + + if(in_array($xchan['xchan_network'],['rss','anon','unknown']) || strpos($xchan['xchan_addr'],'guest:') === 0) { + $connect = false; + } + if(array_key_exists('channel_id',$xchan)) App::$profile_uid = $xchan['channel_id']; diff --git a/include/conversation.php b/include/conversation.php index ce0467770..3834d9866 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -1301,7 +1301,9 @@ function status_editor($a, $x, $popup = false) { $id_select = ''; $webpage = ((x($x,'webpage')) ? $x['webpage'] : ''); - + + $feature_auto_save_draft = ((feature_enabled($x['profile_uid'], 'auto_save_draft')) ? "true" : "false"); + $tpl = get_markup_template('jot-header.tpl'); App::$page['htmlhead'] .= replace_macros($tpl, array( @@ -1323,6 +1325,7 @@ function status_editor($a, $x, $popup = false) { '$modalerroralbum' => t('Error getting album'), '$nocomment_enabled' => t('Comments enabled'), '$nocomment_disabled' => t('Comments disabled'), + '$auto_save_draft' => $feature_auto_save_draft, )); $tpl = get_markup_template('jot.tpl'); diff --git a/include/features.php b/include/features.php index 5481c37a4..c865f6754 100644 --- a/include/features.php +++ b/include/features.php @@ -44,7 +44,7 @@ function feature_level($feature,$def) { return $def; } -function get_features($filtered = true) { +function get_features($filtered = true, $level = (-1)) { $account = \App::get_account(); @@ -246,14 +246,23 @@ function get_features($filtered = true) { [ 'oauth_clients', - t('OAuth Clients'), - t('Manage authenticatication tokens for mobile and remote apps.'), + t('OAuth1 Clients'), + t('Manage OAuth1 authenticatication tokens for mobile and remote apps.'), false, get_config('feature_lock','oauth_clients'), feature_level('oauth_clients',1), ], [ + 'oauth2_clients', + t('OAuth2 Clients'), + t('Manage OAuth2 authenticatication tokens for mobile and remote apps.'), + false, + get_config('feature_lock','oauth2_clients'), + feature_level('oauth2_clients',1), + ], + + [ 'access_tokens', t('Access Tokens'), t('Create access tokens so that non-members can access private content.'), @@ -341,6 +350,15 @@ function get_features($filtered = true) { feature_level('suppress_duplicates',1), ], + [ + 'auto_save_draft', + t('Auto-save drafts of posts and comments'), + t('Automatically saves post and comment drafts in local browser storage to help prevent accidental loss of compositions'), + true, + get_config('feature_lock','auto_save_draft'), + feature_level('auto_save_draft',1), + ], + ], // Network Tools @@ -490,7 +508,7 @@ function get_features($filtered = true) { $arr = $x['features']; - $techlevel = get_account_techlevel(); + $techlevel = (($level >= 0) ? $level : get_account_techlevel()); // removed any locked features and remove the entire category if this makes it empty diff --git a/include/import.php b/include/import.php index d8b7030b6..0d3fb8c32 100644 --- a/include/import.php +++ b/include/import.php @@ -99,7 +99,7 @@ function import_channel($channel, $account_id, $seize) { } if($clean) { - create_table_from_array('channel',$clean); + channel_store_lowlevel($clean); } $r = q("select * from channel where channel_account_id = %d and channel_guid = '%s' limit 1", @@ -180,7 +180,7 @@ function import_profiles($channel, $profiles) { $profile['thumb'] = z_root() . '/photo/' . basename($profile['thumb']); } - create_table_from_array('profile', $profile); + profile_store_lowlevel($profile); } } } diff --git a/include/items.php b/include/items.php index 6ddab9bf8..8bc4595b6 100755 --- a/include/items.php +++ b/include/items.php @@ -3504,11 +3504,14 @@ function item_getfeedattach($item) { } -function item_expire($uid,$days) { +function item_expire($uid,$days,$comment_days = 7) { if((! $uid) || ($days < 1)) return; + if(! $comment_days) + $comment_days = 7; + // $expire_network_only = save your own wall posts // and just expire conversations started by others // do not enable this until we can pass bulk delete messages through zot @@ -3527,6 +3530,7 @@ function item_expire($uid,$days) { $r = q("SELECT id FROM item WHERE uid = %d AND created < %s - INTERVAL %s + AND commented < %s - INTERVAL %s AND item_retained = 0 AND item_thread_top = 1 AND resource_type = '' @@ -3534,7 +3538,9 @@ function item_expire($uid,$days) { $sql_extra $item_normal LIMIT $expire_limit ", intval($uid), db_utcnow(), - db_quoteinterval(intval($days).' DAY') + db_quoteinterval(intval($days) . ' DAY'), + db_utcnow(), + db_quoteinterval(intval($comment_days) . ' DAY') ); if(! $r) diff --git a/include/network.php b/include/network.php index a49e5920d..db9a7d00a 100644 --- a/include/network.php +++ b/include/network.php @@ -1607,6 +1607,7 @@ function get_site_info() { 'register_policy' => $register_policy[get_config('system','register_policy')], 'invitation_only' => (bool) intval(get_config('system','invitation_only')), 'directory_mode' => $directory_mode[get_config('system','directory_mode')], + 'directory_server' => get_config('system','directory_server'), 'language' => get_config('system','language'), 'rss_connections' => (bool) intval(get_config('system','feed_contacts')), 'expiration' => $site_expire, diff --git a/include/plugin.php b/include/plugin.php index 62d443ab8..4545e1e8d 100755 --- a/include/plugin.php +++ b/include/plugin.php @@ -7,6 +7,27 @@ /** + * @brief Handle errors in plugin calls + * + * @param string $plugin name of the addon + * @param string $error_text text of error + * @param bool $uninstall uninstall plugin + */ +function handleerrors_plugin($plugin,$notice,$log,$uninstall=false){ + logger("Addons: [" . $plugin . "] Error: ".$log, LOGGER_ERROR); + if ($notice != '') { + notice("[" . $plugin . "] Error: ".$notice, LOGGER_ERROR); + } + + if ($uninstall) { + $idx = array_search($plugin, \App::$plugins); + unset(\App::$plugins[$idx]); + uninstall_plugin($plugin); + set_config("system","addon", implode(", ",\App::$plugins)); + } +} + +/** * @brief Unloads an addon. * * @param string $plugin name of the addon @@ -17,7 +38,11 @@ function unload_plugin($plugin){ @include_once('addon/' . $plugin . '/' . $plugin . '.php'); if(function_exists($plugin . '_unload')) { $func = $plugin . '_unload'; - $func(); + try { + $func(); + } catch (Exception $e) { + handleerrors_plugin($plugin,"Unable to unload.",$e->getMessage()); + } } } @@ -38,7 +63,11 @@ function uninstall_plugin($plugin) { @include_once('addon/' . $plugin . '/' . $plugin . '.php'); if(function_exists($plugin . '_uninstall')) { $func = $plugin . '_uninstall'; - $func(); + try { + $func(); + } catch (Exception $e) { + handleerrors_plugin($plugin,"Unable to uninstall.","Unable to run _uninstall : ".$e->getMessage()); + } } q("DELETE FROM addon WHERE aname = '%s' ", @@ -64,7 +93,12 @@ function install_plugin($plugin) { @include_once('addon/' . $plugin . '/' . $plugin . '.php'); if(function_exists($plugin . '_install')) { $func = $plugin . '_install'; - $func(); + try { + $func(); + } catch (Exception $e) { + handleerrors_plugin($plugin,"Install failed.","Install failed : ".$e->getMessage()); + return; + } } $plugin_admin = (function_exists($plugin . '_plugin_admin') ? 1 : 0); @@ -94,7 +128,12 @@ function load_plugin($plugin) { @include_once('addon/' . $plugin . '/' . $plugin . '.php'); if(function_exists($plugin . '_load')) { $func = $plugin . '_load'; - $func(); + try { + $func(); + } catch (Exception $e) { + handleerrors_plugin($plugin,"Unable to load.","FAILED loading : ".$e->getMessage(),true); + return; + } // we can add the following with the previous SQL // once most site tables have been updated. @@ -108,7 +147,7 @@ function load_plugin($plugin) { return true; } else { - logger("Addons: FAILED loading " . $plugin); + logger("Addons: FAILED loading " . $plugin . " (missing _load function)"); return false; } } @@ -160,11 +199,21 @@ function reload_plugins() { if(function_exists($pl . '_unload')) { $func = $pl . '_unload'; - $func(); + try { + $func(); + } catch (Exception $e) { + handleerrors_plugin($plugin,"","UNLOAD FAILED (uninstalling) : ".$e->getMessage(),true); + continue; + } } if(function_exists($pl . '_load')) { $func = $pl . '_load'; - $func(); + try { + $func(); + } catch (Exception $e) { + handleerrors_plugin($plugin,"","LOAD FAILED (uninstalling): ".$e->getMessage(),true); + continue; + } } q("UPDATE addon SET tstamp = %d WHERE id = %d", intval($t), diff --git a/include/taxonomy.php b/include/taxonomy.php index 4a3818096..d9bf3ecc4 100644 --- a/include/taxonomy.php +++ b/include/taxonomy.php @@ -212,8 +212,9 @@ function card_tagadelic($uid, $count = 0, $authors = '', $owner = '', $flags = 0 if(! perm_is_allowed($uid,get_observer_hash(),'view_pages')) return array(); + $item_normal = " and item.item_hidden = 0 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 and item.obj_type != 'http://purl.org/zot/activity/file' "; - $item_normal = item_normal(); $sql_options = item_permissions_sql($uid); $count = intval($count); @@ -236,15 +237,16 @@ function card_tagadelic($uid, $count = 0, $authors = '', $owner = '', $flags = 0 // 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 + and otype = %d and item_type = %d $sql_options $item_normal group by term order by total desc %s", intval($uid), intval($type), intval(TERM_OBJ_POST), - intval($restrict), + intval(ITEM_TYPE_CARD), ((intval($count)) ? "limit $count" : '') ); @@ -263,7 +265,9 @@ function article_tagadelic($uid, $count = 0, $authors = '', $owner = '', $flags return array(); - $item_normal = item_normal(); + $item_normal = " and item.item_hidden = 0 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 and item.obj_type != 'http://purl.org/zot/activity/file' "; + $sql_options = item_permissions_sql($uid); $count = intval($count); @@ -288,13 +292,13 @@ function article_tagadelic($uid, $count = 0, $authors = '', $owner = '', $flags // 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 + and otype = %d and item_type = %d $sql_options $item_normal group by term order by total desc %s", intval($uid), intval($type), intval(TERM_OBJ_POST), - intval($restrict), + intval(ITEM_TYPE_ARTICLE), ((intval($count)) ? "limit $count" : '') ); diff --git a/include/text.php b/include/text.php index 255d02c7c..13c4bb819 100644 --- a/include/text.php +++ b/include/text.php @@ -2251,7 +2251,7 @@ function xchan_query(&$items, $abook = true, $effective_uid = 0) { $chans = q("select xchan.*,hubloc.* from xchan left join hubloc on hubloc_hash = xchan_hash where xchan_hash in (" . protect_sprintf(implode(',', $arr)) . ") and hubloc_primary = 1"); } - $xchans = q("select * from xchan where xchan_hash in (" . protect_sprintf(implode(',',$arr)) . ") and xchan_network in ('rss','unknown')"); + $xchans = q("select * from xchan where xchan_hash in (" . protect_sprintf(implode(',',$arr)) . ") and xchan_network in ('rss','unknown', 'anon')"); if(! $chans) $chans = $xchans; else diff --git a/include/zot.php b/include/zot.php index 25ea9b8fb..2ad43f0e5 100644 --- a/include/zot.php +++ b/include/zot.php @@ -3855,11 +3855,14 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { intval($channel['channel_id']) ); if(! $x) { - q("insert into profile ( profile_guid, aid, uid ) values ('%s', %d, %d)", - dbesc($profile['profile_guid']), - intval($channel['channel_account_id']), - intval($channel['channel_id']) + profile_store_lowlevel( + [ + 'aid' => $channel['channel_account_id'], + 'uid' => $channel['channel_id'], + 'profile_guid' => $profile['profile_guid'], + ] ); + $x = q("select * from profile where profile_guid = '%s' and uid = %d limit 1", dbesc($profile['profile_guid']), intval($channel['channel_id']) diff --git a/view/fr/hstrings.php b/view/fr/hstrings.php index d883cc240..fd8676826 100644 --- a/view/fr/hstrings.php +++ b/view/fr/hstrings.php @@ -3046,3 +3046,22 @@ App::$strings["Logged out."] = "Deconnecté."; App::$strings["Failed authentication"] = "Échec de l'authentification"; App::$strings["Help:"] = "Aide :"; App::$strings["Not Found"] = "Introuvable"; +App::$strings["This site requires email verification. After completing this form, please check your email for further instructions."] = "Ce site nécessite une vérification par courriel. Après avoir rempli ce formulaire, veuillez consulter votre courriel pour obtenir des instructions supplémentaires."; +App::$strings["New Member Links"] = "Liens pour les nouveaux membres"; +App::$strings["Profile Creation"] = "Création de profil"; +App::$strings["Upload profile photo"] = "Téléverser la photo du profil"; +App::$strings["Upload cover photo"] = "Téléverser la photo de couverture"; +App::$strings["Find and Connect with others"] = "Trouver et contacter d'autres personnes"; +App::$strings["View the directory"] = "Voir l'annuaire"; +App::$strings["View friend suggestions"] = "Voir les suggestions d'amis"; +App::$strings["Manage your connections"] = "Gérez vos connexions"; +App::$strings["Communicate"] = "Communiquer"; +App::$strings["View your channel homepage"] = "Voir la page d'accueil de votre canal"; +App::$strings["View your network stream"] = "Visualisez votre flux réseau"; +App::$strings["View public stream"] = "Voir le flux public"; +App::$strings["Activity"] = "Activité"; +App::$strings["Public Stream"] = "Flux public"; +App::$strings["New Events"] = "Nouveaux événements"; +App::$strings["Add Apps"] = "Ajouter des applications"; +App::$strings["Arrange Apps"] = "Organiser les applications"; + diff --git a/view/js/autocomplete.js b/view/js/autocomplete.js index 07b9bc449..69ccd1fe5 100644 --- a/view/js/autocomplete.js +++ b/view/js/autocomplete.js @@ -243,8 +243,19 @@ function string2bb(element) { replace: basic_replace, template: contact_format, }; + + // Autocomplete forums + forums = { + match: /(^\!)([^\n]{3,})$/, + index: 2, + search: function(term, callback) { contact_search(term, callback, backend_url, 'f', [], spinelement='#nav-search-spinner'); }, + replace: basic_replace, + template: contact_format + }; + + this.attr('autocomplete', 'off'); - var a = this.textcomplete([contacts], {className:'acpopup', maxCount:100, zIndex: 1020, appendTo:'nav'}); + var a = this.textcomplete([contacts,forums], {className:'acpopup', maxCount:100, zIndex: 1020, appendTo:'nav'}); a.on('textComplete:select', function(e, value, strategy) { submit_form(this); }); }; })( jQuery ); diff --git a/view/js/main.js b/view/js/main.js index e700c4e6e..ab950e4fb 100644 --- a/view/js/main.js +++ b/view/js/main.js @@ -25,13 +25,14 @@ var liveRecurse = 0; var savedTitle = ''; var initialLoad = true; -// Clear the session storage if we switch channel or log out +// Clear the session and local storage if we switch channel or log out var cache_uid = ''; if(sessionStorage.getItem('uid') !== null) { cache_uid = sessionStorage.getItem('uid'); } if(cache_uid !== localUser.toString()) { sessionStorage.clear(); + localStorage.clear(); sessionStorage.setItem('uid', localUser.toString()); } @@ -166,6 +167,16 @@ function handle_comment_form(e) { $('#' + commentElm).addClass('expanded').removeAttr('placeholder'); $('#' + commentElm).attr('tabindex','9'); $('#' + submitElm).attr('tabindex','10'); + + if(auto_save_draft) { + var commentBody = localStorage.getItem("comment_body"); + if(commentBody && $('#' + commentElm).val() === '') { + $('#' + commentElm).val(commentBody); + } + } else { + localStorage.removeItem("comment_body"); + } + form.find(':not(:visible)').show(); } @@ -185,6 +196,30 @@ function handle_comment_form(e) { form.find(':not(.comment-edit-text)').hide(); } }); + + var commentSaveTimer = null; + var emptyCommentElm = form.find('.comment-edit-text').attr('id'); + $(document).on('focusout','#' + emptyCommentElm,function(e){ + if(commentSaveTimer) + clearTimeout(commentSaveTimer); + commentSaveChanges(true); + commentSaveTimer = null; + }); + + $(document).on('focusin','#' + emptyCommentElm,function(e){ + commentSaveTimer = setTimeout(function () { + commentSaveChanges(false); + },10000); + }); + + function commentSaveChanges(isFinal = false) { + if(auto_save_draft) { + localStorage.setItem("comment_body", $('#' + emptyCommentElm).val()); + if( !isFinal) { + commentSaveTimer = setTimeout(commentSaveChanges,10000); + } + } + } } function commentClose(obj, id) { @@ -1106,6 +1141,7 @@ function post_comment(id) { $("#comment-edit-form-" + id).serialize(), function(data) { if(data.success) { + localStorage.removeItem("comment_body"); $("#comment-edit-preview-" + id).hide(); $("#comment-edit-wrapper-" + id).hide(); $("#comment-edit-text-" + id).val(''); diff --git a/view/js/mod_help.js b/view/js/mod_help.js index 8ee89dd61..a37207807 100644 --- a/view/js/mod_help.js +++ b/view/js/mod_help.js @@ -53,7 +53,7 @@ $(document).ready(function () { .removeClass('selected-doco-nav') .eq(i).addClass('selected-doco-nav'); if (typeof ($('#doco-side-toc li').eq(i).find('a').attr('href').split('#')[1]) !== 'undefined') { - window.history.pushState({}, '', location.href.split('#')[0] + '#' + $('#doco-side-toc li').eq(i).find('a').attr('href').split('#')[1]); + window.history.replaceState({}, '', location.href.split('#')[0] + '#' + $('#doco-side-toc li').eq(i).find('a').attr('href').split('#')[1]); } } }); @@ -100,7 +100,7 @@ $(document).ready(function () { } // Update the address bar to reflect the loaded language - window.history.pushState({}, '', '/' + pathParts.join('/')); + window.history.replaceState({}, '', '/' + pathParts.join('/')); // Highlight the language in the language selector that is currently viewed $('.lang-selector').find('.lang-choice:contains("' + help_language + '")').addClass('active'); diff --git a/view/tpl/admin_site.tpl b/view/tpl/admin_site.tpl index fb96ef2cf..7e99b2c86 100755 --- a/view/tpl/admin_site.tpl +++ b/view/tpl/admin_site.tpl @@ -101,6 +101,7 @@ {{include file="field_input.tpl" field=$maxloadavg}} {{include file="field_input.tpl" field=$abandon_days}} {{include file="field_input.tpl" field=$default_expire_days}} + {{include file="field_input.tpl" field=$active_expire_days}} <div class="submit"><input type="submit" name="page_site" value="{{$submit}}" /></div> diff --git a/view/tpl/cloud_header.tpl b/view/tpl/cloud_header.tpl index 307dc8956..642fb1866 100644 --- a/view/tpl/cloud_header.tpl +++ b/view/tpl/cloud_header.tpl @@ -6,7 +6,7 @@ <a href="/sharedwithme" class="btn btn-sm btn-outline-secondary"><i class="fa fa-cloud-download"></i> {{$shared}}</a> {{/if}} <button id="files-create-btn" class="btn btn-sm btn-primary" onclick="openClose('files-mkdir-tools'); closeMenu('files-upload-tools');"><i class="fa fa-folder-o"></i> {{$create}}</button> - <button id="files-upload-btn" class="btn btn-sm btn-success" onclick="openClose('files-upload-tools'); closeMenu('files-mkdir-tools');"><i class="fa fa-arrow-circle-o-up"></i> {{$upload}}</button> + <button id="files-upload-btn" class="btn btn-sm btn-success" onclick="openClose('files-upload-tools'); closeMenu('files-mkdir-tools');"><i class="fa fa-plus-circle"></i> {{$upload}}</button> {{/if}} </div> diff --git a/view/tpl/comment_item.tpl b/view/tpl/comment_item.tpl index 3b51971ec..23594677c 100755 --- a/view/tpl/comment_item.tpl +++ b/view/tpl/comment_item.tpl @@ -1,3 +1,6 @@ + <script> + var auto_save_draft = {{$auto_save_draft}}; + </script> {{if $threaded}} <div class="comment-wwedit-wrapper threaded" id="comment-edit-wrapper-{{$id}}" style="display: block;"> {{else}} diff --git a/view/tpl/jot-header.tpl b/view/tpl/jot-header.tpl index c1dab52d5..991a4c8b1 100755 --- a/view/tpl/jot-header.tpl +++ b/view/tpl/jot-header.tpl @@ -560,3 +560,91 @@ $( document ).on( "click", ".wall-item-delete-link,.page-delete-link,.layout-del } }); </script> + + +<script> + var postSaveTimer = null; + + function postSaveChanges(action, type) { + if({{$auto_save_draft}}) { + + if(action != 'clean') { + localStorage.setItem("post_title", $("#jot-title").val()); + localStorage.setItem("post_body", $("#profile-jot-text").val()); + if($("#jot-category").length) + localStorage.setItem("post_category", $("#jot-category").val()); + } + + if(action == 'start') { + postSaveTimer = setTimeout(function () { + postSaveChanges('start'); + },10000); + } + + if(action == 'stop') { + clearTimeout(postSaveTimer); + postSaveTimer = null; + } + + if(action == 'clean') { + clearTimeout(postSaveTimer); + postSaveTimer = null; + localStorage.removeItem("post_title"); + localStorage.removeItem("post_body"); + localStorage.removeItem("post_category"); + } + } + + } + + $(document).ready(function() { + + var cleaned = false; + + if({{$auto_save_draft}}) { + var postTitle = localStorage.getItem("post_title"); + var postBody = localStorage.getItem("post_body"); + var postCategory = (($("#jot-category").length) ? localStorage.getItem("post_category") : ''); + var openEditor = false; + + if(postTitle) { + $('#jot-title').val(postTitle); + openEditor = true; + } + if(postBody) { + $('#profile-jot-text').val(postBody); + openEditor = true; + } + if(postCategory) { + var categories = postCategory.split(','); + categories.forEach(function(cat) { + $('#jot-category').tagsinput('add', cat); + }); + openEditor = true; + } + if(openEditor) { + initEditor(); + } + } else { + postSaveChanges('clean'); + } + + $(document).on('submit', '#profile-jot-form', function() { + postSaveChanges('clean'); + cleaned = true; + }); + + $(document).on('focusout',"#profile-jot-wrapper",function(e){ + if(! cleaned) + postSaveChanges('stop'); + }); + + $(document).on('focusin',"#profile-jot-wrapper",function(e){ + postSaveTimer = setTimeout(function () { + postSaveChanges('start'); + },10000); + }); + + + }); +</script> diff --git a/view/tpl/photo_album.tpl b/view/tpl/photo_album.tpl index 678e790ac..de59809f4 100755 --- a/view/tpl/photo_album.tpl +++ b/view/tpl/photo_album.tpl @@ -9,7 +9,7 @@ <i class="fa fa-pencil btn btn-outline-secondary btn-sm" title="{{$album_edit.0}}" onclick="openClose('photo-album-edit-wrapper'); closeMenu('photo-upload-form');"></i> {{/if}} {{if $can_post}} - <button class="btn btn-sm btn-success btn-sm" title="{{$usage}}" onclick="openClose('photo-upload-form'); {{if $album_edit.1}}closeMenu('photo-album-edit-wrapper');{{/if}}"><i class="fa fa-arrow-circle-o-up"></i> {{$upload.0}}</button> + <button class="btn btn-sm btn-success btn-sm" title="{{$usage}}" onclick="openClose('photo-upload-form'); {{if $album_edit.1}}closeMenu('photo-album-edit-wrapper');{{/if}}"><i class="fa fa-plus-circle"></i> {{$upload.0}}</button> {{/if}} </div> </div> diff --git a/view/tpl/photos_recent.tpl b/view/tpl/photos_recent.tpl index a9574aade..d24b362f8 100755 --- a/view/tpl/photos_recent.tpl +++ b/view/tpl/photos_recent.tpl @@ -2,7 +2,7 @@ <div class="section-title-wrapper"> <div class="pull-right"> {{if $can_post}} - <button class="btn btn-sm btn-success acl-form-trigger" title="{{$usage}}" onclick="openClose('photo-upload-form');" data-form_id="photos-upload-form"><i class="fa fa-arrow-circle-o-up"></i> {{$upload.0}}</button> + <button class="btn btn-sm btn-success acl-form-trigger" title="{{$usage}}" onclick="openClose('photo-upload-form');" data-form_id="photos-upload-form"><i class="fa fa-plus-circle"></i> {{$upload.0}}</button> {{/if}} </div> <h2>{{$title}}</h2> diff --git a/view/tpl/settings.tpl b/view/tpl/settings.tpl index 0055fa265..d258f1992 100755 --- a/view/tpl/settings.tpl +++ b/view/tpl/settings.tpl @@ -145,6 +145,7 @@ {{if $vnotify13}} {{include file="field_intcheckbox.tpl" field=$vnotify13}} {{/if}} + {{include file="field_intcheckbox.tpl" field=$vnotify14}} {{include file="field_intcheckbox.tpl" field=$always_show_in_notices}} {{include file="field_input.tpl" field=$evdays}} </div> diff --git a/view/tpl/settings_features.tpl b/view/tpl/settings_features.tpl index f8c162e17..998199c8e 100755 --- a/view/tpl/settings_features.tpl +++ b/view/tpl/settings_features.tpl @@ -1,9 +1,31 @@ +<script> + $(document).ready(function() { + $('#id_techlevel').change(function() { + var techlvl = $('#id_techlevel').val(); + window.location.href='{{$baseurl}}/settings/features?f=&techlevel=' + techlvl; + }); + }); +</script> + <div class="generic-content-wrapper"> <div class="section-title-wrapper"> <h2>{{$title}}</h2> </div> <form action="settings/features" method="post" autocomplete="off"> <input type='hidden' name='form_security_token' value='{{$form_security_token}}'> + {{if ! $techlock}} + <div class="section-content-tools-wrapper"> + {{include file="field_select.tpl" field=$techlevel}} + </div> + {{else}} + <input type="hidden" name="techlevel" value="{{$techlevel.2}}" /> + {{/if}} + + {{if $hiddens}} + {{foreach $hiddens as $k => $v}} + <input type="hidden" name="feature_{{$k}}" value="{{$v}}" /> + {{/foreach}} + {{/if}} <div class="panel-group" id="settings" role="tablist" aria-multiselectable="true"> {{foreach $features as $g => $f}} <div class="panel"> diff --git a/view/tpl/settings_oauth2.tpl b/view/tpl/settings_oauth2.tpl new file mode 100755 index 000000000..882d34ea9 --- /dev/null +++ b/view/tpl/settings_oauth2.tpl @@ -0,0 +1,35 @@ +<div class="generic-content-wrapper"> +<div class="section-title-wrapper"> + <h2>{{$title}}</h2> +</div> + +<div class="section-content-tools-wrapper"> +<form action="settings/oauth2" method="post" autocomplete="off"> +<input type='hidden' name='form_security_token' value='{{$form_security_token}}'> + + <div id="profile-edit-links"> + <ul> + <li> + <a id="profile-edit-view-link" href="{{$baseurl}}/settings/oauth2/add">{{$add}}</a> + </li> + </ul> + </div> + + {{foreach $apps as $app}} + <div class='oauthapp'> + {{if $app.client_id}}<h4>{{$app.client_id}}</h4>{{else}}<h4>{{$noname}}</h4>{{/if}} + {{if $app.my}} + {{if $app.oauth_token}} + <div class="settings-submit-wrapper" ><button class="settings-submit" type="submit" name="remove" value="{{$app.oauth_token}}">{{$remove}}</button></div> + {{/if}} + {{/if}} + {{if $app.my}} + <a href="{{$baseurl}}/settings/oauth2/edit/{{$app.client_id}}" title="{{$edit}}"><i class="fa fa-pencil btn btn-outline-secondary"></i></a> + <a href="{{$baseurl}}/settings/oauth2/delete/{{$app.client_id}}?t={{$form_security_token}}" title="{{$delete}}"><i class="fa fa-trash-o btn btn-outline-secondary"></i></a> + {{/if}} + </div> + {{/foreach}} + +</form> +</div> +</div> diff --git a/view/tpl/settings_oauth2_edit.tpl b/view/tpl/settings_oauth2_edit.tpl new file mode 100755 index 000000000..399c64977 --- /dev/null +++ b/view/tpl/settings_oauth2_edit.tpl @@ -0,0 +1,21 @@ +<div class="generic-content-wrapper"> + <div class="section-title-wrapper"> + <h2>{{$title}}</h2> + </div> +<div class="section-content-tools-wrapper"> +<form method="POST"> +<input type='hidden' name='form_security_token' value='{{$form_security_token}}'> +{{include file="field_input.tpl" field=$name}} +{{include file="field_input.tpl" field=$secret}} +{{include file="field_input.tpl" field=$redirect}} +{{include file="field_input.tpl" field=$grant}} +{{include file="field_input.tpl" field=$scope}} + +<div class="settings-submit-wrapper" > +<input type="submit" name="submit" class="settings-submit" value="{{$submit}}" /> +<input type="submit" name="cancel" class="settings-submit" value="{{$cancel}}" /> +</div> + +</form> +</div> +</div> diff --git a/view/tpl/settings_oauth_edit.tpl b/view/tpl/settings_oauth_edit.tpl index b94dec48a..e44b44723 100755 --- a/view/tpl/settings_oauth_edit.tpl +++ b/view/tpl/settings_oauth_edit.tpl @@ -5,7 +5,6 @@ <div class="section-content-tools-wrapper"> <form method="POST"> <input type='hidden' name='form_security_token' value='{{$form_security_token}}'> - {{include file="field_input.tpl" field=$name}} {{include file="field_input.tpl" field=$key}} {{include file="field_input.tpl" field=$secret}} |