Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 223 additions & 23 deletions tinyfilemanager.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@
// eg. decrease to 1MB if nginx reports problem 413 entity too large
$upload_chunk_size_bytes = 2000000; // chunk size 2,000,000 bytes (~2MB)

// Handle file uploads where the file exists
// PROMPT => ask the user to resolve specific conflicts, or leave the temporary file
// NEW => newly uploaded file is renamed with a timestamp appended
// OLD => old file will be renamed with a timestamp appended
// REPLACE => old file will be deleted
$upload_name_conflict_handling = 'PROMPT';

// Possible rules are 'OFF', 'AND' or 'OR'
// OFF => Don't check connection IP, defaults to OFF
// AND => Connection must be on the whitelist, and not on the blacklist
Expand Down Expand Up @@ -667,6 +674,46 @@ function get_file_path()
event_callback(array("fail" => $err));
}
}

// Upload Conflict Resolution
if (isset($_POST['type']) && $_POST['type'] == 'conflict') {
$path = $_POST['path'];
$filename = $_POST['filename'];
$resolution = $_POST['resoultion'];


$fullPath = $path . '/' . $filename;
$tempPath = "{$fullPath}.part";

if (!file_exists($tempPath)) {
echo json_encode(['status' => 'error', 'info' => "Temporary file not found. $tempPath"]);
exit;
}

$ext = pathinfo($filename, PATHINFO_FILENAME) != '' ? strtolower(pathinfo($filename, PATHINFO_EXTENSION)) : '';
$fullPathTarget = $fullPath;
$ext_1 = $ext ? '.' . $ext : '';
$datedPath = $path . '/' . basename($filename, $ext_1) . '_' . date('ymdHis') . $ext_1;

if (fm_is_excluded($filename, $path)) {
$fullPathTarget = $datedPath;
} else switch ($_POST['resolution']) {
case 'OLD':
fm_rename($fullPath, $datedPath);
break;
case 'REPLACE':
if (fm_rdelete($fullPath)) break;
case 'NEW':
default:
$fullPathTarget = $datedPath;
}
fm_rename($tempPath, $fullPathTarget);

echo json_encode(['status' => 'resolved',
'info' => "RESOLVED WITH: $path , $filename, $resolution"]);
exit;
}

exit();
}

Expand Down Expand Up @@ -1045,13 +1092,33 @@ function get_file_path()
}

if ($chunkIndex == $chunkTotal - 1) {
$fullPathTarget = $fullPath;
$ext_1 = $ext ? '.' . $ext : '';
$datedPath = $path . '/' . basename($fullPathInput, $ext_1) . '_' . date('ymdHis') . $ext_1;
if (file_exists($fullPath)) {
$ext_1 = $ext ? '.' . $ext : '';
$fullPathTarget = $path . '/' . basename($fullPathInput, $ext_1) . '_' . date('ymdHis') . $ext_1;
} else {
$fullPathTarget = $fullPath;
if (fm_is_excluded($fullPathInput, $path)) {
$fullPathTarget = $datedPath; // excluded items should not be replaced, renamed, or prompted
} else switch ($upload_name_conflict_handling) {
case 'PROMPT':
$response = array(
'status' => 'conflict',
'info' => "file upload successful with conflict",
'filename' => $fullPathInput,
'path' => $path
);
echo json_encode($response);
exit();
case 'OLD':
fm_rename($fullPath,$datedPath);
break;
case 'REPLACE':
if (fm_rdelete($fullPath)) break;
case 'NEW':
default:
$fullPathTarget = $datedPath;
}
}
rename("{$fullPath}.part", $fullPathTarget);
fm_rename("{$fullPath}.part", $fullPathTarget);
}
} else if (move_uploaded_file($tmp_name, $fullPath)) {
// Be sure that the file has been uploaded
Expand Down Expand Up @@ -1334,7 +1401,7 @@ function get_file_path()
$folders = array();
$files = array();
$current_path = array_slice(explode("/", $path), -1)[0];
if (is_array($objects) && fm_is_exclude_items($current_path, $path)) {
if (is_array($objects) && !fm_is_excluded($current_path, $path)) {
foreach ($objects as $file) {
if ($file == '.' || $file == '..') {
continue;
Expand All @@ -1343,9 +1410,9 @@ function get_file_path()
continue;
}
$new_path = $path . '/' . $file;
if (@is_file($new_path) && fm_is_exclude_items($file, $new_path)) {
if (@is_file($new_path) && !fm_is_excluded($file, $new_path)) {
$files[] = $file;
} elseif (@is_dir($new_path) && $file != '.' && $file != '..' && fm_is_exclude_items($file, $new_path)) {
} elseif (@is_dir($new_path) && $file != '.' && $file != '..' && !fm_is_excluded($file, $new_path)) {
$folders[] = $file;
}
}
Expand Down Expand Up @@ -1421,6 +1488,36 @@ function getUploadExt()
</div>
</div>
</div>
<!-- Upload Conflict Modal -->
<div class="modal modal-alert" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" role="dialog"
id="conflict_modal" data-bs-theme="<?php echo FM_THEME; ?>">
<div class="modal-dialog" role="document">
<form class="modal-content rounded-3 shadow" method="post" autocomplete="off" onsubmit="return false;">
<div class="modal-body p-4 text-center">
<h5 class="mb-3"><?php echo lng('File conflict detected') ?></h5>
<p class="mb-1 conflict-filename"></p>
<div class="form-check text-start mt-3 mb-1 px-4 conflict-checkbox-container"
style="display: none;">
<input class="form-check-input" type="checkbox" value="" id="conflict_checkbox">
<label class="form-check-label small" for="conflict_checkbox"></label>
</div>
</div>
<div class="modal-footer flex-nowrap p-0">
<button type="button" class="btn btn-lg btn-link fs-6 text-decoration-none col-6 m-0 rounded-0 border-end"
data-resolution="NEW" onclick="resolve_conflict_from_modal()"><?php echo lng('New Filename') ?></button>

<button type="button" class="btn btn-lg btn-link fs-6 text-decoration-none col-6 m-0 rounded-0 border-end"
data-resolution="OLD" onclick="resolve_conflict_from_modal()"><?php echo lng('Rename Old') ?></button>
</div>
<div class="modal-footer flex-nowrap p-0">
<button type="button" class="btn btn-lg btn-link fs-6 text-decoration-none col-6 m-0 rounded-0 border-end"
data-resolution="REPLACE" onclick="resolve_conflict_from_modal()"><?php echo lng('Replace') ?></button>
<button type="button" class="btn btn-lg btn-link fs-6 text-decoration-none col-6 m-0 rounded-0"
onclick="cancel_conflict()"><?php echo lng('Cancel') ?></button>
</div>
</form>
</div>
</div>
<?php print_external('js-dropzone'); ?>
<script>
Dropzone.options.fileUploader = {
Expand All @@ -1444,7 +1541,10 @@ function getUploadExt()
}).on("success", function(res) {
try {
let _response = JSON.parse(res.xhr.response);

if (_response.status == "conflict") {
enqueue_conflict(_response.filename, _response.path);
toast(_response.info);
}
if (_response.status == "error") {
toast(_response.info);
}
Expand Down Expand Up @@ -1640,7 +1740,7 @@ function getSelected($l)
</div>
</div>

<small class="text-body-secondary">* <?php echo lng('Sometimes the save action may not work on the first try, so please attempt it again') ?>.</span>
<span class="text-body-secondary">* <?php echo lng('Sometimes the save action may not work on the first try, so please attempt it again') ?>.</span>
</form>
</div>
</div>
Expand Down Expand Up @@ -1712,7 +1812,7 @@ function getSelected($l)
$file = $_GET['view'];
$file = fm_clean_path($file, false);
$file = str_replace('/', '', $file);
if ($file == '' || !is_file($path . '/' . $file) || !fm_is_exclude_items($file, $path . '/' . $file)) {
if ($file == '' || !is_file($path . '/' . $file) || fm_is_excluded($file, $path . '/' . $file)) {
fm_set_msg(lng('File not found'), 'error');
$FM_PATH = FM_PATH;
fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
Expand Down Expand Up @@ -1918,7 +2018,7 @@ class="edit-file"><i class="fa fa-pencil-square"></i> <?php echo lng('AdvancedEd
$file = $_GET['edit'];
$file = fm_clean_path($file, false);
$file = str_replace('/', '', $file);
if ($file == '' || !is_file($path . '/' . $file) || !fm_is_exclude_items($file, $path . '/' . $file)) {
if ($file == '' || !is_file($path . '/' . $file) || fm_is_excluded($file, $path . '/' . $file)) {
fm_set_msg(lng('File not found'), 'error');
$FM_PATH = FM_PATH;
fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
Expand Down Expand Up @@ -2664,26 +2764,23 @@ function fm_get_display_path($file_path)
}

/**
* Check file is in exclude list
* Check if the file, extension, or path is an excluded item
* @param string $name The name of the file/folder
* @param string $path The full path of the file/folder
* @return bool
*/
function fm_is_exclude_items($name, $path)
function fm_is_excluded($name, $path)
{
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
if (isset($exclude_items) and sizeof($exclude_items)) {
unset($exclude_items);
}

$exclude_items = FM_EXCLUDE_ITEMS;
$excluded_items = FM_EXCLUDE_ITEMS; // set by above config or environment
if (version_compare(PHP_VERSION, '7.0.0', '<')) {
$exclude_items = unserialize($exclude_items);
$excluded_items = unserialize($excluded_items); // constants cant hold arrays before PHP 7
}
if (!in_array($name, $exclude_items) && !in_array("*.$ext", $exclude_items) && !in_array($path, $exclude_items)) {
return true;

if (in_array($name, $excluded_items) || in_array("*.$ext", $excluded_items) || in_array($path, $excluded_items)) {
return true; // item is in exclude_items
}
return false;
return false; // item is safe
}

/**
Expand Down Expand Up @@ -4844,6 +4941,103 @@ function rename(e, t) {
}
}

// File Upload Conflict Prompt
let conflict_queue = [];
let conflict_current = null;
let conflict_processing = false;
let apply_choice_to_all = false;
let selected_resolution = null;

function update_conflict_checkbox() {
let remaining_count = conflict_queue.length;
if (remaining_count > 0) {
$('.conflict-checkbox-container').show();
$('#conflict_checkbox').prop('checked', false);
$('.conflict-checkbox-container label').html('<?php echo lng('Apply choice to remaining') ?> <b>' + remaining_count + '</b>');
} else {
$('.conflict-checkbox-container').hide();
$('#conflict_checkbox').prop('checked', false);
}
}

function enqueue_conflict(filename, path) {
conflict_queue.push({filename, path});
if (conflict_processing && document.getElementById('conflict_modal')) {
update_conflict_checkbox(); // show
}
if (!conflict_processing) {
apply_choice_to_all = false; // reset for new batch
selected_resolution = null;
show_next_conflict_dialog();
}
}

function show_next_conflict_dialog() {
if (conflict_queue.length === 0) {
conflict_processing = false;

$('#conflict_modal').modal('hide');
return;
}

conflict_processing = true;
conflict_current = conflict_queue.shift();
const {filename, path} = conflict_current;

if (apply_choice_to_all && selected_resolution) {
resolve_conflict(filename, selected_resolution, path, true);
return;
}
$('#conflict_modal').find('.conflict-filename').html('<?php echo lng('Conflict detected for') ?> <b>' + filename + '</b>.');
$('#conflict_modal').modal('show');
}

function resolve_conflict_from_modal() {
let button = event.currentTarget;
let resolution = button.getAttribute('data-resolution');
if (conflict_current) {
resolve_conflict(conflict_current.filename, resolution, conflict_current.path);
}
}

function resolve_conflict(filename, resolution, path, auto = false) {
if (!auto) {
// Check if user selected "apply to all"
apply_choice_to_all = $('#conflict_checkbox').is(':checked');
if (apply_choice_to_all) {
selected_resolution = resolution;
}
}
$.ajax({
type: 'POST',
url: window.location,
data: {
ajax: true,
type: 'conflict',
filename: filename,
resolution: resolution,
path: path,
token: window.csrf
},
dataType: 'json',
success: function (response) {
if (response.status === 'resolved') {
show_next_conflict_dialog();
} else {
alert('Error: ' + response.message);
}
},
error: function (xhr, status, error) {
alert('AJAX Error: ' + error);
}
});
}

function cancel_conflict() {
$('#conflict_modal').modal('hide');
show_next_conflict_dialog();
}

function change_checkboxes(e, t) {
for (var n = e.length - 1; n >= 0; n--) e[n].checked = "boolean" == typeof t ? t : !e[n].checked
}
Expand Down Expand Up @@ -5514,6 +5708,12 @@ function lng($txt)
$tr['en']['Actions'] = 'Actions';
$tr['en']['Folder is empty'] = 'Folder is empty';
$tr['en']['Upload'] = 'Upload';
$tr['en']['File conflict detected'] = 'File conflict detected';
$tr['en']['Conflict detected for'] = 'Conflict detected for';
$tr['en']['New Filename'] = 'New Filename';
$tr['en']['Rename Old'] = 'Rename Old';
$tr['en']['Apply choice to remaining'] = 'Apply choice to remaining';
$tr['en']['Replace'] = 'Replace';
$tr['en']['Cancel'] = 'Cancel';
$tr['en']['InvertSelection'] = 'Invert Selection';
$tr['en']['DestinationFolder'] = 'Destination Folder';
Expand Down