From 2a154f8c9a772d61e7dabb5e3fd110ba00cc6007 Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 14 Dec 2020 11:02:20 +0000 Subject: merge branch files_ng into dev --- Zotlabs/Lib/Libsync.php | 2 +- Zotlabs/Module/Attach_edit.php | 212 ++++++++++++++++++++++++++ Zotlabs/Module/Cloud.php | 20 ++- Zotlabs/Module/Dav.php | 2 +- Zotlabs/Module/File_upload.php | 20 +-- Zotlabs/Module/Filestorage.php | 17 ++- Zotlabs/Module/Rpost.php | 64 ++++---- Zotlabs/Storage/Browser.php | 339 +++++++++++++++++++++++++++-------------- Zotlabs/Storage/Directory.php | 91 +++++++---- Zotlabs/Widget/Categories.php | 8 +- 10 files changed, 567 insertions(+), 208 deletions(-) create mode 100644 Zotlabs/Module/Attach_edit.php (limited to 'Zotlabs') diff --git a/Zotlabs/Lib/Libsync.php b/Zotlabs/Lib/Libsync.php index 72a9afc48..7b968532a 100644 --- a/Zotlabs/Lib/Libsync.php +++ b/Zotlabs/Lib/Libsync.php @@ -713,7 +713,7 @@ class Libsync { if($arr['locations']) { if($absolute) - self::check_location_move($sender['hash'],$arr['locations']); + Libzot::check_location_move($sender['hash'],$arr['locations']); $xisting = q("select * from hubloc where hubloc_hash = '%s'", dbesc($sender['hash']) diff --git a/Zotlabs/Module/Attach_edit.php b/Zotlabs/Module/Attach_edit.php new file mode 100644 index 000000000..0a41dbb22 --- /dev/null +++ b/Zotlabs/Module/Attach_edit.php @@ -0,0 +1,212 @@ +set_from_array($_REQUEST); + $x = $acl->get(); + + attach_change_permissions($channel_id, $resource, $x['allow_cid'], $x['allow_gid'], $x['deny_cid'], $x['deny_gid'], $recurse, true); + $actions_done .= 'permissions,'; + + if ($notify) { + attach_store_item($channel, $observer, $resource); + $actions_done .= 'notify,'; + } + } + } + + if (! $admin_delete && $actions_done) { + $sync = attach_export_data($channel, $resource, false); + + if ($sync) { + Libsync::build_sync_packet($channel_id, ['file' => [$sync]]); + } + } + + logger('attach_edit: ' . $actions_done); + + } + + if($dnd || $delete) { + json_return_and_die([ 'success' => true ]); + } + + goaway($return_path); + + } + +} diff --git a/Zotlabs/Module/Cloud.php b/Zotlabs/Module/Cloud.php index f595e0fac..39ae0f92f 100644 --- a/Zotlabs/Module/Cloud.php +++ b/Zotlabs/Module/Cloud.php @@ -8,7 +8,11 @@ namespace Zotlabs\Module; */ use Sabre\DAV as SDAV; -use \Zotlabs\Storage; +use \Zotlabs\Web\Controller; +use \Zotlabs\Storage\BasicAuth; +use \Zotlabs\Storage\Directory; +use \Zotlabs\Storage\Browser; + // composer autoloader for SabreDAV require_once('vendor/autoload.php'); @@ -20,7 +24,7 @@ require_once('include/attach.php'); * @brief Cloud Module. * */ -class Cloud extends \Zotlabs\Web\Controller { +class Cloud extends Controller { /** * @brief Fires up the SabreDAV server. @@ -42,7 +46,7 @@ class Cloud extends \Zotlabs\Web\Controller { - $auth = new \Zotlabs\Storage\BasicAuth(); + $auth = new BasicAuth(); $ob_hash = get_observer_hash(); @@ -72,7 +76,7 @@ class Cloud extends \Zotlabs\Web\Controller { if($x !== \App::$query_string) goaway(z_root() . '/' . $x); - $rootDirectory = new \Zotlabs\Storage\Directory('/', $auth); + $rootDirectory = new Directory('/', [], $auth); // A SabreDAV server-object $server = new SDAV\Server($rootDirectory); @@ -85,7 +89,7 @@ class Cloud extends \Zotlabs\Web\Controller { $is_readable = false; // provide a directory view for the cloud in Hubzilla - $browser = new \Zotlabs\Storage\Browser($auth); + $browser = new Browser($auth); $auth->setBrowserPlugin($browser); $server->addPlugin($browser); @@ -105,13 +109,13 @@ class Cloud extends \Zotlabs\Web\Controller { if($browser->build_page) construct_page(); - + killme(); } function DAVException($err) { - + if($err instanceof \Sabre\DAV\Exception\NotFound) { notice( t('Not found') . EOL); } @@ -126,7 +130,7 @@ class Cloud extends \Zotlabs\Web\Controller { } construct_page(); - + killme(); } diff --git a/Zotlabs/Module/Dav.php b/Zotlabs/Module/Dav.php index 11950dda0..949b89950 100644 --- a/Zotlabs/Module/Dav.php +++ b/Zotlabs/Module/Dav.php @@ -100,7 +100,7 @@ class Dav extends \Zotlabs\Web\Controller { $auth->setRealm(ucfirst(\Zotlabs\Lib\System::get_platform_name()) . ' ' . 'WebDAV'); - $rootDirectory = new \Zotlabs\Storage\Directory('/', $auth); + $rootDirectory = new \Zotlabs\Storage\Directory('/', [], $auth); // A SabreDAV server-object $server = new SDAV\Server($rootDirectory); diff --git a/Zotlabs/Module/File_upload.php b/Zotlabs/Module/File_upload.php index 1735e9487..6794dceee 100644 --- a/Zotlabs/Module/File_upload.php +++ b/Zotlabs/Module/File_upload.php @@ -11,17 +11,16 @@ require_once('include/photos.php'); class File_upload extends \Zotlabs\Web\Controller { function post() { - logger('file upload: ' . print_r($_REQUEST,true)); logger('file upload: ' . print_r($_FILES,true)); - + $channel = (($_REQUEST['channick']) ? channelx_by_nick($_REQUEST['channick']) : null); - + if(! $channel) { logger('channel not found'); killme(); } - + $_REQUEST['source'] = 'file_upload'; if($channel['channel_id'] != local_channel()) { @@ -40,13 +39,11 @@ class File_upload extends \Zotlabs\Web\Controller { $r = attach_mkdir($channel, get_observer_hash(), $_REQUEST); if($r['success']) { $hash = $r['data']['hash']; - $sync = attach_export_data($channel,$hash); if($sync) { Libsync::build_sync_packet($channel['channel_id'],array('file' => array($sync))); } - goaway(z_root() . '/cloud/' . $channel['channel_address'] . '/' . $r['data']['display_path']); - + goaway(z_root() . '/' . $_REQUEST['return_url']); } } else { @@ -54,8 +51,6 @@ class File_upload extends \Zotlabs\Web\Controller { $matches = []; $partial = false; - - if(array_key_exists('HTTP_CONTENT_RANGE',$_SERVER)) { $pm = preg_match('/bytes (\d*)\-(\d*)\/(\d*)/',$_SERVER['HTTP_CONTENT_RANGE'],$matches); if($pm) { @@ -83,7 +78,7 @@ class File_upload extends \Zotlabs\Web\Controller { ]; } } - else { + else { if(! array_key_exists('userfile',$_FILES)) { $_FILES['userfile'] = [ 'name' => $_FILES['files']['name'], @@ -103,8 +98,9 @@ class File_upload extends \Zotlabs\Web\Controller { } } + goaway(z_root() . '/' . $_REQUEST['return_url']); - + } - + } diff --git a/Zotlabs/Module/Filestorage.php b/Zotlabs/Module/Filestorage.php index 0c6233493..0d132e998 100644 --- a/Zotlabs/Module/Filestorage.php +++ b/Zotlabs/Module/Filestorage.php @@ -11,6 +11,9 @@ class Filestorage extends \Zotlabs\Web\Controller { function post() { + notice( t('Deprecated!') . EOL); + return; + $channel_id = ((x($_POST, 'uid')) ? intval($_POST['uid']) : 0); if((! $channel_id) || (! local_channel()) || ($channel_id != local_channel())) { @@ -47,6 +50,9 @@ class Filestorage extends \Zotlabs\Web\Controller { function get() { + notice( t('Deprecated!') . EOL); + return; + if(argc() > 1) $which = argv(1); else { @@ -88,7 +94,7 @@ class Filestorage extends \Zotlabs\Web\Controller { } else { notice( t('Permission denied.') . EOL); - if($json_return) + if($json_return) json_return_and_die([ 'success' => false ]); return; } @@ -102,24 +108,23 @@ class Filestorage extends \Zotlabs\Web\Controller { if(! $r) { notice( t('File not found.') . EOL); - if($json_return) + if($json_return) json_return_and_die([ 'success' => false ]); goaway(z_root() . '/cloud/' . $which); } - if(local_channel() !== $owner) { + if((local_channel() !== $owner) && !$admin_delete) { if($r[0]['creator'] && $r[0]['creator'] !== $ob_hash) { notice( t('Permission denied.') . EOL); - if($json_return) + if($json_return) json_return_and_die([ 'success' => false ]); goaway(z_root() . '/cloud/' . $which); } } - $f = $r[0]; $channel = channelx_by_n($owner); @@ -138,7 +143,7 @@ class Filestorage extends \Zotlabs\Web\Controller { if($json_return) json_return_and_die([ 'success' => true ]); - goaway(dirname($url)); + //goaway(dirname($url)); } diff --git a/Zotlabs/Module/Rpost.php b/Zotlabs/Module/Rpost.php index f03dae2bf..031270845 100644 --- a/Zotlabs/Module/Rpost.php +++ b/Zotlabs/Module/Rpost.php @@ -10,7 +10,7 @@ require_once('include/zot.php'); /** * remote post - * + * * https://yoursite/rpost?f=&title=&body=&remote_return= * * This can be called via either GET or POST, use POST for long body content as suhosin often limits GET parameter length @@ -20,7 +20,7 @@ require_once('include/zot.php'); * body= Body of post * url= URL which will be parsed and the results appended to the body * source= Source application - * post_id= post_id of post to 'share' (local use only) + * post_id= post_id of post to 'share' (local use only) * remote_return= absolute URL to return after posting is finished * type= choices are 'html' or 'bbcode', default is 'bbcode' * @@ -32,16 +32,16 @@ require_once('include/zot.php'); class Rpost extends \Zotlabs\Web\Controller { function get() { - + $o = ''; - + if(! local_channel()) { if(remote_channel()) { // redirect to your own site. // We can only do this with a GET request so you'll need to keep the text short or risk getting truncated // by the wretched beast called 'suhosin'. All the browsers now allow long GET requests, but suhosin // blocks them. - + $url = get_rpost_path(\App::get_observer()); // make sure we're not looping to our own hub if(($url) && (! stristr($url, \App::get_hostname()))) { @@ -53,10 +53,10 @@ class Rpost extends \Zotlabs\Web\Controller { goaway($url); } } - + // The login procedure is going to bugger our $_REQUEST variables // so save them in the session. - + if(array_key_exists('body',$_REQUEST)) { $_SESSION['rpost'] = $_REQUEST; } @@ -64,14 +64,14 @@ class Rpost extends \Zotlabs\Web\Controller { } nav_set_selected('Post'); - + // If we have saved rpost session variables, but nothing in the current $_REQUEST, recover the saved variables - + if((! array_key_exists('body',$_REQUEST)) && (array_key_exists('rpost',$_SESSION))) { $_REQUEST = $_SESSION['rpost']; unset($_SESSION['rpost']); } - + if(array_key_exists('channel',$_REQUEST)) { $r = q("select channel_id from channel where channel_account_id = %d and channel_address = '%s' limit 1", intval(get_account_id()), @@ -82,7 +82,7 @@ class Rpost extends \Zotlabs\Web\Controller { $change = change_channel($r[0]['channel_id']); } } - + if($_REQUEST['remote_return']) { $_SESSION['remote_return'] = $_REQUEST['remote_return']; } @@ -91,21 +91,27 @@ class Rpost extends \Zotlabs\Web\Controller { goaway($_SESSION['remote_return']); goaway(z_root() . '/network'); } - + $plaintext = true; - + if(array_key_exists('type', $_REQUEST) && $_REQUEST['type'] === 'html') { require_once('include/html2bbcode.php'); - $_REQUEST['body'] = html2bbcode($_REQUEST['body']); + $_REQUEST['body'] = html2bbcode($_REQUEST['body']); } - + $channel = \App::get_channel(); - - - $acl = new \Zotlabs\Access\AccessList($channel); - - $channel_acl = $acl->get(); - + + if($_REQUEST['acl']) { + $acl = new \Zotlabs\Access\AccessList([]); + $acl->set($_REQUEST['acl']); + $channel_acl = $acl->get(); + } + else { + $acl = new \Zotlabs\Access\AccessList($channel); + $channel_acl = $acl->get(); + } + + if($_REQUEST['url']) { $x = z_fetch_url(z_root() . '/linkinfo?f=&url=' . urlencode($_REQUEST['url'])); if($x['success']) @@ -115,7 +121,7 @@ class Rpost extends \Zotlabs\Web\Controller { if($_REQUEST['post_id']) { $_REQUEST['body'] .= '[share=' . intval($_REQUEST['post_id']) . '][/share]'; } - + $x = array( 'is_owner' => true, 'allow_location' => ((intval(get_pconfig($channel['channel_id'],'system','use_browser_location'))) ? '1' : ''), @@ -137,19 +143,19 @@ class Rpost extends \Zotlabs\Web\Controller { 'bbcode' => true, 'jotnets' => true ); - + $editor = status_editor($a,$x,false,'Rpost'); - + $o .= replace_macros(get_markup_template('edpost_head.tpl'), array( '$title' => t('Edit post'), '$cancel' => '', '$editor' => $editor )); - + return $o; - + } - - - + + + } diff --git a/Zotlabs/Storage/Browser.php b/Zotlabs/Storage/Browser.php index fde66efcd..3d99bf659 100644 --- a/Zotlabs/Storage/Browser.php +++ b/Zotlabs/Storage/Browser.php @@ -3,6 +3,7 @@ namespace Zotlabs\Storage; use Sabre\DAV; +use App; /** * @brief Provides a DAV frontend for the webbrowser. @@ -76,49 +77,71 @@ class Browser extends DAV\Browser\Plugin { * @param string $path which should be displayed */ public function generateDirectoryIndex($path) { - // (owner_id = channel_id) is visitor owner of this directory? - $is_owner = ((local_channel() && $this->auth->owner_id == local_channel()) ? true : false); - - if ($this->auth->getTimezone()) - date_default_timezone_set($this->auth->getTimezone()); require_once('include/conversation.php'); require_once('include/text.php'); - if ($this->auth->owner_nick) { - $html = ''; - } - $files = $this->server->getPropertiesForPath($path, array( - '{DAV:}displayname', - '{DAV:}resourcetype', - '{DAV:}getcontenttype', - '{DAV:}getcontentlength', - '{DAV:}getlastmodified', - ), 1); + $nick = $this->auth->owner_nick; + $channel_id = $this->auth->owner_id; + // Is visitor owner of this directory? + $is_owner = ((local_channel() && $channel_id == local_channel()) ? true : false); + $cat = ((x($_REQUEST,'cat')) ? $_REQUEST['cat'] : ''); + + if ($this->auth->getTimezone()) { + date_default_timezone_set($this->auth->getTimezone()); + } + + $files = $this->server->getPropertiesForPath($path, [], 1); $parent = $this->server->tree->getNodeForPath($path); - $parentpath = array(); - // only show parent if not leaving /cloud/; TODO how to improve this? - if ($path && $path != "cloud") { - list($parentUri) = \Sabre\Uri\split($path); - $fullPath = \Sabre\HTTP\encodePath($this->server->getBaseUri() . $parentUri); + $arr = explode('/', $parent->os_path); + end($arr); + $folder_parent = ((isset($arr[1])) ? prev($arr) : ''); + + $folder_list = attach_folder_select_list($channel_id); + + $parent_path = ''; - $parentpath['icon'] = $this->enableAssets ? '' . t('parent') . '' : ''; - $parentpath['path'] = $fullPath; + $siteroot_disabled = get_config('system', 'cloud_disable_siteroot'); + + // Hide parent folder if in /cloud or category view + if (($channel_id && ! $cat) || ($siteroot_disabled && $path !== 'cloud')) { + list($parent_uri) = \Sabre\Uri\split($path); + $parent_path = \Sabre\HTTP\encodePath($this->server->getBaseUri() . $parent_uri); } - $f = array(); + $is_root_folder = (($path === 'cloud/' . $nick) ? true : false); + + $f = []; + foreach ($files as $file) { - $ft = array(); + + $ft = []; $type = null; - // This is the current directory, we can skip it - if (rtrim($file['href'], '/') == $path) continue; + $href = rtrim($file['href'], '/'); + + // This is the current directory - skip it + if ($href === $path) + continue; + + $node = $this->server->tree->getNodeForPath($href); + $data = $node->data; + $attach_hash = $data['hash']; + $folder_hash = $node->folder_hash; + + list(, $filename) = \Sabre\Uri\split($href); - list(, $name) = \Sabre\Uri\split($file['href']); + $name = isset($file[200]['{DAV:}displayname']) ? $file[200]['{DAV:}displayname'] : $filename; + $name = $this->escapeHTML($name); + + $size = isset($file[200]['{DAV:}getcontentlength']) ? (int)$file[200]['{DAV:}getcontentlength'] : ''; + + $lastmodified = ((isset($file[200]['{DAV:}getlastmodified'])) ? $file[200]['{DAV:}getlastmodified']->getTime()->format('Y-m-d H:i:s') : ''); if (isset($file[200]['{DAV:}resourcetype'])) { + $type = $file[200]['{DAV:}resourcetype']->getValue(); // resourcetype can have multiple values @@ -128,22 +151,22 @@ class Browser extends DAV\Browser\Plugin { // Some name mapping is preferred switch ($v) { case '{DAV:}collection' : - $type[$k] = t('Collection'); + $type[$k] = 'Collection'; break; case '{DAV:}principal' : - $type[$k] = t('Principal'); + $type[$k] = 'Principal'; break; case '{urn:ietf:params:xml:ns:carddav}addressbook' : - $type[$k] = t('Addressbook'); + $type[$k] = 'Addressbook'; break; case '{urn:ietf:params:xml:ns:caldav}calendar' : - $type[$k] = t('Calendar'); + $type[$k] = 'Calendar'; break; case '{urn:ietf:params:xml:ns:caldav}schedule-inbox' : - $type[$k] = t('Schedule Inbox'); + $type[$k] = 'Schedule Inbox'; break; case '{urn:ietf:params:xml:ns:caldav}schedule-outbox' : - $type[$k] = t('Schedule Outbox'); + $type[$k] = 'Schedule Outbox'; break; case '{http://calendarserver.org/ns/}calendar-proxy-read' : $type[$k] = 'Proxy-Read'; @@ -158,124 +181,144 @@ class Browser extends DAV\Browser\Plugin { // If no resourcetype was found, we attempt to use // the contenttype property - if (!$type && isset($file[200]['{DAV:}getcontenttype'])) { + if (! $type && isset($file[200]['{DAV:}getcontenttype'])) { $type = $file[200]['{DAV:}getcontenttype']; } - if (!$type) $type = t('Unknown'); - $size = isset($file[200]['{DAV:}getcontentlength']) ? (int)$file[200]['{DAV:}getcontentlength'] : ''; - $lastmodified = ((isset($file[200]['{DAV:}getlastmodified'])) ? $file[200]['{DAV:}getlastmodified']->getTime()->format('Y-m-d H:i:s') : ''); - - $fullPath = \Sabre\HTTP\encodePath('/' . trim($this->server->getBaseUri() . ($path ? $path . '/' : '') . $name, '/')); - - $displayName = isset($file[200]['{DAV:}displayname']) ? $file[200]['{DAV:}displayname'] : $name; - - $displayName = $this->escapeHTML($displayName); - $type = $this->escapeHTML($type); - - - $icon = ''; - - if ($this->enableAssets) { - $node = $this->server->tree->getNodeForPath(($path ? $path . '/' : '') . $name); - foreach (array_reverse($this->iconMap) as $class=>$iconName) { - if ($node instanceof $class) { - $icon = ''; - break; - } - } - } - - $parentHash = ''; - $owner = $this->auth->owner_id; - $splitPath = explode('/', $fullPath); - if (count($splitPath) > 3) { - for ($i = 3; $i < count($splitPath); $i++) { - $attachName = urldecode($splitPath[$i]); - $attachHash = $this->findAttachHash($owner, $parentHash, $attachName); - $parentHash = $attachHash; - } + if (! $type) { + $type = $data['filetype']; } + $type = $this->escapeHTML($type); - // generate preview icons for tile view. + // generate preview icons for tile view. // Currently we only handle images, but this could potentially be extended with plugins - // to provide document and video thumbnails. SVG, PDF and office documents have some + // to provide document and video thumbnails. SVG, PDF and office documents have some // security concerns and should only be allowed on single-user sites with tightly controlled - // upload access. system.thumbnail_security should be set to 1 if you want to include these - // types + // upload access. system.thumbnail_security should be set to 1 if you want to include these + // types $is_creator = false; $photo_icon = ''; $preview_style = intval(get_config('system','thumbnail_security',0)); - $r = q("select content, creator from attach where hash = '%s' and uid = %d limit 1", - dbesc($attachHash), - intval($owner) - ); + $is_creator = (($data['creator'] === get_observer_hash()) ? true : false); - if($r) { - $is_creator = (($r[0]['creator'] === get_observer_hash()) ? true : false); - if(file_exists(dbunescbin($r[0]['content']) . '.thumb')) { - $photo_icon = 'data:image/jpeg;base64,' . base64_encode(file_get_contents(dbunescbin($r[0]['content']) . '.thumb')); -// logger('found thumb: ' . $photo_icon); - } - } - - if(strpos($type,'image/') === 0 && $attachHash) { - $r = q("select resource_id, imgscale from photo where resource_id = '%s' and imgscale in ( %d, %d ) order by imgscale asc limit 1", - dbesc($attachHash), + if(strpos($type,'image/') === 0 && $attach_hash) { + $p = q("select resource_id, imgscale from photo where resource_id = '%s' and imgscale in ( %d, %d ) order by imgscale asc limit 1", + dbesc($attach_hash), intval(PHOTO_RES_320), intval(PHOTO_RES_PROFILE_80) ); - if($r) { - $photo_icon = 'photo/' . $r[0]['resource_id'] . '-' . $r[0]['imgscale']; + if($p) { + $photo_icon = 'photo/' . $p[0]['resource_id'] . '-' . $p[0]['imgscale']; } if($type === 'image/svg+xml' && $preview_style > 0) { - $photo_icon = $fullPath; + $photo_icon = $href; } } - $g = [ 'resource_id' => $attachHash, 'thumbnail' => $photo_icon, 'security' => $preview_style ]; + $g = [ 'resource_id' => $attach_hash, 'thumbnail' => $photo_icon, 'security' => $preview_style ]; call_hooks('file_thumbnail', $g); $photo_icon = $g['thumbnail']; + $lockstate = (($data['allow_cid'] || $data['allow_gid'] || $data['deny_cid'] || $data['deny_gid']) ? 'lock' : 'unlock'); + $id = $data['id']; - $attachIcon = ""; // ""; + if($id) { + $terms = q("select * from term where oid = %d AND otype = %d", + intval($id), + intval(TERM_OBJ_FILE) + ); + + $categories = []; + $terms_str = ''; + if($terms) { + foreach($terms as $t) { + $term = htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8',false) ; + if(! trim($term)) + continue; + $categories[] = array('term' => $term, 'url' => $t['url']); + if ($terms_str) + $terms_str .= ','; + $terms_str .= $term; + } + $ft['terms'] = replace_macros(get_markup_template('item_categories.tpl'),array( + '$categories' => $categories + )); + } + } // put the array for this file together - $ft['attachId'] = $this->findAttachIdByHash($attachHash); - $ft['fileStorageUrl'] = substr($fullPath, 0, strpos($fullPath, "cloud/")) . "filestorage/" . $this->auth->owner_nick; + $ft['attach_id'] = $id; $ft['icon'] = $icon; $ft['photo_icon'] = $photo_icon; - $ft['attachIcon'] = (($size) ? $attachIcon : ''); - // @todo Should this be an item value, not a global one? $ft['is_owner'] = $is_owner; $ft['is_creator'] = $is_creator; - $ft['fullPath'] = $fullPath; - $ft['displayName'] = $displayName; + $ft['rel_path'] = (($data) ? '/cloud/' . $nick .'/' . $data['display_path'] : $href); + $ft['full_path'] = z_root() . (($data) ? '/cloud/' . $nick .'/' . $data['display_path'] : $href); + $ft['name'] = $name; $ft['type'] = $type; $ft['size'] = $size; - $ft['sizeFormatted'] = userReadableSize($size); - $ft['lastmodified'] = (($lastmodified) ? datetime_convert('UTC', date_default_timezone_get(), $lastmodified) : ''); - $ft['iconFromType'] = getIconFromType($type); + $ft['collection'] = (($type === 'Collection') ? true : false); + $ft['size_formatted'] = userReadableSize($size); + $ft['last_modified'] = (($lastmodified) ? datetime_convert('UTC', date_default_timezone_get(), $lastmodified) : ''); + $ft['icon_from_type'] = getIconFromType($type); + + $ft['allow_cid'] = acl2json($data['allow_cid']); + $ft['allow_gid'] = acl2json($data['allow_gid']); + $ft['deny_cid'] = acl2json($data['deny_cid']); + $ft['deny_gid'] = acl2json($data['deny_gid']); + + $ft['raw_allow_cid'] = $data['allow_cid']; + $ft['raw_allow_gid'] = $data['allow_gid']; + $ft['raw_deny_cid'] = $data['deny_cid']; + $ft['raw_deny_gid'] = $data['deny_gid']; + + $ft['lockstate'] = $lockstate; + $ft['resource'] = $data['hash']; + $ft['folder'] = $data['folder']; + $ft['revision'] = $data['revision']; + $ft['newfilename'] = ['newfilename_' . $id, t('Change filename to'), $name]; + $ft['categories'] = ['categories_' . $id, t('Categories'), $terms_str]; + + // create a copy of the list which we can alter for the current resource + $folders = $folder_list; + if($data['is_dir']) { + // can not copy a folder into itself + unset($folders[$folder_hash]); + } + + $ft['newfolder'] = ['newfolder_' . $id, t('Select a target location'), $data['folder'], '', $folders]; + $ft['copy'] = ['copy_' . $id, t('Copy to target location'), 0, '', [t('No'), t('Yes')]]; + $ft['recurse'] = ['recurse_' . $id, t('Set permissions for all files and sub folders'), 0, '', [t('No'), t('Yes')]]; + $ft['notify'] = ['notify_edit_' . $id, t('Notify your contacts about this file'), 0, '', [t('No'), t('Yes')]]; $f[] = $ft; } - $output = ''; if ($this->enablePost) { - $this->server->emit('onHTMLActionsPanel', array($parent, &$output, $path)); + $this->server->emit('onHTMLActionsPanel', [$parent, &$output, $path]); } $deftiles = (($is_owner) ? 0 : 1); + $tiles = ((array_key_exists('cloud_tiles',$_SESSION)) ? intval($_SESSION['cloud_tiles']) : $deftiles); $_SESSION['cloud_tiles'] = $tiles; - - $html .= replace_macros(get_markup_template('cloud.tpl'), array( - '$header' => t('Files') . ": " . $this->escapeHTML($path) . "/", + + $header = (($cat) ? t('File category') . ": " . $this->escapeHTML($cat) : t('Files')); + + $channel = channelx_by_n($channel_id); + if($channel) { + $acl = new \Zotlabs\Access\AccessList($channel); + $channel_acl = $acl->get(); + $lockstate = (($acl->is_private()) ? 'lock' : 'unlock'); + } + + $html = replace_macros(get_markup_template('cloud.tpl'), array( + '$header' => $header, '$total' => t('Total'), '$actionspanel' => $output, '$shared' => t('Shared'), @@ -284,8 +327,11 @@ class Browser extends DAV\Browser\Plugin { '$is_owner' => $is_owner, '$is_admin' => is_site_admin(), '$admin_delete' => t('Admin Delete'), - '$parentpath' => $parentpath, - '$cpath' => bin2hex(\App::$query_string), + '$parentpath' => $parent_path, + '$folder_parent' => $folder_parent, + '$folder' => $parent->folder_hash, + '$is_root_folder' => $is_root_folder, + '$cpath' => bin2hex(App::$query_string), '$tiles' => intval($_SESSION['cloud_tiles']), '$entries' => $f, '$name' => t('Name'), @@ -293,17 +339,33 @@ class Browser extends DAV\Browser\Plugin { '$size' => t('Size'), '$lastmod' => t('Last Modified'), '$parent' => t('parent'), - '$edit' => t('Edit'), + '$edit' => t('Submit'), '$delete' => t('Delete'), - '$nick' => $this->auth->getCurrentUser() - )); + '$channel_id' => $channel_id, + '$cpdesc' => t('Copy/paste this code to attach file to a post'), + '$cpldesc' => t('Copy/paste this URL to link file from a web page'), + + '$categories' => ['categories', t('Categories')], + '$recurse' => ['recurse', t('Set permissions for all files and sub folders'), 0, '', [t('No'), t('Yes')]], + + '$newfolder' => ['newfolder', t('Select a target location'), $parent->folder_hash, '', $folder_list], + '$copy' => ['copy', t('Copy to target location'), 0, '', [t('No'), t('Yes')]], + '$return_path' => $path, + + '$lockstate' => $lockstate, + '$allow_cid' => acl2json($channel_acl['allow_cid']), + '$allow_gid' => acl2json($channel_acl['allow_gid']), + '$deny_cid' => acl2json($channel_acl['deny_cid']), + '$deny_gid' => acl2json($channel_acl['deny_gid']) + + )); $a = false; nav_set_selected('Files'); - \App::$page['content'] = $html; + App::$page['content'] = $html; load_pdl(); $current_theme = \Zotlabs\Render\Theme::current(); @@ -335,6 +397,7 @@ class Browser extends DAV\Browser\Plugin { // SimpleCollection, we won't need to show the panel either. if (get_class($node) === 'Sabre\\DAV\\SimpleCollection') return; + require_once('include/acl_selectors.php'); $aclselect = null; @@ -387,9 +450,38 @@ class Browser extends DAV\Browser\Plugin { $special = 'cloud/' . $this->auth->owner_nick; $count = strlen($special); + + if(strpos($path,$special) === 0) - $path = trim(substr($path,$count),'/'); + $display_path = trim(substr($path,$count),'/'); + + $breadcrumbs_html = ''; + + if($display_path && ! $_REQUEST['cat']){ + $breadcrumbs = []; + $folders = explode('/', $display_path); + $folder_hashes = explode('/', $node->os_path); + $breadcrumb_path = z_root() . '/cloud/' . $this->auth->owner_nick; + + $breadcrumbs[] = [ + 'name' => $this->auth->owner_nick, + 'hash' => '', + 'path' => $breadcrumb_path + ]; + + foreach($folders as $i => $name) { + $breadcrumb_path .= '/' . $name; + $breadcrumbs[] = [ + 'name' => $name, + 'hash' => $folder_hashes[$i], + 'path' => $breadcrumb_path + ]; + } + $breadcrumbs_html = replace_macros(get_markup_template('breadcrumb.tpl'), array( + '$breadcrumbs' => $breadcrumbs + )); + } $output .= replace_macros(get_markup_template('cloud_actionspanel.tpl'), array( '$folder_header' => t('Create new folder'), @@ -404,11 +496,11 @@ class Browser extends DAV\Browser\Plugin { '$deny_cid' => acl2json($channel_acl['deny_cid']), '$deny_gid' => acl2json($channel_acl['deny_gid']), '$lockstate' => $lockstate, - '$return_url' => \App::$cmd, - '$path' => $path, - '$folder' => find_folder_hash_by_path($this->auth->owner_id, $path), + '$return_url' => $path, + '$folder' => $node->folder_hash, '$dragdroptext' => t('Drop files here to immediately upload'), - '$notify' => ['notify', t('Show in your contacts shared folder'), 0, '', [t('No'), t('Yes')]] + '$notify' => ['notify', t('Show in your contacts shared folder'), 0, '', [t('No'), t('Yes')]], + '$breadcrumbs_html' => $breadcrumbs_html )); } @@ -453,6 +545,21 @@ class Browser extends DAV\Browser\Plugin { return $hash; } + protected function findAttachHashFlat($owner, $attachName) { + $r = q("SELECT hash FROM attach WHERE uid = %d AND filename = '%s' ORDER BY edited DESC LIMIT 1", + intval($owner), + dbesc($attachName) + ); + $hash = ''; + if ($r) { + foreach ($r as $rr) { + $hash = $rr['hash']; + } + } + + return $hash; + } + /** * @brief Returns an attachment's id for a given hash. * diff --git a/Zotlabs/Storage/Directory.php b/Zotlabs/Storage/Directory.php index 1231dfa25..f2a3a603d 100644 --- a/Zotlabs/Storage/Directory.php +++ b/Zotlabs/Storage/Directory.php @@ -25,7 +25,10 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo * @var string $red_path */ private $red_path; - private $folder_hash; + public $folder_hash; + public $data; + + /** * @brief The full path as seen in the browser. * /cloud + $red_path @@ -41,7 +44,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo * * @var string $os_path */ - private $os_path = ''; + public $os_path = ''; /** * @brief Sets up the directory node, expects a full path. @@ -49,7 +52,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo * @param string $ext_path a full path * @param BasicAuth &$auth_plugin */ - public function __construct($ext_path, &$auth_plugin) { + public function __construct($ext_path, $data, &$auth_plugin) { // $ext_path = urldecode($ext_path); logger('directory ' . $ext_path, LOGGER_DATA); $this->ext_path = $ext_path; @@ -61,6 +64,8 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo } $this->auth = $auth_plugin; $this->folder_hash = ''; + $this->data = $data; + $this->getDir(); if($this->auth->browser) { @@ -116,7 +121,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo $modulename = \App::$module; if ($this->red_path === '/' && $name === $modulename) { - return new Directory('/' . $modulename, $this->auth); + return new Directory('/' . $modulename, [], $this->auth); } $x = $this->FileData($this->ext_path . '/' . $name, $this->auth); @@ -269,8 +274,8 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo dbesc($f), dbesc(datetime_convert()), dbesc(datetime_convert()), - '', - '', + '', + '', dbesc($allow_cid), dbesc($allow_gid), dbesc($deny_cid), @@ -293,7 +298,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo else { $size = file_put_contents($f, $data); } - + // delete attach entry if file_put_contents() failed if ($size === false) { logger('file_put_contents() failed to ' . $f); @@ -374,7 +379,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo $args = array( 'resource_id' => $hash, 'album' => $album, 'os_syspath' => $f, 'os_path' => $xpath['os_path'], 'display_path' => $xpath['path'], 'filename' => $name, 'getimagesize' => $gis, 'directory' => $direct); $p = photo_upload($c[0], \App::get_observer(), $args); } - + \Zotlabs\Daemon\Master::Summon([ 'Thumbnail' , $hash ]); $sync = attach_export_data($c[0], $hash); @@ -402,13 +407,14 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo if ($r) { - // When initiated from DAV, set the 'force' flag on attach_mkdir(). This will cause the operation to report success even if the - // folder already exists. + // When initiated from DAV, set the 'force' flag on attach_mkdir(). This will cause the operation to report success even if the + // folder already exists. require_once('include/attach.php'); $result = attach_mkdir($r[0], $this->auth->observer, array('filename' => $name, 'folder' => $this->folder_hash, 'force' => true)); if($result['success']) { + $sync = attach_export_data($r[0],$result['data']['hash']); logger('createDirectory: attach_export_data returns $sync:' . print_r($sync, true), LOGGER_DEBUG); @@ -476,15 +482,16 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo public function moveInto($targetName,$sourcePath, DAV\INode $sourceNode) { - if(! $this->auth->owner_id) { - return false; - } + $channel_id = $this->auth->owner_id; + // Files have $sourceNode->data['hash'] set. For directories rely on $sourceNode->folder_hash. + $resource_id = ((isset($sourceNode->data['hash'])) ? $sourceNode->data['hash'] : $sourceNode->folder_hash); + $new_folder_hash = $this->folder_hash; - if(! ($sourceNode->data && $sourceNode->data->hash)) { + if(!$channel_id && !$resource_id) return false; - } - return attach_move($this->auth->owner_id, $sourceNode->data->hash, $this->folder_hash); + $ret = attach_move($channel_id, $resource_id, $new_folder_hash); + return $ret['success']; } @@ -515,6 +522,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo $file = trim($file, '/'); $path_arr = explode('/', $file); + if (! $path_arr) return; @@ -609,6 +617,9 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo $file = trim($file, '/'); $path_arr = explode('/', $file); + $cat = $_REQUEST['cat']; + + if (! $path_arr) return null; @@ -679,7 +690,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo $_SESSION['cloud_sort'] = 'name'; switch($_SESSION['cloud_sort']) { - case 'size': + case 'size': $suffix = ' order by is_dir desc, filesize asc '; break; // The following provides inconsistent results for directories because we re-calculate the date for directories based on the most recent change @@ -692,17 +703,34 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo break; } - $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) - ); + if ($cat) { + $r = q("select $prefix attach.id, attach.uid, attach.hash, attach.filename, + attach.filetype, attach.filesize, attach.revision, attach.folder, attach.creator, + attach.flags, attach.is_dir, attach.created, attach.edited, attach.display_path, + attach.allow_cid, attach.allow_gid, attach.deny_cid, attach.deny_gid from attach + left join term on attach.id = term.oid + where term.term = '%s' and attach.uid = %d $perms $suffix", + dbesc($cat), + intval($channel_id) + ); + } + else { + $r = q("select $prefix attach.id, attach.uid, attach.hash, attach.filename, + attach.filetype, attach.filesize, attach.revision, attach.folder, attach.creator, + attach.flags, attach.is_dir, attach.created, attach.edited, attach.display_path, + attach.allow_cid, attach.allow_gid, attach.deny_cid, attach.deny_gid from attach + where folder = '%s' and uid = %d $perms $suffix", + dbesc($folder), + intval($channel_id) + ); + } foreach ($r as $rr) { if(\App::$module === 'cloud' && (strpos($rr['filename'],'.') === 0) && (! get_pconfig($channel_id,'system','show_dot_files')) ) continue; // @FIXME I don't think we use revisions currently in attach structures. - // In case we see any in the wild provide a unique filename. This + // In case we see any in the wild provide a unique filename. This // name may or may not be accessible if($rr['revision']) @@ -710,13 +738,12 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo //logger('filename: ' . $rr['filename'], LOGGER_DEBUG); if (intval($rr['is_dir'])) { - $ret[] = new Directory($path . '/' . $rr['filename'], $auth); + $ret[] = new Directory($path . '/' . $rr['filename'], $rr, $auth); } else { $ret[] = new File($path . '/' . $rr['filename'], $rr, $auth); } } - return $ret; } @@ -738,15 +765,14 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo return $ret; } - $r = q("SELECT channel_id, channel_address, profile.publish FROM channel left join profile on profile.uid = channel.channel_id WHERE channel_removed = 0 AND channel_system = 0 AND (channel_pageflags & %d) = 0", + $r = q("SELECT channel_id, channel_address, profile.publish FROM channel left join profile on profile.uid = channel.channel_id WHERE channel_removed = 0 AND channel_system = 0 AND (channel_pageflags & %d) = 0 and profile.is_default = 1", intval(PAGE_HIDDEN) ); - if ($r) { foreach ($r as $rr) { - if (perm_is_allowed($rr['channel_id'], $auth->observer, 'view_storage') && $rr['publish']) { + if ((perm_is_allowed($rr['channel_id'], $auth->observer, 'view_storage') && $rr['publish'])|| $rr['channel_id'] == $this->auth->channel_id) { logger('found channel: /cloud/' . $rr['channel_address'], LOGGER_DATA); - $ret[] = new Directory($rr['channel_address'], $auth); + $ret[] = new Directory($rr['channel_address'], [], $auth); } } } @@ -778,7 +804,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo } if ((! $file) || ($file === '/')) { - return new Directory('/', $auth); + return new Directory('/', [], $auth); } $file = trim($file, '/'); @@ -848,7 +874,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo if ($test) return true; // final component was a directory. - return new Directory($file, $auth); + return new Directory($file, [], $auth); } if ($errors) { @@ -867,7 +893,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo return true; if (intval($r[0]['is_dir'])) { - return new Directory($path . '/' . $r[0]['filename'], $auth); + return new Directory($path . '/' . $r[0]['filename'], [], $auth); } else { return new File($path . '/' . $r[0]['filename'], $r[0], $auth); @@ -888,7 +914,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo $used = 0; $limit = 0; $free = 0; - + if ($this->auth->owner_id) { $channel = channelx_by_n($this->auth->owner_id); if($channel) { @@ -919,5 +945,4 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo return [ (int) $used, (int) $free ]; } - } diff --git a/Zotlabs/Widget/Categories.php b/Zotlabs/Widget/Categories.php index 82c37cd0c..94ad469da 100644 --- a/Zotlabs/Widget/Categories.php +++ b/Zotlabs/Widget/Categories.php @@ -21,7 +21,9 @@ class Categories { if(($articles) && (! Apps::system_app_installed(App::$profile['profile_uid'],'Articles'))) return ''; - if((! App::$profile['profile_uid']) + $files = ((array_key_exists('files',$arr) && $arr['files']) ? true : false); + + if((! App::$profile['profile_uid']) || (! perm_is_allowed(App::$profile['profile_uid'],get_observer_hash(),(($cards || $articles) ? 'view_pages' : 'view_stream')))) { return ''; } @@ -29,12 +31,14 @@ class Categories { $cat = ((x($_REQUEST,'cat')) ? htmlspecialchars($_REQUEST['cat'],ENT_COMPAT,'UTF-8') : ''); $srchurl = (($cards) ? App::$argv[0] . '/' . App::$argv[1] : App::$query_string); $srchurl = rtrim(preg_replace('/cat\=[^\&].*?(\&|$)/is','',$srchurl),'&'); - $srchurl = str_replace(array('?f=','&f='),array('',''),$srchurl); + $srchurl = str_replace(array('?f=','&f=', '/?'),array('', '', ''),$srchurl); if($cards) return cardcategories_widget($srchurl, $cat); elseif($articles) return articlecategories_widget($srchurl, $cat); + elseif($files) + return filecategories_widget($srchurl, $cat); else return categories_widget($srchurl, $cat); -- cgit v1.2.3