diff options
author | Mario <mario@mariovavti.com> | 2020-12-21 21:37:10 +0000 |
---|---|---|
committer | Mario <mario@mariovavti.com> | 2020-12-21 21:37:10 +0000 |
commit | d118ab71e6aa4f300ba6e42663d13a63a2323122 (patch) | |
tree | 08282fa1dfb0a2f975fd9d338df7677fa84ea5e9 | |
parent | 81a1aedeb9a4e07c3d1e11905ad3e2434d635e86 (diff) | |
download | volse-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.php | 128 | ||||
-rw-r--r-- | include/attach.php | 39 | ||||
-rw-r--r-- | view/js/mod_cloud.js | 43 | ||||
-rw-r--r-- | view/tpl/cloud_directory.tpl | 11 |
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}} |