alreadySubmittedText = __( "File already submitted.", 'contact-forms' );
// translators: %size% and %extensions% are custom placeholders replaced via str_replace(), not sprintf().
$this->limitsText = __( 'Maximum filesize: %size%. Allowed extensions: %extensions%.', 'contact-forms' ); // phpcs:ignore WordPress.WP.I18n.UnorderedPlaceholdersText -- Custom placeholders used with str_replace(), not sprintf()
$this->errorsText = array(
'upload' => __( 'Upload failed, please retry. If the problem persists please contact us', 'contact-forms' ),
'size' => __( 'Uploaded file is too big', 'contact-forms' ),
'empty' => __( 'Uploaded file is empty', 'contact-forms' ),
'name' => __( 'File name is not valid. Please rename it avoiding unusual characters', 'contact-forms' ),
// translators: %extensions% is a custom placeholder replaced via str_replace().
'ext' => __( 'File extension not allowed. Allowed extensions are %extensions%', 'contact-forms' ),
);
// Use WordPress upload directory function instead of hardcoded path
$wp_upload_dir = wp_upload_dir();
$accua_upload_dir = $wp_upload_dir['basedir'] . '/accua-forms';
// Create directory if it doesn't exist using WordPress function
if (!file_exists($accua_upload_dir)) {
wp_mkdir_p($accua_upload_dir);
}
// Use trailingslashit to ensure path ends with a slash
$this->destPath = trailingslashit($accua_upload_dir);
$this->maxSize = self::file_upload_max_size();
parent::__construct($label, $name, $properties);
}
public function __sleep() {
return array("attributes", "label", "validation", 'alreadySubmittedText', 'validExtensions', 'maxSize', 'limitsText', 'errorsText', 'destPath');
}
public function render() {
if(empty($this->attributes["value"])){
// Build help text for limits
$helpText = '';
if ($this->limitsText !== ''){
if ($this->validExtensions) {
$validExtensions = implode(', ', $this->validExtensions);
} else {
$validExtensions = '*';
}
$search = array('%size%', '%extensions%');
$replace = array(self::format_size($this->maxSize), $validExtensions);
$helpText = str_replace($search, $replace, $this->limitsText);
}
// Build accept attribute for file extensions
$acceptAttr = '';
if ($this->validExtensions && is_array($this->validExtensions)) {
$acceptAttr = '.' . implode(',.', $this->validExtensions);
}
// Generate unique IDs for ARIA associations
$inputId = esc_attr($this->getID() ?: 'accua-file-' . $this->getName());
$helpId = $inputId . '-help';
$announceId = $inputId . '-announce';
// Open wrapper with data attributes for JS
echo '
';
// Dropzone container
echo '
';
// Visual content for dropzone
echo '';
echo '📁 ';
echo esc_html__('Drag & drop file here or', 'contact-forms') . ' ';
echo '' . esc_html__('browse', 'contact-forms') . '';
echo '';
// Set accept attribute on native file input for browser file picker filtering
if ($acceptAttr !== '') {
$this->attributes['accept'] = $acceptAttr;
}
// Hidden native file input (for accessibility and form submission)
parent::render();
echo '
'; // .accua-file-dropzone
// Help text
if ($helpText !== '') {
echo '
' . esc_html($helpText) . '
';
}
// File list container (populated by JS)
echo '
';
// Screen reader announcement region
echo '
';
echo '
'; // .accua-file-upload-wrapper
} else {
echo esc_html($this->alreadySubmittedText);
}
}
public function getAlreadySubmittedText() {
return $this->alreadySubmittedText;
}
public function hasHelpText() {
return !empty($this->limitsText);
}
public function appendToPostHTML($html) {
$this->postHTML = ($this->postHTML ?? '') . $html;
}
public static function format_size($size) {
if ($size >= 1073741824) {
return round($size/1073741824, 2).' GB';
} else if ($size >= 1048576) {
return round($size/1048576, 2).' MB';
} else if ($size >= 1024) {
return round($size/1024, 2).' KB';
} else if ($size > 0) {
return round($size).' B';
} else {
return '-';
}
}
public static function parse_size($value) {
$value = strtolower( trim( $value ) );
$bytes = (float) $value;
if ( false !== strpos( $value, 'g' ) ) {
$bytes *= 1073741824; // 1024 * 1024 * 1024
} elseif ( false !== strpos( $value, 'm' ) ) {
$bytes *= 1048576; // 1024 * 1024
} elseif ( false !== strpos( $value, 'k' ) ) {
$bytes *= 1024;
}
return min( $bytes, PHP_INT_MAX );
}
public static function file_upload_max_size() {
static $max_size = -1;
if ($max_size < 0) {
$max_size = wp_max_upload_size();
}
return $max_size;
}
public function setMaxSize($size) {
$phpMaxSize = self::file_upload_max_size();
$size = self::parse_size($size);
return $this->maxSize = ($phpMaxSize < $size) ? $phpMaxSize : $size;
}
public function setErrorsText($errors){
if(is_array($errors)) {
$this->errorsText = $errors + $this->errorsText;
}
}
public function setDestPath($path) {
// Check if path is absolute BEFORE normalizing (path_is_absolute handles both Unix and Windows)
$is_absolute = path_is_absolute($path);
// Normalize path separators for consistent handling
$path = wp_normalize_path($path);
// Handle relative paths by prepending ABSPATH
if (!$is_absolute) {
$path = path_join(ABSPATH, $path);
}
// Create directory with proper WordPress function and secure permissions
if (!file_exists($path)) {
wp_mkdir_p($path);
}
// Verify the directory exists and is writable
if (!is_dir($path) || !wp_is_writable($path)) {
// Fall back to WordPress uploads directory if target path is not writable
$upload_dir = wp_upload_dir();
$path = $upload_dir['basedir'] . '/accua-forms';
wp_mkdir_p($path);
}
return $this->destPath = trailingslashit($path);
}
public function handle_upload($filedata){
$valid = true;
$file = array(
'dest_path' => $this->destPath,
);
if (!empty($filedata['error'])){
$valid = false;
switch($filedata['error']){
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$file['errors'][] = $this->errorsText['size'];
break;
default:
$file['errors'][] = $this->errorsText['upload'];
}
} else if ($filedata['size'] <= 0) {
$valid = false;
$file['errors'][] = $this->errorsText['empty'];
} else if ($this->maxSize > 0 && $filedata['size'] > $this->maxSize) {
$valid = false;
$file['errors'][] = $this->errorsText['size'];
} else {
$file['size'] = $filedata['size'];
}
if (isset($filedata['name']) && $filedata['name'] !== ''){
$file['name'] = $filedata['name'];
if ((strpos($file['name'], "\0") !== false) || (strpbrk($file['name'], "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177\\/:*?\"<>|") !== false) ) {
$valid = false;
$file['errors'][] = $this->errorsText['name'];
} else if ($this->validExtensions) {
$ext = strrchr($file['name'], '.');
$ext = ($ext === false) ? '' : strtolower(ltrim($ext, '.'));
$valid_ext_lower = array_map('strtolower', $this->validExtensions);
if (!in_array($ext, $valid_ext_lower, true)) {
$valid = false;
$file['errors'][] = str_replace('%extensions%', implode(', ',$this->validExtensions), $this->errorsText['ext']);
}
}
} else {
$file['name'] = '';
if ($valid) {
$valid = false;
$file['errors'][] = $this->errorsText['name'];
}
}
if ($valid) {
// Additional security check - validate MIME type
$file_info = wp_check_filetype_and_ext($filedata['tmp_name'], $file['name']);
if (empty($file_info['type'])) {
$valid = false;
$file['errors'][] = __('Invalid file type detected', 'contact-forms');
} else {
// Make sure destination directory exists
if (!is_dir($this->destPath)) {
wp_mkdir_p($this->destPath);
}
// Generate a unique filename with sanitization
do {
$tmpname = 'tmp' . wp_generate_password(8, false) . '_' . sanitize_file_name($file['name']);
} while (is_file($this->destPath.$tmpname));
// Move uploaded file
// phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- move_uploaded_file is required for file uploads, no WP alternative
$valid = move_uploaded_file($filedata['tmp_name'], $this->destPath.$tmpname);
if ($valid) {
$file['tmp_name'] = $tmpname;
// Set proper file permissions
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- Direct chmod needed for upload permissions
chmod($this->destPath.$tmpname, 0644);
} else {
$file['errors'][] = $this->errorsText['upload'];
}
}
}
return $file;
}
}