diff --git a/CHANGELOG b/CHANGELOG index 7fc5835a7..bfb5ad2b2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,44 @@ +Hubzilla 1.10 + Wiki: + Lots of enhanced functionality, usability improvements, and bugfixes from v1.8 + Turned into an optional feature (default on) but disabled in UNO + Sync: + Items are now relocated (links patched) when syncing to clones + Access Tokens: + New feature - allows members to create access controlled guest logins and create/share 'dropbox' style links to protected resources. + UI: + Use icons instead of iconic text constructs + Only request geolocation permission when creating a post, not on page load + provide 'redeliver' option on Delivery Report page for when things really stuff up + CalDAV/CardDAV management pages with heaps of functionality + Lib: + z_fetch_url() updated to accept different request methods and request bodies + item_store(), item_store_update() now return the stored items + vcard microformat changes to remain spec compliant + microformat meta tags added to post/comments + AbConfig API changed to use channel_id rather than channel_hash, which was overly complicated to use + SuperCurl class added to provide a framework for re-use of obscure CURL options + Allow absolute links to CSS/JS files on CDN + Add Let'sEncrypt intermediate cert to lib in case you forget to install it on the server + Update fullcalendar and jquery (3.1) libs + Update sabre/dav to 3.2.0 + Change content export from a month/year system to begin/end + Use streaming I/O for delivering large photos + Allow multiple App description files in a single plugin directory + optimise a couple of troublesome/inefficient SQL queries + avoid sending clone sync packets to dead sites + Resolved Issues: + channel home page not providing content to clients with javascript disabled + Replace '@' obfuscation with html entity rather than the unicode look-alike + xchan_query() failing to detect duplicates, resulting in inefficient queries + issues with 'use existing photo' for profile photo + layout editor "list all layouts" returned empty + oembed - better detect video file URLs so they aren't loaded into memory. + handcrafted bbcode tables could end up with way too much whitespace due to CRLF translation + refresh permissions whitescreen in 1.8 + force immediate profile photo update on local site + regression: 'save bookmarks' post action missing + Hubzilla 1.8 Administration: Cleanup and resolve some edge cases with addon repository manager diff --git a/README.md b/README.md index 8a6c003fc..9929f6a16 100644 --- a/README.md +++ b/README.md @@ -47,4 +47,16 @@ Possible website applications include Installing Hubzilla

+**Who Are We and What Are Our Principles?** + +The Hubzilla community is powered by passionate volunteers creating an open source **commons** of decentralised services which are highly integrated and can rival the feature set of centralised providers. We are open to sponsorship and donations to cover expenses and compensate for our time and energy, however the project core is basically non-profit and is not designed for the purpose of commercial gain or exploitation. + +Some sites may include monetisation strategies such as subscriptions and *freemium* models where members pay for resources they consume beyond a basic level. The project community supports such monetisation initiatives (nobody should be forced to pay "out of pocket" to provide a service to others), but we maintain the **commons** to provide open and free access of the software to all. + +The software is not designed for data collection of its members or providing advertising. We don't have a need or desire for these things and feel that software built around these goals is poorly designed and represents compromised principles and ethics. + +As a project, we are inclusive of all beliefs and cultures and do what we are able to provide an environment that is free from hostility and harrassment. Whether or not we succeed in this endaevour requires constant vigilance and help from all members of the community, working together to build an inter-networking tool with amazing potential. + + + [![Build Status](https://travis-ci.org/redmatrix/hubzilla.svg)](https://travis-ci.org/redmatrix/hubzilla) diff --git a/Zotlabs/Daemon/Cron.php b/Zotlabs/Daemon/Cron.php index f23cb14dc..c6e82b13a 100644 --- a/Zotlabs/Daemon/Cron.php +++ b/Zotlabs/Daemon/Cron.php @@ -41,7 +41,6 @@ class Cron { require_once('include/sharedwithme.php'); apply_updates(); - // expire any expired mail q("delete from mail where expires != '%s' and expires < %s ", @@ -63,6 +62,15 @@ class Cron { } + // delete expired access tokens + + q("delete from atoken where atoken_expires != '%s' && atoken_expires < %s", + dbesc(NULL_DATE), + db_utcnow() + ); + + + // Ensure that every channel pings a directory server once a month. This way we can discover // channels and sites that quietly vanished and prevent the directory from accumulating stale // or dead entries. @@ -93,6 +101,18 @@ class Cron { intval($rr['id']) ); if($x) { + $z = q("select * from item where id = %d", + intval($message_id) + ); + if($z) { + xchan_query($z); + $sync_item = fetch_post_tags($z); + build_sync_packet($sync_item[0]['uid'], + [ + 'item' => [ encode_item($sync_item[0],true) ] + ] + ); + } Master::Summon(array('Notifier','wall-new',$rr['id'])); } } diff --git a/Zotlabs/Daemon/Cron_weekly.php b/Zotlabs/Daemon/Cron_weekly.php index 1d8420947..ba4b67ff5 100644 --- a/Zotlabs/Daemon/Cron_weekly.php +++ b/Zotlabs/Daemon/Cron_weekly.php @@ -15,7 +15,6 @@ class Cron_weekly { call_hooks('cron_weekly',datetime_convert()); - z_check_cert(); require_once('include/hubloc.php'); diff --git a/Zotlabs/Daemon/CurlAuth.php b/Zotlabs/Daemon/CurlAuth.php new file mode 100644 index 000000000..be12bc779 --- /dev/null +++ b/Zotlabs/Daemon/CurlAuth.php @@ -0,0 +1,55 @@ +start(); + + $_SESSION['authenticated'] = 1; + $_SESSION['uid'] = $argv[1]; + + $x = session_id(); + + $f = 'store/[data]/cookie_' . $argv[1]; + $c = 'store/[data]/cookien_' . $argv[1]; + + $e = file_exists($f); + + $output = ''; + + if($e) { + $lines = file($f); + if($lines) { + foreach($lines as $line) { + if(strlen($line) > 0 && $line[0] != '#' && substr_count($line, "\t") == 6) { + $tokens = explode("\t", $line); + $tokens = array_map('trim', $tokens); + if($tokens[4] > time()) { + $output .= $line . "\n"; + } + } + else + $output .= $line; + } + } + } + $t = time() + (24 * 3600); + file_put_contents($f, $output . 'HttpOnly_' . \App::get_hostname() . "\tFALSE\t/\tTRUE\t$t\tPHPSESSID\t" . $x, (($e) ? FILE_APPEND : 0)); + + file_put_contents($c,$x); + + killme(); + } +} \ No newline at end of file diff --git a/Zotlabs/Daemon/Expire.php b/Zotlabs/Daemon/Expire.php index 0ba83b240..215513e87 100644 --- a/Zotlabs/Daemon/Expire.php +++ b/Zotlabs/Daemon/Expire.php @@ -38,7 +38,7 @@ class Expire { logger('site_expire: ' . $site_expire); - $r = q("SELECT channel_id, channel_address, channel_pageflags, channel_expire_days from channel where true"); + $r = q("SELECT channel_id, channel_system, channel_address, channel_expire_days from channel where true"); if ($r) { foreach ($r as $rr) { diff --git a/Zotlabs/Daemon/README.md b/Zotlabs/Daemon/README.md new file mode 100644 index 000000000..cb5b00a56 --- /dev/null +++ b/Zotlabs/Daemon/README.md @@ -0,0 +1,43 @@ +Daemon (background) Processes +============================= + + +This directory provides background tasks which are executed by a +command-line process and detached from normal web processing. + +Background tasks are invoked by calling + + + Zotlabs\Daemon\Master::Summon([ $cmd, $arg1, $argn... ]); + +The Master class loads the desired command file and passes the arguments. + + +To create a background task 'Foo' use the following template. + + request_data = $s; + $this->filepos = 0; + } + + public function curl_read($ch,$fh,$size) { + + if($this->filepos < 0) { + unset($fh); + return ''; + } + + $s = substr($this->request_data,$this->filepos,$size); + + if(strlen($s) < $size) + $this->filepos = (-1); + else + $this->filepos = $this->filepos + $size; + + return $s; + } + + + public function __construct($opts = array()) { + $this->set($opts); + } + + private function set($opts = array()) { + if($opts) { + foreach($opts as $k => $v) { + switch($k) { + case 'http_auth': + $this->auth = $v; + break; + case 'magicauth': + // currently experimental + $this->magicauth = $v; + \Zotlabs\Daemon\Master::Summon([ 'CurlAuth', $v ]); + break; + case 'custom': + $this->request_method = $v; + break; + case 'url': + $this->url = $v; + break; + case 'data': + $this->set_data($v); + if($v) { + $this->upload = true; + } + else { + $this->upload = false; + } + break; + case 'headers': + $this->headers = $v; + break; + default: + $this->curlopts[$k] = $v; + break; + } + } + } + } + + function exec() { + $opts = $this->curlopts; + $url = $this->url; + if($this->auth) + $opts['http_auth'] = $this->auth; + if($this->magicauth) { + $opts['cookiejar'] = 'store/[data]/cookie_' . $this->magicauth; + $opts['cookiefile'] = 'store/[data]/cookie_' . $this->magicauth; + $opts['cookie'] = 'PHPSESSID=' . trim(file_get_contents('store/[data]/cookien_' . $this->magicauth)); + $c = channelx_by_n($this->magicauth); + if($c) + $url = zid($this->url,$c['channel_address'] . '@' . \App::get_hostname()); + } + if($this->custom) + $opts['custom'] = $this->custom; + if($this->headers) + $opts['headers'] = $this->headers; + if($this->upload) { + $opts['upload'] = true; + $opts['infile'] = $this->filehandle; + $opts['infilesize'] = strlen($this->request_data); + $opts['readfunc'] = [ $this, 'curl_read' ] ; + } + + $recurse = 0; + return z_fetch_url($this->url,true,$recurse,(($opts) ? $opts : null)); + + } + + +} diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php index f724ac95d..638afeb6b 100644 --- a/Zotlabs/Lib/ThreadItem.php +++ b/Zotlabs/Lib/ThreadItem.php @@ -248,7 +248,7 @@ class ThreadItem { $has_bookmarks = false; if(is_array($item['term'])) { foreach($item['term'] as $t) { - if(!UNO && $t['type'] == TERM_BOOKMARK) + if(!UNO && $t['ttype'] == TERM_BOOKMARK) $has_bookmarks = true; } } @@ -418,7 +418,7 @@ class ThreadItem { if(($nb_children > $visible_comments) || ($thread_level > 1)) { $result['children'][0]['comment_firstcollapsed'] = true; $result['children'][0]['num_comments'] = $comment_count_txt; - $result['children'][0]['hide_text'] = t('[+] show all'); + $result['children'][0]['hide_text'] = sprintf( t('%s show all'), ''); if($thread_level > 1) { $result['children'][$nb_children - 1]['comment_lastcollapsed'] = true; } diff --git a/Zotlabs/Module/Achievements.php b/Zotlabs/Module/Achievements.php index 8ddefb3e5..1529448d3 100644 --- a/Zotlabs/Module/Achievements.php +++ b/Zotlabs/Module/Achievements.php @@ -18,7 +18,7 @@ class Achievements extends \Zotlabs\Web\Controller { $profile = 0; $profile = argv(1); - profile_load($a,$which,$profile); + profile_load($which,$profile); $r = q("select channel_id from channel where channel_address = '%s'", dbesc($which) diff --git a/Zotlabs/Module/Acl.php b/Zotlabs/Module/Acl.php index 2bc4ba62d..76a001fdd 100644 --- a/Zotlabs/Module/Acl.php +++ b/Zotlabs/Module/Acl.php @@ -1,7 +1,18 @@ standard ACL request + // 'g' => Groups only ACL request + // 'c' => Connections only ACL request or editor (textarea) mention request + // $_REQUEST['search'] contains ACL search text. + + + // $type = + // 'm' => autocomplete private mail recipient (checks post_mail permission) + // 'a' => autocomplete connections (mod_connections, mod_poke, mod_sources, mod_photos) + // 'x' => nav search bar autocomplete (match any xchan) + // $_REQUEST['query'] contains autocomplete search text. - // List of channels whose connections to also suggest, e.g. currently viewed channel or channels mentioned in a post + // List of channels whose connections to also suggest, + // e.g. currently viewed channel or channels mentioned in a post + $extra_channels = (x($_REQUEST,'extra_channels') ? $_REQUEST['extra_channels'] : array()); - // For use with jquery.autocomplete for private mail completion + // The different autocomplete libraries use different names for the search text + // parameter. Internaly we'll use $search to represent the search text no matter + // what request variable it was attached to. - if(x($_REQUEST,'query') && strlen($_REQUEST['query'])) { - if(! $type) - $type = 'm'; + if(array_key_exists('query',$_REQUEST)) { $search = $_REQUEST['query']; } - if(!(local_channel())) - if(!($type == 'x' || $type == 'c')) - killme(); + if( (! local_channel()) && (! ($type == 'x' || $type == 'c'))) + killme(); - if ($search != "") { + if($search) { $sql_extra = " AND `name` LIKE " . protect_sprintf( "'%" . dbesc($search) . "%'" ) . " "; $sql_extra2 = "AND ( xchan_name LIKE " . protect_sprintf( "'%" . dbesc($search) . "%'" ) . " OR xchan_addr LIKE " . protect_sprintf( "'%" . dbesc($search) . ((strpos($search,'@') === false) ? "%@%'" : "%'")) . ") "; - // This horrible mess is needed because position also returns 0 if nothing is found. W/ould be MUCH easier if it instead returned a very large value - // Otherwise we could just order by LEAST(POSITION($search IN xchan_name),POSITION($search IN xchan_addr)). - $order_extra2 = "CASE WHEN xchan_name LIKE " . protect_sprintf( "'%" . dbesc($search) . "%'" ) ." then POSITION('".dbesc($search)."' IN xchan_name) else position('".dbesc($search)."' IN xchan_addr) end, "; + // This horrible mess is needed because position also returns 0 if nothing is found. + // Would be MUCH easier if it instead returned a very large value + // Otherwise we could just + // order by LEAST(POSITION($search IN xchan_name),POSITION($search IN xchan_addr)). + + $order_extra2 = "CASE WHEN xchan_name LIKE " + . protect_sprintf( "'%" . dbesc($search) . "%'" ) + . " then POSITION('" . dbesc($search) + . "' IN xchan_name) else position('" . dbesc($search) . "' IN xchan_addr) end, "; + $col = ((strpos($search,'@') !== false) ? 'xchan_addr' : 'xchan_name' ); $sql_extra3 = "AND $col like " . protect_sprintf( "'%" . dbesc($search) . "%'" ) . " "; - } else { + } + else { $sql_extra = $sql_extra2 = $sql_extra3 = ""; } @@ -51,7 +85,7 @@ class Acl extends \Zotlabs\Web\Controller { $groups = array(); $contacts = array(); - if ($type=='' || $type=='g'){ + if($type == '' || $type == 'g') { $r = q("SELECT `groups`.`id`, `groups`.`hash`, `groups`.`gname` FROM `groups`,`group_member` @@ -82,7 +116,7 @@ class Acl extends \Zotlabs\Web\Controller { } } - if ($type=='' || $type=='c') { + if($type == '' || $type == 'c') { $extra_channels_sql = ''; // Only include channels who allow the observer to view their permissions foreach($extra_channels as $channel) { @@ -96,13 +130,40 @@ class Acl extends \Zotlabs\Web\Controller { if(local_channel()) { if($extra_channels_sql != '') $extra_channels_sql = " OR (abook_channel IN ($extra_channels_sql)) and abook_hidden = 0 "; + + $r2 = null; + + $r1 = q("select * from atoken where atoken_uid = %d", + intval(local_channel()) + ); + if($r1) { + require_once('include/security.php'); + $r2 = array(); + foreach($r1 as $rr) { + $x = atoken_xchan($rr); + $r2[] = [ + 'id' => 'a' . $rr['atoken_id'] , + 'hash' => $x['xchan_hash'], + 'name' => $x['xchan_name'], + 'micro' => $x['xchan_photo_m'], + 'url' => z_root(), + 'nick' => $x['xchan_addr'], + 'abook_their_perms' => 0, + 'abook_flags' => 0, + 'abook_self' => 0 + ]; + } + } + $r = q("SELECT abook_id as id, xchan_hash as hash, xchan_name as name, xchan_photo_s as micro, xchan_url as url, xchan_addr as nick, abook_their_perms, abook_flags, abook_self FROM abook left join xchan on abook_xchan = xchan_hash WHERE (abook_channel = %d $extra_channels_sql) AND abook_blocked = 0 and abook_pending = 0 and xchan_deleted = 0 $sql_extra2 order by $order_extra2 xchan_name asc" , intval(local_channel()) ); - + if($r2) + $r = array_merge($r2,$r); + } else { // Visitors $r = q("SELECT xchan_hash as id, xchan_hash as hash, xchan_name as name, xchan_photo_s as micro, xchan_url as url, xchan_addr as nick, 0 as abook_their_perms, 0 as abook_flags, 0 as abook_self @@ -161,7 +222,7 @@ class Acl extends \Zotlabs\Web\Controller { } elseif($type == 'm') { - $r = q("SELECT xchan_hash as id, xchan_name as name, xchan_addr as nick, xchan_photo_s as micro, xchan_url as url + $r = q("SELECT xchan_hash as hash, xchan_name as name, xchan_addr as nick, xchan_photo_s as micro, xchan_url as url FROM abook left join xchan on abook_xchan = xchan_hash WHERE abook_channel = %d and ( (abook_their_perms = null) or (abook_their_perms & %d )>0) and xchan_deleted = 0 @@ -171,7 +232,7 @@ class Acl extends \Zotlabs\Web\Controller { intval(PERMS_W_MAIL) ); } - elseif(($type == 'a') || ($type == 'p')) { + elseif($type == 'a') { $r = q("SELECT abook_id as id, xchan_name as name, xchan_hash as hash, xchan_addr as nick, xchan_photo_s as micro, xchan_network as network, xchan_url as url, xchan_addr as attag , abook_their_perms FROM abook left join xchan on abook_xchan = xchan_hash WHERE abook_channel = %d @@ -296,7 +357,7 @@ class Acl extends \Zotlabs\Web\Controller { $url = $directory['url'] . '/dirsearch'; } - $count = (x($_REQUEST,'count')?$_REQUEST['count']:100); + $count = (x($_REQUEST,'count') ? $_REQUEST['count'] : 100); if($url) { $query = $url . '?f=' ; $query .= '&name=' . urlencode($search) . "&limit=$count" . (($address) ? '&address=' . urlencode($search) : ''); diff --git a/Zotlabs/Module/Block.php b/Zotlabs/Module/Block.php index 062befdb5..e671730f6 100644 --- a/Zotlabs/Module/Block.php +++ b/Zotlabs/Module/Block.php @@ -12,7 +12,7 @@ class Block extends \Zotlabs\Web\Controller { $which = argv(1); $profile = 0; - profile_load($a,$which,$profile); + profile_load($which,$profile); if(\App::$profile['profile_uid']) head_set_icon(\App::$profile['thumb']); @@ -52,8 +52,8 @@ class Block extends \Zotlabs\Web\Controller { require_once('include/security.php'); $sql_options = item_permissions_sql($u[0]['channel_id']); - $r = q("select item.* from item left join item_id on item.id = item_id.iid - where item.uid = %d and sid = '%s' and service = 'BUILDBLOCK' and + $r = q("select item.* from item left join iconfig on item.id = iconfig.iid + where item.uid = %d and iconfig.cat = 'system' and iconfig.v = '%s' and iconfig.k = 'BUILDBLOCK' and item_type = %d $sql_options $revision limit 1", intval($u[0]['channel_id']), dbesc($page_id), @@ -64,8 +64,8 @@ class Block extends \Zotlabs\Web\Controller { // Check again with no permissions clause to see if it is a permissions issue - $x = q("select item.* from item left join item_id on item.id = item_id.iid - where item.uid = %d and sid = '%s' and service = 'BUILDBLOCK' and + $x = q("select item.* from item left join iconfig on item.id = iconfig.iid + where item.uid = %d and iconfig.cat = 'system' and iconfig.v = '%s' and iconfig.k = 'BUILDBLOCK' and item_type = %d $revision limit 1", intval($u[0]['channel_id']), dbesc($page_id), diff --git a/Zotlabs/Module/Blocks.php b/Zotlabs/Module/Blocks.php index 32650a090..e6a97794d 100644 --- a/Zotlabs/Module/Blocks.php +++ b/Zotlabs/Module/Blocks.php @@ -22,12 +22,12 @@ class Blocks extends \Zotlabs\Web\Controller { else return; - profile_load($a,$which); + profile_load($which); } - function get() { + function get() { if(! \App::$profile) { notice( t('Requested profile is not available.') . EOL ); @@ -111,8 +111,11 @@ class Blocks extends \Zotlabs\Web\Controller { $editor = status_editor($a,$x); - $r = q("select iid, sid, mid, title, body, mimetype, created, edited from item_id left join item on item_id.iid = item.id - where item_id.uid = %d and service = 'BUILDBLOCK' and item_type = %d order by item.created desc", + + $r = q("select iconfig.iid, iconfig.k, iconfig.v, mid, title, body, mimetype, created, edited from iconfig + left join item on iconfig.iid = item.id + where uid = %d and iconfig.cat = 'system' and iconfig.k = 'BUILDBLOCK' + and item_type = %d order by item.created desc", intval($owner), intval(ITEM_TYPE_BLOCK) ); @@ -129,12 +132,12 @@ class Blocks extends \Zotlabs\Web\Controller { 'created' => $rr['created'], 'edited' => $rr['edited'], 'mimetype' => $rr['mimetype'], - 'pagetitle' => $rr['sid'], + 'pagetitle' => $rr['v'], 'mid' => $rr['mid'] ); $pages[$rr['iid']][] = array( 'url' => $rr['iid'], - 'name' => $rr['sid'], + 'name' => $rr['v'], 'title' => $rr['title'], 'created' => $rr['created'], 'edited' => $rr['edited'], diff --git a/Zotlabs/Module/Cal.php b/Zotlabs/Module/Cal.php index 1da42684d..fd4169e68 100644 --- a/Zotlabs/Module/Cal.php +++ b/Zotlabs/Module/Cal.php @@ -20,7 +20,7 @@ class Cal extends \Zotlabs\Web\Controller { if(argc() > 1) { $nick = argv(1); - profile_load($a,$nick); + profile_load($nick); $channelx = channelx_by_nick($nick); diff --git a/Zotlabs/Module/Channel.php b/Zotlabs/Module/Channel.php index 29bfcbc3c..c74802ec5 100644 --- a/Zotlabs/Module/Channel.php +++ b/Zotlabs/Module/Channel.php @@ -9,7 +9,6 @@ require_once('include/security.php'); require_once('include/conversation.php'); require_once('include/acl_selectors.php'); require_once('include/permissions.php'); -require_once('include/PermissionDescription.php'); class Channel extends \Zotlabs\Web\Controller { @@ -48,7 +47,7 @@ class Channel extends \Zotlabs\Web\Controller { // Run profile_load() here to make sure the theme is set before // we start loading content - profile_load($a,$which,$profile); + profile_load($which,$profile); } @@ -133,7 +132,7 @@ class Channel extends \Zotlabs\Web\Controller { '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, \PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post') : ''), + 'acl' => (($is_owner) ? populate_acl($channel_acl,true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post') : ''), 'showacl' => (($is_owner) ? 'yes' : ''), 'bang' => '', 'visitor' => (($is_owner || $observer) ? true : false), diff --git a/Zotlabs/Module/Chat.php b/Zotlabs/Module/Chat.php index 026e8369a..ff55a9319 100644 --- a/Zotlabs/Module/Chat.php +++ b/Zotlabs/Module/Chat.php @@ -39,7 +39,7 @@ class Chat extends \Zotlabs\Web\Controller { // Run profile_load() here to make sure the theme is set before // we start loading content - profile_load($a,$which,$profile); + profile_load($which,$profile); } diff --git a/Zotlabs/Module/Cloud.php b/Zotlabs/Module/Cloud.php index b691475ce..68d84e070 100644 --- a/Zotlabs/Module/Cloud.php +++ b/Zotlabs/Module/Cloud.php @@ -13,6 +13,9 @@ use \Zotlabs\Storage; // composer autoloader for SabreDAV require_once('vendor/autoload.php'); +require_once('include/attach.php'); + + /** * @brief Fires up the SabreDAV server. * @@ -23,7 +26,6 @@ require_once('vendor/autoload.php'); class Cloud extends \Zotlabs\Web\Controller { function init() { - require_once('include/reddav.php'); if (! is_dir('store')) os_mkdir('store', STORAGE_DEFAULT_PERMISSIONS, false); @@ -37,7 +39,7 @@ class Cloud extends \Zotlabs\Web\Controller { \App::$page['htmlhead'] .= '' . "\r\n"; if ($which) - profile_load($a, $which, $profile); + profile_load( $which, $profile); $auth = new \Zotlabs\Storage\BasicAuth(); @@ -79,17 +81,6 @@ class Cloud extends \Zotlabs\Web\Controller { $is_readable = false; - if($_SERVER['REQUEST_METHOD'] === 'GET') { - try { - $x = RedFileData('/' . \App::$cmd, $auth); - } - catch(\Exception $e) { - if($e instanceof Sabre\DAV\Exception\Forbidden) { - http_status_exit(401, 'Permission denied.'); - } - } - } - // provide a directory view for the cloud in Hubzilla $browser = new \Zotlabs\Storage\Browser($auth); $auth->setBrowserPlugin($browser); diff --git a/Zotlabs/Module/Common.php b/Zotlabs/Module/Common.php index 1c428d256..2f3c57267 100644 --- a/Zotlabs/Module/Common.php +++ b/Zotlabs/Module/Common.php @@ -21,7 +21,7 @@ class Common extends \Zotlabs\Web\Controller { ); if($x) - profile_load($a,$x[0]['channel_address'],0); + profile_load($x[0]['channel_address'],0); } diff --git a/Zotlabs/Module/Connect.php b/Zotlabs/Module/Connect.php index f68e0baac..962c05cce 100644 --- a/Zotlabs/Module/Connect.php +++ b/Zotlabs/Module/Connect.php @@ -26,7 +26,7 @@ class Connect extends \Zotlabs\Web\Controller { if($r) \App::$data['channel'] = $r[0]; - profile_load($a,$which,''); + profile_load($which,''); } function post() { diff --git a/Zotlabs/Module/Connedit.php b/Zotlabs/Module/Connedit.php index 4f700e2e7..7db4950b1 100644 --- a/Zotlabs/Module/Connedit.php +++ b/Zotlabs/Module/Connedit.php @@ -16,14 +16,14 @@ require_once('include/zot.php'); require_once('include/widgets.php'); require_once('include/photos.php'); -/* @brief Initialize the connection-editor - * - * - */ - class Connedit extends \Zotlabs\Web\Controller { + /* @brief Initialize the connection-editor + * + * + */ + function init() { if(! local_channel()) @@ -51,7 +51,7 @@ class Connedit extends \Zotlabs\Web\Controller { * */ - function post() { + function post() { if(! local_channel()) return; @@ -219,7 +219,7 @@ class Connedit extends \Zotlabs\Web\Controller { //Update profile photo permissions logger('A new profile was assigned - updating profile photos'); - profile_photo_set_profile_perms($profile_id); + profile_photo_set_profile_perms(local_channel(),$profile_id); } @@ -345,7 +345,7 @@ class Connedit extends \Zotlabs\Web\Controller { unset($clone['abook_account']); unset($clone['abook_channel']); - $abconfig = load_abconfig($channel['channel_hash'],$clone['abook_xchan']); + $abconfig = load_abconfig($channel['channel_id'],$clone['abook_xchan']); if($abconfig) $clone['abconfig'] = $abconfig; @@ -357,7 +357,7 @@ class Connedit extends \Zotlabs\Web\Controller { * */ - function get() { + function get() { $sort_type = 0; $o = ''; @@ -418,7 +418,13 @@ class Connedit extends \Zotlabs\Web\Controller { goaway(z_root() . '/connedit/' . $contact_id); } - + if($cmd === 'resetphoto') { + q("update xchan set xchan_photo_date = '2001-01-01 00:00:00' where xchan_hash = '%s' limit 1", + dbesc($orig_record[0]['xchan_hash']) + ); + $cmd = 'refresh'; + } + if($cmd === 'refresh') { if($orig_record[0]['xchan_network'] === 'zot') { if(! zot_refresh($orig_record[0],\App::get_channel())) diff --git a/Zotlabs/Module/Cover_photo.php b/Zotlabs/Module/Cover_photo.php index a72c3389f..886958b37 100644 --- a/Zotlabs/Module/Cover_photo.php +++ b/Zotlabs/Module/Cover_photo.php @@ -29,7 +29,7 @@ class Cover_photo extends \Zotlabs\Web\Controller { } $channel = \App::get_channel(); - profile_load($a,$channel['channel_address']); + profile_load($channel['channel_address']); } @@ -40,7 +40,7 @@ class Cover_photo extends \Zotlabs\Web\Controller { * */ - function post() { + function post() { if(! local_channel()) { return; @@ -50,7 +50,7 @@ class Cover_photo extends \Zotlabs\Web\Controller { check_form_security_token_redirectOnErr('/cover_photo', 'cover_photo'); - if((x($_POST,'cropfinal')) && ($_POST['cropfinal'] == 1)) { + if((array_key_exists('cropfinal',$_POST)) && ($_POST['cropfinal'] == 1)) { // phase 2 - we have finished cropping @@ -271,7 +271,7 @@ class Cover_photo extends \Zotlabs\Web\Controller { */ - function get() { + function get() { if(! local_channel()) { notice( t('Permission denied.') . EOL ); diff --git a/Zotlabs/Module/Dav.php b/Zotlabs/Module/Dav.php index 2fddabe19..aaf69844c 100644 --- a/Zotlabs/Module/Dav.php +++ b/Zotlabs/Module/Dav.php @@ -14,6 +14,7 @@ use \Zotlabs\Storage; // composer autoloader for SabreDAV require_once('vendor/autoload.php'); +require_once('include/attach.php'); /** * @brief Fires up the SabreDAV server. @@ -44,60 +45,16 @@ class Dav extends \Zotlabs\Web\Controller { } } - require_once('include/reddav.php'); - if (! is_dir('store')) os_mkdir('store', STORAGE_DEFAULT_PERMISSIONS, false); - $which = null; if (argc() > 1) - $which = argv(1); + profile_load(argv(1),0); - $profile = 0; - - \App::$page['htmlhead'] .= '' . "\r\n"; - - if ($which) - profile_load($a, $which, $profile); - - - $auth = new \Zotlabs\Storage\BasicAuth(); - $auth->setRealm(ucfirst(\Zotlabs\Lib\System::get_platform_name()) . 'WebDAV'); + $auth->setRealm(ucfirst(\Zotlabs\Lib\System::get_platform_name()) . ' ' . 'WebDAV'); -// $authBackend = new \Sabre\DAV\Auth\Backend\BasicCallBack(function($userName,$password) { -// if(account_verify_password($userName,$password)) -// return true; -// return false; -// }); - -// $ob_hash = get_observer_hash(); - -// if ($ob_hash) { -// if (local_channel()) { -// $channel = \App::get_channel(); -// $auth->setCurrentUser($channel['channel_address']); -// $auth->channel_id = $channel['channel_id']; -// $auth->channel_hash = $channel['channel_hash']; -// $auth->channel_account_id = $channel['channel_account_id']; -// if($channel['channel_timezone']) -// $auth->setTimezone($channel['channel_timezone']); -// } -// $auth->observer = $ob_hash; -// } - -// if ($_GET['davguest']) -// $_SESSION['davguest'] = true; - -// $_SERVER['QUERY_STRING'] = str_replace(array('?f=', '&f='), array('', ''), $_SERVER['QUERY_STRING']); -// $_SERVER['QUERY_STRING'] = strip_zids($_SERVER['QUERY_STRING']); -// $_SERVER['QUERY_STRING'] = preg_replace('/[\?&]davguest=(.*?)([\?&]|$)/ism', '', $_SERVER['QUERY_STRING']); -// -// $_SERVER['REQUEST_URI'] = str_replace(array('?f=', '&f='), array('', ''), $_SERVER['REQUEST_URI']); -// $_SERVER['REQUEST_URI'] = strip_zids($_SERVER['REQUEST_URI']); -// $_SERVER['REQUEST_URI'] = preg_replace('/[\?&]davguest=(.*?)([\?&]|$)/ism', '', $_SERVER['REQUEST_URI']); - $rootDirectory = new \Zotlabs\Storage\Directory('/', $auth); // A SabreDAV server-object @@ -114,48 +71,13 @@ class Dav extends \Zotlabs\Web\Controller { $server->addPlugin($lockPlugin); - // The next section of code allows us to bypass prompting for http-auth if a - // FILE is being accessed anonymously and permissions allow this. This way - // one can create hotlinks to public media files in their cloud and anonymous - // viewers won't get asked to login. - // If a DIRECTORY is accessed or there are permission issues accessing the - // file and we aren't previously authenticated via zot, prompt for HTTP-auth. - // This will be the default case for mounting a DAV directory. - // In order to avoid prompting for passwords for viewing a DIRECTORY, add - // the URL query parameter 'davguest=1'. - -// $isapublic_file = false; -// $davguest = ((x($_SESSION, 'davguest')) ? true : false); - -// if ((! $auth->observer) && ($_SERVER['REQUEST_METHOD'] === 'GET')) { -// try { -// $x = RedFileData('/' . \App::$cmd, $auth); -// if($x instanceof \Zotlabs\Storage\File) -// $isapublic_file = true; -// } -// catch (Exception $e) { -// $isapublic_file = false; -// } -// } - -// if ((! $auth->observer) && (! $isapublic_file) && (! $davguest)) { -// try { -// $auth->Authenticate($server, t('$Projectname channel')); -// } -// catch (Exception $e) { -// logger('mod_cloud: auth exception' . $e->getMessage()); -// http_status_exit($e->getHTTPCode(), $e->getMessage()); -// } -// } - - // require_once('Zotlabs/Storage/Browser.php'); // provide a directory view for the cloud in Hubzilla $browser = new \Zotlabs\Storage\Browser($auth); $auth->setBrowserPlugin($browser); // Experimental QuotaPlugin - // require_once('Zotlabs/Storage/QuotaPlugin.php'); - // $server->addPlugin(new \Zotlabs\Storage\QuotaPlugin($auth)); + // require_once('Zotlabs/Storage/QuotaPlugin.php'); + // $server->addPlugin(new \Zotlabs\Storage\QuotaPlugin($auth)); // All we need to do now, is to fire up the server $server->exec(); diff --git a/Zotlabs/Module/Display.php b/Zotlabs/Module/Display.php index c1a0d84bc..d1d4edc7d 100644 --- a/Zotlabs/Module/Display.php +++ b/Zotlabs/Module/Display.php @@ -106,12 +106,13 @@ class Display extends \Zotlabs\Web\Controller { $x = q("select * from channel where channel_id = %d limit 1", intval($target_item['uid']) ); - $y = q("select * from item_id where uid = %d and service = 'WEBPAGE' and iid = %d limit 1", + $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']) ); if($x && $y) { - goaway(z_root() . '/page/' . $x[0]['channel_address'] . '/' . $y[0]['sid']); + goaway(z_root() . '/page/' . $x[0]['channel_address'] . '/' . $y[0]['v']); } else { notice( t('Page not found.') . EOL); diff --git a/Zotlabs/Module/Dreport.php b/Zotlabs/Module/Dreport.php index e8709c952..d2933b464 100644 --- a/Zotlabs/Module/Dreport.php +++ b/Zotlabs/Module/Dreport.php @@ -16,7 +16,24 @@ class Dreport extends \Zotlabs\Web\Controller { $channel = \App::get_channel(); $mid = ((argc() > 1) ? argv(1) : ''); - + + if($mid === 'push') { + $table = 'push'; + $mid = ((argc() > 2) ? argv(2) : ''); + if($mid) { + $i = q("select id from item where mid = '%s' and author_xchan = '%s' and uid = %d", + dbesc($mid), + dbesc($channel['channel_hash']), + intval($channel['channel_id']) + ); + if($i) { + \Zotlabs\Daemon\Master::Summon([ 'Notifier', 'edit_post', $i[0]['id'] ]); + } + } + sleep(3); + goaway(z_root() . '/dreport/' . urlencode($mid)); + } + if($mid === 'mail') { $table = 'mail'; $mid = ((argc() > 2) ? argv(2) : ''); @@ -59,11 +76,7 @@ class Dreport extends \Zotlabs\Web\Controller { notice( t('no results') . EOL); return; } - - $o .= '
'; - $o .= '

' . sprintf( t('Delivery report for %1$s'),substr($mid,0,32)) . '...' . '

'; - $o .= ''; - + for($x = 0; $x < count($r); $x++ ) { $r[$x]['name'] = escape_tags(substr($r[$x]['dreport_recip'],strpos($r[$x]['dreport_recip'],' '))); @@ -119,13 +132,25 @@ class Dreport extends \Zotlabs\Web\Controller { } usort($r,'self::dreport_gravity_sort'); - - + + $entries = array(); foreach($r as $rr) { - $o .= ''; + $entries[] = [ + 'name' => $rr['name'], + 'result' => escape_tags($rr['dreport_result']), + 'time' => escape_tags(datetime_convert('UTC',date_default_timezone_get(),$rr['dreport_time'])) + ]; } - $o .= '
' . $rr['name'] . '' . escape_tags($rr['dreport_result']) . '' . escape_tags($rr['dreport_time']) . '
'; - $o .= '
'; + + $o = replace_macros(get_markup_template('dreport.tpl'), array( + '$title' => sprintf( t('Delivery report for %1$s'),substr($mid,0,32)) . '...', + '$table' => $table, + '$mid' => urlencode($mid), + '$options' => t('Options'), + '$push' => t('Redeliver'), + '$entries' => $entries + )); + return $o; diff --git a/Zotlabs/Module/Editblock.php b/Zotlabs/Module/Editblock.php index fb86557f2..6a9fa5f2d 100644 --- a/Zotlabs/Module/Editblock.php +++ b/Zotlabs/Module/Editblock.php @@ -21,7 +21,7 @@ class Editblock extends \Zotlabs\Web\Controller { else return; - profile_load($a,$which); + profile_load($which); } @@ -85,11 +85,11 @@ class Editblock extends \Zotlabs\Web\Controller { intval($owner) ); if($itm) { - $item_id = q("select * from item_id where service = 'BUILDBLOCK' and iid = %d limit 1", + $item_id = q("select * from iconfig where cat = 'system' and k = 'BUILDBLOCK' and iid = %d limit 1", intval($itm[0]['id']) ); if($item_id) - $block_title = $item_id[0]['sid']; + $block_title = $item_id[0]['v']; } else { notice( t('Item not found') . EOL); diff --git a/Zotlabs/Module/Editlayout.php b/Zotlabs/Module/Editlayout.php index 5028882d2..26732dc77 100644 --- a/Zotlabs/Module/Editlayout.php +++ b/Zotlabs/Module/Editlayout.php @@ -21,7 +21,7 @@ class Editlayout extends \Zotlabs\Web\Controller { else return; - profile_load($a,$which); + profile_load($which); } @@ -96,11 +96,12 @@ class Editlayout extends \Zotlabs\Web\Controller { intval($owner) ); - $item_id = q("select * from item_id where service = 'PDL' and iid = %d limit 1", + $item_id = q("select * from iconfig where cat = 'system' and k = 'PDL' and iid = %d limit 1", intval($itm[0]['id']) ); if($item_id) - $layout_title = $item_id[0]['sid']; + $layout_title = $item_id[0]['v']; + $rp = 'layouts/' . $which; diff --git a/Zotlabs/Module/Editwebpage.php b/Zotlabs/Module/Editwebpage.php index 1b5c320a0..be4803a07 100644 --- a/Zotlabs/Module/Editwebpage.php +++ b/Zotlabs/Module/Editwebpage.php @@ -4,7 +4,6 @@ namespace Zotlabs\Module; require_once('include/channel.php'); require_once('include/acl_selectors.php'); require_once('include/conversation.php'); -require_once('include/PermissionDescription.php'); class Editwebpage extends \Zotlabs\Web\Controller { @@ -23,7 +22,7 @@ class Editwebpage extends \Zotlabs\Web\Controller { else return; - profile_load($a,$which); + profile_load($which); } @@ -114,11 +113,11 @@ class Editwebpage extends \Zotlabs\Web\Controller { $itm[0]['body'] = crypto_unencapsulate(json_decode_plus($itm[0]['body']),$key); } - $item_id = q("select * from item_id where service = 'WEBPAGE' and iid = %d limit 1", + $item_id = q("select * from iconfig where cat = 'system' and k = 'WEBPAGE' and iid = %d limit 1", intval($itm[0]['id']) ); if($item_id) - $page_title = $item_id[0]['sid']; + $page_title = $item_id[0]['v']; $mimetype = $itm[0]['mimetype']; @@ -151,7 +150,7 @@ class Editwebpage extends \Zotlabs\Web\Controller { 'body' => undo_post_tagging($itm[0]['body']), 'post_id' => $post_id, 'visitor' => ($is_owner) ? true : false, - 'acl' => populate_acl($itm[0],false,\PermissionDescription::fromGlobalPermission('view_pages')), + 'acl' => populate_acl($itm[0],false,\Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_pages')), 'showacl' => ($is_owner) ? true : false, 'mimetype' => $mimetype, 'mimeselect' => true, diff --git a/Zotlabs/Module/Embedphotos.php b/Zotlabs/Module/Embedphotos.php new file mode 100644 index 000000000..0dac873c5 --- /dev/null +++ b/Zotlabs/Module/Embedphotos.php @@ -0,0 +1,180 @@ + 1 && argv(1) === 'album') { + // API: /embedphotos/album + $name = (x($_POST,'name') ? $_POST['name'] : null ); + if (!$name) { + json_return_and_die(array('errormsg' => 'Error retrieving album', 'status' => false)); + } + $album = $this->embedphotos_widget_album(array('channel' => \App::get_channel(), 'album' => $name)); + json_return_and_die(array('status' => true, 'content' => $album)); + + } + if (argc() > 1 && argv(1) === 'albumlist') { + // API: /embedphotos/albumlist + $album_list = $this->embedphotos_album_list($a); + json_return_and_die(array('status' => true, 'albumlist' => $album_list)); + + } + if (argc() > 1 && argv(1) === 'photolink') { + // API: /embedphotos/photolink + $href = (x($_POST,'href') ? $_POST['href'] : null ); + if (!$href) { + json_return_and_die(array('errormsg' => 'Error retrieving link ' . $href, 'status' => false)); + } + $resource_id = array_pop(explode("/", $href)); + $r = q("SELECT obj from item where resource_type = 'photo' and resource_id = '%s' limit 1", + dbesc($resource_id) + ); + if(!$r) { + json_return_and_die(array('errormsg' => 'Error retrieving resource ' . $resource_id, 'status' => false)); + } + $obj = json_decode($r[0]['obj'], true); + if(x($obj,'body')) { + $photolink = $obj['body']; + } elseif (x($obj,'bbcode')) { + $photolink = $obj['bbcode']; + } else { + json_return_and_die(array('errormsg' => 'Error retrieving resource ' . $resource_id, 'status' => false)); + } + json_return_and_die(array('status' => true, 'photolink' => $photolink)); + + } + } + + +/** + * Copied from include/widgets.php::widget_album() with a modification to get the profile_uid from + * the input array as in widget_item() + * @param type $name + * @return string + */ +function embedphotos_widget_album($args) { + + $channel_id = 0; + if(array_key_exists('channel',$args)) + $channel = $args['channel']; + $channel_id = intval($channel['channel_id']); + if(! $channel_id) + $channel_id = \App::$profile_uid; + if(! $channel_id) + return ''; + $owner_uid = $channel_id; + require_once('include/security.php'); + $sql_extra = permissions_sql($channel_id); + + if(! perm_is_allowed($channel_id,get_observer_hash(),'view_storage')) + return ''; + + if($args['album']) + $album = $args['album']; + if($args['title']) + $title = $args['title']; + + /** + * This may return incorrect permissions if you have multiple directories of the same name. + * It is a limitation of the photo table using a name for a photo album instead of a folder hash + */ + + if($album) { + $x = q("select hash from attach where filename = '%s' and uid = %d limit 1", + dbesc($album), + intval($owner_uid) + ); + if($x) { + $y = attach_can_view_folder($owner_uid,get_observer_hash(),$x[0]['hash']); + if(! $y) + return ''; + } + } + + $order = 'DESC'; + + $r = q("SELECT p.resource_id, p.id, p.filename, p.mimetype, p.imgscale, p.description, p.created FROM photo p INNER JOIN + (SELECT resource_id, max(imgscale) imgscale FROM photo WHERE uid = %d AND album = '%s' AND imgscale <= 4 AND photo_usage IN ( %d, %d ) $sql_extra GROUP BY resource_id) ph + ON (p.resource_id = ph.resource_id AND p.imgscale = ph.imgscale) + ORDER BY created $order", + intval($owner_uid), + dbesc($album), + intval(PHOTO_NORMAL), + intval(PHOTO_PROFILE) + ); + + $photos = array(); + if(count($r)) { + $twist = 'rotright'; + foreach($r as $rr) { + + if($twist == 'rotright') + $twist = 'rotleft'; + else + $twist = 'rotright'; + + $ext = $phototypes[$rr['mimetype']]; + + $imgalt_e = $rr['filename']; + $desc_e = $rr['description']; + + $imagelink = (z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/image/' . $rr['resource_id'] + . (($_GET['order'] === 'posted') ? '?f=&order=posted' : '')); + + $photos[] = array( + 'id' => $rr['id'], + 'twist' => ' ' . $twist . rand(2,4), + 'link' => $imagelink, + 'title' => t('View Photo'), + 'src' => z_root() . '/photo/' . $rr['resource_id'] . '-' . $rr['imgscale'] . '.' .$ext, + 'alt' => $imgalt_e, + 'desc'=> $desc_e, + 'ext' => $ext, + 'hash'=> $rr['resource_id'], + 'unknown' => t('Unknown') + ); + } + } + + $tpl = get_markup_template('photo_album.tpl'); + $o .= replace_macros($tpl, array( + '$photos' => $photos, + '$album' => (($title) ? $title : $album), + '$album_id' => rand(), + '$album_edit' => array(t('Edit Album'), $album_edit), + '$can_post' => false, + '$upload' => array(t('Upload'), z_root() . '/photos/' . \App::$profile['channel_address'] . '/upload/' . bin2hex($album)), + '$order' => false, + '$upload_form' => $upload_form, + '$no_fullscreen_btn' => true + )); + + return $o; +} + + +function embedphotos_album_list($a) { + $o = ''; + require_once('include/photos.php'); + $p = photos_albums_list(\App::get_channel(), \App::get_observer()); + if ($p['success']) { + return $p['albums']; + } else { + return null; + } +} + +} diff --git a/Zotlabs/Module/Events.php b/Zotlabs/Module/Events.php index 3f3f9fb4c..def5c437b 100644 --- a/Zotlabs/Module/Events.php +++ b/Zotlabs/Module/Events.php @@ -6,7 +6,6 @@ require_once('include/bbcode.php'); require_once('include/datetime.php'); require_once('include/event.php'); require_once('include/items.php'); -require_once('include/PermissionDescription.php'); class Events extends \Zotlabs\Web\Controller { @@ -471,7 +470,7 @@ class Events extends \Zotlabs\Web\Controller { '$permissions' => t('Permission settings'), // populating the acl dialog was a permission description from view_stream because Cal.php, which // displays events, says "since we don't currently have an event permission - use the stream permission" - '$acl' => (($orig_event['event_xchan']) ? '' : populate_acl(((x($orig_event)) ? $orig_event : $perm_defaults), false, \PermissionDescription::fromGlobalPermission('view_stream'))), + '$acl' => (($orig_event['event_xchan']) ? '' : populate_acl(((x($orig_event)) ? $orig_event : $perm_defaults), false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'))), '$submit' => t('Submit'), '$advanced' => t('Advanced Options') @@ -668,8 +667,10 @@ class Events extends \Zotlabs\Web\Controller { '$export' => array(z_root()."/events/$y/$m/export",t('Export'),'',''), '$calendar' => cal($y,$m,$links, ' eventcal'), '$events' => $events, - '$upload' => t('Import'), - '$submit' => t('Submit'), + '$view_label' => t('View'), + '$month' => t('Month'), + '$week' => t('Week'), + '$day' => t('Day'), '$prev' => t('Previous'), '$next' => t('Next'), '$today' => t('Today'), diff --git a/Zotlabs/Module/Filestorage.php b/Zotlabs/Module/Filestorage.php index 2861f31be..c3ef22e32 100644 --- a/Zotlabs/Module/Filestorage.php +++ b/Zotlabs/Module/Filestorage.php @@ -6,7 +6,6 @@ namespace Zotlabs\Module; */ require_once('include/attach.php'); -require_once('include/PermissionDescription.php'); /** @@ -134,7 +133,7 @@ class Filestorage extends \Zotlabs\Web\Controller { $cloudpath = get_cloudpath($f) . (intval($f['is_dir']) ? '?f=&davguest=1' : ''); $parentpath = get_parent_cloudpath($channel['channel_id'], $channel['channel_address'], $f['hash']); - $aclselect_e = populate_acl($f, false, \PermissionDescription::fromGlobalPermission('view_storage')); + $aclselect_e = populate_acl($f, false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_storage')); $is_a_dir = (intval($f['is_dir']) ? true : false); $lockstate = (($f['allow_cid'] || $f['allow_gid'] || $f['deny_cid'] || $f['deny_gid']) ? 'lock' : 'unlock'); diff --git a/Zotlabs/Module/Follow.php b/Zotlabs/Module/Follow.php index 1df382a89..3641330c9 100644 --- a/Zotlabs/Module/Follow.php +++ b/Zotlabs/Module/Follow.php @@ -43,7 +43,7 @@ class Follow extends \Zotlabs\Web\Controller { unset($clone['abook_account']); unset($clone['abook_channel']); - $abconfig = load_abconfig($channel['channel_hash'],$clone['abook_xchan']); + $abconfig = load_abconfig($channel['channel_id'],$clone['abook_xchan']); if($abconfig) $clone['abconfig'] = $abconfig; diff --git a/Zotlabs/Module/Hcard.php b/Zotlabs/Module/Hcard.php index 2636e676b..93c8d3ece 100644 --- a/Zotlabs/Module/Hcard.php +++ b/Zotlabs/Module/Hcard.php @@ -40,7 +40,7 @@ class Hcard extends \Zotlabs\Web\Controller { } } - profile_load($a,$which,$profile); + profile_load($which,$profile); } diff --git a/Zotlabs/Module/Home.php b/Zotlabs/Module/Home.php index f3ba96fdd..79449c3b2 100644 --- a/Zotlabs/Module/Home.php +++ b/Zotlabs/Module/Home.php @@ -28,6 +28,19 @@ class Home extends \Zotlabs\Web\Controller { goaway($dest); } + + if(remote_channel() && (! $splash) && $_SESSION['atoken']) { + $r = q("select * from atoken where atoken_id = %d", + intval($_SESSION['atoken']) + ); + if($r) { + $x = channelx_by_n($r[0]['atoken_uid']); + if($x) { + goaway(z_root() . '/channel/' . $x['channel_address']); + } + } + } + if(get_account_id() && ! $splash) { goaway(z_root() . '/new_channel'); diff --git a/Zotlabs/Module/Id.php b/Zotlabs/Module/Id.php index 6a94b57f5..e053bf99c 100644 --- a/Zotlabs/Module/Id.php +++ b/Zotlabs/Module/Id.php @@ -57,7 +57,7 @@ class Id extends \Zotlabs\Web\Controller { $profile = ''; $channel = \App::get_channel(); - profile_load($a,$which,$profile); + profile_load($which,$profile); $op = new MysqlProvider; $op->server(); diff --git a/Zotlabs/Module/Impel.php b/Zotlabs/Module/Impel.php index e326f7818..735c311d0 100644 --- a/Zotlabs/Module/Impel.php +++ b/Zotlabs/Module/Impel.php @@ -137,9 +137,7 @@ class Impel extends \Zotlabs\Web\Controller { require_once('library/urlify/URLify.php'); $pagetitle = strtolower(\URLify::transliterate($j['pagetitle'])); } - - - + // Verify ability to use html or php!!! $execflag = false; @@ -154,21 +152,14 @@ class Impel extends \Zotlabs\Web\Controller { } } - $remote_id = 0; - - $z = q("select * from item_id where sid = '%s' and service = '%s' and uid = %d limit 1", - dbesc($pagetitle), - dbesc($namespace), - intval(local_channel()) - ); - $i = q("select id, edited, item_deleted from item where mid = '%s' and uid = %d limit 1", dbesc($arr['mid']), intval(local_channel()) ); + + \Zotlabs\Lib\IConfig::Set($arr,'system',$namespace,(($pagetitle) ? $pagetitle : substr($arr['mid'],0,16)),true); - if($z && $i) { - $remote_id = $z[0]['id']; + if($i) { $arr['id'] = $i[0]['id']; // don't update if it has the same timestamp as the original if($arr['edited'] > $i[0]['edited']) @@ -182,12 +173,12 @@ class Impel extends \Zotlabs\Web\Controller { intval(local_channel()) ); } - $x = item_store($arr,$execflag); + else + $x = item_store($arr,$execflag); } - if($x['success']) { + if($x && $x['success']) { $item_id = $x['item_id']; - update_remote_id($channel,$item_id,$arr['item_type'],$pagetitle,$namespace,$remote_id,$arr['mid']); } } @@ -199,7 +190,8 @@ class Impel extends \Zotlabs\Web\Controller { notice( sprintf( t('%s element installation failed'), $installed_type)); } - //??? should perhaps return ret? + //??? should perhaps return ret? + json_return_and_die(true); } diff --git a/Zotlabs/Module/Import.php b/Zotlabs/Module/Import.php index 122e27e90..e34f5e49e 100644 --- a/Zotlabs/Module/Import.php +++ b/Zotlabs/Module/Import.php @@ -131,6 +131,8 @@ class Import extends \Zotlabs\Web\Controller { // import channel + $relocate = ((array_key_exists('relocate',$data)) ? $data['relocate'] : null); + if(array_key_exists('channel',$data)) { if($completed < 1) { @@ -387,8 +389,7 @@ class Import extends \Zotlabs\Web\Controller { if($abconfig) { // @fixme does not handle sync of del_abconfig foreach($abconfig as $abc) { - if($abc['chan'] === $channel['channel_hash']) - set_abconfig($abc['chan'],$abc['xchan'],$abc['cat'],$abc['k'],$abc['v']); + set_abconfig($channel['channel_id'],$abc['xchan'],$abc['cat'],$abc['k'],$abc['v']); } } @@ -475,7 +476,7 @@ class Import extends \Zotlabs\Web\Controller { import_events($channel,$data['event']); if(is_array($data['event_item'])) - import_items($channel,$data['event_item']); + import_items($channel,$data['event_item'],false,$relocate); if(is_array($data['menu'])) import_menus($channel,$data['menu']); @@ -486,7 +487,7 @@ class Import extends \Zotlabs\Web\Controller { $saved_notification_flags = notifications_off($channel['channel_id']); if($import_posts && array_key_exists('item',$data) && $data['item']) - import_items($channel,$data['item']); + import_items($channel,$data['item'],false,$relocate); notifications_on($channel['channel_id'],$saved_notification_flags); diff --git a/Zotlabs/Module/Import_items.php b/Zotlabs/Module/Import_items.php index a862836c5..f20cbfe7e 100644 --- a/Zotlabs/Module/Import_items.php +++ b/Zotlabs/Module/Import_items.php @@ -78,6 +78,8 @@ class Import_items extends \Zotlabs\Web\Controller { // logger('import: data: ' . print_r($data,true)); // print_r($data); + if(! is_array($data)) + return; if(array_key_exists('compatibility',$data) && array_key_exists('database',$data['compatibility'])) { $v1 = substr($data['compatibility']['database'],-4); @@ -92,7 +94,7 @@ class Import_items extends \Zotlabs\Web\Controller { if(array_key_exists('item',$data) && $data['item']) { - import_items($channel,$data['item']); + import_items($channel,$data['item'],false,((array_key_exists('relocate',$data)) ? $data['relocate'] : null)); } if(array_key_exists('item_id',$data) && $data['item_id']) { @@ -106,7 +108,7 @@ class Import_items extends \Zotlabs\Web\Controller { - function get() { + function get() { if(! local_channel()) { notice( t('Permission denied') . EOL); diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php index 2601feb0a..b54099d74 100644 --- a/Zotlabs/Module/Item.php +++ b/Zotlabs/Module/Item.php @@ -1,4 +1,5 @@ %s - INTERVAL %s and body = '%s' limit 1", intval($profile_uid), + db_utcnow(), + db_quoteinterval('2 MINUTE'), dbesc($body) ); if($z) { - foreach($z as $zz) { - if($zz['created'] > datetime_convert('UTC','UTC', 'now - 2 minutes')) { - $datarray['cancel'] = 1; - notice( t('Duplicate post suppressed.') . EOL); - logger('Duplicate post. Faking plugin cancel.'); - } - } + $datarray['cancel'] = 1; + notice( t('Duplicate post suppressed.') . EOL); + logger('Duplicate post. Faking plugin cancel.'); } } @@ -880,13 +885,21 @@ class Item extends \Zotlabs\Web\Controller { } } + if($webpage) { + Zlib\IConfig::Set($datarray,'system', webpage_to_namespace($webpage), + (($pagetitle) ? $pagetitle : substr($datarray['mid'],0,16)),true); + } + elseif($namespace) { + Zlib\IConfig::Set($datarray,'system', $namespace, + (($remote_id) ? $remote_id : substr($datarray['mid'],0,16)),true); + } + + if($orig_post) { $datarray['id'] = $post_id; - item_store_update($datarray,$execflag); - - update_remote_id($channel,$post_id,$webpage,$pagetitle,$namespace,$remote_id,$mid); - + $x = item_store_update($datarray,$execflag); + if(! $parent) { $r = q("select * from item where id = %d", intval($post_id) @@ -894,10 +907,7 @@ class Item extends \Zotlabs\Web\Controller { if($r) { xchan_query($r); $sync_item = fetch_post_tags($r); - $rid = q("select * from item_id where iid = %d", - intval($post_id) - ); - build_sync_packet($uid,array('item' => array(encode_item($sync_item[0],true)),'item_id' => $rid)); + build_sync_packet($profile_uid,array('item' => array(encode_item($sync_item[0],true)))); } } if(! $nopush) @@ -978,10 +988,7 @@ class Item extends \Zotlabs\Web\Controller { goaway(z_root() . "/" . $return_path ); // NOTREACHED } - - - update_remote_id($channel,$post_id,$webpage,$pagetitle,$namespace,$remote_id,$mid); - + if(($parent) && ($parent != $post_id)) { // Store the comment signature information in case we need to relay to Diaspora //$ditem = $datarray; @@ -995,10 +1002,7 @@ class Item extends \Zotlabs\Web\Controller { if($r) { xchan_query($r); $sync_item = fetch_post_tags($r); - $rid = q("select * from item_id where iid = %d", - intval($post_id) - ); - build_sync_packet($uid,array('item' => array(encode_item($sync_item[0],true)),'item_id' => $rid)); + build_sync_packet($profile_uid,array('item' => array(encode_item($sync_item[0],true)))); } } @@ -1012,11 +1016,6 @@ class Item extends \Zotlabs\Web\Controller { logger('post_complete'); - - - - - // figure out how to return, depending on from whence we came if($api_source) diff --git a/Zotlabs/Module/Layouts.php b/Zotlabs/Module/Layouts.php index 9b9fc22f3..c07f65ce1 100644 --- a/Zotlabs/Module/Layouts.php +++ b/Zotlabs/Module/Layouts.php @@ -21,7 +21,7 @@ class Layouts extends \Zotlabs\Web\Controller { else return; - profile_load($a,$which); + profile_load($which); } @@ -90,13 +90,14 @@ class Layouts extends \Zotlabs\Web\Controller { return; } - //This feature is not exposed in redbasic ui since it is not clear why one would want to - //download a json encoded pdl file - we dont have a possibility to import it. - //Use the buildin share/install feature instead. + // This feature is not exposed in redbasic ui since it is not clear why one would want to + // download a json encoded pdl file - we dont have a possibility to import it. + // Use the buildin share/install feature instead. + if((argc() > 3) && (argv(2) === 'share') && (argv(3))) { - $r = q("select sid, service, mimetype, title, body from item_id - left join item on item.id = item_id.iid - where item_id.uid = %d and item.mid = '%s' and service = 'PDL' order by sid asc", + $r = q("select iconfig.v, iconfig.k, mimetype, title, body from iconfig + left join item on item.id = iconfig.iid + where uid = %d and mid = '%s' and iconfig.cat = 'system' and iconfig.k = 'PDL' order by iconfig.v asc", intval($owner), dbesc(argv(3)) ); @@ -141,8 +142,9 @@ class Layouts extends \Zotlabs\Web\Controller { $editor = status_editor($a,$x); - $r = q("select iid, sid, mid, title, body, mimetype, created, edited, item_type from item_id left join item on item_id.iid = item.id - where item_id.uid = %d and service = 'PDL' and item_type = %d order by item.created desc", + $r = q("select iconfig.iid, iconfig.v, mid, title, body, mimetype, created, edited, item_type from iconfig + left join item on iconfig.iid = item.id + where uid = %d and iconfig.cat = 'system' and iconfig.k = 'PDL' and item_type = %d order by item.created desc", intval($owner), intval(ITEM_TYPE_PDL) ); @@ -164,7 +166,7 @@ class Layouts extends \Zotlabs\Web\Controller { ); $pages[$rr['iid']][] = array( 'url' => $rr['iid'], - 'title' => $rr['sid'], + 'title' => $rr['v'], 'descr' => $rr['title'], 'mid' => $rr['mid'], 'created' => $rr['created'], diff --git a/Zotlabs/Module/Lockview.php b/Zotlabs/Module/Lockview.php index 4776e1c56..d86a3c1d8 100644 --- a/Zotlabs/Module/Lockview.php +++ b/Zotlabs/Module/Lockview.php @@ -1,17 +1,31 @@ 1) ? argv(1) : 0); if (is_numeric($type)) { $item_id = intval($type); $type='item'; - } else { + } + else { $item_id = ((argc() > 2) ? intval(argv(2)) : 0); } @@ -98,6 +112,13 @@ class Lockview extends \Zotlabs\Web\Controller { if($r) foreach($r as $rr) $l[] = '
  • ' . $rr['xchan_name'] . '
  • '; + if($atokens) { + foreach($atokens as $at) { + if(in_array("'" . $at['xchan_hash'] . "'",$allowed_users)) { + $l[] = '
  • ' . $at['xchan_name'] . '
  • '; + } + } + } } if(count($deny_groups)) { $r = q("SELECT gname FROM `groups` WHERE hash IN ( " . implode(', ', $deny_groups) . " )"); @@ -110,6 +131,16 @@ class Lockview extends \Zotlabs\Web\Controller { if($r) foreach($r as $rr) $l[] = '
  • ' . $rr['xchan_name'] . '
  • '; + + if($atokens) { + foreach($atokens as $at) { + if(in_array("'" . $at['xchan_hash'] . "'",$deny_users)) { + $l[] = '
  • ' . $at['xchan_name'] . '
  • '; + } + } + } + + } echo $o . implode($l); diff --git a/Zotlabs/Module/Login.php b/Zotlabs/Module/Login.php index ff75e5268..ae35b922f 100644 --- a/Zotlabs/Module/Login.php +++ b/Zotlabs/Module/Login.php @@ -7,6 +7,9 @@ class Login extends \Zotlabs\Web\Controller { function get() { if(local_channel()) goaway(z_root()); + if(remote_channel() && $_SESSION['atoken']) + goaway(z_root()); + return login((\App::$config['system']['register_policy'] == REGISTER_CLOSED) ? false : true); } diff --git a/Zotlabs/Module/Network.php b/Zotlabs/Module/Network.php index 87ed326e2..3b88cd8d6 100644 --- a/Zotlabs/Module/Network.php +++ b/Zotlabs/Module/Network.php @@ -6,8 +6,6 @@ require_once('include/group.php'); require_once('include/contact_widgets.php'); require_once('include/conversation.php'); require_once('include/acl_selectors.php'); -require_once('include/PermissionDescription.php'); - class Network extends \Zotlabs\Web\Controller { @@ -171,7 +169,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, \PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post'), + 'acl' => populate_acl((($private_editing) ? $def_acl : $channel_acl), true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post'), 'bang' => (($private_editing) ? '!' : ''), 'visitor' => true, 'profile_uid' => local_channel(), diff --git a/Zotlabs/Module/New_channel.php b/Zotlabs/Module/New_channel.php index 30d7c83c6..26883b6e2 100644 --- a/Zotlabs/Module/New_channel.php +++ b/Zotlabs/Module/New_channel.php @@ -62,7 +62,7 @@ class New_channel extends \Zotlabs\Web\Controller { } - function post() { + function post() { $arr = $_POST; @@ -96,7 +96,7 @@ class New_channel extends \Zotlabs\Web\Controller { } - function get() { + function get() { $acc = \App::get_account(); @@ -125,9 +125,9 @@ class New_channel extends \Zotlabs\Web\Controller { } } - $name = array('name', t('Name or caption'), ((x($_REQUEST,'name')) ? $_REQUEST['name'] : ''), t('Examples: "Bob Jameson", "Lisa and her Horses", "Soccer", "Aviation Group"')); + $name = array('name', t('Name or caption'), ((x($_REQUEST,'name')) ? $_REQUEST['name'] : ''), t('Examples: "Bob Jameson", "Lisa and her Horses", "Soccer", "Aviation Group"'), "*"); $nickhub = '@' . \App::get_hostname(); - $nickname = array('nickname', t('Choose a short nickname'), ((x($_REQUEST,'nickname')) ? $_REQUEST['nickname'] : ''), sprintf( t('Your nickname will be used to create an easy to remember channel address e.g. nickname%s'), $nickhub)); + $nickname = array('nickname', t('Choose a short nickname'), ((x($_REQUEST,'nickname')) ? $_REQUEST['nickname'] : ''), sprintf( t('Your nickname will be used to create an easy to remember channel address e.g. nickname%s'), $nickhub), "*"); $privacy_role = ((x($_REQUEST,'permissions_role')) ? $_REQUEST['permissions_role'] : "" ); $role = array('permissions_role' , t('Channel role and privacy'), ($privacy_role) ? $privacy_role : 'social', t('Select a channel role with your privacy requirements.') . ' ' . t('Read more about roles') . '',get_roles()); diff --git a/Zotlabs/Module/Openid.php b/Zotlabs/Module/Openid.php index 7a6e4a81f..8cbc6d2fd 100644 --- a/Zotlabs/Module/Openid.php +++ b/Zotlabs/Module/Openid.php @@ -48,7 +48,7 @@ class Openid extends \Zotlabs\Web\Controller { $_SESSION['uid'] = $r[0]['channel_id']; $_SESSION['account_id'] = $r[0]['channel_account_id']; $_SESSION['authenticated'] = true; - authenticate_success($record,true,true,true,true); + authenticate_success($record,$r[0],true,true,true,true); goaway(z_root()); } } diff --git a/Zotlabs/Module/Page.php b/Zotlabs/Module/Page.php index 14273614c..6ef285dd0 100644 --- a/Zotlabs/Module/Page.php +++ b/Zotlabs/Module/Page.php @@ -13,7 +13,7 @@ class Page extends \Zotlabs\Web\Controller { $which = argv(1); $profile = 0; - profile_load($a,$which,$profile); + profile_load($which,$profile); @@ -65,9 +65,10 @@ class Page extends \Zotlabs\Web\Controller { require_once('include/security.php'); $sql_options = item_permissions_sql($u[0]['channel_id']); - $r = q("select item.* from item left join item_id on item.id = item_id.iid - where item.uid = %d and sid = '%s' and item.item_delayed = 0 and (( service = 'WEBPAGE' and item_type = %d ) - OR ( service = 'PDL' AND item_type = %d )) $sql_options $revision limit 1", + $r = q("select item.* from item left join iconfig on item.id = iconfig.iid + where item.uid = %d and iconfig.cat = 'system' and iconfig.v = '%s' and item.item_delayed = 0 + and (( iconfig.k = 'WEBPAGE' and item_type = %d ) + OR ( iconfig.k = 'PDL' AND item_type = %d )) $sql_options $revision limit 1", intval($u[0]['channel_id']), dbesc($page_id), intval(ITEM_TYPE_WEBPAGE), @@ -77,9 +78,9 @@ class Page extends \Zotlabs\Web\Controller { // Check again with no permissions clause to see if it is a permissions issue - $x = q("select item.* from item left join item_id on item.id = item_id.iid - where item.uid = %d and sid = '%s' and item.item_delayed = 0 and service = 'WEBPAGE' and - item_type = %d $revision limit 1", + $x = q("select item.* from item left join iconfig on item.id = iconfig.iid + where item.uid = %d and iconfig.cat = 'system' and iconfig.v = '%s' and item.item_delayed = 0 + and iconfig.k = 'WEBPAGE' and item_type = %d $revision limit 1", intval($u[0]['channel_id']), dbesc($page_id), intval(ITEM_TYPE_WEBPAGE) @@ -119,11 +120,8 @@ class Page extends \Zotlabs\Web\Controller { \App::$data['webpage'] = $r; } - - - - - function get() { + + function get() { $r = \App::$data['webpage']; if(! $r) diff --git a/Zotlabs/Module/Photo.php b/Zotlabs/Module/Photo.php index 92c9ac3c0..66aaec49f 100644 --- a/Zotlabs/Module/Photo.php +++ b/Zotlabs/Module/Photo.php @@ -2,6 +2,7 @@ namespace Zotlabs\Module; require_once('include/security.php'); +require_once('include/attach.php'); require_once('include/photo/photo_driver.php'); @@ -10,6 +11,8 @@ class Photo extends \Zotlabs\Web\Controller { function init() { $prvcachecontrol = false; + $streaming = null; + $channel = null; switch(argc()) { case 4: @@ -62,7 +65,7 @@ class Photo extends \Zotlabs\Web\Controller { intval($uid), intval(PHOTO_PROFILE) ); - if(count($r)) { + if($r) { $data = dbunescbin($r[0]['content']); $mimetype = $r[0]['mimetype']; } @@ -79,7 +82,7 @@ class Photo extends \Zotlabs\Web\Controller { * Other photos */ - /* Check for a cookie to indicate display pixel density, in order to detect high-resolution + /* Check for a cookie to indicate display pixel density, in order to detect high-resolution displays. This procedure was derived from the "Retina Images" by Jeremey Worboys, used in accordance with the Creative Commons Attribution 3.0 Unported License. Project link: https://github.com/Retina-Images/Retina-Images @@ -131,6 +134,8 @@ class Photo extends \Zotlabs\Web\Controller { $sql_extra = permissions_sql($r[0]['uid']); + $channel = channelx_by_n($r[0]['uid']); + // Now we'll see if we can access the photo $r = q("SELECT * FROM photo WHERE resource_id = '%s' AND imgscale = %d $sql_extra LIMIT 1", @@ -141,8 +146,9 @@ class Photo extends \Zotlabs\Web\Controller { if($r && $allowed) { $data = dbunescbin($r[0]['content']); $mimetype = $r[0]['mimetype']; - if(intval($r[0]['os_storage'])) - $data = file_get_contents($data); + if(intval($r[0]['os_storage'])) { + $streaming = $data; + } } else { @@ -242,7 +248,25 @@ class Photo extends \Zotlabs\Web\Controller { header("Cache-Control: max-age=" . $cache); } - echo $data; + + // If it's a file resource, stream it. + + if($streaming && $channel) { + if(strpos($streaming,'store') !== false) + $istream = fopen($streaming,'rb'); + else + $istream = fopen('store/' . $channel['channel_address'] . '/' . $streaming,'rb'); + $ostream = fopen('php://output','wb'); + if($istream && $ostream) { + pipe_streams($istream,$ostream); + fclose($istream); + fclose($ostream); + } + } + else { + echo $data; + } + killme(); // NOTREACHED } diff --git a/Zotlabs/Module/Photos.php b/Zotlabs/Module/Photos.php index 1bdc23897..1eeab1461 100644 --- a/Zotlabs/Module/Photos.php +++ b/Zotlabs/Module/Photos.php @@ -9,8 +9,6 @@ require_once('include/bbcode.php'); require_once('include/security.php'); require_once('include/attach.php'); require_once('include/text.php'); -require_once('include/PermissionDescription.php'); - class Photos extends \Zotlabs\Web\Controller { @@ -27,7 +25,7 @@ class Photos extends \Zotlabs\Web\Controller { if(argc() > 1) { $nick = argv(1); - profile_load($a,$nick); + profile_load($nick); $channelx = channelx_by_nick($nick); @@ -633,7 +631,7 @@ class Photos extends \Zotlabs\Web\Controller { $lockstate = (($acl->is_private()) ? 'lock' : 'unlock'); } - $aclselect = (($_is_owner) ? populate_acl($channel_acl,false, \PermissionDescription::fromGlobalPermission('view_storage')) : ''); + $aclselect = (($_is_owner) ? populate_acl($channel_acl,false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_storage')) : ''); // this is wrong but is to work around an issue with js_upload wherein it chokes if these variables // don't exist. They really should be set to a parseable representation of the channel's default permissions @@ -1023,7 +1021,7 @@ class Photos extends \Zotlabs\Web\Controller { if($can_post) { $album_e = $ph[0]['album']; $caption_e = $ph[0]['description']; - $aclselect_e = (($_is_owner) ? populate_acl($ph[0], true, \PermissionDescription::fromGlobalPermission('view_storage')) : ''); + $aclselect_e = (($_is_owner) ? populate_acl($ph[0], true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_storage')) : ''); $albums = ((array_key_exists('albums', \App::$data)) ? \App::$data['albums'] : photos_albums_list(\App::$data['channel'],\App::$data['observer'])); $_SESSION['album_return'] = bin2hex($ph[0]['album']); diff --git a/Zotlabs/Module/Ping.php b/Zotlabs/Module/Ping.php index 5cbf45daa..e01c3b49a 100644 --- a/Zotlabs/Module/Ping.php +++ b/Zotlabs/Module/Ping.php @@ -173,7 +173,7 @@ class Ping extends \Zotlabs\Web\Controller { ); break; case 'all_events': - $r = q("update event set `dimissed` = 1 where `dismissed` = 0 and uid = %d AND dtstart < '%s' AND dtstart > '%s' ", + $r = q("update event set `dismissed` = 1 where `dismissed` = 0 and uid = %d AND dtstart < '%s' AND dtstart > '%s' ", intval(local_channel()), dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now + ' . intval($evdays) . ' days')), dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now - 1 days')) diff --git a/Zotlabs/Module/Profile.php b/Zotlabs/Module/Profile.php index 8bf358bc8..9e868db92 100644 --- a/Zotlabs/Module/Profile.php +++ b/Zotlabs/Module/Profile.php @@ -48,7 +48,7 @@ class Profile extends \Zotlabs\Web\Controller { } } - profile_load($a,$which,$profile); + profile_load($which,$profile); } diff --git a/Zotlabs/Module/Profile_photo.php b/Zotlabs/Module/Profile_photo.php index bb332f4ab..f459f7deb 100644 --- a/Zotlabs/Module/Profile_photo.php +++ b/Zotlabs/Module/Profile_photo.php @@ -23,19 +23,18 @@ class Profile_photo extends \Zotlabs\Web\Controller { /* @brief Initalize the profile-photo edit view * - * @param $a Current application * @return void * */ - function init() { + function init() { if(! local_channel()) { return; } $channel = \App::get_channel(); - profile_load($a,$channel['channel_address']); + profile_load($channel['channel_address']); } @@ -46,7 +45,7 @@ class Profile_photo extends \Zotlabs\Web\Controller { * */ - function post() { + function post() { if(! local_channel()) { return; @@ -54,24 +53,7 @@ class Profile_photo extends \Zotlabs\Web\Controller { check_form_security_token_redirectOnErr('/profile_photo', 'profile_photo'); - if((x($_POST,'cropfinal')) && ($_POST['cropfinal'] == 1)) { - - // unless proven otherwise - $is_default_profile = 1; - - if($_REQUEST['profile']) { - $r = q("select id, profile_guid, is_default, gender from profile where id = %d and uid = %d limit 1", - intval($_REQUEST['profile']), - intval(local_channel()) - ); - if($r) { - $profile = $r[0]; - if(! intval($profile['is_default'])) - $is_default_profile = 0; - } - } - - + if((array_key_exists('cropfinal',$_POST)) && (intval($_POST['cropfinal']) == 1)) { // phase 2 - we have finished cropping @@ -86,18 +68,33 @@ class Profile_photo extends \Zotlabs\Web\Controller { $scale = substr($image_id,-1,1); $image_id = substr($image_id,0,-2); } - + + + // unless proven otherwise + $is_default_profile = 1; + + if($_REQUEST['profile']) { + $r = q("select id, profile_guid, is_default, gender from profile where id = %d and uid = %d limit 1", + intval($_REQUEST['profile']), + intval(local_channel()) + ); + if($r) { + $profile = $r[0]; + if(! intval($profile['is_default'])) + $is_default_profile = 0; + } + } + $srcX = $_POST['xstart']; $srcY = $_POST['ystart']; $srcW = $_POST['xfinal'] - $srcX; $srcH = $_POST['yfinal'] - $srcY; - + $r = q("SELECT * FROM photo WHERE resource_id = '%s' AND uid = %d AND imgscale = %d LIMIT 1", dbesc($image_id), dbesc(local_channel()), intval($scale)); - if($r) { $base_image = $r[0]; @@ -110,30 +107,38 @@ class Profile_photo extends \Zotlabs\Web\Controller { $aid = get_account_id(); - $p = array('aid' => $aid, 'uid' => local_channel(), 'resource_id' => $base_image['resource_id'], - 'filename' => $base_image['filename'], 'album' => t('Profile Photos')); + $p = [ + 'aid' => $aid, + 'uid' => local_channel(), + 'resource_id' => $base_image['resource_id'], + 'filename' => $base_image['filename'], + 'album' => t('Profile Photos') + ]; - $p['imgscale'] = 4; + $p['imgscale'] = PHOTO_RES_PROFILE_300; $p['photo_usage'] = (($is_default_profile) ? PHOTO_PROFILE : PHOTO_NORMAL); $r1 = $im->save($p); $im->scaleImage(80); - $p['imgscale'] = 5; + $p['imgscale'] = PHOTO_RES_PROFILE_80; $r2 = $im->save($p); $im->scaleImage(48); - $p['imgscale'] = 6; + $p['imgscale'] = PHOTO_RES_PROFILE_48; $r3 = $im->save($p); if($r1 === false || $r2 === false || $r3 === false) { // if one failed, delete them all so we can start over. notice( t('Image resize failed.') . EOL ); - $x = q("delete from photo where resource_id = '%s' and uid = %d and imgscale >= 4 ", + $x = q("delete from photo where resource_id = '%s' and uid = %d and imgscale in ( %d, %d, %d ) ", dbesc($base_image['resource_id']), - local_channel() + local_channel(), + intval(PHOTO_RES_PROFILE_300), + intval(PHOTO_RES_PROFILE_80), + intval(PHOTO_RES_PROFILE_48) ); return; } @@ -175,6 +180,8 @@ class Profile_photo extends \Zotlabs\Web\Controller { dbesc(datetime_convert()), dbesc($channel['xchan_hash']) ); + // Similarly, tell the nav bar to bypass the cache and update the avater image. + $_SESSION['reload_avatar'] = true; info( t('Shift-reload the page or clear browser cache if the new photo does not display immediately.') . EOL); @@ -183,10 +190,7 @@ class Profile_photo extends \Zotlabs\Web\Controller { // Now copy profile-permissions to pictures, to prevent privacyleaks by automatically created folder 'Profile Pictures' - profile_photo_set_profile_perms($_REQUEST['profile']); - - - + profile_photo_set_profile_perms(local_channel(),$_REQUEST['profile']); } else notice( t('Unable to process image') . EOL); @@ -196,7 +200,9 @@ class Profile_photo extends \Zotlabs\Web\Controller { return; // NOTREACHED } - + // A new photo was uploaded. Store it and save some important details + // in App::$data for use in the cropping function + $hash = photo_new_resource(); $smallest = 0; @@ -220,7 +226,7 @@ class Profile_photo extends \Zotlabs\Web\Controller { $os_storage = false; foreach($i as $ii) { - if(intval($ii['imgscale']) < 2) { + if(intval($ii['imgscale']) < PHOTO_RES_640) { $smallest = intval($ii['imgscale']); $os_storage = intval($ii['os_storage']); $imagedata = $ii['content']; @@ -238,7 +244,10 @@ class Profile_photo extends \Zotlabs\Web\Controller { } return $this->profile_photo_crop_ui_head($a, $ph, $hash, $smallest); - + + // This will "fall through" to the get() method, and since + // App::$data['imagecrop'] is set, it will proceed to cropping + // rather than present the upload form } @@ -269,11 +278,19 @@ class Profile_photo extends \Zotlabs\Web\Controller { notice( t('Permission denied.') . EOL ); return; }; - - // check_form_security_token_redirectOnErr('/profile_photo', 'profile_photo'); - + $resource_id = argv(2); + // When using an existing photo, we don't have a dialogue to offer a choice of profiles, + // so it gets attached to the default + + $p = q("select id from profile where is_default = 1 and uid = %d", + intval(local_channel()) + ); + if($p) { + $_REQUEST['profile'] = $p[0]['id']; + } + $r = q("SELECT id, album, imgscale FROM photo WHERE uid = %d AND resource_id = '%s' ORDER BY imgscale ASC", intval(local_channel()), @@ -285,11 +302,11 @@ class Profile_photo extends \Zotlabs\Web\Controller { } $havescale = false; foreach($r as $rr) { - if($rr['imgscale'] == 5) + if($rr['imgscale'] == PHOTO_RES_PROFILE_80) $havescale = true; } - // set an already loaded photo as profile photo + // set an already loaded and cropped photo as profile photo if(($r[0]['album'] == t('Profile Photos')) && ($havescale)) { // unset any existing profile photos @@ -310,7 +327,7 @@ class Profile_photo extends \Zotlabs\Web\Controller { dbesc($channel['xchan_hash']) ); - profile_photo_set_profile_perms(); //Reset default photo permissions to public + profile_photo_set_profile_perms(local_channel()); // Reset default photo permissions to public \Zotlabs\Daemon\Master::Summon(array('Directory',local_channel())); goaway(z_root() . '/profiles'); } @@ -342,7 +359,7 @@ class Profile_photo extends \Zotlabs\Web\Controller { if($i) { $hash = $i[0]['resource_id']; foreach($i as $ii) { - if(intval($ii['imgscale']) < 2) { + if(intval($ii['imgscale']) < PHOTO_RES_640) { $smallest = intval($ii['imgscale']); } } @@ -350,9 +367,14 @@ class Profile_photo extends \Zotlabs\Web\Controller { } $this->profile_photo_crop_ui_head($a, $ph, $hash, $smallest); + + // falls through with App::$data['imagecrop'] set so we go straight to the cropping section } - $profiles = q("select id, profile_name as name, is_default from profile where uid = %d", + + // present an upload form + + $profiles = q("select id, profile_name as name, is_default from profile where uid = %d order by id asc", intval(local_channel()) ); @@ -379,6 +401,9 @@ class Profile_photo extends \Zotlabs\Web\Controller { return $o; } else { + + // present a cropping form + $filename = \App::$data['imagecrop'] . '-' . \App::$data['imagecrop_resolution']; $resolution = \App::$data['imagecrop_resolution']; $tpl = get_markup_template("cropbody.tpl"); @@ -416,13 +441,13 @@ class Profile_photo extends \Zotlabs\Web\Controller { if($max_length > 0) $ph->scaleImage($max_length); - $width = $ph->getWidth(); - $height = $ph->getHeight(); + \App::$data['width'] = $ph->getWidth(); + \App::$data['height'] = $ph->getHeight(); - if($width < 500 || $height < 500) { + if(\App::$data['width'] < 500 || \App::$data['height'] < 500) { $ph->scaleImageUp(400); - $width = $ph->getWidth(); - $height = $ph->getHeight(); + \App::$data['width'] = $ph->getWidth(); + \App::$data['height'] = $ph->getHeight(); } diff --git a/Zotlabs/Module/Profiles.php b/Zotlabs/Module/Profiles.php index 06e5cfd7b..899c79b15 100644 --- a/Zotlabs/Module/Profiles.php +++ b/Zotlabs/Module/Profiles.php @@ -193,7 +193,7 @@ class Profiles extends \Zotlabs\Web\Controller { $chan = \App::get_channel(); - profile_load($a,$chan['channel_address'],$r[0]['id']); + profile_load($chan['channel_address'],$r[0]['id']); } } @@ -584,7 +584,7 @@ class Profiles extends \Zotlabs\Web\Controller { if($is_default) { // reload the info for the sidebar widget - why does this not work? - profile_load($a,$channel['channel_address']); + profile_load($channel['channel_address']); \Zotlabs\Daemon\Master::Summon(array('Directory',local_channel())); } } diff --git a/Zotlabs/Module/Profperm.php b/Zotlabs/Module/Profperm.php index 33e9d1ece..b1da147c1 100644 --- a/Zotlabs/Module/Profperm.php +++ b/Zotlabs/Module/Profperm.php @@ -17,7 +17,7 @@ class Profperm extends \Zotlabs\Web\Controller { $profile = \App::$argv[1]; - profile_load($a,$which,$profile); + profile_load($which,$profile); } @@ -97,7 +97,7 @@ class Profperm extends \Zotlabs\Web\Controller { //Time to update the permissions on the profile-pictures as well - profile_photo_set_profile_perms($profile['id']); + profile_photo_set_profile_perms(local_channel(),$profile['id']); $r = q("SELECT * FROM abook left join xchan on abook_xchan = xchan_hash WHERE abook_channel = %d AND abook_profile = '%s'", intval(local_channel()), diff --git a/Zotlabs/Module/README.md b/Zotlabs/Module/README.md new file mode 100644 index 000000000..3b870dd7b --- /dev/null +++ b/Zotlabs/Module/README.md @@ -0,0 +1,80 @@ +Zotlabs/Module +============== + + +This directory contains controller modules for handling web requests. The +lowercase class name indicates the head of the URL path which this module +handles. There are other methods of attaching (routing) URL paths to +controllers, but this is the primary method used in this project. + +Module controllers MUST reside in this directory and namespace to be +autoloaded (unless other specific routing methods are employed). They +typically use and extend the class definition in Zotlabs/Web/Controller +as a template. + +Template: + + $email, '$pass1' => $password, '$pass2' => $password2, - '$submit' => ((UNO || $auto_create || $registration_is) ? t('Register') : t('Proceed to create your first channel')) + '$submit' => t('Register'), + '$verify_note' => t('This site may require email verification after submitting this form. If you are returned to a login page, please check your email for instructions.') )); return $o; diff --git a/Zotlabs/Module/Removeaccount.php b/Zotlabs/Module/Removeaccount.php index 39e06bb7f..9fac7838e 100644 --- a/Zotlabs/Module/Removeaccount.php +++ b/Zotlabs/Module/Removeaccount.php @@ -25,7 +25,8 @@ class Removeaccount extends \Zotlabs\Web\Controller { $account = \App::get_account(); $account_id = get_account_id(); - if(! account_verify_password($account['account_email'],$_POST['qxz_password'])) + $x = account_verify_password($account['account_email'],$_POST['qxz_password']); + if(! ($x && $x['account'])) return; if($account['account_password_changed'] != NULL_DATE) { diff --git a/Zotlabs/Module/Removeme.php b/Zotlabs/Module/Removeme.php index e611d8112..bc18fe0f8 100644 --- a/Zotlabs/Module/Removeme.php +++ b/Zotlabs/Module/Removeme.php @@ -24,7 +24,9 @@ class Removeme extends \Zotlabs\Web\Controller { $account = \App::get_account(); - if(! account_verify_password($account['account_email'],$_POST['qxz_password'])) + + $x = account_verify_password($account['account_email'],$_POST['qxz_password']); + if(! ($x && $x['account'])) return; if($account['account_password_changed'] != NULL_DATE) { diff --git a/Zotlabs/Module/Rmagic.php b/Zotlabs/Module/Rmagic.php index 26b0c46a6..04536e54e 100644 --- a/Zotlabs/Module/Rmagic.php +++ b/Zotlabs/Module/Rmagic.php @@ -2,7 +2,6 @@ namespace Zotlabs\Module; - class Rmagic extends \Zotlabs\Web\Controller { function init() { diff --git a/Zotlabs/Module/Rpost.php b/Zotlabs/Module/Rpost.php index 1396f2a55..9e3043d10 100644 --- a/Zotlabs/Module/Rpost.php +++ b/Zotlabs/Module/Rpost.php @@ -7,7 +7,6 @@ require_once('include/items.php'); require_once('include/taxonomy.php'); require_once('include/conversation.php'); require_once('include/zot.php'); -require_once('include/PermissionDescription.php'); /** * remote post @@ -116,7 +115,7 @@ class Rpost extends \Zotlabs\Web\Controller { 'default_location' => $channel['channel_location'], 'nickname' => $channel['channel_address'], 'lockstate' => (($acl->is_private()) ? 'lock' : 'unlock'), - 'acl' => populate_acl($channel_acl, true, \PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post'), + 'acl' => populate_acl($channel_acl, true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post'), 'bang' => '', 'visitor' => true, 'profile_uid' => local_channel(), diff --git a/Zotlabs/Module/Settings.php b/Zotlabs/Module/Settings.php index 875004fae..b1258e049 100644 --- a/Zotlabs/Module/Settings.php +++ b/Zotlabs/Module/Settings.php @@ -2,8 +2,6 @@ namespace Zotlabs\Module; /** @file */ require_once('include/zot.php'); -require_once('include/PermissionDescription.php'); - class Settings extends \Zotlabs\Web\Controller { @@ -30,7 +28,7 @@ class Settings extends \Zotlabs\Web\Controller { } - function post() { + function post() { if(! local_channel()) return; @@ -119,6 +117,60 @@ class Settings extends \Zotlabs\Web\Controller { build_sync_packet(); return; } + + + if((argc() > 1) && (argv(1) == 'tokens')) { + check_form_security_token_redirectOnErr('/settings/tokens', 'settings_tokens'); + $token_errs = 0; + if(array_key_exists('token',$_POST)) { + $atoken_id = (($_POST['atoken_id']) ? intval($_POST['atoken_id']) : 0); + $name = trim(escape_tags($_POST['name'])); + $token = trim($_POST['token']); + if((! $name) || (! $token)) + $token_errs ++; + if(trim($_POST['expires'])) + $expires = datetime_convert(date_default_timezone_get(),'UTC',$_POST['expires']); + else + $expires = NULL_DATE; + $max_atokens = service_class_fetch(local_channel(),'access_tokens'); + if($max_atokens) { + $r = q("select count(atoken_id) as total where atoken_uid = %d", + intval(local_channel()) + ); + if($r && intval($r[0]['total']) >= $max_tokens) { + notice( sprintf( t('This channel is limited to %d tokens'), $max_tokens) . EOL); + return; + } + } + } + if($token_errs) { + notice( t('Name and Password are required.') . EOL); + return; + } + if($atoken_id) { + $r = q("update atoken set atoken_name = '%s', atoken_token = '%s' atoken_expires = '%s' + where atoken_id = %d and atoken_uid = %d", + dbesc($name), + dbesc($token), + dbesc($expires), + intval($atoken_id), + intval($channel['channel_id']) + ); + } + else { + $r = q("insert into atoken ( atoken_aid, atoken_uid, atoken_name, atoken_token, atoken_expires ) + values ( %d, %d, '%s', '%s', '%s' ) ", + intval($channel['channel_account_id']), + intval($channel['channel_id']), + dbesc($name), + dbesc($token), + dbesc($expires) + ); + } + + info( t('Token saved.') . EOL); + return; + } @@ -708,6 +760,53 @@ class Settings extends \Zotlabs\Web\Controller { )); return $o; } + + if((argc() > 1) && (argv(1) === 'tokens')) { + $atoken = null; + if(argc() > 2) { + $id = argv(2); + + $atoken = q("select * from atoken where atoken_id = %d and atoken_uid = %d", + intval($id), + intval(local_channel()) + ); + + if($atoken) + $atoken = $atoken[0]; + + if($atoken && argc() > 3 && argv(3) === 'drop') { + $r = q("delete from atoken where atoken_id = %d", + intval($id) + ); + } + } + $t = q("select * from atoken where atoken_uid = %d", + intval(local_channel()) + ); + + $desc = t('Use this form to create temporary access identifiers to share things with non-members. These identities may be used in Access Control Lists and visitors may login using these credentials to access the private content.'); + + $desc2 = t('You may also provide dropbox style access links to friends and associates by adding the Login Password to any specific site URL as shown. Examples:'); + + $tpl = get_markup_template("settings_tokens.tpl"); + $o .= replace_macros($tpl, array( + '$form_security_token' => get_form_security_token("settings_tokens"), + '$title' => t('Guest Access Tokens'), + '$desc' => $desc, + '$desc2' => $desc2, + '$tokens' => $t, + '$atoken' => $atoken, + '$url1' => z_root() . '/channel/' . $channel['channel_address'], + '$url2' => z_root() . '/photos/' . $channel['channel_address'], + '$name' => array('name', t('Login Name') . ' *', (($atoken) ? $atoken['atoken_name'] : ''),''), + '$token'=> array('token', t('Login Password') . ' *',(($atoken) ? $atoken['atoken_token'] : autoname(8)), ''), + '$expires'=> array('expires', t('Expires (yyyy-mm-dd)'), (($atoken['atoken_expires'] && $atoken['atoken_expires'] != NULL_DATE) ? datetime_convert('UTC',date_default_timezone_get(),$atoken['atoken_expires']) : ''), ''), + '$submit' => t('Submit') + )); + return $o; + } + + @@ -1066,7 +1165,7 @@ class Settings extends \Zotlabs\Web\Controller { '$maxreq' => array('maxreq', t('Maximum Friend Requests/Day:'), intval($channel['channel_max_friend_req']) , t('May reduce spam activity')), '$permissions' => t('Default Post and Publish Permissions'), '$permdesc' => t("\x28click to open/close\x29"), - '$aclselect' => populate_acl($perm_defaults, false, \PermissionDescription::fromDescription(t('Use my default audience setting for the type of object published'))), + '$aclselect' => populate_acl($perm_defaults, false, \Zotlabs\Lib\PermissionDescription::fromDescription(t('Use my default audience setting for the type of object published'))), '$suggestme' => $suggestme, '$group_select' => $group_select, '$role' => array('permissions_role' , t('Channel permissions category:'), $permissions_role, '', get_roles()), diff --git a/Zotlabs/Module/Setup.php b/Zotlabs/Module/Setup.php index c4878e217..802f0c216 100644 --- a/Zotlabs/Module/Setup.php +++ b/Zotlabs/Module/Setup.php @@ -493,7 +493,6 @@ class Setup extends \Zotlabs\Web\Controller { $this->check_add($ck_funcs, t('OpenSSL PHP module'), true, true); $this->check_add($ck_funcs, t('mysqli or postgres PHP module'), true, true); $this->check_add($ck_funcs, t('mb_string PHP module'), true, true); - $this->check_add($ck_funcs, t('mcrypt PHP module'), true, true); $this->check_add($ck_funcs, t('xml PHP module'), true, true); if(function_exists('apache_get_modules')){ @@ -530,10 +529,6 @@ class Setup extends \Zotlabs\Web\Controller { $ck_funcs[4]['status'] = false; $ck_funcs[4]['help'] = t('Error: mb_string PHP module required but not installed.'); } - if(! function_exists('mcrypt_encrypt')) { - $ck_funcs[5]['status'] = false; - $ck_funcs[5]['help'] = t('Error: mcrypt PHP module required but not installed.'); - } if(! extension_loaded('xml')) { $ck_funcs[6]['status'] = false; $ck_funcs[6]['help'] = t('Error: xml PHP module required for DAV but not installed.'); @@ -596,7 +591,7 @@ class Setup extends \Zotlabs\Web\Controller { if(! is_writable('store')) { $status = false; - $help = t('Red uses the store directory to save uploaded files. The web server needs to have write access to the store directory under the Red top level folder') . EOL; + $help = t('This software uses the store directory to save uploaded files. The web server needs to have write access to the store directory under the Red top level folder') . EOL; $help .= t('Please ensure that the user that your web server runs as (e.g. www-data) has write access to this folder.').EOL; } @@ -639,6 +634,9 @@ class Setup extends \Zotlabs\Web\Controller { $help .= t('If your certificate is not recognized, members of other sites (who may themselves have valid certificates) will get a warning message on their own site complaining about security issues.') . EOL; $help .= t('This can cause usability issues elsewhere (not just on your own site) so we must insist on this requirement.') .EOL; $help .= t('Providers are available that issue free certificates which are browser-valid.'). EOL; + + $help .= t('If you are confident that the certificate is valid and signed by a trusted authority, check to see if you have failed to install an intermediate cert. These are not normally required by browsers, but are required for server-to-server communications.') . EOL; + $this->check_add($checks, t('SSL certificate validation'), false, true, $help); } @@ -695,6 +693,7 @@ class Setup extends \Zotlabs\Web\Controller { // install the standard theme set_config('system', 'allowed_themes', 'redbasic'); + // Set a lenient list of ciphers if using openssl. Other ssl engines // (e.g. NSS used in RedHat) require different syntax, so hopefully // the default curl cipher list will work for most sites. If not, @@ -704,7 +703,9 @@ class Setup extends \Zotlabs\Web\Controller { // z_fetch_url() is also used to import shared links and other content // so in theory most any cipher could show up and we should do our best // to make the content available rather than tell folks that there's a - // weird SSL error which they can't do anything about. + // weird SSL error which they can't do anything about. This does not affect + // the SSL server, but is only a client negotiation to find something workable. + // Hence it will not make your system susceptible to POODL or other nasties. $x = curl_version(); if(stristr($x['ssl_version'],'openssl')) diff --git a/Zotlabs/Module/Siteinfo.php b/Zotlabs/Module/Siteinfo.php index f6b34d0c8..a15e2896d 100644 --- a/Zotlabs/Module/Siteinfo.php +++ b/Zotlabs/Module/Siteinfo.php @@ -27,27 +27,11 @@ class Siteinfo extends \Zotlabs\Web\Controller { else { $version = $commit = ''; } - $visible_plugins = array(); - if(is_array(\App::$plugins) && count(\App::$plugins)) { - $r = q("select * from addon where hidden = 0"); - if(count($r)) - foreach($r as $rr) - $visible_plugins[] = $rr['aname']; - } - $plugins_list = ''; - if(count($visible_plugins)) { - $plugins_text = t('Installed plugins/addons/apps:'); - $sorted = $visible_plugins; - $s = ''; - sort($sorted); - foreach($sorted as $p) { - if(strlen($p)) { - if(strlen($s)) $s .= ', '; - $s .= $p; - } - } - $plugins_list .= $s; - } + + $plugins_list = implode(', ',visible_plugin_list()); + + if($plugins_list) + $plugins_text = t('Installed plugins/addons/apps:'); else $plugins_text = t('No installed plugins/addons/apps'); diff --git a/Zotlabs/Module/Starred.php b/Zotlabs/Module/Starred.php index 73a2dc808..4f1d99ec6 100644 --- a/Zotlabs/Module/Starred.php +++ b/Zotlabs/Module/Starred.php @@ -30,7 +30,20 @@ class Starred extends \Zotlabs\Web\Controller { intval(local_channel()), intval($message_id) ); - + + $r = q("select * from item where id = %d", + intval($message_id) + ); + if($r) { + xchan_query($r); + $sync_item = fetch_post_tags($r); + build_sync_packet(local_channel(),[ + 'item' => [ + encode_item($sync_item[0],true) + ] + ]); + } + header('Content-type: application/json'); echo json_encode(array('result' => $item_starred)); killme(); diff --git a/Zotlabs/Module/Tagger.php b/Zotlabs/Module/Tagger.php index 0a46cf56d..25f518d53 100644 --- a/Zotlabs/Module/Tagger.php +++ b/Zotlabs/Module/Tagger.php @@ -129,9 +129,14 @@ class Tagger extends \Zotlabs\Web\Controller { store_item_tag($item['uid'],$item['id'],TERM_OBJ_POST,TERM_COMMUNITYTAG,$term,$tagid); $ret = post_activity_item($arr); - - if($ret['success']) - \Zotlabs\Daemon\Master::Summon(array('Notifier','tag',$ret['activity']['id'])); + + if($ret['success']) { + build_sync_packet(local_channel(), + [ + 'item' => [ encode_item($ret['activity'],true) ] + ] + ); + } killme(); diff --git a/Zotlabs/Module/Uexport.php b/Zotlabs/Module/Uexport.php index d48f96d76..f36d77174 100644 --- a/Zotlabs/Module/Uexport.php +++ b/Zotlabs/Module/Uexport.php @@ -44,7 +44,7 @@ class Uexport extends \Zotlabs\Web\Controller { } } - function get() { + function get() { $y = datetime_convert('UTC',date_default_timezone_get(),'now','Y'); diff --git a/Zotlabs/Module/Viewconnections.php b/Zotlabs/Module/Viewconnections.php index ea478f92a..4364d482a 100644 --- a/Zotlabs/Module/Viewconnections.php +++ b/Zotlabs/Module/Viewconnections.php @@ -10,8 +10,11 @@ class Viewconnections extends \Zotlabs\Web\Controller { if(observer_prohibited()) { return; } - if(argc() > 1) - profile_load($a,argv(1)); + + if(argc() > 1) { + profile_load(argv(1)); + } + } function get() { diff --git a/Zotlabs/Module/Webpages.php b/Zotlabs/Module/Webpages.php index bb8d454c8..cc0a01cce 100644 --- a/Zotlabs/Module/Webpages.php +++ b/Zotlabs/Module/Webpages.php @@ -4,7 +4,6 @@ namespace Zotlabs\Module; require_once('include/channel.php'); require_once('include/conversation.php'); require_once('include/acl_selectors.php'); -require_once('include/PermissionDescription.php'); class Webpages extends \Zotlabs\Web\Controller { @@ -23,12 +22,12 @@ class Webpages extends \Zotlabs\Web\Controller { else return; - profile_load($a,$which); + profile_load($which); } - function get() { + function get() { if(! \App::$profile) { notice( t('Requested profile is not available.') . EOL ); @@ -105,7 +104,7 @@ class Webpages extends \Zotlabs\Web\Controller { 'is_owner' => true, 'nickname' => \App::$profile['channel_address'], 'lockstate' => (($channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 'lock' : 'unlock'), - 'acl' => (($is_owner) ? populate_acl($channel_acl,false, \PermissionDescription::fromGlobalPermission('view_pages')) : ''), + 'acl' => (($is_owner) ? populate_acl($channel_acl,false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_pages')) : ''), 'showacl' => (($is_owner) ? true : false), 'visitor' => true, 'hide_location' => true, @@ -138,11 +137,19 @@ class Webpages extends \Zotlabs\Web\Controller { $sql_extra = item_permissions_sql($owner); - $r = q("select * from item_id left join item on item_id.iid = item.id - where item_id.uid = %d and service = 'WEBPAGE' and item_type = %d $sql_extra order by item.created desc", + + $r = 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_type = %d + $sql_extra order by item.created desc", intval($owner), intval(ITEM_TYPE_WEBPAGE) ); + +// $r = q("select * from item_id left join item on item_id.iid = item.id +// where item_id.uid = %d and service = 'WEBPAGE' and item_type = %d $sql_extra order by item.created desc", +// intval($owner), +// intval(ITEM_TYPE_WEBPAGE) +// ); $pages = null; @@ -160,13 +167,13 @@ class Webpages extends \Zotlabs\Web\Controller { 'created' => $rr['created'], 'edited' => $rr['edited'], 'mimetype' => $rr['mimetype'], - 'pagetitle' => $rr['sid'], + 'pagetitle' => $rr['v'], 'mid' => $rr['mid'], 'layout_mid' => $rr['layout_mid'] ); $pages[$rr['iid']][] = array( 'url' => $rr['iid'], - 'pagetitle' => $rr['sid'], + 'pagetitle' => $rr['v'], 'title' => $rr['title'], 'created' => datetime_convert('UTC',date_default_timezone_get(),$rr['created']), 'edited' => datetime_convert('UTC',date_default_timezone_get(),$rr['edited']), diff --git a/Zotlabs/Module/Wiki.php b/Zotlabs/Module/Wiki.php index 1e6446904..55a52ea6d 100644 --- a/Zotlabs/Module/Wiki.php +++ b/Zotlabs/Module/Wiki.php @@ -1,6 +1,6 @@ - false, 'new_wiki_button' => false, ...) $wiki_owner = false; @@ -72,8 +89,11 @@ class Wiki extends \Zotlabs\Web\Controller { switch (argc()) { case 2: // Configure page template - $wikiheader = t('Wiki Sandbox'); - $content = '"# Wiki Sandbox\n\nContent you **edit** and **preview** here *will not be saved*."'; + $wikiheaderName = t('Wiki'); + $wikiheaderPage = t('Sandbox'); + require_once('library/markdown.php'); + $content = t('"# Wiki Sandbox\n\nContent you **edit** and **preview** here *will not be saved*."'); + $renderedContent = Markdown(json_decode($content)); $hide_editor = false; $showPageControls = false; $showNewWikiButton = $wiki_owner; @@ -113,13 +133,18 @@ class Wiki extends \Zotlabs\Web\Controller { } else { $wiki_editor = true; } - $wikiheader = urldecode($wikiUrlName) . ': ' . urldecode($pageUrlName); // show wiki name and page + $wikiheaderName = urldecode($wikiUrlName); + $wikiheaderPage = urldecode($pageUrlName); $p = wiki_get_page_content(array('resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); if(!$p['success']) { notice('Error retrieving page content' . EOL); goaway('/'.argv(0).'/'.argv(1).'/'.$wikiUrlName); } - $content = ($p['content'] !== '' ? $p['content'] : '"# New page\n"'); + $content = ($p['content'] !== '' ? htmlspecialchars_decode($p['content'],ENT_COMPAT) : '"# New page\n"'); + // Render the Markdown-formatted page content in HTML + require_once('library/markdown.php'); + $html = wiki_generate_toc(purify_html(Markdown(json_decode($content)))); + $renderedContent = wiki_convert_links($html,argv(0).'/'.argv(1).'/'.$wikiUrlName); $hide_editor = false; $showPageControls = $wiki_editor; $showNewWikiButton = $wiki_owner; @@ -131,11 +156,25 @@ class Wiki extends \Zotlabs\Web\Controller { default: // Strip the extraneous URL components goaway('/'.argv(0).'/'.argv(1).'/'.$wikiUrlName.'/'.$pageUrlName); } - // Render the Markdown-formatted page content in HTML - require_once('library/markdown.php'); + $wikiModalID = random_string(3); + $wikiModal = replace_macros( + get_markup_template('generic_modal.tpl'), array( + '$id' => $wikiModalID, + '$title' => t('Revision Comparison'), + '$ok' => t('Revert'), + '$cancel' => t('Cancel') + ) + ); + + $is_owner = ((local_channel()) && (local_channel() == \App::$profile['profile_uid']) ? true : false); + + $o .= profile_tabs($a, $is_owner, \App::$profile['channel_address']); + + $o .= replace_macros(get_markup_template('wiki.tpl'),array( - '$wikiheader' => $wikiheader, + '$wikiheaderName' => $wikiheaderName, + '$wikiheaderPage' => $wikiheaderPage, '$hideEditor' => $hide_editor, '$showPageControls' => $showPageControls, '$showNewWikiButton'=> $showNewWikiButton, @@ -149,11 +188,25 @@ class Wiki extends \Zotlabs\Web\Controller { '$acl' => $x['acl'], '$bang' => $x['bang'], '$content' => $content, - '$renderedContent' => Markdown(json_decode($content)), + '$renderedContent' => $renderedContent, '$wikiName' => array('wikiName', t('Enter the name of your new wiki:'), '', ''), '$pageName' => array('pageName', t('Enter the name of the new page:'), '', ''), + '$pageRename' => array('pageRename', t('Enter the new name:'), '', ''), '$commitMsg' => array('commitMsg', '', '', '', '', 'placeholder="(optional) Enter a custom message when saving the page..."'), - '$pageHistory' => $pageHistory['history'] + '$pageHistory' => $pageHistory['history'], + '$wikiModal' => $wikiModal, + '$wikiModalID' => $wikiModalID, + '$commit' => 'HEAD', + '$embedPhotos' => t('Embed image from photo albums'), + '$embedPhotosModalTitle' => t('Embed an image from your albums'), + '$embedPhotosModalCancel' => t('Cancel'), + '$embedPhotosModalOK' => t('OK'), + '$modalchooseimages' => t('Choose images to embed'), + '$modalchoosealbum' => t('Choose an album'), + '$modaldiffalbum' => t('Choose a different album...'), + '$modalerrorlist' => t('Error getting album list'), + '$modalerrorlink' => t('Error getting photo link'), + '$modalerroralbum' => t('Error getting album'), )); head_add_js('library/ace/ace.js'); // Ace Code Editor return $o; @@ -166,8 +219,12 @@ class Wiki extends \Zotlabs\Web\Controller { // Render mardown-formatted text in HTML for preview if((argc() > 2) && (argv(2) === 'preview')) { $content = $_POST['content']; + $resource_id = $_POST['resource_id']; require_once('library/markdown.php'); - $html = purify_html(Markdown($content)); + $html = wiki_generate_toc(purify_html(Markdown($content))); + $w = wiki_get_wiki($resource_id); + $wikiURL = argv(0).'/'.argv(1).'/'.$w['urlName']; + $html = wiki_convert_links($html,$wikiURL); json_return_and_die(array('html' => $html, 'success' => true)); } @@ -185,6 +242,7 @@ class Wiki extends \Zotlabs\Web\Controller { } $wiki = array(); // Generate new wiki info from input name + $wiki['postVisible'] = ((intval($_POST['postVisible']) === 0) ? 0 : 1); $wiki['rawName'] = $_POST['wikiName']; $wiki['htmlName'] = escape_tags($_POST['wikiName']); $wiki['urlName'] = urlencode($_POST['wikiName']); @@ -218,20 +276,7 @@ class Wiki extends \Zotlabs\Web\Controller { if (local_channel() !== intval($channel['channel_id'])) { logger('Wiki delete permission denied.' . EOL); json_return_and_die(array('message' => 'Wiki delete permission denied.', 'success' => false)); - } else { - /* - $channel = get_channel_by_nick($nick); - $observer_hash = get_observer_hash(); - // Figure out who the page owner is. - $perms = get_all_perms(intval($channel['channel_id']), $observer_hash); - // TODO: Create a new permission setting for wiki analogous to webpages. Until - // then, use webpage permissions - if (!$perms['write_pages']) { - logger('Wiki delete permission denied.' . EOL); - json_return_and_die(array('success' => false)); - } - */ - } + } $resource_id = $_POST['resource_id']; $deleted = wiki_delete_wiki($resource_id); if ($deleted['success']) { @@ -377,7 +422,7 @@ class Wiki extends \Zotlabs\Web\Controller { if($deleted['success']) { $ob = \App::get_observer(); $commit = wiki_git_commit(array( - 'commit_msg' => 'Deleted ' . $pageHtmlName, + 'commit_msg' => 'Deleted ' . $pageUrlName, 'resource_id' => $resource_id, 'observer' => $ob, 'files' => null @@ -408,7 +453,7 @@ class Wiki extends \Zotlabs\Web\Controller { json_return_and_die(array('success' => false)); } } - $reverted = wiki_revert_page(array('commitHash' => $commitHash, 'observer' => \App::get_observer(), 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); + $reverted = wiki_revert_page(array('commitHash' => $commitHash, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); if($reverted['success']) { json_return_and_die(array('content' => $reverted['content'], 'message' => '', 'success' => true)); } else { @@ -416,6 +461,73 @@ class Wiki extends \Zotlabs\Web\Controller { } } + // Compare page revisions + if ((argc() === 4) && (argv(2) === 'compare') && (argv(3) === 'page')) { + $resource_id = $_POST['resource_id']; + $pageUrlName = $_POST['name']; + $compareCommit = $_POST['compareCommit']; + $currentCommit = $_POST['currentCommit']; + // Determine if observer has permission to revert pages + $nick = argv(1); + $channel = get_channel_by_nick($nick); + if (local_channel() !== intval($channel['channel_id'])) { + $observer_hash = get_observer_hash(); + $perms = wiki_get_permissions($resource_id, intval($channel['channel_id']), $observer_hash); + if(!$perms['read']) { + logger('Wiki read permission denied.' . EOL); + json_return_and_die(array('success' => false)); + } + } + $compare = wiki_compare_page(array('currentCommit' => $currentCommit, 'compareCommit' => $compareCommit, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); + if($compare['success']) { + $diffHTML = '
    Current RevisionSelected Revision
    ' . $compare['diff']; + json_return_and_die(array('diff' => $diffHTML, 'message' => '', 'success' => true)); + } else { + json_return_and_die(array('diff' => '', 'message' => 'Error comparing page', 'success' => false)); + } + } + + // Rename a page + if ((argc() === 4) && (argv(2) === 'rename') && (argv(3) === 'page')) { + $resource_id = $_POST['resource_id']; + $pageUrlName = $_POST['oldName']; + $pageNewName = $_POST['newName']; + if ($pageUrlName === 'Home') { + json_return_and_die(array('message' => 'Cannot rename Home','success' => false)); + } + if(urlencode(escape_tags($pageNewName)) === '') { + json_return_and_die(array('message' => 'Error renaming page. Invalid name.', 'success' => false)); + } + // Determine if observer has permission to rename pages + $nick = argv(1); + $channel = get_channel_by_nick($nick); + if (local_channel() !== intval($channel['channel_id'])) { + $observer_hash = get_observer_hash(); + $perms = wiki_get_permissions($resource_id, intval($channel['channel_id']), $observer_hash); + if(!$perms['write']) { + logger('Wiki write permission denied. ' . EOL); + json_return_and_die(array('success' => false)); + } + } + $renamed = wiki_rename_page(array('resource_id' => $resource_id, 'pageUrlName' => $pageUrlName, 'pageNewName' => $pageNewName)); + if($renamed['success']) { + $ob = \App::get_observer(); + $commit = wiki_git_commit(array( + 'commit_msg' => 'Renamed ' . urldecode($pageUrlName) . ' to ' . $renamed['page']['htmlName'], + 'resource_id' => $resource_id, + 'observer' => $ob, + 'files' => array($pageUrlName . '.md', $renamed['page']['fileName']), + 'all' => true + )); + if($commit['success']) { + json_return_and_die(array('name' => $renamed['page'], 'message' => 'Wiki git repo commit made', 'success' => true)); + } else { + json_return_and_die(array('message' => 'Error making git commit','success' => false)); + } + } else { + json_return_and_die(array('message' => 'Error renaming page', 'success' => false)); + } + } //notice('You must be authenticated.'); json_return_and_die(array('message' => 'You must be authenticated.', 'success' => false)); diff --git a/Zotlabs/Render/Comanche.php b/Zotlabs/Render/Comanche.php index 1017ec6aa..820897ee9 100644 --- a/Zotlabs/Render/Comanche.php +++ b/Zotlabs/Render/Comanche.php @@ -179,7 +179,8 @@ class Comanche { $channel_id = $this->get_channel_id(); if($channel_id) { - $r = q("select * from item inner join item_id on iid = item.id and item_id.uid = item.uid and item.uid = %d and service = 'BUILDBLOCK' and sid = '%s' limit 1", + $r = q("select * from item inner join iconfig on iconfig.iid = item.id and item.uid = %d + and iconfig.cat = 'system' and iconfig.k = 'BUILDBLOCK' and iconfig.v = '%s' limit 1", intval($channel_id), dbesc($name) ); @@ -282,12 +283,12 @@ class Comanche { /** - * Widgets will have to get any operational arguments from the session, the - * global app environment, or config storage until we implement argument passing + * Render a widget * * @param string $name * @param string $text */ + function widget($name, $text) { $vars = array(); $matches = array(); @@ -314,7 +315,7 @@ class Comanche { require_once(theme_include($theme_widget)); } - if (function_exists($func)) + if(function_exists($func)) return $func($vars); } diff --git a/Zotlabs/Storage/BasicAuth.php b/Zotlabs/Storage/BasicAuth.php index 121a9c3a1..2325a614b 100644 --- a/Zotlabs/Storage/BasicAuth.php +++ b/Zotlabs/Storage/BasicAuth.php @@ -3,6 +3,8 @@ namespace Zotlabs\Storage; use Sabre\DAV; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; /** * @brief Authentication backend class for DAV. @@ -89,33 +91,20 @@ class BasicAuth extends DAV\Auth\Backend\AbstractBasic { require_once('include/auth.php'); $record = account_verify_password($username, $password); - if ($record && $record['account_default_channel']) { - $r = q("SELECT * FROM channel WHERE channel_account_id = %d AND channel_id = %d LIMIT 1", - intval($record['account_id']), - intval($record['account_default_channel']) - ); - if($r && $this->check_module_access($r[0]['channel_id'])) { - return $this->setAuthenticated($r[0]); + if($record && $record['account']) { + if($record['channel']) + $channel = $record['channel']; + else { + $r = q("SELECT * FROM channel WHERE channel_account_id = %d AND channel_id = %d LIMIT 1", + intval($record['account']['account_id']), + intval($record['account']['account_default_channel']) + ); + if($r) + $channel = $r[0]; } } - $r = q("SELECT * FROM channel WHERE channel_address = '%s' LIMIT 1", - dbesc($username) - ); - if ($r) { - $x = q("SELECT account_flags, account_salt, account_password FROM account WHERE account_id = %d LIMIT 1", - intval($r[0]['channel_account_id']) - ); - if ($x) { - // @fixme this foreach should not be needed? - foreach ($x as $record) { - if ((($record['account_flags'] == ACCOUNT_OK) || ($record['account_flags'] == ACCOUNT_UNVERIFIED)) - && (hash('whirlpool', $record['account_salt'] . $password) === $record['account_password'])) { - logger('password verified for ' . $username); - if($this->check_module_access($r[0]['channel_id'])) - return $this->setAuthenticated($r[0]); - } - } - } + if($channel && $this->check_module_access($channel['channel_id'])) { + return $this->setAuthenticated($channel); } if($this->module_disabled) @@ -145,6 +134,58 @@ class BasicAuth extends DAV\Auth\Backend\AbstractBasic { return true; } + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + + if(local_channel()) { + $this->setAuthenticated(\App::get_channel()); + return [ true, $this->principalPrefix . $this->channel_name ]; + } + + $auth = new \Sabre\HTTP\Auth\Basic( + $this->realm, + $request, + $response + ); + + $userpass = $auth->getCredentials(); + if (!$userpass) { + return [false, "No 'Authorization: Basic' header found. Either the client didn't send one, or the server is misconfigured"]; + } + if (!$this->validateUserPass($userpass[0], $userpass[1])) { + return [false, "Username or password was incorrect"]; + } + return [true, $this->principalPrefix . $userpass[0]]; + + } + protected function check_module_access($channel_id) { if($channel_id && \App::$module === 'cdav') { $x = get_pconfig($channel_id,'cdav','enabled'); @@ -222,4 +263,4 @@ class BasicAuth extends DAV\Auth\Backend\AbstractBasic { logger('owner_id ' . $this->owner_id, LOGGER_DATA); logger('owner_nick ' . $this->owner_nick, LOGGER_DATA); } -} \ No newline at end of file +} diff --git a/Zotlabs/Storage/Browser.php b/Zotlabs/Storage/Browser.php index f875cbf33..713d75108 100644 --- a/Zotlabs/Storage/Browser.php +++ b/Zotlabs/Storage/Browser.php @@ -219,7 +219,7 @@ class Browser extends DAV\Browser\Plugin { $output = ''; if ($this->enablePost) { - $this->server->emit('onHTMLActionsPanel', array($parent, &$output)); + $this->server->emit('onHTMLActionsPanel', array($parent, &$output, $path)); } $html .= replace_macros(get_markup_template('cloud.tpl'), array( @@ -266,7 +266,7 @@ class Browser extends DAV\Browser\Plugin { * @param \Sabre\DAV\INode $node * @param string &$output */ - public function htmlActionsPanel(DAV\INode $node, &$output) { + public function htmlActionsPanel(DAV\INode $node, &$output, $path) { if (! $node instanceof DAV\ICollection) return; diff --git a/Zotlabs/Storage/CalDAVClient.php b/Zotlabs/Storage/CalDAVClient.php new file mode 100644 index 000000000..c1a8db932 --- /dev/null +++ b/Zotlabs/Storage/CalDAVClient.php @@ -0,0 +1,738 @@ +username = $user; + $this->password = $pass; + $this->url = $url; + + } + + private function set_data($s) { + $this->request_data = $s; + $this->filepos = 0; + } + + public function curl_read($ch,$fh,$size) { + + if($this->filepos < 0) { + unset($fh); + return ''; + } + + $s = substr($this->request_data,$this->filepos,$size); + + if(strlen($s) < $size) + $this->filepos = (-1); + else + $this->filepos = $this->filepos + $size; + + return $s; + } + + function ctag_fetch() { + $headers = [ 'Depth: 0', 'Prefer: return-minimal', 'Content-Type: application/xml; charset=utf-8']; + + // recommended ctag fetch by sabre + + $this->set_data(' + + + + + + +'); + + // thunderbird uses this - it's a bit more verbose on what capabilities + // are provided by the server + + $this->set_data(' + + + + + + + + + +'); + + + + $auth = $this->username . ':' . $this->password; + + $recurse = 0; + + $x = z_fetch_url($this->url,true,$recurse, + [ 'headers' => $headers, + 'http_auth' => $auth, + 'custom' => 'PROPFIND', + 'upload' => true, + 'infile' => 3, + 'infilesize' => strlen($this->request_data), + 'readfunc' => [ $this, 'curl_read' ] + ]); + + return $x; + + } + + + function detail_fetch() { + $headers = [ 'Depth: 1', 'Prefer: return-minimal', 'Content-Type: application/xml; charset=utf-8']; + + // this query should return all objects in the given calendar, you can filter it appropriately + // using filter options + + $this->set_data(' + + + + + + + + +'); + + $auth = $this->username . ':' . $this->password; + + $recurse = 0; + $x = z_fetch_url($this->url,true,$recurse, + [ 'headers' => $headers, + 'http_auth' => $auth, + 'custom' => 'REPORT', + 'upload' => true, + 'infile' => 3, + 'infilesize' => strlen($this->request_data), + 'readfunc' => [ $this, 'curl_read' ] + ]); + + + return $x; + + } + + +} + + + +/* + +PROPFIND /calendars/johndoe/home/ HTTP/1.1 +Depth: 0 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + + +// Responses: success + +HTTP/1.1 207 Multi-status +Content-Type: application/xml; charset=utf-8 + + + + /calendars/johndoe/home/ + + + Home calendar + 3145 + + HTTP/1.1 200 OK + + + + +// Responses: fail + +HTTP/1.1 207 Multi-status +Content-Type: application/xml; charset=utf-8 + + + + /calendars/johndoe/home/ + + + + + + HTTP/1.1 403 Forbidden + + + + + +// sample request body in DOM +// prepare request body +$doc = new DOMDocument('1.0', 'utf-8'); +$doc->formatOutput = true; + +$query = $doc->createElement('c:calendar-query'); +$query->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:c', 'urn:ietf:params:xml:ns:caldav'); +$query->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:d', 'DAV:'); + +$prop = $doc->createElement('d:prop'); +$prop->appendChild($doc->createElement('d:getetag')); +$prop->appendChild($doc->createElement('c:calendar-data')); +$query->appendChild($prop); +$doc->appendChild($query); +$body = $doc->saveXML(); + +echo "Body: " . $body . "
    "; + + +Now we download every single object in this calendar. To do this, we use a REPORT method. + +REPORT /calendars/johndoe/home/ HTTP/1.1 +Depth: 1 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + + + + + +This request will give us every object that's a VCALENDAR object, and its etag. + +If you're only interested in VTODO (because you're writing a todo app) you can also filter for just those: + +REPORT /calendars/johndoe/home/ HTTP/1.1 +Depth: 1 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + + + + + + + +Similarly it's also possible to filter to just events, or only get events within a specific time-range. + +This report will return a multi-status object again: + +HTTP/1.1 207 Multi-status +Content-Type: application/xml; charset=utf-8 + + + + /calendars/johndoe/home/132456762153245.ics + + + "2134-314" + BEGIN:VCALENDAR + VERSION:2.0 + CALSCALE:GREGORIAN + BEGIN:VTODO + UID:132456762153245 + SUMMARY:Do the dishes + DUE:20121028T115600Z + END:VTODO + END:VCALENDAR + + + HTTP/1.1 200 OK + + + + /calendars/johndoe/home/132456-34365.ics + + + "5467-323" + BEGIN:VCALENDAR + VERSION:2.0 + CALSCALE:GREGORIAN + BEGIN:VEVENT + UID:132456-34365 + SUMMARY:Weekly meeting + DTSTART:20120101T120000 + DURATION:PT1H + RRULE:FREQ=WEEKLY + END:VEVENT + END:VCALENDAR + + + HTTP/1.1 200 OK + + + + +This calendar only contained 2 objects. A todo and a weekly event. + +So after you retrieved and processed these, for each object you must retain: + + The calendar data itself + The url + The etag + +In this case all urls ended with .ics. This is often the case, but you must not rely on this. In this case the UID in the calendar object was also identical to a part of the url. This too is often the case, but again not something you can rely on, so don't make any assumptions. + +The url and the UID have no meaningful relationship, so treat both those items as separate unique identifiers. +Finding out if anything changed + +To see if anything in a calendar changed, we simply request the ctag again on the calendar. If the ctag did not change, you still have the latest copy. + +If it did change, you must request all the etags in the entire calendar again: + +REPORT /calendars/johndoe/home/ HTTP/1.1 +Depth: 1 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + + + + + + +Note that this last request is extremely similar to a previous one, but we are only asking fo the etag, not the calendar-data. + +The reason for this, is that calendars can be rather huge. It will save a TON of bandwidth to only check the etag first. + +HTTP/1.1 207 Multi-status +Content-Type: application/xml; charset=utf-8 + + + + /calendars/johndoe/home/132456762153245.ics + + + "xxxx-xxx" + + HTTP/1.1 200 OK + + + + /calendars/johndoe/home/fancy-caldav-client-1234253678.ics + + + "5-12" + + HTTP/1.1 200 OK + + + + +Judging from this last request, 3 things have changed: + + The etag for the task has changed, so the contents must be different + There's a new url, some other client must have added an object + One object is missing, something must have deleted it. + +So based on those 3 items we know that we need to delete an object from our local list, and fetch the contents for the new item, and the updated one. + +To fetch the data for these, you can simply issue GET requests: + +GET /calendars/johndoe/home/132456762153245.ics HTTP/1.1 + +But, because in a worst-case scenario this could result in a LOT of GET requests we can do a 'multiget'. + +REPORT /calendars/johndoe/home/ HTTP/1.1 +Depth: 1 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + /calendars/johndoe/home/132456762153245.ics + /calendars/johndoe/home/fancy-caldav-client-1234253678.ics + + +This request will simply return a multi-status again with the calendar-data and etag. +A small note about application design + +If you read this far and understood what's been said, you may have realized that it's a bit cumbersome to have a separate step for the initial sync, and subsequent updates. + +It would totally be possible to skip the 'initial sync', and just use calendar-query and calendar-multiget REPORTS for the initial sync as well. +Updating a calendar object + +Updating a calendar object is rather simple: + +PUT /calendars/johndoe/home/132456762153245.ics HTTP/1.1 +Content-Type: text/calendar; charset=utf-8 +If-Match: "2134-314" + +BEGIN:VCALENDAR +.... +END:VCALENDAR + +A response to this will be something like this: + +HTTP/1.1 204 No Content +ETag: "2134-315" + +The update gave us back the new ETag. SabreDAV gives this ETag on updates back most of the time, but not always. + +There are cases where the caldav server must modify the iCalendar object right after storage. In those cases an ETag will not be returned, and you should issue a GET request immediately to get the correct object. + +A few notes: + + You must not change the UID of the original object + Every object should hold only 1 event or task. + You cannot change an VEVENT into a VTODO. + +Creating a calendar object + +Creating a calendar object is almost identical, except that you don't have a url yet to a calendar object. + +Instead, it is up to you to determine the new url. + +PUT /calendars/johndoe/home/somerandomstring.ics HTTP/1.1 +Content-Type: text/calendar; charset=utf-8 + +BEGIN:VCALENDAR +.... +END:VCALENDAR + +A response to this will be something like this: + +HTTP/1.1 201 Created +ETag: "21345-324" + +Similar to updating, an ETag is often returned, but there are cases where this is not true. +Deleting a calendar object + +Deleting is simple enough: + +DELETE /calendars/johndoe/home/132456762153245.ics HTTP/1.1 +If-Match: "2134-314" + +Speeding up Sync with WebDAV-Sync + +WebDAV-Sync is a protocol extension that is defined in rfc6578. Because this extension was defined later, some servers may not support this yet. + +SabreDAV supports this since 2.0. + +WebDAV-Sync allows a client to ask just for calendars that have changed. The process on a high-level is as follows: + + Client requests sync-token from server. + Server reports token 15. + Some time passes. + Client does a Sync REPORT on an calendar, and supplied token 15. + Server returns vcard urls that have changed or have been deleted and returns token 17. + +As you can see, after the initial sync, only items that have been created, modified or deleted will ever be sent. + +This has a lot of advantages. The transmitted xml bodies can generally be a lot shorter, and is also easier on both client and server in terms of memory and CPU usage, because only a limited set of items will have to be compared. + +It's important to note, that a client should only do Sync operations, if the server reports that it has support for it. The quickest way to do so, is to request {DAV}sync-token on the calendar you wish to sync. + +Technically, a server may support 'sync' on one calendar, and it may not support it on another, although this is probably rare. +Getting the first sync-token + +Initially, we just request a sync token when asking for calendar information: + +PROPFIND /calendars/johndoe/home/ HTTP/1.1 +Depth: 0 +Content-Type: application/xml; charset=utf-8 + + + + + + + + + +This would return something as follows: + + + + /calendars/johndoe/home/ + + + My calendar + 3145 + http://sabredav.org/ns/sync-token/3145 + + HTTP/1.1 200 OK + + + + +As you can see, the sync-token is a url. It always should be a url. Even though a number appears in the url, you are not allowed to attach any meaning to that url. Some servers may have use an increasing number, another server may use a completely random string. +Receiving changes + +After a sync token has been obtained, and the client already has the initial copy of the calendar, the client is able to request all changes since the token was issued. + +This is done with a REPORT request that may look like this: + +REPORT /calendars/johndoe/home/ HTTP/1.1 +Host: dav.example.org +Content-Type: application/xml; charset="utf-8" + + + + http://sabredav.org/ns/sync/3145 + 1 + + + + + +This requests all the changes since sync-token identified by http://sabredav.org/ns/sync/3145, and for the calendar objects that have been added or modified, we're requesting the etag. + +The response to a query like this is another multistatus xml body. Example: + +HTTP/1.1 207 Multi-Status +Content-Type: application/xml; charset="utf-8" + + + + + /calendars/johndoe/home/newevent.ics + + + "33441-34321" + + HTTP/1.1 200 OK + + + + /calendars/johndoe/home/updatedevent.ics + + + "33541-34696" + + HTTP/1.1 200 OK + + + + /calendars/johndoe/home/deletedevent.ics + HTTP/1.1 404 Not Found + + http://sabredav.org/ns/sync/5001 + + +The last response reported two changes: newevent.ics and updatedevent.ics. There's no way to tell from the response wether those cards got created or updated, you, as a client can only infer this based on the vcards you are already aware of. + +The entry with name deletedevent.ics got deleted as indicated by the 404 status. Note that the status element is here a child of d:response when in all previous examples it has been a child of d:propstat. + +The other difference with the other multi-status examples, is that this one has a sync-token element with the latest sync-token. +Caveats + +Note that a server is free to 'forget' any sync-tokens that have been previously issued. In this case it may be needed to do a full-sync again. + +In case the supplied sync-token is not recognized by the server, a HTTP error is emitted. SabreDAV emits a 403. +Discovery + +Ideally you will want to make sure that all the calendars in an account are automatically discovered. The best user interface would be to just have to ask for three items: + + Username + Password + Server + +And the server should be as short as possible. This is possible with most servers. + +If, for example a user specified 'dav.example.org' for the server, the first thing you should do is attempt to send a PROPFIND request to https://dav.example.org/. Note that you SHOULD try the https url before the http url. + +This PROPFIND request looks as follows: + +PROPFIND / HTTP/1.1 +Depth: 0 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + +This will return a response such as the following: + +HTTP/1.1 207 Multi-status +Content-Type: application/xml; charset=utf-8 + + + + / + + + + /principals/users/johndoe/ + + + HTTP/1.1 200 OK + + + + +A 'principal' is a user. The url that's being returned, is a url that refers to the current user. On this url you can request additional information about the user. + +What we need from this url, is their 'calendar home'. The calendar home is a collection that contains all of the users' calendars. + +To request that, issue the following request: + +PROPFIND /principals/users/johndoe/ HTTP/1.1 +Depth: 0 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + +This will return a response such as the following: + +HTTP/1.1 207 Multi-status +Content-Type: application/xml; charset=utf-8 + + + + /principals/users/johndoe/ + + + + /calendars/johndoe/ + + + HTTP/1.1 200 OK + + + + +Lastly, to list all the calendars for the user, issue a PROPFIND request with Depth: 1. + +PROPFIND /calendars/johndoe/ HTTP/1.1 +Depth: 1 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + + + + +In that last request, we asked for 4 properties. + +The resourcetype tells us what type of object we're getting back. You must read out the resourcetype and ensure that it contains at least a calendar element in the CalDAV namespace. Other items may be returned, including non- calendar, which your application should ignore. + +The displayname is a human-readable string for the calendarname, the ctag was already covered in an earlier section. + +Lastly, supported-calendar-component-set. This gives us a list of components that the calendar accepts. This could be just VTODO, VEVENT, VJOURNAL or a combination of these three. + +If you are just creating a todo-list application, this means you should only list the calendars that support the VTODO component. + +HTTP/1.1 207 Multi-status +Content-Type: application/xml; charset=utf-8 + + + + /calendars/johndoe/ + + + + + + + HTTP/1.1 200 OK + + + + /calendars/johndoe/home/ + + + + + + + Home calendar + 3145 + + + + + HTTP/1.1 200 OK + + + + /calendars/johndoe/tasks/ + + + + + + + My TODO list + 3345 + + + + + HTTP/1.1 200 OK + + + +*/ diff --git a/Zotlabs/Storage/Directory.php b/Zotlabs/Storage/Directory.php index 0ccd9da47..15e06e28f 100644 --- a/Zotlabs/Storage/Directory.php +++ b/Zotlabs/Storage/Directory.php @@ -3,6 +3,7 @@ namespace Zotlabs\Storage; use Sabre\DAV; +use Sabre\HTTP; /** * @brief RedDirectory class. @@ -91,7 +92,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { throw new DAV\Exception\Forbidden('Permission denied.'); } - $contents = RedCollectionData($this->red_path, $this->auth); + $contents = $this->CollectionData($this->red_path, $this->auth); return $contents; } @@ -119,7 +120,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { return new Directory('/' . $modulename, $this->auth); } - $x = RedFileData($this->ext_path . '/' . $name, $this->auth); + $x = $this->FileData($this->ext_path . '/' . $name, $this->auth); if ($x) { return $x; } @@ -159,7 +160,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { throw new DAV\Exception\Forbidden('Permission denied.'); } - list($parent_path, ) = DAV\URLUtil::splitPath($this->red_path); + list($parent_path, ) = HTTP\URLUtil::splitPath($this->red_path); $new_path = $parent_path . '/' . $name; $r = q("UPDATE attach SET filename = '%s' WHERE hash = '%s' AND uid = %d", @@ -206,6 +207,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { throw new DAV\Exception\Forbidden('Permission denied.'); } + $mimetype = z_mime_content_type($name); $c = q("SELECT * FROM channel WHERE channel_id = %d AND channel_removed = 0 LIMIT 1", @@ -432,8 +434,8 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { return true; } - $x = RedFileData($this->ext_path . '/' . $name, $this->auth, true); - //logger('RedFileData returns: ' . print_r($x, true), LOGGER_DATA); + $x = $this->FileData($this->ext_path . '/' . $name, $this->auth, true); + //logger('FileData returns: ' . print_r($x, true), LOGGER_DATA); if ($x) return true; @@ -566,4 +568,280 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { $free ); } + + + /** + * @brief Array with all Directory and File DAV\Node items for the given path. + * + * + * @param string $file path to a directory + * @param \Zotlabs\Storage\BasicAuth &$auth + * @returns null|array \Sabre\DAV\INode[] + * @throw \Sabre\DAV\Exception\Forbidden + * @throw \Sabre\DAV\Exception\NotFound + */ + + function CollectionData($file, &$auth) { + $ret = array(); + + $x = strpos($file, '/cloud'); + if ($x === 0) { + $file = substr($file, 6); + } + + // return a list of channel if we are not inside a channel + if ((! $file) || ($file === '/')) { + return $this->ChannelList($auth); + } + + $file = trim($file, '/'); + $path_arr = explode('/', $file); + + if (! $path_arr) + return null; + + $channel_name = $path_arr[0]; + + $r = q("SELECT channel_id FROM channel WHERE channel_address = '%s' LIMIT 1", + dbesc($channel_name) + ); + + if (! $r) + return null; + + $channel_id = $r[0]['channel_id']; + $perms = permissions_sql($channel_id); + + $auth->owner_id = $channel_id; + + $path = '/' . $channel_name; + + $folder = ''; + $errors = false; + $permission_error = false; + + for ($x = 1; $x < count($path_arr); $x++) { + $r = q("SELECT id, hash, filename, flags, is_dir FROM attach WHERE folder = '%s' AND filename = '%s' AND uid = %d AND is_dir != 0 $perms LIMIT 1", + dbesc($folder), + dbesc($path_arr[$x]), + intval($channel_id) + ); + if (! $r) { + // path wasn't found. Try without permissions to see if it was the result of permissions. + $errors = true; + $r = q("select id, hash, filename, flags, is_dir from attach where folder = '%s' and filename = '%s' and uid = %d and is_dir != 0 limit 1", + dbesc($folder), + basename($path_arr[$x]), + intval($channel_id) + ); + if ($r) { + $permission_error = true; + } + break; + } + + if ($r && intval($r[0]['is_dir'])) { + $folder = $r[0]['hash']; + $path = $path . '/' . $r[0]['filename']; + } + } + + if ($errors) { + if ($permission_error) { + throw new DAV\Exception\Forbidden('Permission denied.'); + } + else { + throw new DAV\Exception\NotFound('A component of the request file path could not be found.'); + } + } + + // This should no longer be needed since we just returned errors for paths not found + if ($path !== '/' . $file) { + logger("Path mismatch: $path !== /$file"); + return NULL; + } + if(ACTIVE_DBTYPE == DBTYPE_POSTGRES) { + $prefix = 'DISTINCT ON (filename)'; + $suffix = 'ORDER BY filename'; + } + else { + $prefix = ''; + $suffix = 'GROUP BY filename'; + } + $r = q("select $prefix id, uid, hash, filename, filetype, filesize, revision, folder, flags, is_dir, created, edited from attach where folder = '%s' and uid = %d $perms $suffix", + dbesc($folder), + intval($channel_id) + ); + + foreach ($r as $rr) { + //logger('filename: ' . $rr['filename'], LOGGER_DEBUG); + if (intval($rr['is_dir'])) { + $ret[] = new Directory($path . '/' . $rr['filename'], $auth); + } + else { + $ret[] = new File($path . '/' . $rr['filename'], $rr, $auth); + } + } + + return $ret; + } + + + /** + * @brief Returns an array with viewable channels. + * + * Get a list of Directory objects with all the channels where the visitor + * has view_storage perms. + * + * + * @param BasicAuth &$auth + * @return array Directory[] + */ + + function ChannelList(&$auth) { + $ret = array(); + + $r = q("SELECT channel_id, channel_address FROM channel WHERE channel_removed = 0 + AND channel_system = 0 AND NOT (channel_pageflags & %d)>0", + intval(PAGE_HIDDEN) + ); + + if ($r) { + foreach ($r as $rr) { + if (perm_is_allowed($rr['channel_id'], $auth->observer, 'view_storage')) { + logger('found channel: /cloud/' . $rr['channel_address'], LOGGER_DATA); + // @todo can't we drop '/cloud'? It gets stripped off anyway in RedDirectory + $ret[] = new Directory('/cloud/' . $rr['channel_address'], $auth); + } + } + } + return $ret; + } + + + /** + * @brief + * + * + * @param string $file + * path to file or directory + * @param BasicAuth &$auth + * @param boolean $test (optional) enable test mode + * @return File|Directory|boolean|null + * @throw \Sabre\DAV\Exception\Forbidden + */ + + function FileData($file, &$auth, $test = false) { + logger($file . (($test) ? ' (test mode) ' : ''), LOGGER_DATA); + + $x = strpos($file, '/cloud'); + if ($x === 0) { + $file = substr($file, 6); + } + else { + $x = strpos($file,'/dav'); + if($x === 0) + $file = substr($file,4); + } + + + if ((! $file) || ($file === '/')) { + return new Directory('/', $auth); + } + + $file = trim($file, '/'); + + $path_arr = explode('/', $file); + + if (! $path_arr) + return null; + + $channel_name = $path_arr[0]; + + $r = q("select channel_id from channel where channel_address = '%s' limit 1", + dbesc($channel_name) + ); + + if (! $r) + return null; + + $channel_id = $r[0]['channel_id']; + + $path = '/' . $channel_name; + + $auth->owner_id = $channel_id; + + $permission_error = false; + + $folder = ''; + + require_once('include/security.php'); + $perms = permissions_sql($channel_id); + + $errors = false; + + for ($x = 1; $x < count($path_arr); $x++) { + $r = q("select id, hash, filename, flags, is_dir from attach where folder = '%s' and filename = '%s' and uid = %d and is_dir != 0 $perms", + dbesc($folder), + dbesc($path_arr[$x]), + intval($channel_id) + ); + + if ($r && intval($r[0]['is_dir'])) { + $folder = $r[0]['hash']; + $path = $path . '/' . $r[0]['filename']; + } + if (! $r) { + $r = q("select id, uid, hash, filename, filetype, filesize, revision, folder, flags, is_dir, os_storage, created, edited from attach + where folder = '%s' and filename = '%s' and uid = %d $perms order by filename limit 1", + dbesc($folder), + dbesc(basename($file)), + intval($channel_id) + ); + } + if (! $r) { + $errors = true; + $r = q("select id, uid, hash, filename, filetype, filesize, revision, folder, flags, is_dir, os_storage, created, edited from attach + where folder = '%s' and filename = '%s' and uid = %d order by filename limit 1", + dbesc($folder), + dbesc(basename($file)), + intval($channel_id) + ); + if ($r) + $permission_error = true; + } + } + + if ($path === '/' . $file) { + if ($test) + return true; + // final component was a directory. + return new Directory($file, $auth); + } + + if ($errors) { + logger('not found ' . $file); + if ($test) + return false; + if ($permission_error) { + logger('permission error ' . $file); + throw new DAV\Exception\Forbidden('Permission denied.'); + } + return; + } + + if ($r) { + if ($test) + return true; + + if (intval($r[0]['is_dir'])) { + return new Directory($path . '/' . $r[0]['filename'], $auth); + } + else { + return new File($path . '/' . $r[0]['filename'], $r[0], $auth); + } + } + return false; + } + } diff --git a/Zotlabs/Storage/File.php b/Zotlabs/Storage/File.php index ecd15cc55..5a70a99f1 100644 --- a/Zotlabs/Storage/File.php +++ b/Zotlabs/Storage/File.php @@ -337,6 +337,10 @@ class File extends DAV\Node implements DAV\IFile { } } + if(get_pconfig($this->auth->owner_id,'system','os_delete_prohibit') && \App::$module == 'dav') { + throw new DAV\Exception\Forbidden('Permission denied.'); + } + attach_delete($this->auth->owner_id, $this->data['hash']); $ch = channelx_by_n($this->auth->owner_id); diff --git a/Zotlabs/Web/CheckJS.php b/Zotlabs/Web/CheckJS.php index 5f9856a8c..109790fa5 100644 --- a/Zotlabs/Web/CheckJS.php +++ b/Zotlabs/Web/CheckJS.php @@ -21,6 +21,9 @@ class CheckJS { $page = urlencode(\App::$query_string); if($test) { + self::$jsdisabled = 1; + if(array_key_exists('jsdisabled',$_COOKIE)) + self::$jsdisabled = $_COOKIE['jsdisabled']; if(! array_key_exists('jsdisabled',$_COOKIE)) { \App::$page['htmlhead'] .= "\r\n" . '' . "\r\n"; diff --git a/Zotlabs/Web/Controller.php b/Zotlabs/Web/Controller.php index ac835e008..2d0f58891 100644 --- a/Zotlabs/Web/Controller.php +++ b/Zotlabs/Web/Controller.php @@ -9,4 +9,5 @@ class Controller { function post() {} function get() {} -} \ No newline at end of file +} + diff --git a/Zotlabs/Web/SessionHandler.php b/Zotlabs/Web/SessionHandler.php index 6e7333b4b..93b27a7e8 100644 --- a/Zotlabs/Web/SessionHandler.php +++ b/Zotlabs/Web/SessionHandler.php @@ -24,7 +24,8 @@ class SessionHandler implements \SessionHandlerInterface { return $r[0]['sess_data']; } else { - q("INSERT INTO `session` (sid, expire) values ('%s', '%s')", + q("INSERT INTO `session` (sess_data, sid, expire) values ('%s', '%s', '%s')", + dbesc(''), dbesc($id), dbesc(time() + 300) ); diff --git a/Zotlabs/Web/WebServer.php b/Zotlabs/Web/WebServer.php index 88ab4995b..d4f3cb9ea 100644 --- a/Zotlabs/Web/WebServer.php +++ b/Zotlabs/Web/WebServer.php @@ -59,7 +59,14 @@ class WebServer { \App::$query_string = strip_zids(\App::$query_string); if(! local_channel()) { $_SESSION['my_address'] = $_GET['zid']; - zid_init($a); + zid_init(); + } + } + + if((x($_GET,'zat')) && (! \App::$install)) { + \App::$query_string = strip_zats(\App::$query_string); + if(! local_channel()) { + zat_init(); } } diff --git a/Zotlabs/Zot/Finger.php b/Zotlabs/Zot/Finger.php index 229fda8bd..e7603442f 100644 --- a/Zotlabs/Zot/Finger.php +++ b/Zotlabs/Zot/Finger.php @@ -28,7 +28,7 @@ class Finger { if (strpos($webbie,'@') === false) { $address = $webbie; - $host = App::get_hostname(); + $host = \App::get_hostname(); } else { $address = substr($webbie,0,strpos($webbie,'@')); $host = substr($webbie,strpos($webbie,'@')+1); @@ -127,4 +127,4 @@ class Finger { return $x; } -} \ No newline at end of file +} diff --git a/boot.php b/boot.php index e4007eb3a..4909c8e75 100755 --- a/boot.php +++ b/boot.php @@ -34,7 +34,6 @@ require_once('include/text.php'); require_once('include/datetime.php'); require_once('include/language.php'); require_once('include/nav.php'); -require_once('include/cache.php'); require_once('include/permissions.php'); require_once('library/Mobile_Detect/Mobile_Detect.php'); require_once('include/features.php'); @@ -45,10 +44,10 @@ require_once('include/account.php'); define ( 'PLATFORM_NAME', 'hubzilla' ); -define ( 'STD_VERSION', '1.8.1' ); +define ( 'STD_VERSION', '1.10' ); define ( 'ZOT_REVISION', '1.1' ); -define ( 'DB_UPDATE_VERSION', 1176 ); +define ( 'DB_UPDATE_VERSION', 1180 ); /** @@ -771,6 +770,7 @@ class App { public static $groups; public static $language; public static $langsave; + public static $rtl = false; public static $plugins_admin; public static $module_loaded = false; public static $query_string; @@ -1703,7 +1703,7 @@ function login($register = false, $form_id = 'main-login', $hiddens=false) { '$logout' => t('Logout'), '$login' => t('Login'), '$form_id' => $form_id, - '$lname' => array('username', t('Email') , '', ''), + '$lname' => array('username', t('Login/Email') , '', ''), '$lpassword' => array('password', t('Password'), '', ''), '$remember_me' => array('remember_me', t('Remember me'), '', '',array(t('No'),t('Yes'))), '$hiddens' => $hiddens, @@ -2282,6 +2282,12 @@ function construct_page(&$a) { $page = App::$page; $profile = App::$profile; + // There's some experimental support for right-to-left text in the view/php/default.php page template. + // In v1.9 we started providing direction preference in the per language hstrings.php file + // This requires somebody with fluency in a RTL language to make happen + + $page['direction'] = 0; // ((App::$rtl) ? 1 : 0); + header("Content-type: text/html; charset=utf-8"); // security headers - see https://securityheaders.io @@ -2457,9 +2463,10 @@ function check_cron_broken() { return; } + set_config('system','lastcroncheck',datetime_convert()); + if(($d) && ($d > datetime_convert('UTC','UTC','now - 3 days'))) { // Scheduled tasks have run successfully in the last 3 days. - set_config('system','lastcroncheck',datetime_convert()); return; } diff --git a/doc/bbcode.html b/doc/bbcode.html index 5a51135ea..7a2c969eb 100644 --- a/doc/bbcode.html +++ b/doc/bbcode.html @@ -14,6 +14,7 @@
  • [img float=right]https://zothub.com/images/default_profile_photos/rainbow_man/48.jpg[/img] Image/photo
  • [code]code[/code] code
    +
  • [code=xxx]syntax highlighted code[/code] supported languages php, css, mysql, sql, abap, diff, html, perl, ruby, vbscript, avrc, dtd, java, xml, cpp, python, javascript, js, json, sh
  • [quote]quote[/quote]
    quote

  • [quote=Author]Author? Me? No, no, no...[/quote]
    Author wrote:
    Author? Me? No, no, no...

  • [nobb] may be used to escape bbcode.
    @@ -83,7 +84,7 @@ or

    [dl terms="b"]
    [*= First element term] First element descript
  • [spoiler] for hiding spoilers

  • [rpost=title]Text to post[/rpost] The observer will be returned to their home hub to enter a post with the specified title and body. Both are optional
  • -
  • [qr]text to post[/qr] - create a QR code.
  • +
  • [qr]text to post[/qr] - create a QR code (if the qrator plugin is installed).
  • [toc] - create a table of content in a webpage. Please refer to the original jquery toc to get more explanations.