Skip to content
← Back to Snippets
Code

Simple File Upload

Validates one uploaded file, builds a collision-resistant filename, and moves it into an approved upload directory.

Purpose

Validates one uploaded file, builds a collision-resistant filename, and moves it into an approved upload directory.

Snippet details

ContextFileLevelProductionCopy-and-paste statusMarked safe after review.

Categories

  • Forms and Validation

Code

<?php

/*
 * Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
 * Free for personal and internal use. Paid project use requires visible credit
 * to Jeffery L. Paris. Corporate use requires a paid license fee unless a
 * separate written license states otherwise.
 */

/**
 * Simple File Upload.
 *
 * Purpose:
 * Handles one uploaded file using explicit size, extension, upload-error, and
 * destination-directory checks before calling move_uploaded_file().
 *
 * @param array $file_info One file entry from $_FILES.
 * @param string $upload_directory Approved server-side upload directory.
 * @param array $allowed_extensions Lowercase extension allowlist.
 * @param int $max_bytes Maximum allowed file size in bytes.
 * @return array Upload result and saved filename.
 */
function ogSnippetSimpleFileUpload(array $file_info, string $upload_directory, array $allowed_extensions, int $max_bytes): array {
	$required_keys = array('name', 'tmp_name', 'error', 'size');

	foreach ($required_keys as $required_key) {
		if (isset($file_info[$required_key]) === false) {
			return array('success' => false, 'saved_name' => '', 'message' => 'Upload payload is incomplete.');
		}
	}

	if ((int) $file_info['error'] !== UPLOAD_ERR_OK) {
		return array('success' => false, 'saved_name' => '', 'message' => 'The upload did not complete successfully.');
	}

	if ((int) $file_info['size'] < 1 || (int) $file_info['size'] > $max_bytes) {
		return array('success' => false, 'saved_name' => '', 'message' => 'The uploaded file size is not allowed.');
	}

	$upload_directory = rtrim($upload_directory, DIRECTORY_SEPARATOR);

	if ($upload_directory === '' || is_dir($upload_directory) === false || is_writable($upload_directory) === false) {
		return array('success' => false, 'saved_name' => '', 'message' => 'The upload directory is not writable.');
	}

	$original_name = basename((string) $file_info['name']);
	$extension = strtolower(pathinfo($original_name, PATHINFO_EXTENSION));

	if ($extension === '' || in_array($extension, $allowed_extensions, true) === false) {
		return array('success' => false, 'saved_name' => '', 'message' => 'The uploaded file extension is not allowed.');
	}

	if (is_uploaded_file((string) $file_info['tmp_name']) === false) {
		return array('success' => false, 'saved_name' => '', 'message' => 'The upload temporary file is invalid.');
	}

	$random_token = bin2hex(random_bytes(12));
	$saved_name = 'upload_'.$random_token.'.'.$extension;
	$destination_path = $upload_directory.DIRECTORY_SEPARATOR.$saved_name;

	if (move_uploaded_file((string) $file_info['tmp_name'], $destination_path) === false) {
		return array('success' => false, 'saved_name' => '', 'message' => 'The uploaded file could not be moved.');
	}

	return array(
		'success' => true,
		'original_name' => $original_name,
		'saved_name' => $saved_name,
		'saved_path' => $destination_path,
		'message' => 'File uploaded successfully.'
	);
}

/*
$result = ogSnippetSimpleFileUpload($_FILES['mission_patch'], __DIR__.'/uploads', array('png', 'webp'), 1048576);
print_r($result);
*/