aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMario <mario@mariovavti.com>2020-12-21 21:37:10 +0000
committerMario <mario@mariovavti.com>2020-12-21 21:37:10 +0000
commitd118ab71e6aa4f300ba6e42663d13a63a2323122 (patch)
tree08282fa1dfb0a2f975fd9d338df7677fa84ea5e9
parent81a1aedeb9a4e07c3d1e11905ad3e2434d635e86 (diff)
downloadvolse-hubzilla-d118ab71e6aa4f300ba6e42663d13a63a2323122.tar.gz
volse-hubzilla-d118ab71e6aa4f300ba6e42663d13a63a2323122.tar.bz2
volse-hubzilla-d118ab71e6aa4f300ba6e42663d13a63a2323122.zip
files_ng: implement directory and bulk file download
-rw-r--r--Zotlabs/Module/Attach.php128
-rw-r--r--include/attach.php39
-rw-r--r--view/js/mod_cloud.js43
-rw-r--r--view/tpl/cloud_directory.tpl11
4 files changed, 198 insertions, 23 deletions
diff --git a/Zotlabs/Module/Attach.php b/Zotlabs/Module/Attach.php
index 490d5edd0..86183056c 100644
--- a/Zotlabs/Module/Attach.php
+++ b/Zotlabs/Module/Attach.php
@@ -1,46 +1,100 @@
<?php
namespace Zotlabs\Module;
+use ZipArchive;
+use Zotlabs\Web\Controller;
+
require_once('include/security.php');
require_once('include/attach.php');
+class Attach extends Controller {
+
+ function post() {
+
+ $attach_ids = ((x($_REQUEST, 'attach_ids')) ? $_REQUEST['attach_ids'] : []);
+
+ if ($attach_ids) {
+
+ $ret = ['success' => false];
+
+ $channel_id = ((x($_REQUEST, 'channel_id')) ? intval($_REQUEST['channel_id']) : 0);
+ $channel = channelx_by_n($channel_id);
+
+ if (! $channel) {
+ notice(t('Channel not found.') . EOL);
+ return;
+ }
+
+ $zip_dir = 'store/[data]/' . $channel['channel_address'] . '/tmp';
+ if (! is_dir($zip_dir))
+ mkdir($zip_dir, STORAGE_DEFAULT_PERMISSIONS, true);
+
+ $rnd = random_string(10);
+
+ $zip_file = 'download_' . $rnd . '.zip';
+ $zip_path = $zip_dir . '/' . $zip_file;
+
+ $zip = new ZipArchive();
-class Attach extends \Zotlabs\Web\Controller {
+ if ($zip->open($zip_path, ZipArchive::CREATE) === true) {
+
+ $filename = self::zip_archive_handler($zip, $attach_ids);
+
+ $zip->close();
+
+ header('Content-Type: application/zip');
+ header('Content-Disposition: attachment; filename="' . $filename . '"');
+ header('Content-Length: ' . filesize($zip_path));
+
+ $istream = fopen($zip_path, 'rb');
+ $ostream = fopen('php://output', 'wb');
+
+ if ($istream && $ostream) {
+ pipe_streams($istream,$ostream);
+ fclose($istream);
+ fclose($ostream);
+ }
+
+ unlink($zip_path);
+ killme();
+ }
+ }
+ }
+
+ function get() {
- function init() {
-
if(argc() < 2) {
notice( t('Item not available.') . EOL);
return;
}
-
+
$r = attach_by_hash(argv(1),get_observer_hash(),((argc() > 2) ? intval(argv(2)) : 0));
-
+
if(! $r['success']) {
notice( $r['message'] . EOL);
return;
}
-
+
$c = q("select channel_address from channel where channel_id = %d limit 1",
intval($r['data']['uid'])
);
-
+
if(! $c)
return;
-
-
+
+
$unsafe_types = array('text/html','text/css','application/javascript');
-
+
if(in_array($r['data']['filetype'],$unsafe_types) && (! channel_codeallowed($r['data']['uid']))) {
- header('Content-type: text/plain');
+ header('Content-Type: text/plain');
}
else {
- header('Content-type: ' . $r['data']['filetype']);
+ header('Content-Type: ' . $r['data']['filetype']);
}
-
- header('Content-disposition: attachment; filename="' . $r['data']['filename'] . '"');
+
+ header('Content-Disposition: attachment; filename="' . $r['data']['filename'] . '"');
if(intval($r['data']['os_storage'])) {
- $fname = dbunescbin($r['data']['content']);
+ $fname = $r['data']['content'];
if(strpos($fname,'store') !== false)
$istream = fopen($fname,'rb');
else
@@ -53,9 +107,47 @@ class Attach extends \Zotlabs\Web\Controller {
}
}
else
- echo dbunescbin($r['data']['content']);
+ echo $r['data']['content'];
killme();
-
+
+ }
+
+ function zip_archive_handler($zip, $attach_ids, $pass = 1) {
+
+ $observer_hash = get_observer_hash();
+ $single = ((count($attach_ids) == 1) ? true : false);
+ $filename = 'download.zip';
+
+ foreach($attach_ids as $attach_id) {
+
+ $r = attach_by_id($attach_id, $observer_hash);
+
+ if (! $r['success']) {
+ continue;
+ }
+
+ if ($r['data']['is_dir'] && $single && $pass === 1)
+ $filename = $r['data']['filename'] . '.zip';
+
+ if ($r['data']['is_dir']) {
+ $zip->addEmptyDir($r['data']['display_path']);
+
+ $d = q("SELECT id FROM attach WHERE folder = '%s'",
+ dbesc($r['data']['hash'])
+ );
+
+ $attach_ids = ids_to_array($d);
+ self::zip_archive_handler($zip, $attach_ids, $observer_hash, $pass++);
+ }
+ else {
+ $file_path = $r['data']['content'];
+ $file_name = $r['data']['display_path'];
+ $zip->addFile($file_path, $file_name);
+ }
+
+ }
+
+ return $filename;
}
-
+
}
diff --git a/include/attach.php b/include/attach.php
index 8ebe2d243..b4f697f81 100644
--- a/include/attach.php
+++ b/include/attach.php
@@ -300,6 +300,44 @@ function attach_by_hash($hash, $observer_hash, $rev = 0) {
}
+/**
+ * @brief Find an attachment by id.
+ *
+ * Returns the entire attach structure including data.
+ *
+ * This could exhaust memory so most useful only when immediately sending the data.
+ *
+ * @param string $hash
+ * @param string $observer_hash
+ * @return array
+ */
+function attach_by_id($id, $observer_hash) {
+
+ $ret = array('success' => false);
+
+ // Check for existence, which will also provide us the owner uid
+
+ $r = q("SELECT * FROM attach WHERE id = %d",
+ intval($id)
+ );
+ if(! $r) {
+ $ret['message'] = t('Item was not found.');
+ return $ret;
+ }
+
+ if(! attach_can_view($r[0]['uid'], $observer_hash, $r[0]['hash'])) {
+ $ret['message'] = t('Permission denied.');
+ return $ret;
+ }
+
+ $r[0]['content'] = dbunescbin($r[0]['content']);
+
+ $ret['success'] = true;
+ $ret['data'] = $r[0];
+
+ return $ret;
+}
+
function attach_can_view($uid,$ob_hash,$resource) {
$sql_extra = permissions_sql($uid,$ob_hash);
@@ -1120,7 +1158,6 @@ function attach_mkdir($channel, $observer_hash, $arr = null) {
if(! is_dir($os_basepath))
os_mkdir($os_basepath,STORAGE_DEFAULT_PERMISSIONS, true);
-
$os_basepath .= '/';
if(! perm_is_allowed($channel_id, $observer_hash, 'write_storage')) {
diff --git a/view/js/mod_cloud.js b/view/js/mod_cloud.js
index cbd9c2a08..736dec471 100644
--- a/view/js/mod_cloud.js
+++ b/view/js/mod_cloud.js
@@ -56,6 +56,24 @@ $(document).ready(function () {
close_and_deactivate_all_panels();
});
+ $('.cloud-tool-dir-download-btn').on('click', function (e) {
+ e.preventDefault();
+ close_and_deactivate_all_panels()
+
+ let id = $(this).data('id');
+ if(! id) {
+ return false;
+ }
+
+ close_and_deactivate_all_panels();
+
+ // some trickery to trigger download action via ajax
+ let form = $('<form></form>').attr('action', 'attach').attr('method', 'post');
+ form.append($("<input></input>").attr('type', 'hidden').attr('name', 'channel_id').attr('value', channelId));
+ form.append($("<input></input>").attr('type', 'hidden').attr('name', 'attach_ids[]').attr('value', id));
+ form.appendTo('body').submit().remove();
+ });
+
$('.cloud-tool-delete-btn').on('click', function (e) {
e.preventDefault();
let id = $(this).data('id');
@@ -255,14 +273,37 @@ $(document).ready(function () {
$('#cloud-multi-tool-submit, #cloud-multi-tool-categories').show();
});
+ $('#cloud-multi-tool-download-btn').on('click', function (e) {
+ e.preventDefault();
+
+ let post_data = $('.cloud-multi-tool-checkbox:checked');
+
+ if(! post_data.length) {
+ return false;
+ }
+
+ close_and_deactivate_all_panels();
+
+ // some trickery to trigger download action via ajax
+ var form = $('<form></form>').attr('action', 'attach').attr('method', 'post');
+ form.append($("<input></input>").attr('type', 'hidden').attr('name', 'channel_id').attr('value', channelId));
+ post_data.each(function () {
+ form.append($("<input></input>").attr('type', 'hidden').attr('name', 'attach_ids[]').attr('value', this.value));
+ });
+ form.appendTo('body').submit().remove();
+ });
+
$('#cloud-multi-tool-delete-btn').on('click', function (e) {
e.preventDefault();
- let post_data = $('.cloud-multi-tool-checkbox').serializeArray();
+ close_and_deactivate_all_panels();
+
+ let post_data = $('.cloud-multi-tool-checkbox:checked').serializeArray();
if(! post_data.length) {
return false;
}
+
let confirm = confirmDelete();
if (confirm) {
$('body').css('cursor', 'wait');
diff --git a/view/tpl/cloud_directory.tpl b/view/tpl/cloud_directory.tpl
index a6993fe97..5886391bb 100644
--- a/view/tpl/cloud_directory.tpl
+++ b/view/tpl/cloud_directory.tpl
@@ -87,6 +87,7 @@
{{/if}}
<a id="cloud-multi-tool-move-btn" class="dropdown-item" href="#"><i class="fa fa-fw fa-copy"></i> Move or copy</a>
<a id="cloud-multi-tool-categories-btn" class="dropdown-item" href="#"><i class="fa fa-fw fa-asterisk"></i> Categories</a>
+ <a id="cloud-multi-tool-download-btn" class="dropdown-item" href="#"><i class="fa fa-fw fa-cloud-download"></i> Download</a>
<a id="cloud-multi-tool-delete-btn" class="dropdown-item" href="#"><i class="fa fa-fw fa-trash-o"></i> {{$delete}}</a>
</div>
</div>
@@ -172,23 +173,27 @@
<a id="cloud-tool-rename-btn-{{$item.attach_id}}" class="dropdown-item cloud-tool-rename-btn" href="#" data-id="{{$item.attach_id}}"><i class="fa fa-fw fa-pencil"></i> Rename</a>
<a id="cloud-tool-move-btn-{{$item.attach_id}}" class="dropdown-item cloud-tool-move-btn" href="#" data-id="{{$item.attach_id}}"><i class="fa fa-fw fa-copy"></i> Move or copy</a>
<a id="cloud-tool-categories-btn-{{$item.attach_id}}" class="dropdown-item cloud-tool-categories-btn" href="#" data-id="{{$item.attach_id}}"><i class="fa fa-fw fa-asterisk"></i> Categories</a>
- {{if !$item.collection}}
{{if $item.is_owner}}
<a id="cloud-tool-share-btn-{{$item.attach_id}}" class="dropdown-item cloud-tool-share-btn" href="/rpost?attachment=[attachment]{{$item.resource}},{{$item.revision}}[/attachment]&acl[allow_cid]={{$item.raw_allow_cid}}&acl[allow_gid]={{$item.raw_allow_gid}}&acl[deny_cid]={{$item.raw_deny_cid}}&acl[deny_gid]={{$item.raw_deny_gid}}" data-id="{{$item.attach_id}}"><i class="fa fa-fw fa-share-square-o"></i> Post</a>
{{/if}}
+ {{if $item.collection}}
+ <a id="cloud-tool-dir-download-btn-{{$item.attach_id}}" class="dropdown-item cloud-tool-dir-download-btn" href="#" data-id="{{$item.attach_id}}"><i class="fa fa-fw fa-cloud-download"></i> Download</a>
+ {{else}}
<a id="cloud-tool-download-btn-{{$item.attach_id}}" class="dropdown-item cloud-tool-download-btn" href="/attach/{{$item.resource}}" data-id="{{$item.attach_id}}"><i class="fa fa-fw fa-cloud-download"></i> Download</a>
{{/if}}
<a id="cloud-tool-delete-btn-{{$item.attach_id}}" class="dropdown-item cloud-tool-delete-btn" href="#" data-id="{{$item.attach_id}}"><i class="fa fa-fw fa-trash-o"></i> {{$delete}}</a>
</div>
</div>
{{else}}
- {{if ($is_admin || !$item.collection) && $item.attach_id}}
+ {{if $is_admin || $item.attach_id}}
<div class="dropdown">
<button class="btn btn-link btn-sm" id="dropdown-button-{{$item.attach_id}}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-fw fa-ellipsis-v"></i>
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdown-button-{{$item.attach_id}}">
- {{if !$item.collection}}
+ {{if $item.collection}}
+ <a id="cloud-tool-dir-download-btn-{{$item.attach_id}}" class="dropdown-item cloud-tool-dir-download-btn" href="#" data-id="{{$item.attach_id}}"><i class="fa fa-fw fa-cloud-download"></i> Download</a>
+ {{else}}
<a id="cloud-tool-download-btn-{{$item.attach_id}}" class="dropdown-item cloud-tool-download-btn" href="/attach/{{$item.resource}}" data-id="{{$item.attach_id}}"><i class="fa fa-fw fa-cloud-download"></i> Download</a>
{{/if}}
{{if $is_admin}}