field_key( $field );
}
/**
* The file types allowed for this field.
*
* @param array $field The field settings.
*
* @return array
*/
public function allowed_types( $field ) {
$raw = isset( $field['file_types'] ) ? (string) $field['file_types'] : '';
if ( '' === trim( $raw ) ) {
return Ranq_Forms_Uploads::default_types();
}
$list = array();
foreach ( preg_split( '/[\s,]+/', strtolower( $raw ) ) as $ext ) {
$ext = ltrim( trim( $ext ), '.' );
$ext = preg_replace( '/[^a-z0-9]/', '', $ext );
// What is forbidden stays forbidden even when someone types it in by hand.
if ( '' !== $ext && ! in_array( $ext, Ranq_Forms_Uploads::forbidden(), true ) ) {
$list[] = $ext;
}
}
return $list ? array_values( array_unique( $list ) ) : Ranq_Forms_Uploads::default_types();
}
/**
* The largest size for this field, in bytes.
*
* Never more than what the server itself allows, whatever the settings say.
*
* @param array $field The field settings.
*
* @return int
*/
public function max_size( $field ) {
$server = Ranq_Forms_Uploads::server_limit();
$wanted = isset( $field['max_size'] ) ? (int) $field['max_size'] : 0;
if ( $wanted < 1 ) {
return $server;
}
return min( $server, $wanted * MB_IN_BYTES );
}
/**
* How many files at most.
*
* @param array $field The field settings.
*
* @return int
*/
public function max_files( $field ) {
$count = isset( $field['max_files'] ) ? (int) $field['max_files'] : 1;
return max( 1, min( 10, $count ) );
}
/**
* The settings.
*
* @return array
*/
public function get_settings() {
$settings = parent::get_settings();
$settings['basic'][] = array(
'key' => 'file_types',
'type' => 'text',
'label' => __( 'Allowed file types', '2ranq-forms' ),
'help' => sprintf(
/* translators: %s: default list of file types. */
__( 'Separate with commas. Leave empty for: %s', '2ranq-forms' ),
implode( ', ', Ranq_Forms_Uploads::default_types() )
),
);
$settings['basic'][] = array(
'key' => 'max_size',
'type' => 'number',
'label' => __( 'Largest file, in MB', '2ranq-forms' ),
'help' => sprintf(
/* translators: %s: server limit, for example 64 MB. */
__( 'This server allows up to %s no matter what you put here.', '2ranq-forms' ),
size_format( Ranq_Forms_Uploads::server_limit() )
),
);
$settings['basic'][] = array(
'key' => 'max_files',
'type' => 'number',
'label' => __( 'How many files', '2ranq-forms' ),
'help' => __( 'Between 1 and 10.', '2ranq-forms' ),
);
return $settings;
}
/**
* Prints the field.
*
* @param array $field The field settings.
* @param int $form_id The form id.
*
* @return void
*/
public function render( $field, $form_id ) {
$describedby = $this->describedby( $field, $form_id );
$id = $this->input_id( $field, $form_id );
$types = $this->allowed_types( $field );
$multiple = $this->max_files( $field ) > 1;
$accept = array();
foreach ( $types as $ext ) {
$accept[] = '.' . $ext;
}
?>
input_name( $field );
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- the nonce is checked before the call, in Ranq_Forms_Submission.
if ( ! isset( $_FILES[ $name ] ) ) {
return array();
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- every file goes through the full check in Ranq_Forms_Uploads::accept().
$raw = $_FILES[ $name ];
$files = self::normalize( $raw );
$allowed = $this->allowed_types( $field );
$max = $this->max_size( $field );
$limit = $this->max_files( $field );
$saved = array();
$errors = array();
$count = 0;
foreach ( $files as $file ) {
// An empty row shows up when a person picked no file. That is not an error.
if ( isset( $file['error'] ) && UPLOAD_ERR_NO_FILE === (int) $file['error'] ) {
continue;
}
++$count;
if ( $count > $limit ) {
$errors[] = sprintf(
/* translators: %d: how many files are allowed. */
_n( 'Only %d file can be attached.', 'Only %d files can be attached.', $limit, '2ranq-forms' ),
$limit
);
break;
}
$result = Ranq_Forms_Uploads::accept( $file, $allowed, $max );
if ( is_wp_error( $result ) ) {
$errors[] = $result->get_error_message();
continue;
}
$saved[] = $result;
}
// The error travels with the value, so `validate()` only reads it.
return array(
'files' => $saved,
'errors' => $errors,
);
}
/**
* Validation.
*
* @param mixed $value What `sanitize()` returned.
* @param array $field The field settings.
*
* @return string
*/
public function validate( $value, $field ) {
if ( ! empty( $value['errors'] ) ) {
return (string) $value['errors'][0];
}
if ( ! empty( $field['required'] ) && empty( $value['files'] ) ) {
return $this->required_message( $field );
}
return '';
}
/**
* How it is shown in the entry and in the email.
*
* The ORIGINAL name is shown, while the file on disk carries a random one.
*
* @param mixed $value The value.
* @param array $field The field settings.
*
* @return string
*/
public function format_value( $value, $field ) {
unset( $field );
if ( empty( $value['files'] ) ) {
return '';
}
$names = array();
foreach ( $value['files'] as $file ) {
$names[] = $file['name'] . ' (' . size_format( (int) $file['size'] ) . ')';
}
return implode( ', ', $names );
}
/**
* For one field with several files PHP builds an array of arrays. This turns
* that into a plain list of files, so each one can be handled on its own.
*
* @param array $raw One row out of `$_FILES`.
*
* @return array
*/
private static function normalize( $raw ) {
if ( ! isset( $raw['name'] ) ) {
return array();
}
if ( ! is_array( $raw['name'] ) ) {
return array( $raw );
}
$files = array();
foreach ( array_keys( $raw['name'] ) as $index ) {
$files[] = array(
'name' => isset( $raw['name'][ $index ] ) ? $raw['name'][ $index ] : '',
'type' => isset( $raw['type'][ $index ] ) ? $raw['type'][ $index ] : '',
'tmp_name' => isset( $raw['tmp_name'][ $index ] ) ? $raw['tmp_name'][ $index ] : '',
'error' => isset( $raw['error'][ $index ] ) ? $raw['error'][ $index ] : UPLOAD_ERR_NO_FILE,
'size' => isset( $raw['size'][ $index ] ) ? $raw['size'][ $index ] : 0,
);
}
return $files;
}
}