| 1 |
<?php |
| 2 |
defined('ABSPATH') || die(); |
| 3 |
|
| 4 |
class HashFormFieldUpload extends HashFormFieldType { |
| 5 |
|
| 6 |
protected $type = 'upload'; |
| 7 |
|
| 8 |
protected function field_settings_for_type() { |
| 9 |
return array( |
| 10 |
'default' => false, |
| 11 |
); |
| 12 |
} |
| 13 |
|
| 14 |
protected function extra_field_default_opts() { |
| 15 |
return array( |
| 16 |
'upload_label' => esc_html__('Upload File', 'hash-form'), |
| 17 |
'max_upload_size' => 10, |
| 18 |
'min_upload_size' => '', |
| 19 |
'extensions' => 'jpg,jpeg,gif,png', |
| 20 |
'extensions_error_message' => esc_html__('Invalid Extension', 'hash-form'), |
| 21 |
'multiple_uploads' => 'on', |
| 22 |
'multiple_uploads_limit' => 5, |
| 23 |
'multiple_uploads_error_message' => esc_html__('Maximum file upload limit exceeded', 'hash-form'), |
| 24 |
); |
| 25 |
} |
| 26 |
|
| 27 |
protected function input_html() { |
| 28 |
$field = $this->get_field(); |
| 29 |
$max_size = isset($field['max_upload_size']) ? absint($field['max_upload_size']) : ''; |
| 30 |
$max_size = $max_size ? $max_size : 10; |
| 31 |
$max_size = $max_size * 1024 * 1024; |
| 32 |
// Kilobytes in the builder, because the useful values here are small, |
| 33 |
// but bytes on the wire to match the maximum. |
| 34 |
$min_size = isset($field['min_upload_size']) ? absint($field['min_upload_size']) : 0; |
| 35 |
$min_size = $min_size * 1024; |
| 36 |
$new_extensions = isset($field['extensions']) ? hashform_sanitize_allowed_file_extensions($field['extensions']) : 'jpg,jpeg,gif,png'; |
| 37 |
|
| 38 |
if (is_admin() && !HashFormHelper::is_preview_page()) { |
| 39 |
// Static twin of the dropzone frontend.js builds, so the builder |
| 40 |
// shows what the visitor will see. The uploader script never runs |
| 41 |
// on this screen, so there is no list and no drop overlay, and the |
| 42 |
// id has to stay on the button itself because the label setting |
| 43 |
// live updates it by id. |
| 44 |
?> |
| 45 |
<div class="hf-file-uploader-wrapper"> |
| 46 |
<div class="hf-file-uploader"> |
| 47 |
<div class="qq-uploader"> |
| 48 |
<div class="hf-upload-dropzone"> |
| 49 |
<?php self::dropzone_icon(); ?> |
| 50 |
<span class="hf-upload-dropzone-title"> |
| 51 |
<?php |
| 52 |
echo isset($field['multiple_uploads']) && $field['multiple_uploads'] == 'on' |
| 53 |
? esc_html__('Drag and drop your files here', 'hash-form') |
| 54 |
: esc_html__('Drag and drop your file here', 'hash-form'); |
| 55 |
?> |
| 56 |
</span> |
| 57 |
<span class="hf-upload-dropzone-or"><?php esc_html_e('or', 'hash-form'); ?></span> |
| 58 |
<div id="hf-editor-upload-label-text-<?php echo absint($field['id']); ?>" class="qq-upload-button"><?php echo isset($field['upload_label']) && $field['upload_label'] ? esc_html($field['upload_label']) : esc_html__('Upload File', 'hash-form'); ?></div> |
| 59 |
<?php |
| 60 |
$hint = self::constraints_hint($field, $new_extensions, $max_size, $min_size); |
| 61 |
if ($hint) { |
| 62 |
?> |
| 63 |
<span class="hf-upload-dropzone-hint"><?php echo esc_html($hint); ?></span> |
| 64 |
<?php |
| 65 |
} |
| 66 |
?> |
| 67 |
</div> |
| 68 |
</div> |
| 69 |
</div> |
| 70 |
</div> |
| 71 |
<?php |
| 72 |
} else { |
| 73 |
?> |
| 74 |
<div class="hf-file-uploader-wrapper"> |
| 75 |
<div class="hf-file-uploader" id="hf-file-uploader-<?php echo esc_attr($this->html_id()); ?>" data-upload-label="<?php echo isset($field['upload_label']) && $field['upload_label'] ? esc_attr($field['upload_label']) : esc_html__('Upload File', 'hash-form'); ?>" data-extensions="<?php echo esc_attr($new_extensions); ?>" data-extensions-error-message="<?php echo isset($field['extensions_error_message']) ? esc_attr($field['extensions_error_message']) : ''; ?>" data-multiple-uploads="<?php echo isset($field['multiple_uploads']) && $field['multiple_uploads'] == 'on' ? 'true' : 'false'; ?>" data-multiple-uploads-limit="<?php echo isset($field['multiple_uploads']) && $field['multiple_uploads'] == 'on' ? absint($field['multiple_uploads_limit']) : '-1'; ?>" data-multiple-uploads-error-message="<?php echo isset($field['multiple_uploads_error_message']) ? esc_attr($field['multiple_uploads_error_message']) : ''; ?>" data-max-upload-size="<?php echo esc_attr($max_size); ?>" data-min-upload-size="<?php echo esc_attr($min_size); ?>" data-field-uploader-id="<?php echo esc_attr($this->html_id()); ?>"> |
| 76 |
<div class="qq-uploader qq-fake-uploader"> |
| 77 |
<div class="qq-upload-button" style="position: relative; overflow: hidden; direction: ltr;"> |
| 78 |
<?php echo isset($field['upload_label']) && $field['upload_label'] ? esc_attr($field['upload_label']) : esc_html__('Upload File', 'hash-form'); ?> |
| 79 |
</div> |
| 80 |
</div> |
| 81 |
</div> |
| 82 |
|
| 83 |
<div class="hf-file-preview"></div> |
| 84 |
|
| 85 |
<input type="hidden" class="hf-uploaded-files" <?php $this->field_attrs(); ?>> |
| 86 |
<input type="hidden" class="hf-multiple-upload-limit" value="0"> |
| 87 |
</div> |
| 88 |
<?php |
| 89 |
} |
| 90 |
} |
| 91 |
|
| 92 |
/** |
| 93 |
* The dropzone icon. |
| 94 |
* |
| 95 |
* Twin of UPLOAD_ICON in frontend.js. It has to exist in both because the |
| 96 |
* uploader script overwrites the element's markup with its own template on |
| 97 |
* the front end, while the builder renders this PHP instead. |
| 98 |
*/ |
| 99 |
private static function dropzone_icon() { |
| 100 |
?> |
| 101 |
<svg class="hf-upload-dropzone-icon" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"> |
| 102 |
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> |
| 103 |
<polyline points="17 8 12 3 7 8" /> |
| 104 |
<line x1="12" y1="3" x2="12" y2="15" /> |
| 105 |
</svg> |
| 106 |
<?php |
| 107 |
} |
| 108 |
|
| 109 |
/** |
| 110 |
* "JPG, PNG · up to 10 MB · 5 files max". |
| 111 |
* |
| 112 |
* Built from the same field options the uploader is configured with, so the |
| 113 |
* line cannot promise something the field does not actually allow. |
| 114 |
*/ |
| 115 |
private static function constraints_hint($field, $extensions, $max_size, $min_size = 0) { |
| 116 |
$parts = array(); |
| 117 |
|
| 118 |
$extensions = array_filter(array_map('trim', explode(',', (string) $extensions))); |
| 119 |
if ($extensions) { |
| 120 |
$parts[] = implode(', ', array_map('strtoupper', $extensions)); |
| 121 |
} |
| 122 |
|
| 123 |
if ($min_size > 0) { |
| 124 |
/* translators: 1: minimum file size, 2: maximum file size, both formatted, for example "5 KB" and "10 MB". */ |
| 125 |
$parts[] = sprintf(esc_html__('%1$s to %2$s', 'hash-form'), size_format($min_size), size_format($max_size)); |
| 126 |
} elseif ($max_size > 0) { |
| 127 |
/* translators: %s: maximum file size, already formatted, for example "10 MB". */ |
| 128 |
$parts[] = sprintf(esc_html__('up to %s', 'hash-form'), size_format($max_size)); |
| 129 |
} |
| 130 |
|
| 131 |
if (isset($field['multiple_uploads']) && $field['multiple_uploads'] == 'on') { |
| 132 |
$limit = isset($field['multiple_uploads_limit']) ? absint($field['multiple_uploads_limit']) : 0; |
| 133 |
if ($limit > 0) { |
| 134 |
/* translators: %d: maximum number of files. */ |
| 135 |
$parts[] = sprintf(esc_html__('%d files max', 'hash-form'), $limit); |
| 136 |
} |
| 137 |
} |
| 138 |
|
| 139 |
return implode(' · ', $parts); |
| 140 |
} |
| 141 |
|
| 142 |
public function set_value_before_save($files) { |
| 143 |
$new_files = array(); |
| 144 |
$files_arr = explode(',', $files); |
| 145 |
$field = $this->get_field(); |
| 146 |
HashFormBuilder::remove_old_temp_files(); |
| 147 |
|
| 148 |
do_action('hashform_file_before_upload_action', array( |
| 149 |
'files_arr' => $files_arr, |
| 150 |
'form_id' => isset($field->form_id) ? $field->form_id : '' |
| 151 |
)); |
| 152 |
|
| 153 |
if (apply_filters('hashform_store_local', true)) { |
| 154 |
/* |
| 155 |
* The extension is checked again here. What reaches this point is |
| 156 |
* the name the browser was told to post back, and the temp |
| 157 |
* directory is a staging area rather than a trusted one, so a file |
| 158 |
* that got in under a different set of rules does not become |
| 159 |
* permanent on the strength of having been uploaded once. |
| 160 |
* |
| 161 |
* Falls back to the shared list when the field carries no explicit |
| 162 |
* setting, so a field saved before that option existed is still |
| 163 |
* held to something rather than to nothing. |
| 164 |
*/ |
| 165 |
$field_extensions = hashform_sanitize_allowed_file_extensions((string) HashFormFields::get_option($field, 'extensions')); |
| 166 |
$allowed_extensions = array_filter(array_map('trim', explode(',', $field_extensions))); |
| 167 |
|
| 168 |
if (!$allowed_extensions) { |
| 169 |
$allowed_extensions = hashform_allowed_file_extensions(); |
| 170 |
} |
| 171 |
|
| 172 |
foreach ($files_arr as $file) { |
| 173 |
$file_info = pathinfo($file); |
| 174 |
|
| 175 |
// pathinfo() drops any directory part; sanitize_file_name() is |
| 176 |
// idempotent against the name handleUpload() already stored. |
| 177 |
$file_name = isset($file_info['basename']) ? sanitize_file_name(wp_basename($file_info['basename'])) : ''; |
| 178 |
$extension = isset($file_info['extension']) ? strtolower($file_info['extension']) : ''; |
| 179 |
|
| 180 |
if ('' === $file_name || '' === $extension) { |
| 181 |
continue; |
| 182 |
} |
| 183 |
|
| 184 |
if (!in_array($extension, $allowed_extensions, true)) { |
| 185 |
continue; |
| 186 |
} |
| 187 |
|
| 188 |
$upload_dir = wp_upload_dir(); |
| 189 |
|
| 190 |
$file_path = $upload_dir['basedir'] . HASHFORM_UPLOAD_DIR; |
| 191 |
$file_url = $upload_dir['baseurl'] . HASHFORM_UPLOAD_DIR; |
| 192 |
$temp_file_path = $file_path . '/temp/' . $file_name; |
| 193 |
$to_path = $file_path . '/' . $file_name; |
| 194 |
$to_url = $file_url . '/' . $file_name; |
| 195 |
|
| 196 |
if (!file_exists($temp_file_path)) { |
| 197 |
continue; |
| 198 |
} |
| 199 |
|
| 200 |
if (copy($temp_file_path, $to_path)) { |
| 201 |
$new_files[] = $to_url; |
| 202 |
} |
| 203 |
} |
| 204 |
} |
| 205 |
return implode(',', apply_filters('hashform_file_upload_filters', $new_files)); |
| 206 |
} |
| 207 |
|
| 208 |
} |
| 209 |
|