Files
JavaScript.ajax-file-upload/test/backend.php
Clemens Schwaighofer 32455459da AJAX simple file uploader
A simple javascript powered file uploader with progress and hooks
support.

Was created as a loose replacement for FineUploader.
Currently only works with local uploads

Below is the log history from the previous folder:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
commit 6ea59f91314be8b2b936920d4b0a3f04932c9e73 ┃
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━
Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com>
Date:   Thu Jun 3 10:31:54 2021 +0900

    AJAX file uploader code clean up

    Add global abort and connected clean up of function calls and element
    show/hide blocks

    Add simple progress bar for upload (in connection with percent upload)

    Move running AFUS object into the config object

    Added new config object entries for this:
    - abort: flagged true if global abort is triggered
    - running: moved from AFUS_running into config, how many uploads are
      currently running
    - current: current file_pos running (that is queue position in array)
    - current_xhr: current XHR upload object, used for abort calls

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
commit b061008ca0b496f6157b8f073d7ca14abb469ef5 ┃
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━
Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com>
Date:   Thu May 27 11:51:47 2021 +0900

    Update AJAX file uploader to work sequential

    Rewrite flow in ajax uploader to use promsies to launch a new upload
    only if the previous upload was finished (in any way)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
commit a2ec369a8cae65e216fe402ac8b5d106e3db99af ┃
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━
Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com>
Date:   Thu May 20 11:48:38 2021 +0900

    Updated ajax simple file uploader to allow multiple file uploads

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
commit 66878f89c82fe914031e113e780fd72e9ff09b78 ┃
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━
Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com>
Date:   Mon May 10 09:13:25 2021 +0900

    Excel vendor sub module, ajax delay test, ajax file upload fix, others

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
commit e4efbbfdf843d8b2e53e4d3c873799b839875721 ┃
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━
Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com>
Date:   Fri Apr 9 13:44:28 2021 +0900

    ajax file upload updates, byte format test fixes, ssh2 test fixes, array append test add, nested array recursive walk test add

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
commit 88734ebaf7420a704a538f0a923ce6996b0b5237 ┃
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━
Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com>
Date:   Mon Jan 25 17:15:14 2021 +0900

    Add ajax uploader on error external function call

    Like post success uploaded, this function is called on any "non success"
    return falue.

    Passes on the full returned ajax data object

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
commit 2098fe53d510c03f291eaa5c4b2aefd60c24448e ┃
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━
Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com>
Date:   Wed Jan 13 06:29:20 2021 +0900

    Add additional parameters method parameter

    Added on init of form, when submitted before additional external form
    parameters function is called

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
commit 0c2211e2312abf368c4151dbdf54d4fa96a121dc ┃
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━
Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com>
Date:   Tue Jan 12 10:09:42 2021 +0900

    AJAX File uploader change for external function call.

    External functions are passed into the uploader as parameters.
    With this we can have unique functions for each init

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
commit c1eb15e30df319db1d5104bc5e4ab0cd8bb7cc8c ┃
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━
Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com>
Date:   Fri Jan 8 09:59:33 2021 +0900

    AJAX file uploader target action router value dynamic setting update, other test php files updates and additions

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
commit 429af6db892d6968ad87a3db662f3ff1a9d74270 ┃
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━
Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com>
Date:   Thu Nov 5 11:33:30 2020 +0900

    AJAX file uploader test code

    Simple single file AJAX file uploader with backend code sample.

    Work in progress with open
    - better, simpler frontend code insert part
    - multiple file upload
    - flag auto handling zip files (or tar, etc)
    - make it not rely on Jquery at all
2021-10-12 08:55:21 +09:00

337 lines
9.2 KiB
PHP

<?php
header("Content-Type: application/json; charset=UTF-8");
session_start();
define('DS', DIRECTORY_SEPARATOR);
define('DIR', __DIR__ . DS);
define('LOG', 'log' . DS);
// run id
DEFINE('RUNUID', session_id());
function logWrite($message)
{
global $fh;
if (!$fh) {
$fh = fopen(DIR . LOG . 'backend.' . date("Y-m-d") . '.log', 'a');
}
fwrite($fh, '{' . RUNUID . '} [' . printTime() . '] ' . $message . "\n");
}
function logHandleClose()
{
global $fh;
if ($fh) {
fclose($fh);
}
}
function printTime()
{
$set_microtime = 4;
list($microtime, $timestamp) = explode(' ', microtime());
$string = date("Y-m-d H:i:s", $timestamp);
// if microtime flag is -1 no round, if 0, no microtime, if >= 1, round that size
$string .= substr(number_format(round($microtime, $set_microtime), $set_microtime), 1);
return $string;
}
function errMsg(string $level, string $message): void
{
global $error_string;
$error_string[] = [
'level' => $level,
'str' => $message
];
}
/**
* [printAr description]
* @param array $array [description]
* @return string [description]
*/
function printAr(array $array): string
{
return "<pre>" . print_r($array, true) . "</pre>";
}
/**
* helper function for PHP file upload error messgaes to messge string
* @param int $error_code integer _FILE upload error code
* @return string message string, translated
*/
function fileUploadErrorMessage(int $error_code): string
{
switch ($error_code) {
case UPLOAD_ERR_INI_SIZE:
$message = 'The uploaded file exceeds the upload_max_filesize directive in php.ini';
break;
case UPLOAD_ERR_FORM_SIZE:
$message = 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form';
break;
case UPLOAD_ERR_PARTIAL:
$message = 'The uploaded file was only partially uploaded';
break;
case UPLOAD_ERR_NO_FILE:
$message = 'No file was uploaded';
break;
case UPLOAD_ERR_NO_TMP_DIR:
$message = 'Missing a temporary folder';
break;
case UPLOAD_ERR_CANT_WRITE:
$message = 'Failed to write file to disk';
break;
case UPLOAD_ERR_EXTENSION:
$message = 'File upload stopped by extension';
break;
default:
$message = 'Unknown upload error';
break;
}
return $message;
}
/**
* creates psuedo random uuid v4
* Code take from class here:
* https://www.php.net/manual/en/function.uniqid.php#94959
* @return string pseudo random uuid v4
*/
function uuidv4(): string
{
return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low"
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
// 16 bits for "time_mid"
mt_rand(0, 0xffff),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
mt_rand(0, 0x0fff) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
mt_rand(0, 0x3fff) | 0x8000,
// 48 bits for "node"
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff)
);
}
/**
* @param string|int|float $bytes bytes as string int or pure int
* @return string converted byte number (float) with suffix
*/
function humanReadableByteFormat($bytes): string
{
// if not numeric, return as is
if (is_numeric($bytes)) {
// space before name
$space = true;
// use sprintf instead of round
$adjust = true;
// use SI 1000 mod and not 1024 mod
$si = false;
// si or normal
$unit = $si ? 1000 : 1024;
// always positive
$abs_bytes = $bytes == PHP_INT_MIN ? PHP_INT_MAX : abs($bytes);
// smaller than unit is always B
if ($abs_bytes < $unit) {
return $bytes . 'B';
}
// labels in order of size [Y, Z]
$labels = array('', 'K', 'M', 'G', 'T', 'P', 'E');
// exp position calculation
$exp = floor(log($abs_bytes, $unit));
// avoid printing out anything larger than max labels
if ($exp >= count($labels)) {
$exp = count($labels) - 1;
}
// deviation calculation
$dev = pow($unit, $exp) * ($unit - 0.05);
// shift the exp +1 for on the border units
if (
$exp < 6 &&
$abs_bytes > ($dev - (((int)$dev & 0xfff) == 0xd00 ? 52 : 0))
) {
$exp++;
}
// label name, including leading space if flagged
$pre = ($space ? ' ' : '') . ($labels[$exp] ?? '>E') . ($si ? 'i' : '') . 'B';
$bytes_calc = $abs_bytes / pow($unit, $exp);
if ($adjust) {
return sprintf("%.2f%s", $bytes_calc, $pre);
} else {
return round($bytes_calc, 2) . $pre;
}
} else {
// if anything other return as string
return (string)$bytes;
}
}
$error = false;
$status = 'error';
$error_string = [];
$ajax_data = [];
$folder = DIR . 'uploaded' . DS;
logWrite(print_r($_POST, true));
// backend receiver calls
if (isset($_POST['action'])) {
// action switch statement
switch ($_POST['action']) {
case 'chainAction':
$ajax_data['chain'] = 'Just A chain action: ' . ($_POST['chain'] ?? '-');
$status = 'ok';
errMsg($status, 'Successful chained action');
break;
// list current files
case 'fileList':
$ajax_data['reference_id'] = uniqid();
if (is_dir($folder)) {
$file_array = [];
// sorted by date
$files = glob($folder . DIRECTORY_SEPARATOR . '*');
// oldest top
usort($files, function ($x, $y) {
return filemtime($x) > filemtime($y) ? 1 : -1;
});
foreach ($files as $file) {
$file_array[] = [
'name' => basename($file),
'create_date' => date("Y-m-d H:i:s", filemtime($file)),
'size' => humanReadableByteFormat((int)filesize($file))
];
}
$ajax_data['file_list'] = $file_array;
$status = 'ok';
if (!count($file_array)) {
$status = 'warn';
$ajax_data['info'] = 'No files uploaded yet';
}
} else {
$error = true;
errMsg($status, 'Uploaded folder not found');
}
break;
// for simple single file upload
case 'fileUpload':
// has a target name (file prefix flag)
$upload_name = $_POST['uploadName'];
// $this->debug('FILE UPLOAD', 'ALL FILES: '.$this->printAr($_FILES));
// errMsg('debug', 'Called for: ' . $upload_name . ' with data: '
// . (isset($_FILES[$upload_name]) ? printAr($_FILES[$upload_name]) : 'NOT FOUND'));
// return string & array init
$file_upload_message = [];
// do we have an upload actually
if (
isset($_FILES[$upload_name]['error']) &&
$_FILES[$upload_name]['error'] == UPLOAD_ERR_OK
) {
// set status to success post all checks
$status = 'success';
// strip out -file from upload name for search
$_upload_name = str_replace('-file', '', $upload_name);
$mime_type = null;
$ajax_data = [
'msg' => [],
'file_uid' => null,
'file_url' => null,
'file_name' => null,
'file_size' => null,
'file_size_raw' => null,
'file_type' => null,
];
// check mime type for file and do check if we have a "valid_files" settings
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$mime_type = $finfo->file($_FILES[$upload_name]['tmp_name']);
if (!in_array($mime_type, ['text/csv', 'text/plain', 'text/x-php'])) {
$file_upload_message[] = 'File type must be CSV or PHP: ' . $mime_type;
$ajax_data['msg'] = $file_upload_message;
$ajax_data['error'] = 'File type must be CSV or PHP: ' . $mime_type;
$status = 'error';
}
// ON ERROR set status = 'error';
// file type ok and file size ok, we commence actualy upload
if ($status == 'success') {
// basic file id data
$file_uid = null;
$file_url = null;
$file_name = null;
$file_size = null;
$file_size_raw = null;
$file_upload_message[] = 'File upload successful';
// internal file id
$file_uid = uuidv4();
// move file to tmp location and return new name
move_uploaded_file(
$_FILES[$upload_name]['tmp_name'],
$folder . $file_uid
);
$file_name = $_FILES[$upload_name]['name'];
$file_size = humanReadableByteFormat($_FILES[$upload_name]['size']);
$file_size_raw = $_FILES[$upload_name]['size'];
// correct the image rotation
// $this->correctImageOrientation(BASE.TMP.$file_uid);
// make a copy to layout/cache/images for file url
// as a thumbnail in defined size
// $file_url = $this->form_base_path
// . $this->createThumbnailSimple(BASE.TMP.$file_uid, THUMB_WIDTH, THUMB_HEIGHT);
// write back data for frontend processing
$ajax_data = [
'msg' => $file_upload_message,
'file_uid' => $file_uid,
'file_url' => $file_url,
'file_name' => $file_name,
'file_size' => $file_size,
'file_size_raw' => $file_size_raw,
'file_type' => $mime_type,
];
} else {
$info_msg = 'File uploaded and check failed';
errMsg($status, $info_msg);
$ajax_data['msg'][] = $info_msg;
}
} else {
$info_msg = isset($_FILES[$upload_name]) ?
'File upload filed: ' . fileUploadErrorMessage($_FILES[$upload_name]['error']) :
'General file upload error';
errMsg($status, $info_msg);
$ajax_data = [
'msg' => [
$info_msg
]
];
}
break;
default:
$error = true;
errMsg($status, 'Abnormal action');
break;
}
} else {
$error = true;
errMsg($status, 'No action set');
}
$data = [
'status' => $status,
'msg' => $error_string,
'action' => $_POST['action'] ?? null,
'content' => $ajax_data
];
// print the JSON data out to the browsers
$output = json_encode($data);
print $output;
// __END__