diff --git a/Zotlabs/Module/Appman.php b/Zotlabs/Module/Appman.php index 96e4cdd87..ba2a64f35 100644 --- a/Zotlabs/Module/Appman.php +++ b/Zotlabs/Module/Appman.php @@ -1,5 +1,6 @@ - escape_tags($_REQUEST['photo']), 'version' => escape_tags($_REQUEST['version']), 'price' => escape_tags($_REQUEST['price']), - 'sig' => escape_tags($_REQUEST['sig']) + 'requires' => escape_tags($_REQUEST['requires']), + 'system' => intval($_REQUEST['system']), + 'sig' => escape_tags($_REQUEST['sig']), + 'categories' => escape_tags($_REQUEST['categories']) ); $_REQUEST['appid'] = app_install(local_channel(),$arr); @@ -64,7 +68,7 @@ class Appman extends \Zotlabs\Web\Controller { } - function get() { + function get() { if(! local_channel()) { notice( t('Permission denied.') . EOL); @@ -79,8 +83,23 @@ class Appman extends \Zotlabs\Web\Controller { dbesc($_REQUEST['appid']), dbesc(local_channel()) ); - if($r) + if($r) { $app = $r[0]; + + $term = q("select * from term where otype = %d and oid = %d", + intval(TERM_OBJ_APP), + intval($r[0]['id']) + ); + if($term) { + $app['categories'] = ''; + foreach($term as $t) { + if($app['categories']) + $app['categories'] .= ','; + $app['categories'] .= $t['term']; + } + } + } + $embed = array('embed', t('Embed code'), app_encode($app,true),'', 'onclick="this.select();"'); } @@ -96,9 +115,12 @@ class Appman extends \Zotlabs\Web\Controller { '$url' => array('url', t('Location (URL) of app'),(($app) ? $app['app_url'] : ''), t('Required')), '$desc' => array('desc', t('Description'),(($app) ? $app['app_desc'] : ''), ''), '$photo' => array('photo', t('Photo icon URL'),(($app) ? $app['app_photo'] : ''), t('80 x 80 pixels - optional')), + '$categories' => array('categories',t('Categories (optional, comma separated list)'),(($app) ? $app['categories'] : ''),''), '$version' => array('version', t('Version ID'),(($app) ? $app['app_version'] : ''), ''), '$price' => array('price', t('Price of app'),(($app) ? $app['app_price'] : ''), ''), '$page' => array('page', t('Location (URL) to purchase app'),(($app) ? $app['app_page'] : ''), ''), + '$system' => (($app) ? intval($app['app_system']) : 0), + '$requires' => (($app) ? $app['app_requires'] : ''), '$embed' => $embed, '$submit' => t('Submit') )); diff --git a/Zotlabs/Module/Apps.php b/Zotlabs/Module/Apps.php index 4066966ca..33259b319 100644 --- a/Zotlabs/Module/Apps.php +++ b/Zotlabs/Module/Apps.php @@ -17,16 +17,21 @@ class Apps extends \Zotlabs\Web\Controller { $apps = array(); - $syslist = get_system_apps(); if(local_channel()) { - $list = app_list(local_channel()); + import_system_apps(); + $syslist = array(); + $list = app_list(local_channel(), false, $_GET['cat']); if($list) { foreach($list as $x) { $syslist[] = app_encode($x); } } + translate_system_apps($syslist); } + else + $syslist = get_system_apps(true); + usort($syslist,'app_name_compare'); // logger('apps: ' . print_r($syslist,true)); @@ -37,6 +42,7 @@ class Apps extends \Zotlabs\Web\Controller { return replace_macros(get_markup_template('myapps.tpl'), array( '$sitename' => get_config('system','sitename'), + '$cat' => ((array_key_exists('cat',$_GET) && $_GET['cat']) ? ' - ' . escape_tags($_GET['cat']) : ''), '$title' => t('Apps'), '$apps' => $apps, )); diff --git a/Zotlabs/Module/Channel.php b/Zotlabs/Module/Channel.php index 36f13e775..d0c6d83d8 100644 --- a/Zotlabs/Module/Channel.php +++ b/Zotlabs/Module/Channel.php @@ -126,13 +126,21 @@ function get($update = 0, $load = false) { if($perms['post_wall']) { + // I'm trying to make two points in this description text - warn about finality of wall + // post permissions, and try to clear up confusion that these permissions set who is + // *shown* the post, istead of who is able to see the post, i.e. make it clear that clicking + // the "Show" button on a group does not post it to the feed of people in that group, it + // mearly allows those people to view the post if they are viewing/following this channel. + $aclDesc = t('Post permissions cannot be changed after a post is sent.These permissions set who is allowed to view the post.'); + $aclContextHelpCmd = 'acl_dialog_post'; + $x = array( 'is_owner' => $is_owner, 'allow_location' => ((($is_owner || $observer) && (intval(get_pconfig(\App::$profile['profile_uid'],'system','use_browser_location')))) ? true : false), 'default_location' => (($is_owner) ? \App::$profile['channel_location'] : ''), 'nickname' => \App::$profile['channel_address'], 'lockstate' => (((strlen(\App::$profile['channel_allow_cid'])) || (strlen(\App::$profile['channel_allow_gid'])) || (strlen(\App::$profile['channel_deny_cid'])) || (strlen(\App::$profile['channel_deny_gid']))) ? 'lock' : 'unlock'), - 'acl' => (($is_owner) ? populate_acl($channel_acl,true,((\App::$profile['channel_r_stream'] & PERMS_PUBLIC) ? t('Public') : '')) : ''), + 'acl' => (($is_owner) ? populate_acl($channel_acl,true,((\App::$profile['channel_r_stream'] & PERMS_PUBLIC) ? t('Public') : ''), $aclDesc, $aclContextHelpCmd) : ''), 'showacl' => (($is_owner) ? 'yes' : ''), 'bang' => '', 'visitor' => (($is_owner || $observer) ? true : false), diff --git a/Zotlabs/Module/Editblock.php b/Zotlabs/Module/Editblock.php index 2821b3fe0..0204d0994 100644 --- a/Zotlabs/Module/Editblock.php +++ b/Zotlabs/Module/Editblock.php @@ -4,46 +4,43 @@ namespace Zotlabs\Module; require_once('include/identity.php'); require_once('include/acl_selectors.php'); - class Editblock extends \Zotlabs\Web\Controller { function init() { - + if(argc() > 1 && argv(1) === 'sys' && is_site_admin()) { $sys = get_sys_channel(); if($sys && intval($sys['channel_id'])) { \App::$is_sys = true; } } - + if(argc() > 1) $which = argv(1); else return; - + profile_load($a,$which); - + } - - - - function get() { - + + function get() { + if(! \App::$profile) { notice( t('Requested profile is not available.') . EOL ); \App::$error = 404; return; } - + $which = argv(1); - + $uid = local_channel(); $owner = 0; $channel = null; $observer = \App::get_observer(); - + $channel = \App::get_channel(); - + if(\App::$is_sys && is_site_admin()) { $sys = get_sys_channel(); if($sys && intval($sys['channel_id'])) { @@ -52,7 +49,7 @@ class Editblock extends \Zotlabs\Web\Controller { $observer = $sys; } } - + if(! $owner) { // Figure out who the page owner is. $r = q("select channel_id from channel where channel_address = '%s'", @@ -62,27 +59,26 @@ class Editblock extends \Zotlabs\Web\Controller { $owner = intval($r[0]['channel_id']); } } - + $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 = ''; - + // Figure out which post we're editing $post_id = ((argc() > 2) ? intval(argv(2)) : 0); - - + if(! ($post_id && $owner)) { notice( t('Item not found') . EOL); return; } - + $itm = q("SELECT * FROM `item` WHERE `id` = %d and uid = %s LIMIT 1", intval($post_id), intval($owner) @@ -98,20 +94,20 @@ class Editblock extends \Zotlabs\Web\Controller { notice( t('Item not found') . EOL); return; } - + $plaintext = true; - + $mimeselect = ''; $mimetype = $itm[0]['mimetype']; - + if($mimetype != 'text/bbcode') $plaintext = true; - + if(get_config('system','page_mimetype')) - $mimeselect = ''; + $mimeselect = ''; else $mimeselect = mimetype_select($itm[0]['uid'],$mimetype); - + \App::$page['htmlhead'] .= replace_macros(get_markup_template('jot-header.tpl'), array( '$baseurl' => z_root(), '$editselect' => (($plaintext) ? 'none' : '/(profile-jot-text|prvmail-text)/'), @@ -122,17 +118,17 @@ class Editblock extends \Zotlabs\Web\Controller { '$confirmdelete' => t('Delete block?'), '$bbco_autocomplete'=> (($mimetype == 'text/bbcode') ? 'bbcode' : 'comanche-block') )); - + $tpl = get_markup_template("jot.tpl"); - + $jotplugins = ''; $jotnets = ''; - + call_hooks('jot_tool', $jotplugins); call_hooks('jot_networks', $jotnets); - + $rp = 'blocks/' . $channel['channel_address']; - + $editor = replace_macros($tpl,array( '$return_path' => $rp, '$action' => 'item', @@ -174,18 +170,16 @@ class Editblock extends \Zotlabs\Web\Controller { '$defexpire' => '', '$bbcode' => (($mimetype == 'text/bbcode') ? true : false) )); - + $o .= replace_macros(get_markup_template('edpost_head.tpl'), array( '$title' => t('Edit Block'), '$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/Follow.php b/Zotlabs/Module/Follow.php index d3114557b..1701328bf 100644 --- a/Zotlabs/Module/Follow.php +++ b/Zotlabs/Module/Follow.php @@ -19,7 +19,10 @@ class Follow extends \Zotlabs\Web\Controller { $confirm = intval($_REQUEST['confirm']); $channel = \App::get_channel(); - + + // Warning: Do not edit the following line. The first symbol is UTF-8 @ + $url = str_replace('@','@',$url); + $result = new_contact($uid,$url,$channel,true,$confirm); if($result['success'] == false) { diff --git a/Zotlabs/Module/Network.php b/Zotlabs/Module/Network.php index 77353da05..e4b936dc2 100644 --- a/Zotlabs/Module/Network.php +++ b/Zotlabs/Module/Network.php @@ -154,6 +154,14 @@ class Network extends \Zotlabs\Web\Controller { } nav_set_selected('network'); + + // I'm trying to make two points in this description text - warn about finality of wall + // post permissions, and try to clear up confusion that these permissions set who is + // *shown* the post, istead of who is able to see the post, i.e. make it clear that clicking + // the "Show" button on a group does not post it to the feed of people in that group, it + // mearly allows those people to view the post if they are viewing/following this channel. + $aclDesc = t('Post permissions cannot be changed after a post is sent.These permissions set who is allowed to view the post.'); + $aclContextHelpCmd = 'acl_dialog_post'; $channel_acl = array( 'allow_cid' => $channel['channel_allow_cid'], @@ -161,7 +169,7 @@ class Network extends \Zotlabs\Web\Controller { 'deny_cid' => $channel['channel_deny_cid'], 'deny_gid' => $channel['channel_deny_gid'] ); - + $private_editing = ((($group || $cid) && (! intval($_GET['pf']))) ? true : false); $x = array( @@ -170,7 +178,7 @@ class Network extends \Zotlabs\Web\Controller { 'default_location' => $channel['channel_location'], 'nickname' => $channel['channel_address'], 'lockstate' => (($private_editing || $channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 'lock' : 'unlock'), - 'acl' => populate_acl((($private_editing) ? $def_acl : $channel_acl), true, (($channel['channel_r_stream'] & PERMS_PUBLIC) ? t('Public') : '')), + 'acl' => populate_acl((($private_editing) ? $def_acl : $channel_acl), true, (($channel['channel_r_stream'] & PERMS_PUBLIC) ? t('Public') : ''), $aclDesc, $aclContextHelpCmd), 'bang' => (($private_editing) ? '!' : ''), 'visitor' => true, 'profile_uid' => local_channel(), diff --git a/Zotlabs/Text/Tagadelic.php b/Zotlabs/Text/Tagadelic.php new file mode 100644 index 000000000..55ecf2d75 --- /dev/null +++ b/Zotlabs/Text/Tagadelic.php @@ -0,0 +1,44 @@ + + +
Sometimes called Access Control List, or ACL, the permissions set who is able to see your new post.
+ +Pressing the ACL button ( or ) beside the Submit button will display a dialog in which you can select what channels and/or privacy groups can see the post. You can also select who is explicitly denied access. For example, say you are planning a surprise party for a friend. You can send an invitation post to everyone in your Friends group except the friend you are surprising. In this case you "Show" the Friends group but "Don't show" that one person. + +
You are able to change permissons to your files, photos and the likes, but not to posts after you have saved them. The main reason is: Once you have saved a post it is being distributed either to the public channel and from there to other Hubzilla servers or to those you intended it to go. Just like you cannot reclaim something you gave to another person, you cannot change permissions to Hubzilla posts. We would need to track everywhere your posting goes, keep track of everyone you allowed to see it and then keep track of from whom to delete it.
+If a posting is public this is even harder, as the Hubzilla is a global network and there is no way to follow a post, let alone reclaim it reliably. Other networks that may receive your post have no reliable way to delete or reclaim the post.
\ No newline at end of file diff --git a/doc/to_do_code.bb b/doc/to_do_code.bb index d0860cf3a..577d0e66f 100644 --- a/doc/to_do_code.bb +++ b/doc/to_do_code.bb @@ -25,8 +25,8 @@ We need much more than this, but here are areas where developers can help. Pleas [li](less advanced) create a way to preview Comanche results on a preview page while editing on another page[/li] [li]External post connectors - create standard interface[/li] [li]External post connectors, add popular services[/li] -[li](in progress Habeas Codice) service classes - provide a pluggable subscription payment gateway for premium accounts[/li] -[li](in progress Habeas Codice) service classes - account overview page showing resources consumed by channel. With special consideration this page can also be accessed at a meta level by the site admin to drill down on problematic accounts/channels.[/li] +[li]service classes - provide a pluggable subscription payment gateway for premium accounts[/li] +[li]service classes - account overview page showing resources consumed by channel. With special consideration this page can also be accessed at a meta level by the site admin to drill down on problematic accounts/channels.[/li] [li]implement CalDAV/CardDAV sync[/li] [li]Uploads - integrate #^[url=https://github.com/blueimp/jQuery-File-Upload]https://github.com/blueimp/jQuery-File-Upload[/url][/li] [li]API extensions, for Twitter API - search, friending, threading. For Red API, lots of stuff[/li] @@ -36,7 +36,7 @@ We need much more than this, but here are areas where developers can help. Pleas [li]Customisable App collection pages[/li] [li]replace the tinymce visual editor and/or make the visual editor pluggable and responsive to different output formats. We probably want library/bbedit for bbcode. This needs a fair bit of work to catch up with our "enhanced bbcode", but start with images, links, bold and highlight and work from there.[/li] [li]Create mobile clients for the top platforms - which involves extending the API so that we can do stuff far beyond the current crop of Twitter/Statusnet clients. Ditto for mobile themes. We can probably use something like the Friendica Android app as a base to start from.[/li] -[li](in progress Habeas Codice) Implement owned and exchangeable "things".[/li] +[li]Implement owned and exchangeable "things".[/li] [li]Family Account creation - using service classes (an account holder can create a certain number of sub-accounts which are all tied to their subscription - if the subscription lapses they all go away).[/li] diff --git a/include/acl_selectors.php b/include/acl_selectors.php index 92f9436a2..584a70142 100644 --- a/include/acl_selectors.php +++ b/include/acl_selectors.php @@ -210,12 +210,24 @@ function fixacl(&$item) { $item = str_replace(array('<','>'),array('',''),$item); } -function populate_acl($defaults = null,$show_jotnets = true, $showall = '') { +/** +* Builds a modal dialog for editing permissions, using acl_selector.tpl as the template. +* +* @param array $default Optional access control list for the initial state of the dialog. +* @param boolean $show_jotnets Whether plugins for federated networks should be included in the permissions dialog +* @param string $showall_caption An optional caption to describe the scope of an unrestricted post. e.g. "Public" +* @param string $dialog_description Optional message to include at the top of the dialog. E.g. "Warning: Post permissions cannot be changed once sent". +* @param string $context_help Allows the dialog to present a help icon. E.g. "acl_dialog_post" +* @param boolean $readonly Not implemented yet. When implemented, the dialog will use acl_readonly.tpl instead, so that permissions may be viewed for posts that can no longer have their permissions changed. +* +* @return string html modal dialog build from acl_selector.tpl +*/ +function populate_acl($defaults = null,$show_jotnets = true, $showall_caption = '', $dialog_description = '', $context_help = '', $readonly = false) { $allow_cid = $allow_gid = $deny_cid = $deny_gid = false; - if(! $showall) - $showall = t('Visible to your default audience'); + if(! $showall_caption) + $showall_caption = t('Visible to your default audience'); if(is_array($defaults)) { $allow_cid = ((strlen($defaults['allow_cid'])) @@ -239,9 +251,12 @@ function populate_acl($defaults = null,$show_jotnets = true, $showall = '') { $tpl = get_markup_template("acl_selector.tpl"); $o = replace_macros($tpl, array( - '$showall' => $showall, + '$showall' => $showall_caption, + '$showlimited' => t("Limit access:"), + '$showlimitedDesc' => t('Select "Show" to allow viewing. "Don\'t show" lets you override and limit the scope of "Show".'), '$show' => t("Show"), '$hide' => t("Don't show"), + '$search' => t("Search"), '$allowcid' => json_encode($allow_cid), '$allowgid' => json_encode($allow_gid), '$denycid' => json_encode($deny_cid), @@ -249,7 +264,9 @@ function populate_acl($defaults = null,$show_jotnets = true, $showall = '') { '$jnetModalTitle' => t('Other networks and post services'), '$jotnets' => $jotnets, '$aclModalTitle' => t('Permissions'), - '$aclModalDismiss' => t('Close') + '$aclModalDesc' => $dialog_description, + '$aclModalDismiss' => t('Close'), + '$helpUrl' => (($context_help == '') ? '' : (z_root() . '/help/' . $context_help)) )); return $o; diff --git a/include/apps.php b/include/apps.php index fac58b850..7439be6d4 100644 --- a/include/apps.php +++ b/include/apps.php @@ -8,7 +8,7 @@ require_once('include/plugin.php'); require_once('include/identity.php'); -function get_system_apps() { +function get_system_apps($translate = true) { $ret = array(); if(is_dir('apps')) @@ -17,7 +17,7 @@ function get_system_apps() { $files = glob('app/*.apd'); if($files) { foreach($files as $f) { - $x = parse_app_description($f); + $x = parse_app_description($f,$translate); if($x) { $ret[] = $x; } @@ -28,7 +28,7 @@ function get_system_apps() { foreach($files as $f) { $n = basename($f,'.apd'); if(plugin_is_installed($n)) { - $x = parse_app_description($f); + $x = parse_app_description($f,$translate); if($x) { $ret[] = $x; } @@ -40,11 +40,37 @@ function get_system_apps() { } + +function import_system_apps() { + if(! local_channel()) + return; + + // Eventually we want to look at modification dates and update system apps. + + $installed = get_pconfig(local_channel(),'system','apps_installed'); + if($installed) + return; + $apps = get_system_apps(false); + if($apps) { + foreach($apps as $app) { + $app['uid'] = local_channel(); + $app['guid'] = hash('whirlpool',$app['name']); + $app['system'] = 1; + app_install(local_channel(),$app); + } + } + set_pconfig(local_channel(),'system','apps_installed',1); +} + + + + function app_name_compare($a,$b) { return strcmp($a['name'],$b['name']); } -function parse_app_description($f) { + +function parse_app_description($f,$translate = true) { $ret = array(); $baseurl = z_root(); @@ -116,7 +142,8 @@ function parse_app_description($f) { } } if($ret) { - translate_system_apps($ret); + if($translate) + translate_system_apps($ret); return $ret; } return false; @@ -126,8 +153,13 @@ function parse_app_description($f) { function translate_system_apps(&$arr) { $apps = array( 'Site Admin' => t('Site Admin'), - 'Bookmarks' => t('Bookmarks'), - 'Address Book' => t('Address Book'), + 'Bug Report' => t('Bug Report'), + 'View Bookmarks' => t('View Bookmarks'), + 'My Chatrooms' => t('My Chatrooms'), + 'Connections' => t('Connections'), + 'Firefox Share' => t('Firefox Share'), + 'Remote Diagnostics' => t('Remote Diagnostics'), + 'Suggest Channels' => t('Suggest Channels'), 'Login' => t('Login'), 'Channel Manager' => t('Channel Manager'), 'Grid' => t('Grid'), @@ -135,7 +167,7 @@ function translate_system_apps(&$arr) { 'Files' => t('Files'), 'Webpages' => t('Webpages'), 'Channel Home' => t('Channel Home'), - 'Profile' => t('Profile'), + 'View Profile' => t('View Profile'), 'Photos' => t('Photos'), 'Events' => t('Events'), 'Directory' => t('Directory'), @@ -264,6 +296,7 @@ function app_render($papp,$mode = 'view') { function app_install($uid,$app) { $app['uid'] = $uid; + if(app_installed($uid,$app)) $x = app_update($app); else @@ -274,9 +307,17 @@ function app_install($uid,$app) { dbesc($x['app_id']), intval($uid) ); - if($r) - build_sync_packet($uid,array('app' => $r[0])); - + if($r) { + if(! $r[0]['app_system']) { + if($app['categories'] && (! $app['term'])) { + $r[0]['term'] = q("select * from term where otype = %d and oid = d", + intval(TERM_OBJ_APP), + intval($r[0]['id']) + ); + build_sync_packet($uid,array('app' => $r[0])); + } + } + } return $x['app_id']; } return false; @@ -291,15 +332,28 @@ function app_destroy($uid,$app) { dbesc($app['guid']), intval($uid) ); - $x[0]['app_deleted'] = 1; + if($x) { + $x[0]['app_deleted'] = 1; + q("delete from term where otype = %d and oid = %d", + intval(TERM_OBJ_APP), + intval($x[0]['id']) + ); + if($x[0]['app_system']) { + $r = q("update app set app_deleted = 1 where app_id = '%s' and app_channel = %d", + dbesc($app['guid']), + intval($uid) + ); + } + else { + $r = q("delete from app where app_id = '%s' and app_channel = %d", + dbesc($app['guid']), + intval($uid) + ); - - $r = q("delete from app where app_id = '%s' and app_channel = %d", - dbesc($app['guid']), - intval($uid) - ); - - build_sync_packet($uid,array('app' => $x)); + // we don't sync system apps - they may be completely different on the other system + build_sync_packet($uid,array('app' => $x)); + } + } } } @@ -316,13 +370,40 @@ function app_installed($uid,$app) { } -function app_list($uid) { - $r = q("select * from app where app_channel = %d order by app_name asc", +function app_list($uid, $deleted = false, $cat = '') { + if($deleted) + $sql_extra = " and app_deleted = 1 "; + else + $sql_extra = " and app_deleted = 0 "; + + if($cat) { + $r = q("select oid from term where otype = %d and term = '%s'", + intval(TERM_OBJ_APP), + dbesc($cat) + ); + if(! $r) + return $r; + $sql_extra .= " and app.id in ( "; + $s = ''; + foreach($r as $rr) { + if($s) + $s .= ','; + $s .= intval($rr['oid']); + } + $sql_extra .= $s . ') '; + } + + $r = q("select * from app where app_channel = %d $sql_extra order by app_name asc", intval($uid) ); if($r) { for($x = 0; $x < count($r); $x ++) { - $r[$x]['type'] = 'personal'; + if(! $r[$x]['app_system']) + $r[$x]['type'] = 'personal'; + $r[$x]['term'] = q("select * from term where otype = %d and oid = %d", + intval(TERM_OBJ_APP), + intval($r[$x]['id']) + ); } } return($r); @@ -365,10 +446,12 @@ function app_store($arr) { $darray['app_price'] = ((x($arr,'price')) ? escape_tags($arr['price']) : ''); $darray['app_page'] = ((x($arr,'page')) ? escape_tags($arr['page']) : ''); $darray['app_requires'] = ((x($arr,'requires')) ? escape_tags($arr['requires']) : ''); + $darray['app_system'] = ((x($arr,'system')) ? intval($arr['system']) : 0); + $darray['app_deleted'] = ((x($arr,'deleted')) ? intval($arr['deleted']) : 0); $created = datetime_convert(); - $r = q("insert into app ( app_id, app_sig, app_author, app_name, app_desc, app_url, app_photo, app_version, app_channel, app_addr, app_price, app_page, app_requires, app_created, app_edited ) values ( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s' )", + $r = q("insert into app ( app_id, app_sig, app_author, app_name, app_desc, app_url, app_photo, app_version, app_channel, app_addr, app_price, app_page, app_requires, app_created, app_edited, app_system, app_deleted ) values ( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s', %d, %d )", dbesc($darray['app_id']), dbesc($darray['app_sig']), dbesc($darray['app_author']), @@ -383,12 +466,30 @@ function app_store($arr) { dbesc($darray['app_page']), dbesc($darray['app_requires']), dbesc($created), - dbesc($created) + dbesc($created), + intval($darray['app_system']), + intval($darray['app_deleted']) ); if($r) { $ret['success'] = true; $ret['app_id'] = $darray['app_id']; } + if($arr['categories']) { + $x = q("select id from app where app_id = '%s' and app_channel = %d limit 1", + dbesc($darray['app_id']), + intval($darray['app_channel']) + ); + $y = explode(',',$arr['categories']); + if($y) { + foreach($y as $t) { + $t = trim($t); + if($t) { + store_item_tag($darray['app_channel'],$x[0]['id'],TERM_OBJ_APP,TERM_CATEGORY,escape_tags($t),escape_tags(z_root() . '/apps/?f=&cat=' . escape_tags($t))); + } + } + } + } + return $ret; } @@ -420,10 +521,12 @@ function app_update($arr) { $darray['app_price'] = ((x($arr,'price')) ? escape_tags($arr['price']) : ''); $darray['app_page'] = ((x($arr,'page')) ? escape_tags($arr['page']) : ''); $darray['app_requires'] = ((x($arr,'requires')) ? escape_tags($arr['requires']) : ''); + $darray['app_system'] = ((x($arr,'system')) ? intval($arr['system']) : 0); + $darray['app_deleted'] = ((x($arr,'deleted')) ? intval($arr['deleted']) : 0); $edited = datetime_convert(); - $r = q("update app set app_sig = '%s', app_author = '%s', app_name = '%s', app_desc = '%s', app_url = '%s', app_photo = '%s', app_version = '%s', app_addr = '%s', app_price = '%s', app_page = '%s', app_requires = '%s', app_edited = '%s' where app_id = '%s' and app_channel = %d", + $r = q("update app set app_sig = '%s', app_author = '%s', app_name = '%s', app_desc = '%s', app_url = '%s', app_photo = '%s', app_version = '%s', app_addr = '%s', app_price = '%s', app_page = '%s', app_requires = '%s', app_edited = '%s', app_system = %d, app_deleted = %d where app_id = '%s' and app_channel = %d", dbesc($darray['app_sig']), dbesc($darray['app_author']), dbesc($darray['app_name']), @@ -436,6 +539,8 @@ function app_update($arr) { dbesc($darray['app_page']), dbesc($darray['app_requires']), dbesc($edited), + intval($darray['app_system']), + intval($darray['app_deleted']), dbesc($darray['app_id']), intval($darray['app_channel']) ); @@ -444,6 +549,28 @@ function app_update($arr) { $ret['app_id'] = $darray['app_id']; } + $x = q("select id from app where app_id = '%s' and app_channel = %d limit 1", + dbesc($darray['app_id']), + intval($darray['app_channel']) + ); + if($x) { + q("delete from term where otype = %d and oid = %d", + intval(TERM_OBJ_APP), + intval($x[0]['id']) + ); + if($arr['categories']) { + $y = explode(',',$arr['categories']); + if($y) { + foreach($y as $t) { + $t = trim($t); + if($t) { + store_item_tag($darray['app_channel'],$x[0]['id'],TERM_OBJ_APP,TERM_CATEGORY,escape_tags($t),escape_tags(z_root() . '/apps/?f=&cat=' . escape_tags($t))); + } + } + } + } + } + return $ret; } @@ -494,9 +621,29 @@ function app_encode($app,$embed = false) { if($app['app_requires']) $ret['requires'] = $app['app_requires']; + if($app['app_system']) + $ret['system'] = $app['app_system']; + + if($app['app_deleted']) + $ret['deleted'] = $app['app_deleted']; + + if($app['term']) { + $s = ''; + foreach($app['term'] as $t) { + if($s) + $s .= ','; + $s .= $t['term']; + } + $ret['categories'] = $s; + } + + if(! $embed) return $ret; + if(array_key_exists('categories',$ret)) + unset($ret['categories']); + $j = json_encode($ret); return '[app]' . chunk_split(base64_encode($j),72,"\n") . '[/app]'; diff --git a/include/contact_widgets.php b/include/contact_widgets.php index ba1241fcb..e62d57aa2 100644 --- a/include/contact_widgets.php +++ b/include/contact_widgets.php @@ -79,12 +79,14 @@ function categories_widget($baseurl,$selected = '') { where item.uid = %d and term.uid = item.uid and term.type = %d + and term.otype = %d and item.owner_xchan = '%s' and item.item_wall = 1 $item_normal 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)) { diff --git a/include/identity.php b/include/identity.php index c60c846c0..53bed7c0f 100644 --- a/include/identity.php +++ b/include/identity.php @@ -580,11 +580,18 @@ function identity_basic_export($channel_id, $items = false) { if($r) $ret['obj'] = $r; - $r = q("select * from app where app_channel = %d", + $r = q("select * from app where app_channel = %d and app_system = 0", intval($channel_id) ); - if($r) + if($r) { + for($x = 0; $x < count($r); $x ++) { + $r[$x]['term'] = q("select * from term where otype = %d and oid = %d", + intval(TERM_OBJ_APP), + intval($r[$x]['id']) + ); + } $ret['app'] = $r; + } $r = q("select * from chatroom where cr_uid = %d", intval($channel_id) diff --git a/include/import.php b/include/import.php index f6e62f9e0..321f275b1 100644 --- a/include/import.php +++ b/include/import.php @@ -297,8 +297,11 @@ function import_apps($channel,$apps) { if($channel && $apps) { foreach($apps as $app) { + $term = ((array_key_exists('term',$app) && is_array($app['term'])) ? $app['term'] : null); + unset($app['id']); unset($app['app_channel']); + unset($app['term']); $app['app_channel'] = $channel['channel_id']; @@ -307,6 +310,8 @@ function import_apps($channel,$apps) { $app['app_photo'] = $x[0]; } + $hash = $app['app_id']; + dbesc_array($app); $r = dbq("INSERT INTO app (`" . implode("`, `", array_keys($app)) @@ -314,6 +319,21 @@ function import_apps($channel,$apps) { . implode("', '", array_values($app)) . "')" ); + + if($term) { + $x = q("select * from app where app_id = '%s' and app_channel = %d limit 1", + dbesc($hash), + intval($channel['channel_id']) + ); + if($x) { + foreach($term as $t) { + store_item_tag($channel['channel_id'],$x[0]['id'],TERM_OBJ_APP,$t['type'],escape_tags($t['term']),escape_tags($t['url'])); + } + } + } + + + } } } @@ -325,16 +345,41 @@ function sync_apps($channel,$apps) { if($channel && $apps) { foreach($apps as $app) { - if(array_key_exists('app_deleted',$app) && $app['app_deleted'] && $app['app_id']) { + $exists = false; + $term = ((array_key_exists('term',$app)) ? $app['term'] : null); + + $x = q("select * from app where app_id = '%s' and app_channel = %d limit 1", + dbesc($app['app_id']), + intval($channel['channel_id']) + ); + if($x) { + $exists = $x[0]; + } + + if(array_key_exists('app_deleted',$app) && $app['app_deleted'] && $app['app_id']) { q("delete from app where app_id = '%s' and app_channel = %d limit 1", dbesc($app['app_id']), intval($channel['channel_id']) ); + if($exists) { + q("delete from term where otype = %d and oid = %d", + intval(TERM_OBJ_APP), + intval($exists['id']) + ); + } continue; } unset($app['id']); unset($app['app_channel']); + unset($app['term']); + + if($exists) { + q("delete from term where otype = %d and oid = %d", + intval(TERM_OBJ_APP), + intval($exists['id']) + ); + } if(! $app['app_created'] || $app['app_created'] === NULL_DATE) $app['app_created'] = datetime_convert(); @@ -348,16 +393,15 @@ function sync_apps($channel,$apps) { $app['app_photo'] = $x[0]; } - $exists = false; + if($exists && $term) { + foreach($term as $t) { + store_item_tag($channel['channel_id'],$exists['id'],TERM_OBJ_APP,$t['type'],escape_tags($t['term']),escape_tags($t['url'])); + } + } - $x = q("select * from app where app_id = '%s' and app_channel = %d limit 1", - dbesc($app['app_id']), - intval($channel['channel_id']) - ); - if($x) { - if($x[0]['app_edited'] >= $app['app_edited']) + if($exists) { + if($exists['app_edited'] >= $app['app_edited']) continue; - $exists = true; } $hash = $app['app_id']; @@ -380,6 +424,17 @@ function sync_apps($channel,$apps) { . implode("', '", array_values($app)) . "')" ); + if($term) { + $x = q("select * from app where app_id = '%s' and app_channel = %d limit 1", + dbesc($hash), + intval($channel['channel_id']) + ); + if($x) { + foreach($term as $t) { + store_item_tag($channel['channel_id'],$x[0]['id'],TERM_OBJ_APP,$t['type'],escape_tags($t['term']),escape_tags($t['url'])); + } + } + } } } } diff --git a/include/taxonomy.php b/include/taxonomy.php index 71ed6e91d..e43f5e5d0 100644 --- a/include/taxonomy.php +++ b/include/taxonomy.php @@ -156,47 +156,16 @@ function tagadelic($uid, $count = 0, $authors = '', $owner = '', $flags = 0, $re if(! $r) return array(); - // Find minimum and maximum log-count. - $tags = array(); - $min = 1e9; - $max = -1e9; + return Zotlabs\Text\Tagadelic::calc($r); - $x = 0; - foreach($r as $rr) { - $tags[$x][0] = $rr['term']; - $tags[$x][1] = log($rr['total']); - $tags[$x][2] = 0; - $min = min($min,$tags[$x][1]); - $max = max($max,$tags[$x][1]); - $x ++; - } - - usort($tags,'tags_sort'); - - $range = max(.01, $max - $min) * 1.0001; - - for($x = 0; $x < count($tags); $x ++) { - $tags[$x][2] = 1 + floor(9 * ($tags[$x][1] - $min) / $range); - } - - return $tags; } - -function tags_sort($a,$b) { - if(strtolower($a[0]) == strtolower($b[0])) - return 0; - - return((strtolower($a[0]) < strtolower($b[0])) ? -1 : 1); -} - - function dir_tagadelic($count = 0) { $count = intval($count); // Fetch tags - $r = q("select xtag_term, count(xtag_term) as total from xtag where xtag_flags = 0 + $r = q("select xtag_term as term, count(xtag_term) as total from xtag where xtag_flags = 0 group by xtag_term order by total desc %s", ((intval($count)) ? "limit $count" : '') ); @@ -204,30 +173,49 @@ function dir_tagadelic($count = 0) { if(! $r) return array(); - // Find minimum and maximum log-count. - $tags = array(); - $min = 1e9; - $max = -1e9; - $x = 0; - foreach($r as $rr) { - $tags[$x][0] = $rr['xtag_term']; - $tags[$x][1] = log($rr['total']); - $tags[$x][2] = 0; - $min = min($min,$tags[$x][1]); - $max = max($max,$tags[$x][1]); - $x ++; + return Zotlabs\Text\Tagadelic::calc($r); + +} + + +function app_tagblock($link,$count = 0) { + $o = ''; + + $r = app_tagadelic($count); + + if($r) { + $o = ' '; } - usort($tags,'tags_sort'); + return $o; +} - $range = max(.01, $max - $min) * 1.0001; +function app_tagadelic($count = 0) { - for($x = 0; $x < count($tags); $x ++) { - $tags[$x][2] = 1 + floor(9 * ($tags[$x][1] - $min) / $range); - } + if(! local_channel()) + return ''; + + $count = intval($count); + + + // Fetch tags + $r = q("select term, count(term) as total from term left join app on term.uid = app_channel where term.uid = %d + and term.otype = %d group by term order by total desc %s", + intval(local_channel()), + intval(TERM_OBJ_APP), + ((intval($count)) ? "limit $count" : '') + ); + + if(! $r) + return array(); + + return Zotlabs\Text\Tagadelic::calc($r); - return $tags; } diff --git a/include/widgets.php b/include/widgets.php index fa92901ae..2641a718e 100644 --- a/include/widgets.php +++ b/include/widgets.php @@ -103,8 +103,8 @@ function widget_appselect($arr) { '$system' => t('System'), '$authed' => ((local_channel()) ? true : false), '$personal' => t('Personal'), - '$new' => t('Create Personal App'), - '$edit' => t('Edit Personal App') + '$new' => t('New App'), + '$edit' => t('Edit App') )); } @@ -400,6 +400,55 @@ function widget_categories($arr) { } +function widget_appcategories($arr) { + + if(! local_channel()) + return ''; + + $cat = ((x($_REQUEST,'cat')) ? htmlspecialchars($_REQUEST['cat'],ENT_COMPAT,'UTF-8') : ''); + $srchurl = App::$query_string; + $srchurl = rtrim(preg_replace('/cat\=[^\&].*?(\&|$)/is','',$srchurl),'&'); + $srchurl = str_replace(array('?f=','&f='),array('',''),$srchurl); + + $terms = array(); + + $r = q("select distinct(term.term) + from term join app on term.oid = app.id + where app_channel = %d + and term.uid = app_channel + and term.otype = %d + order by term.term asc", + intval(local_channel()), + intval(TERM_OBJ_APP) + ); + if($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' => $srchurl, + + )); + } + + + +} + + + +function widget_appcloud($arr) { + if(! local_channel()) + return ''; + return app_tagblock(z_root() . '/apps'); +} + + function widget_tagcloud_wall($arr) { diff --git a/install/schema_mysql.sql b/install/schema_mysql.sql index 2305d4a0b..4751106da 100644 --- a/install/schema_mysql.sql +++ b/install/schema_mysql.sql @@ -123,6 +123,8 @@ CREATE TABLE IF NOT EXISTS `app` ( `app_price` char(255) NOT NULL DEFAULT '', `app_page` char(255) NOT NULL DEFAULT '', `app_requires` char(255) NOT NULL DEFAULT '', + `app_deleted` int(11) NOT NULL DEFAULT '0', + `app_system` int(11) NOT NULL DEFAULT '0', `app_created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `app_edited` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', PRIMARY KEY (`id`), @@ -134,6 +136,8 @@ CREATE TABLE IF NOT EXISTS `app` ( KEY `app_channel` (`app_channel`), KEY `app_price` (`app_price`), KEY `app_created` (`app_created`), + KEY `app_deleted` (`app_deleted`), + KEY `app_system` (`app_system`), KEY `app_edited` (`app_edited`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; diff --git a/install/schema_postgres.sql b/install/schema_postgres.sql index 2c0847cbf..75e53d3dd 100644 --- a/install/schema_postgres.sql +++ b/install/schema_postgres.sql @@ -120,6 +120,8 @@ CREATE TABLE "app" ( "app_price" text NOT NULL DEFAULT '', "app_page" text NOT NULL DEFAULT '', "app_requires" text NOT NULL DEFAULT '', + "app_deleted" smallint NOT NULL DEFAULT '0', + "app_system" smallint NOT NULL DEFAULT '0', "app_created" timestamp NOT NULL DEFAULT '0001-01-01 00:00:00', "app_edited" timestamp NOT NULL DEFAULT '0001-01-01 00:00:00', PRIMARY KEY ("id") @@ -133,6 +135,8 @@ create index "app_channel" on app ("app_channel"); create index "app_price" on app ("app_price"); create index "app_created" on app ("app_created"); create index "app_edited" on app ("app_edited"); +create index "app_deleted" on app ("app_deleted"); +create index "app_system" on app ("app_system"); CREATE TABLE "attach" ( "id" serial NOT NULL, "aid" bigint NOT NULL DEFAULT '0', diff --git a/install/update.php b/install/update.php index b8e20786c..ea1bd8bc7 100644 --- a/install/update.php +++ b/install/update.php @@ -1,6 +1,6 @@