'error', 'message' => 'Authentication failed']); die(); } if ($_POST['action'] == 'icc_upload_chunk') { icc_upload_and_shorten_handle_chunk(); } if ($_POST['action'] == 'icc_upload_finish') { icc_upload_and_shorten_handle_finish(); } } } function icc_upload_and_shorten_handle_chunk() { $nonce = $_POST['nonce'] ?? ''; if (!yourls_verify_nonce('icc_upload_chunk', $nonce)) { echo json_encode(['status' => 'error', 'message' => 'Security check failed']); die(); } if (!isset($_FILES['file_chunk']) || $_FILES['file_chunk']['error'] != UPLOAD_ERR_OK) { echo json_encode(['status' => 'error', 'message' => 'Upload error']); die(); } $upload_id = preg_replace('/[^a-zA-Z0-9_]/', '', $_POST['upload_id']); $temp_dir = yourls_get_option('icc_upload_share_dir'); if (!$temp_dir) $temp_dir = sys_get_temp_dir(); // Create a temp directory for this upload $target_dir = rtrim($temp_dir, '/') . '/icc_temp_' . $upload_id; if (!is_dir($target_dir)) mkdir($target_dir, 0755, true); $chunk_index = intval($_POST['chunk_index']); $target_file = $target_dir . '/part_' . $chunk_index; if (move_uploaded_file($_FILES['file_chunk']['tmp_name'], $target_file)) { echo json_encode(['status' => 'success']); } else { echo json_encode(['status' => 'error', 'message' => 'Failed to move chunk']); } die(); } function icc_upload_and_shorten_handle_finish() { $nonce = $_POST['nonce'] ?? ''; if (!yourls_verify_nonce('icc_upload_chunk', $nonce)) { echo json_encode(['status' => 'error', 'message' => 'Security check failed']); die(); } $upload_id = preg_replace('/[^a-zA-Z0-9_]/', '', $_POST['upload_id']); $file_name = $_POST['file_name']; $temp_dir = yourls_get_option('icc_upload_share_dir'); if (!$temp_dir) $temp_dir = sys_get_temp_dir(); $target_dir = rtrim($temp_dir, '/') . '/icc_temp_' . $upload_id; $final_file_path = $target_dir . '/' . $file_name; // Assemble chunks if ($fp = fopen($final_file_path, 'wb')) { $chunks = glob($target_dir . '/part_*'); natsort($chunks); foreach ($chunks as $chunk) { $chunk_content = file_get_contents($chunk); fwrite($fp, $chunk_content); unlink($chunk); } fclose($fp); rmdir($target_dir); // Remove temp dir // now process the file // Pass essential POST data for filename conversion if needed $result = icc_upload_and_shorten_process_upload($final_file_path, $file_name); // Since the result is HTML string, we might want to return it or parse it // But for this AJAX response we return it in message echo json_encode(['status' => 'success', 'message' => $result]); } else { echo json_encode(['status' => 'error', 'message' => 'Failed to assemble file']); } die(); } // Display admin page function icc_upload_and_shorten_do_page() { // Check if a form was submitted if (isset($_POST['action']) && $_POST['action'] == 'icc_upload_and_shorten_save') { icc_upload_and_shorten_update_settings(); } // Handle Deletion if (isset($_POST['action']) && $_POST['action'] == 'delete_local_file' && isset($_POST['file_name'])) { $nonce = $_POST['nonce'] ?? ''; if (yourls_verify_nonce('icc_delete_local_file', $nonce)) { $share_dir = yourls_get_option('icc_upload_share_dir'); $file_name = $_POST['file_name']; // Validating filename to prevent directory traversal if (basename($file_name) == $file_name) { $file_path = rtrim($share_dir, '/') . '/' . $file_name; if (file_exists($file_path)) { if (unlink($file_path)) { echo "
File deleted successfully: " . htmlspecialchars($file_name) . "
"; } else { echo "
Failed to delete file. Check permissions.
"; } } else { echo "
File not found.
"; } } else { echo "
Invalid filename.
"; } } else { echo "
Security check failed.
"; } } if (isset($_POST['action']) && $_POST['action'] == 'delete_file' && isset($_POST['file_key'])) { $nonce = $_POST['nonce'] ?? ''; if (yourls_verify_nonce('icc_delete_file', $nonce)) { try { $s3_key = yourls_get_option('icc_upload_s3_key'); $s3_secret = yourls_get_option('icc_upload_s3_secret'); $s3_region = yourls_get_option('icc_upload_s3_region'); $s3_bucket = yourls_get_option('icc_upload_s3_bucket'); $s3 = icc_get_aws_client($s3_key, $s3_secret, $s3_region); if ($s3) { $s3->deleteObject([ 'Bucket' => $s3_bucket, 'Key' => $_POST['file_key'] ]); echo "
File deleted successfully: " . htmlspecialchars($_POST['file_key']) . "
"; } else { echo "
Failed to initialize S3 client for deletion.
"; } } catch (Aws\S3\Exception\S3Exception $e) { echo "
Failed to delete file: " . $e->getMessage() . "
"; } } else { echo "
Security check failed (Invalid Nonce).
"; } } // Manual Cleanup Handler if (isset($_POST['action']) && $_POST['action'] == 'icc_manual_cleanup') { $nonce = $_POST['nonce'] ?? ''; if (yourls_verify_nonce('icc_manual_cleanup', $nonce)) { echo '
'; echo 'Starting Manual Cleanup Diagnostics...
'; $temp_dir = yourls_get_option('icc_upload_share_dir'); if (!$temp_dir) $temp_dir = sys_get_temp_dir(); echo "Target Directory: " . htmlspecialchars($temp_dir) . "
"; if (!is_dir($temp_dir)) { echo "Directory does not exist!
"; } elseif (!is_writable($temp_dir)) { echo "Directory is not writable! Permissions issues likely.
"; } else { $files = scandir($temp_dir); $found = 0; foreach ($files as $file) { if ($file == '.' || $file == '..') continue; // Only look for icc_temp_ folders if (strpos($file, 'icc_temp_') !== 0) continue; $path = rtrim($temp_dir, '/') . '/' . $file; if (!is_dir($path)) continue; $found++; $age = time() - filemtime($path); echo "
Found: " . htmlspecialchars($file) . "
"; echo "Path: " . htmlspecialchars($path) . "
"; echo "Age: " . $age . " seconds (" . round($age/3600, 2) . " hours)
"; // Force delete if requested via manual action, or just standard check // For manual diagnostics, we'll try to delete anything > 24 hours just like the automated one if ($age > 86400) { echo "Status: Older than 24 hours. Attempting deletion...
"; icc_rrmdir($path); if (!file_exists($path)) { echo "Result: DELETED SUCCESS.
"; } else { echo "Result: DELETED FAILED. Check server log/permissions.
"; } } else { echo "Status: Kept (Not old enough).
"; } } if ($found == 0) { echo "No temporary 'icc_temp_' folders found.
"; } } echo 'Diagnostics Complete.
'; } else { echo "
Security check failed.
"; } } $message = ''; if (isset($_POST['submit']) && $_POST['submit'] == 'Upload') $message = icc_upload_and_shorten_process_upload(); $storage_type = yourls_get_option('icc_upload_storage_type', 'local'); $share_url = yourls_get_option('icc_upload_share_url'); $share_dir = yourls_get_option('icc_upload_share_dir'); $suffix_length = yourls_get_option('icc_upload_suffix_length', 4); // S3 Config $s3_key = yourls_get_option('icc_upload_s3_key'); $s3_secret = yourls_get_option('icc_upload_s3_secret'); $s3_region = yourls_get_option('icc_upload_s3_region'); $s3_bucket = yourls_get_option('icc_upload_s3_bucket'); $s3_disable_acl = yourls_get_option('icc_upload_s3_disable_acl', false); // input form echo '

Upload & Shorten

Send a file to ' . ($storage_type == 's3' ? 'AWS S3' : 'your webserver') . ' and create a short-URL for it.

'; // Limits Diagnostics $max_upload = ini_get('upload_max_filesize'); $max_post = ini_get('post_max_size'); echo "

Server Limits: Upload Max Filesize: $max_upload, Post Max Size: $max_post.
The Smart Uploader bypasses these limits by splitting files into chunks!

"; if (!empty($message)) { echo "

$message

"; } if ( ($storage_type == 'local' && (empty($share_url) || empty($share_dir))) || ($storage_type == 's3' && (empty($s3_key) || empty($s3_secret) || empty($s3_region) || empty($s3_bucket))) ) { echo '

Please configure the plugin below before using this plugin.

'; } $chunk_nonce = yourls_create_nonce('icc_upload_chunk'); echo '
Select a file

'; // YOURLS options echo '
YOURLS database options

'; // filename handling echo '
Filename conversions (optional)

(Recommended if the file should be accessed by web-browsers.)
Ex.: "my not safe&clean filename #1.txt" -> https://example.com/my_not_safe_clean_filename_1.txt

(Adds a random alphanumeric suffix to the filename.)
Ex.: "file.txt" -> https://example.com/file_a1b2.txt

(Browser-safe filenames with a slight protection against systematic crawling your web-directory.)
Ex.: "mypicture.jpg" -> https://example.com/9a3e97434689.jpg

'; // do it! echo '

'; // JS for Chunked Upload echo ' '; // File Manager if ($storage_type == 's3' && !empty($s3_key) && !empty($s3_secret) && !empty($s3_bucket)) { icc_upload_and_shorten_file_manager($s3_key, $s3_secret, $s3_region, $s3_bucket); } elseif ($storage_type == 'local' && !empty($share_dir)) { icc_upload_and_shorten_local_file_manager($share_dir, $share_url); } // Configuration Section $nonce = yourls_create_nonce('icc_upload_and_shorten_settings'); echo '

Configuration


Local Server Settings


Example: https://example.com/file/


Example: /home/username/htdocs/example.com/file/ (Directory must exist)

AWS S3 Settings





General Settings



Run Cleanup & Diagnostics Now (Checks for \'icc_temp_\' folders older than 24 hours and attempts to delete them)

'; // footer echo '

Ivan Carlos » Buy Me a Coffee

'; } function icc_upload_and_shorten_update_settings() { yourls_verify_nonce('icc_upload_and_shorten_settings', $_REQUEST['nonce']); if (isset($_POST['icc_upload_storage_type'])) yourls_update_option('icc_upload_storage_type', $_POST['icc_upload_storage_type']); if (isset($_POST['icc_upload_share_url'])) yourls_update_option('icc_upload_share_url', rtrim($_POST['icc_upload_share_url'], '/') . '/'); if (isset($_POST['icc_upload_share_dir'])) yourls_update_option('icc_upload_share_dir', rtrim($_POST['icc_upload_share_dir'], '/') . '/'); if (isset($_POST['icc_upload_s3_key'])) yourls_update_option('icc_upload_s3_key', trim($_POST['icc_upload_s3_key'])); if (isset($_POST['icc_upload_s3_secret'])) yourls_update_option('icc_upload_s3_secret', trim($_POST['icc_upload_s3_secret'])); if (isset($_POST['icc_upload_s3_region'])) yourls_update_option('icc_upload_s3_region', trim($_POST['icc_upload_s3_region'])); if (isset($_POST['icc_upload_s3_bucket'])) yourls_update_option('icc_upload_s3_bucket', trim($_POST['icc_upload_s3_bucket'])); if (isset($_POST['icc_upload_s3_disable_acl'])) { yourls_update_option('icc_upload_s3_disable_acl', true); } else { yourls_update_option('icc_upload_s3_disable_acl', false); } if (isset($_POST['icc_upload_suffix_length'])) yourls_update_option('icc_upload_suffix_length', intval($_POST['icc_upload_suffix_length'])); echo "
Settings saved
"; } // Local File Manager Function function icc_upload_and_shorten_local_file_manager($dir, $url) { echo '
'; echo '

Local File Manager

'; if (!is_dir($dir)) { echo '

Directory not found: ' . htmlspecialchars($dir) . '

'; return; } $raw_files = scandir($dir); $files = []; foreach ($raw_files as $f) { if ($f == '.' || $f == '..') continue; $full_path = rtrim($dir, '/') . '/' . $f; // Exclude directories (like the temp ones if they exist) if (!is_dir($full_path)) { $files[] = $f; } } // Sort by modification time (Newest first) usort($files, function ($a, $b) use ($dir) { return filemtime(rtrim($dir, '/') . '/' . $b) - filemtime(rtrim($dir, '/') . '/' . $a); }); // $files = array_values($files); // Already indexed 0..n by sorting // Pagination $per_page = 20; $total_files = count($files); $total_pages = ceil($total_files / $per_page); $current_page = isset($_GET['local_page']) ? max(1, intval($_GET['local_page'])) : 1; $offset = ($current_page - 1) * $per_page; $page_files = array_slice($files, $offset, $per_page); if (empty($page_files)) { echo '

No files found.

'; } else { $nonce = yourls_create_nonce('icc_delete_local_file'); echo ''; echo ''; echo ''; foreach ($page_files as $file) { $filepath = rtrim($dir, '/') . '/' . $file; $size = file_exists($filepath) ? round(filesize($filepath) / 1024, 2) . ' KB' : 'N/A'; $date = file_exists($filepath) ? date("Y-m-d H:i:s", filemtime($filepath)) : 'N/A'; $file_url = rtrim($url, '/') . '/' . $file; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo ''; echo '
File NameSizeLast ModifiedAction
' . htmlspecialchars($file) . '' . $size . '' . $date . ''; echo '
'; echo ''; echo ''; echo ''; echo ''; echo '
'; echo '
'; // Pagination Controls if ($total_pages > 1) { echo '
'; echo '' . $total_files . ' items'; $url = 'plugins.php?page=icc_upload_and_shorten'; if ($current_page > 1) { echo '« First '; echo '‹ Previous '; } echo ' Page ' . $current_page . ' of ' . $total_pages . ' '; if ($current_page < $total_pages) { echo ' Next › '; echo 'Last »'; } echo '
'; } } } // Recursive directory removal function icc_rrmdir($dir) { if (is_dir($dir)) { $objects = scandir($dir); foreach ($objects as $object) { if ($object != "." && $object != "..") { if (is_dir($dir . "/" . $object) && !is_link($dir . "/" . $object)) icc_rrmdir($dir . "/" . $object); else unlink($dir . "/" . $object); } } rmdir($dir); } } // Cleanup Temp Folders function icc_upload_and_shorten_cleanup_temp() { $temp_dir = yourls_get_option('icc_upload_share_dir'); if (!$temp_dir) $temp_dir = sys_get_temp_dir(); if (!is_dir($temp_dir)) return; // Scan for icc_temp_* directories $files = scandir($temp_dir); foreach ($files as $file) { if ($file == '.' || $file == '..') continue; $path = rtrim($temp_dir, '/') . '/' . $file; if (is_dir($path) && strpos($file, 'icc_temp_') === 0) { // Check age (1 hour = 3600 seconds) if (filemtime($path) < (time() - 86400)) { icc_rrmdir($path); } } } } // Check for AWS SDK function icc_get_aws_client($key, $secret, $region) { if (!file_exists(dirname(__FILE__) . '/aws.phar')) { return false; } require_once dirname(__FILE__) . '/aws.phar'; try { $s3 = new Aws\S3\S3Client([ 'version' => 'latest', 'region' => $region, 'credentials' => [ 'key' => $key, 'secret' => $secret, ], ]); return $s3; } catch (Exception $e) { return false; } } // S3 File Manager Function function icc_upload_and_shorten_file_manager($key, $secret, $region, $bucket) { echo '
'; echo '

S3 File Manager

'; $s3 = icc_get_aws_client($key, $secret, $region); if (!$s3) { echo '

Failed to initialize AWS Client.

'; return; } // Pagination $continuation_token = isset($_GET['s3_next_token']) ? $_GET['s3_next_token'] : null; try { $params = [ 'Bucket' => $bucket, 'MaxKeys' => 20 ]; if ($continuation_token) { $params['ContinuationToken'] = $continuation_token; } $objects = $s3->listObjectsV2($params); if (!isset($objects['Contents']) || empty($objects['Contents'])) { echo '

No files found in bucket.

'; if ($continuation_token) { echo '

Start Over

'; } } else { $nonce = yourls_create_nonce('icc_delete_file'); echo ''; echo ''; echo ''; foreach ($objects['Contents'] as $object) { // Construct the file URL (Path-style S3 URL format) $file_url = "https://s3.{$region}.amazonaws.com/{$bucket}/" . $object['Key']; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo ''; echo '
File NameSizeLast ModifiedAction
' . htmlspecialchars($object['Key']) . '' . round($object['Size'] / 1024, 2) . ' KB' . $object['LastModified'] . ''; echo '
'; echo ''; echo ''; echo ''; echo ''; echo '
'; echo '
'; echo '

Showing files from S3 bucket.

'; // Pagination History (to allow 'Previous') $history_raw = isset($_GET['s3_history']) ? $_GET['s3_history'] : ''; $history = $history_raw ? explode(',', $history_raw) : []; // Pagination Controls echo '
'; $url_base = 'plugins.php?page=icc_upload_and_shorten'; // First Page if ($continuation_token) { echo '« First '; } // Previous Page if (!empty($history)) { $prev_token = array_pop($history); $prev_history = implode(',', $history); $prev_url = $url_base; if ($prev_token && $prev_token !== '__TOP__') { $prev_url .= '&s3_next_token=' . urlencode($prev_token); } if ($prev_history) { $prev_url .= '&s3_history=' . urlencode($prev_history); } echo '‹ Previous '; } // Next Page if (isset($objects['NextContinuationToken'])) { $next_token = $objects['NextContinuationToken']; // Append current token to history $current_history = $history_raw; $token_to_add = $continuation_token ? $continuation_token : '__TOP__'; if ($current_history) { $current_history .= ',' . $token_to_add; } else { $current_history = $token_to_add; } echo 'Next ›'; } echo '
'; } } catch (Aws\S3\Exception\S3Exception $e) { echo '

Error listing files: ' . $e->getMessage() . '

'; } } // Update option in database function icc_upload_and_shorten_process_upload($local_file_path = null, $original_filename = null) { // If not coming from chunked upload, standard validations if (!$local_file_path) { // did the user select any file? if ($_FILES['file_upload']['error'] == UPLOAD_ERR_NO_FILE) { return 'You need to select a file to upload.'; } } // Increase limits for processing large files set_time_limit(0); $storage_type = yourls_get_option('icc_upload_storage_type', 'local'); // Check Config if ($storage_type == 'local') { $my_url = yourls_get_option('icc_upload_share_url'); $my_uploaddir = yourls_get_option('icc_upload_share_dir'); if (empty($my_url) || empty($my_uploaddir)) return 'Plugin not configured for local storage.'; // Check if directory exists and is writable if (!is_dir($my_uploaddir) || !is_writable($my_uploaddir)) { return 'Upload directory does not exist or is not writable: ' . $my_uploaddir; } } elseif ($storage_type == 's3') { $key = yourls_get_option('icc_upload_s3_key'); $secret = yourls_get_option('icc_upload_s3_secret'); $region = yourls_get_option('icc_upload_s3_region'); $bucket = yourls_get_option('icc_upload_s3_bucket'); $disable_acl = yourls_get_option('icc_upload_s3_disable_acl', false); if (empty($key) || empty($secret) || empty($region) || empty($bucket)) return 'Plugin not configured for S3 storage.'; $s3 = icc_get_aws_client($key, $secret, $region); if (!$s3) return 'AWS SDK not found or failed to initialize, please ensure aws.phar is in the plugin folder.'; } $file_name_to_use = $local_file_path ? $original_filename : $_FILES['file_upload']['name']; // Handle the filename's extension $my_upload_extension = pathinfo($file_name_to_use, PATHINFO_EXTENSION); // If there is any extension at all then append it with a leading dot $my_extension = ''; if (isset($my_upload_extension) && $my_upload_extension != NULL) { $my_extension = '.' . $my_upload_extension; } $my_upload_filename = pathinfo($file_name_to_use, PATHINFO_FILENAME); $my_filename = $my_upload_filename; // Default if (isset($_POST['convert_filename'])) { switch ($_POST['convert_filename']) { case 'browser-safe': { // make the filename web-safe: $my_filename_trim = trim($my_upload_filename); $my_filename_trim = strtolower($my_filename_trim); // Force lowercase $my_extension = strtolower($my_extension); $my_RemoveChars = array("([^()_\-\.,0-9a-zA-Z\[\]])"); // replace what's NOT in here! $my_filename = preg_replace($my_RemoveChars, "_", $my_filename_trim); $my_filename = preg_replace("(_{2,})", "_", $my_filename); $my_extension = preg_replace($my_RemoveChars, "_", $my_extension); $my_extension = preg_replace("(_{2,})", "_", $my_extension); } break; case 'safe_suffix': { // browser-safe + random suffix $my_filename_trim = trim($my_upload_filename); $my_filename_trim = strtolower($my_filename_trim); // Force lowercase $my_extension = strtolower($my_extension); $my_RemoveChars = array("([^()_\-\.,0-9a-zA-Z\[\]])"); $my_filename = preg_replace($my_RemoveChars, "_", $my_filename_trim); $my_filename = preg_replace("(_{2,})", "_", $my_filename); $my_extension = preg_replace($my_RemoveChars, "_", $my_extension); $my_extension = preg_replace("(_{2,})", "_", $my_extension); $suffix_length = yourls_get_option('icc_upload_suffix_length', 4); $suffix = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz0123456789'), 0, $suffix_length); $my_filename .= '_' . $suffix; } break; case 'randomized': { // make up a random name for the uploaded file $my_filename = substr(md5($my_upload_filename . strtotime("now")), 0, 12); } break; } } // avoid duplicate filenames if ($storage_type == 'local') { $my_count = 2; $my_path = $my_uploaddir . $my_filename . $my_extension; $my_final_file_name = $my_filename . $my_extension; while (file_exists($my_path)) { $my_path = $my_uploaddir . $my_filename . '.' . $my_count . $my_extension; $my_final_file_name = $my_filename . '.' . $my_count . $my_extension; $my_count++; } } else { // For S3, exact duplicate check is hard without API call, so we assume timestamp or suffix makes it unique enough // Or we can just overwrite as S3 versioning might be on, but user asked for simple upload // We will just use the name derived. $my_final_file_name = $my_filename . $my_extension; // If we are processing a chunked upload, source is the assembled file // If it's a standard upload, it's the temp file $my_path = $local_file_path ? $local_file_path : $_FILES['file_upload']['tmp_name']; } $my_upload_fullname = pathinfo($file_name_to_use, PATHINFO_BASENAME); // Upload Logic $upload_success = false; if ($storage_type == 'local') { // If local file path provided (Chunked), rename it to destination if ($local_file_path) { if (rename($local_file_path, $my_path)) { $upload_success = true; $final_url = $my_url . $my_final_file_name; } } else { if (move_uploaded_file($_FILES['file_upload']['tmp_name'], $my_path)) { $upload_success = true; $final_url = $my_url . $my_final_file_name; } } } elseif ($storage_type == 's3') { try { $args = [ 'Bucket' => $bucket, 'Key' => $my_final_file_name, 'SourceFile' => $my_path, ]; if (!$disable_acl) { $args['ACL'] = 'public-read'; } $result = $s3->putObject($args); // Cleanup temp file if it was a chunked upload if ($local_file_path && file_exists($local_file_path)) { unlink($local_file_path); } // Use S3 Object URL directly $final_url = $result['ObjectURL']; $upload_success = true; } catch (Aws\S3\Exception\S3Exception $e) { return 'S3 Upload failed: ' . $e->getMessage() . ''; } } if ($upload_success) { // On success: // obey custom shortname, if given: $my_custom_shortname = ''; if (isset($_POST['custom_shortname']) && $_POST['custom_shortname'] != NULL) { $my_custom_shortname = $_POST['custom_shortname']; } // change custom title, if given. Default is original filename, but if user provided one, use it: $my_custom_title = $_POST['convert_filename'] . ': ' . $my_upload_fullname; if (isset($_POST['custom_title']) && $_POST['custom_title'] != NULL) { $my_custom_title = $_POST['custom_title']; } // let YOURLS create the link: $my_short_url = yourls_add_new_link($final_url, $my_custom_shortname, $my_custom_title); return '"' . $my_upload_fullname . '" successfully sent to ' . ($storage_type == 's3' ? 'S3' : 'Server') . '. Links:
' . 'Direct: ' . $final_url . '
' . 'Short: ' . $my_short_url['shorturl'] . ''; } else { $error = isset($_FILES['file_upload']) ? $_FILES['file_upload']['error'] : 'Unknown error'; return 'Upload failed, sorry! The error was ' . $error . ''; } }