| @@ -1,43 +1,24 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | class AccuaForm_Element_File extends Element_File { |
| 3 | 3 | protected $destPath; |
| 4 | 4 | protected $alreadySubmittedText; |
| 5 | - // Modern default extensions: documents, images, archives | |
| 6 | - protected $validExtensions = array( | |
| 7 | - // Documents | |
| 8 | - 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'odt', 'ods', 'odp', 'txt', 'rtf', 'csv', | |
| 9 | - // Images | |
| 10 | - 'jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'heic', | |
| 11 | - // Archives | |
| 12 | - 'zip', 'rar', '7z', 'tar', 'gz' | |
| 13 | - ); | |
| 14 | - protected $maxSize; protected $limitsText; | |
| 5 | + protected $validExtensions = array('txt','doc','rtf','pdf','jpg','jpeg','png','zip','gz','bz','bz2'); | |
| 6 | + protected $maxSize; | |
| 7 | + protected $limitsText; | |
| 15 | 8 | protected $errorsText; |
| 16 | - public function __construct($label, $name, ?array $properties = null) { | |
| 17 | - $this->alreadySubmittedText = __( "File already submitted.", 'contact-forms' ); | |
| 18 | - // translators: %size% and %extensions% are custom placeholders replaced via str_replace(), not sprintf(). | |
| 19 | - $this->limitsText = __( 'Maximum filesize: %size%. Allowed extensions: %extensions%.', 'contact-forms' ); // phpcs:ignore WordPress.WP.I18n.UnorderedPlaceholdersText -- Custom placeholders used with str_replace(), not sprintf() | |
| 9 | + | |
| 10 | + public function __construct($label, $name, array $properties = null) { | |
| 11 | + $this->alreadySubmittedText = __("File already submitted.", 'contact-forms'); | |
| 12 | + $this->limitsText = __("Maximum filesize: %size%. Allowed extensions: %extensions%.", 'contact-forms'); | |
| 20 | 13 | $this->errorsText = array( |
| 21 | - 'upload' => __( 'Upload failed, please retry. If the problem persists please contact us', 'contact-forms' ), | |
| 22 | - 'size' => __( 'Uploaded file is too big', 'contact-forms' ), | |
| 23 | - 'empty' => __( 'Uploaded file is empty', 'contact-forms' ), | |
| 24 | - 'name' => __( 'File name is not valid. Please rename it avoiding unusual characters', 'contact-forms' ), | |
| 25 | - // translators: %extensions% is a custom placeholder replaced via str_replace(). | |
| 26 | - 'ext' => __( 'File extension not allowed. Allowed extensions are %extensions%', 'contact-forms' ), | |
| 14 | + 'upload' => __('Upload failed, please retry. If the problem persists please contact us', 'contact-forms'), | |
| 15 | + 'size' => __('Uploaded file is too big', 'contact-forms'), | |
| 16 | + 'empty' => __('Uploaded file is empty', 'contact-forms'), | |
| 17 | + 'name' => __('File name is not valid. Please rename it avoiding unusual characters', 'contact-forms'), | |
| 18 | + 'ext' => __('File extension not allowed. Allowed extensions are %extensions%', 'contact-forms'), | |
| 27 | 19 | ); |
| 28 | - | |
| 29 | - // Use WordPress upload directory function instead of hardcoded path | |
| 30 | - $wp_upload_dir = wp_upload_dir(); | |
| 31 | - $accua_upload_dir = $wp_upload_dir['basedir'] . '/accua-forms'; | |
| 32 | - | |
| 33 | - // Create directory if it doesn't exist using WordPress function | |
| 34 | - if (!file_exists($accua_upload_dir)) { | |
| 35 | - wp_mkdir_p($accua_upload_dir); | |
| 36 | - } | |
| 37 | - | |
| 38 | - // Use trailingslashit to ensure path ends with a slash | |
| 39 | - $this->destPath = trailingslashit($accua_upload_dir); | |
| 20 | + $this->destPath = realpath(ABSPATH . '/wp-content/uploads/accua-forms').'/'; | |
| 40 | 21 | $this->maxSize = self::file_upload_max_size(); |
| 41 | 22 | parent::__construct($label, $name, $properties); |
| 42 | 23 | } |
| 43 | 24 | |
| @@ -46,10 +27,9 @@ | ||
| 46 | 27 | } |
| 47 | 28 | |
| 48 | 29 | public function render() { |
| 49 | 30 | if(empty($this->attributes["value"])){ |
| 50 | - // Build help text for limits | |
| 51 | - $helpText = ''; | |
| 31 | + parent::render(); | |
| 52 | 32 | if ($this->limitsText !== ''){ |
| 53 | 33 | if ($this->validExtensions) { |
| 54 | 34 | $validExtensions = implode(', ', $this->validExtensions); |
| 55 | 35 | } else { |
| @@ -55,63 +35,13 @@ | ||
| 55 | 35 | } else { |
| 56 | 36 | $validExtensions = '*'; |
| 57 | 37 | } |
| 58 | 38 | $search = array('%size%', '%extensions%'); |
| 59 | - $replace = array(self::format_size($this->maxSize), $validExtensions); | |
| 60 | - $helpText = str_replace($search, $replace, $this->limitsText); | |
| 39 | + $replace = array(self::format_size($this->maxSize),$validExtensions); | |
| 40 | + echo '<br /><small>', str_replace($search, $replace, $this->limitsText), '</small>'; | |
| 61 | 41 | } |
| 62 | - | |
| 63 | - // Build accept attribute for file extensions | |
| 64 | - $acceptAttr = ''; | |
| 65 | - if ($this->validExtensions && is_array($this->validExtensions)) { | |
| 66 | - $acceptAttr = '.' . implode(',.', $this->validExtensions); | |
| 67 | - } | |
| 68 | - | |
| 69 | - // Generate unique IDs for ARIA associations | |
| 70 | - $inputId = esc_attr($this->getID() ?: 'accua-file-' . $this->getName()); | |
| 71 | - $helpId = $inputId . '-help'; | |
| 72 | - $announceId = $inputId . '-announce'; | |
| 73 | - | |
| 74 | - // Open wrapper with data attributes for JS | |
| 75 | - echo '<div class="accua-file-upload-wrapper" data-max-size="' . esc_attr($this->maxSize) . '" data-accept="' . esc_attr($acceptAttr) . '">'; | |
| 76 | - | |
| 77 | - // Dropzone container | |
| 78 | - echo '<div class="accua-file-dropzone" role="button" tabindex="0" '; | |
| 79 | - echo 'aria-describedby="' . esc_attr($helpId) . '" '; | |
| 80 | - echo 'aria-dropeffect="none">'; | |
| 81 | - | |
| 82 | - // Visual content for dropzone | |
| 83 | - echo '<span class="accua-file-dropzone-text">'; | |
| 84 | - echo '<span class="accua-file-dropzone-icon" aria-hidden="true">📁</span> '; | |
| 85 | - echo esc_html__('Drag & drop file here or', 'contact-forms') . ' '; | |
| 86 | - echo '<span class="accua-file-browse-btn">' . esc_html__('browse', 'contact-forms') . '</span>'; | |
| 87 | - echo '</span>'; | |
| 88 | - | |
| 89 | - // Set accept attribute on native file input for browser file picker filtering | |
| 90 | - if ($acceptAttr !== '') { | |
| 91 | - $this->attributes['accept'] = $acceptAttr; | |
| 92 | - } | |
| 93 | - | |
| 94 | - // Hidden native file input (for accessibility and form submission) | |
| 95 | - parent::render(); | |
| 96 | - | |
| 97 | - echo '</div>'; // .accua-file-dropzone | |
| 98 | - | |
| 99 | - // Help text | |
| 100 | - if ($helpText !== '') { | |
| 101 | - echo '<p class="pfbc-help" id="' . esc_attr($helpId) . '">' . esc_html($helpText) . '</p>'; | |
| 102 | - } | |
| 103 | - | |
| 104 | - // File list container (populated by JS) | |
| 105 | - echo '<div class="accua-file-list" hidden aria-live="polite" aria-relevant="additions removals"></div>'; | |
| 106 | - | |
| 107 | - // Screen reader announcement region | |
| 108 | - echo '<div class="accua-file-sr-announcement" id="' . esc_attr($announceId) . '" aria-live="polite" aria-atomic="true" class="screen-reader-text"></div>'; | |
| 109 | - | |
| 110 | - echo '</div>'; // .accua-file-upload-wrapper | |
| 111 | - | |
| 112 | 42 | } else { |
| 113 | - echo esc_html($this->alreadySubmittedText); | |
| 43 | + echo $this->alreadySubmittedText; | |
| 114 | 44 | } |
| 115 | 45 | } |
| 116 | 46 | |
| 117 | 47 | public function getAlreadySubmittedText() { |
| @@ -117,16 +47,8 @@ | ||
| 117 | 47 | public function getAlreadySubmittedText() { |
| 118 | 48 | return $this->alreadySubmittedText; |
| 119 | 49 | } |
| 120 | 50 | |
| 121 | - public function hasHelpText() { | |
| 122 | - return !empty($this->limitsText); | |
| 123 | - } | |
| 124 | - | |
| 125 | - public function appendToPostHTML($html) { | |
| 126 | - $this->postHTML = ($this->postHTML ?? '') . $html; | |
| 127 | - } | |
| 128 | - | |
| 129 | 51 | public static function format_size($size) { |
| 130 | 52 | if ($size >= 1073741824) { |
| 131 | 53 | return round($size/1073741824, 2).' GB'; |
| 132 | 54 | } else if ($size >= 1048576) { |
| @@ -174,34 +96,17 @@ | ||
| 174 | 96 | if(is_array($errors)) { |
| 175 | 97 | $this->errorsText = $errors + $this->errorsText; |
| 176 | 98 | } |
| 177 | 99 | } |
| 178 | - public function setDestPath($path) { | |
| 179 | - // Check if path is absolute BEFORE normalizing (path_is_absolute handles both Unix and Windows) | |
| 180 | - $is_absolute = path_is_absolute($path); | |
| 181 | - | |
| 182 | - // Normalize path separators for consistent handling | |
| 183 | - $path = wp_normalize_path($path); | |
| 184 | - | |
| 185 | - // Handle relative paths by prepending ABSPATH | |
| 186 | - if (!$is_absolute) { | |
| 187 | - $path = path_join(ABSPATH, $path); | |
| 100 | + | |
| 101 | + public function setDestPath($path) { | |
| 102 | + if (substr($path,0,1) !== '/') { | |
| 103 | + $path = ABSPATH . '/' . $path; | |
| 188 | 104 | } |
| 189 | - | |
| 190 | - // Create directory with proper WordPress function and secure permissions | |
| 191 | - if (!file_exists($path)) { | |
| 192 | - wp_mkdir_p($path); | |
| 105 | + if (!is_dir($path)){ | |
| 106 | + @ mkdir($path, 0777, true); | |
| 193 | 107 | } |
| 194 | - | |
| 195 | - // Verify the directory exists and is writable | |
| 196 | - if (!is_dir($path) || !wp_is_writable($path)) { | |
| 197 | - // Fall back to WordPress uploads directory if target path is not writable | |
| 198 | - $upload_dir = wp_upload_dir(); | |
| 199 | - $path = $upload_dir['basedir'] . '/accua-forms'; | |
| 200 | - wp_mkdir_p($path); | |
| 201 | - } | |
| 202 | - | |
| 203 | - return $this->destPath = trailingslashit($path); | |
| 108 | + return $this->destPath = realpath($path) . '/'; | |
| 204 | 109 | } |
| 205 | 110 | |
| 206 | 111 | public function handle_upload($filedata){ |
| 207 | 112 | $valid = true; |
| @@ -236,10 +141,9 @@ | ||
| 236 | 141 | $file['errors'][] = $this->errorsText['name']; |
| 237 | 142 | } else if ($this->validExtensions) { |
| 238 | 143 | $ext = strrchr($file['name'], '.'); |
| 239 | 144 | $ext = ($ext === false) ? '' : strtolower(ltrim($ext, '.')); |
| 240 | - $valid_ext_lower = array_map('strtolower', $this->validExtensions); | |
| 241 | - if (!in_array($ext, $valid_ext_lower, true)) { | |
| 145 | + if (!in_array($ext, $this->validExtensions, true)) { | |
| 242 | 146 | $valid = false; |
| 243 | 147 | $file['errors'][] = str_replace('%extensions%', implode(', ',$this->validExtensions), $this->errorsText['ext']); |
| 244 | 148 | } |
| 245 | 149 | } |
| @@ -249,36 +153,23 @@ | ||
| 249 | 153 | $valid = false; |
| 250 | 154 | $file['errors'][] = $this->errorsText['name']; |
| 251 | 155 | } |
| 252 | 156 | } |
| 157 | + | |
| 158 | + if ($valid) { | |
| 159 | + if (!is_dir($this->destPath)){ | |
| 160 | + @ mkdir($this->destPath, 0777, true); | |
| 161 | + } | |
| 162 | + | |
| 163 | + do { | |
| 164 | + $tmpname = 'tmp' . mt_rand() . '_' . $file['name']; | |
| 165 | + } while (is_file($this->destPath.$tmpname)); | |
| 166 | + | |
| 167 | + @ $valid = move_uploaded_file($filedata['tmp_name'], $this->destPath.$tmpname); | |
| 253 | 168 | if ($valid) { |
| 254 | - // Additional security check - validate MIME type | |
| 255 | - $file_info = wp_check_filetype_and_ext($filedata['tmp_name'], $file['name']); | |
| 256 | - if (empty($file_info['type'])) { | |
| 257 | - $valid = false; | |
| 258 | - $file['errors'][] = __('Invalid file type detected', 'contact-forms'); | |
| 169 | + $file['tmp_name'] = $tmpname; | |
| 259 | 170 | } else { |
| 260 | - // Make sure destination directory exists | |
| 261 | - if (!is_dir($this->destPath)) { | |
| 262 | - wp_mkdir_p($this->destPath); | |
| 263 | - } | |
| 264 | - | |
| 265 | - // Generate a unique filename with sanitization | |
| 266 | - do { | |
| 267 | - $tmpname = 'tmp' . wp_generate_password(8, false) . '_' . sanitize_file_name($file['name']); | |
| 268 | - } while (is_file($this->destPath.$tmpname)); | |
| 269 | - | |
| 270 | - // Move uploaded file | |
| 271 | - // phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- move_uploaded_file is required for file uploads, no WP alternative | |
| 272 | - $valid = move_uploaded_file($filedata['tmp_name'], $this->destPath.$tmpname); | |
| 273 | - if ($valid) { | |
| 274 | - $file['tmp_name'] = $tmpname; | |
| 275 | - // Set proper file permissions | |
| 276 | - // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- Direct chmod needed for upload permissions | |
| 277 | - chmod($this->destPath.$tmpname, 0644); | |
| 278 | - } else { | |
| 279 | - $file['errors'][] = $this->errorsText['upload']; | |
| 280 | - } | |
| 171 | + $file['errors'][] = $this->errorsText['upload']; | |
| 281 | 172 | } |
| 282 | 173 | |
| 283 | 174 | } |
| 284 | 175 | |