File "FrmProFileField.php"
Full path: /home/bud/public_html/swamp/wp-admin/wp-content/plugins/formidable-pro/classes/models/FrmProFileField.php
File size: 71.43 KB
MIME-type: text/x-php
Charset: utf-8
<?php
if ( ! defined( 'ABSPATH' ) ) {
die( 'You are not allowed to call this page directly.' );
}
class FrmProFileField {
/**
* @since x.x
*/
const WRITE_ONLY = 0200;
/**
* @since x.x
*/
const READ_ONLY = 0400;
/**
* @var array $all_file_upload_field_ids
*/
private static $all_file_upload_field_ids;
/**
* @var array $all_file_upload_item_metas
*/
private static $all_file_upload_item_metas;
/**
* @var bool $uploading_temporary_files
*/
private static $uploading_temporary_files = false;
/**
* @var stdClass $active_upload_field
*/
private static $active_upload_field;
/**
* @var array $new_temporary_file_ids
*/
private static $new_temporary_file_ids;
/**
* @param array $field (no array for field options)
* @param array $atts
*/
public static function setup_dropzone( $field, $atts ) {
global $frm_vars;
$is_multiple = FrmField::is_option_true( $field, 'multiple' );
if ( ! isset( $frm_vars['dropzone_loaded'] ) || ! is_array( $frm_vars['dropzone_loaded'] ) ) {
$frm_vars['dropzone_loaded'] = array();
}
$the_id = $atts['file_name'];
if ( ! isset( $frm_vars['dropzone_loaded'][ $the_id ] ) ) {
if ( $is_multiple ) {
$max = empty( $field['max'] ) ? 99 : absint( $field['max'] );
} else {
$max = 1;
}
$file_size = self::get_max_file_size( $field['size'] );
$form_id = isset( $field['parent_form_id'] ) ? $field['parent_form_id'] : $field['form_id'];
$frm_vars['dropzone_loaded'][ $the_id ] = array(
'maxFilesize' => round( $file_size, 2 ),
'maxFiles' => $max,
'htmlID' => $the_id,
'label' => $atts['html_id'],
'uploadMultiple' => $is_multiple,
'fieldID' => $field['id'],
'formID' => $field['form_id'],
'parentFormID' => $form_id,
'fieldName' => $atts['field_name'],
'mockFiles' => array(),
'defaultMessage' => __( 'Drop files here to upload', 'formidable-pro' ),
'fallbackMessage' => __( 'Your browser does not support drag and drop file uploads.', 'formidable-pro' ),
'fallbackText' => __( 'Please use the fallback form below to upload your files like in the olden days.', 'formidable-pro' ),
/* translators: %sMB: File size limit (Megabytes). */
'fileTooBig' => sprintf( __( 'That file is too big. It must be less than %sMB.', 'formidable-pro' ), '{{maxFilesize}}' ),
'invalidFileType' => self::get_invalid_file_type_message( $field['name'], $field['invalid'] ),
/* translators: %s: Status code */
'responseError' => sprintf( __( 'Server responded with %s code.', 'formidable-pro' ), '{{statusCode}}' ),
'cancel' => __( 'Cancel upload', 'formidable-pro' ),
'cancelConfirm' => __( 'Are you sure you want to cancel this upload?', 'formidable-pro' ),
'remove' => __( 'Remove file', 'formidable-pro' ),
'maxFilesExceeded' => self::get_max_file_limit_error( $max ),
'resizeHeight' => null,
'resizeWidth' => null,
'timeout' => self::get_timeout(),
);
if ( array_key_exists( 'honeypot', $frm_vars ) && array_key_exists( $form_id, $frm_vars['honeypot'] ) ) {
$frm_vars['dropzone_loaded'][ $the_id ]['checkHoneypot'] = 'strict' === $frm_vars['honeypot'][ $form_id ];
}
$file_types = self::get_allowed_mimes( $field );
if ( ! empty( $file_types ) ) {
// Expected formats: image/*,application/pdf,.psd
$frm_vars['dropzone_loaded'][ $the_id ]['acceptedFiles'] = implode( ',', array_unique( $file_types ) );
foreach ( $file_types as $file_type => $mime ) {
$file_type = explode( '|', $file_type );
$frm_vars['dropzone_loaded'][ $the_id ]['acceptedFiles'] .= ',.' . implode( ',.', $file_type );
}
}
if ( $field['resize'] && ! empty( $field['new_size'] ) ) {
$setting_name = 'resize' . ucfirst( $field['resize_dir'] );
$frm_vars['dropzone_loaded'][ $the_id ][ $setting_name ] = $field['new_size'];
}
if ( strpos( $the_id, '-i' ) ) {
// we are editing, so get the base settings added too
$id_parts = explode( '-i', $the_id );
$base_id = $id_parts[0] . '-0';
$base_settings = $frm_vars['dropzone_loaded'][ $the_id ];
if ( ! isset( $frm_vars['dropzone_loaded'][ $base_id ] ) && strpos( $base_settings['fieldName'], '[i' . $id_parts[1] . ']' ) ) {
$base_settings['htmlID'] = $base_id;
$base_settings['fieldName'] = str_replace( '[i' . $id_parts[1] . ']', '[0]', $base_settings['fieldName'] );
$frm_vars['dropzone_loaded'][ $base_id ] = $base_settings;
}
}
self::add_mock_files( $field['value'], $frm_vars['dropzone_loaded'][ $the_id ]['mockFiles'] );
}
}
/**
* @since 5.0.09
*
* @param int $max
* @return string
*/
private static function get_max_file_limit_error( $max ) {
/* translators: %d: File limit number */
return sprintf( __( 'You have uploaded more than %d file(s).', 'formidable-pro' ), $max );
}
/**
* Increase the default timeout from 30 based on server limits
*
* @since 3.01.02
*/
private static function get_timeout() {
$timeout = absint( ini_get( 'max_execution_time' ) );
if ( $timeout <= 1 ) {
// allow for -1 or 0 for unlimited
$timeout = 5000 * 1000;
} elseif ( $timeout > 30 ) {
$timeout = $timeout * 1000;
} else {
$timeout = 30000;
}
return $timeout;
}
/**
* @param array $media_ids
* @param array $mock_files
* @return void
*/
private static function add_mock_files( $media_ids, &$mock_files ) {
FrmProAppHelper::unserialize_or_decode( $media_ids );
if ( ! $media_ids ) {
return;
}
foreach ( (array) $media_ids as $media_id ) {
$file = self::get_mock_file( $media_id );
if ( $file ) {
$mock_files[] = $file;
}
}
}
/**
* @param int $media_id
* @return array
*/
public static function get_mock_file( $media_id ) {
$file_url = self::get_file_url( $media_id );
$path = get_attached_file( $media_id );
$file_type = wp_check_filetype( $path );
$file = array(
'name' => basename( $path ),
'url' => self::get_file_url( $media_id, 'thumbnail' ),
'id' => $media_id,
'file_url' => $file_url,
'accessible' => self::user_has_permission( $media_id ),
'ext' => $file_type['ext'],
'type' => $file_type['type'],
);
if ( file_exists( $path ) ) {
$file['size'] = filesize( $path );
}
return $file;
}
/**
* Get path to use for file that checks for permissions first before trying to show files the user cannot access.
*
* @since 5.4.1
*
* @param array $file Mock file data.
* @return string
*/
public static function get_safe_file_icon( $file ) {
if ( ! empty( $file['accessible'] ) && self::file_type_matches_image( $file['type'] ) ) {
return $file['url'];
}
// Use a placeholder for type instead.
$images_url = FrmProAppHelper::plugin_url() . '/images/';
if ( in_array( $file['ext'], array( 'pdf', 'doc', 'xls', 'docx', 'xlsx' ), true ) ) {
$ext = substr( $file['ext'], 0, 3 );
return $images_url . $ext . '.svg';
}
return $images_url . 'doc.svg';
}
/**
* Always hide the temp files from queries.
* Hide all unattached form uploads from those without permission.
*
* @param WP_Query $query
*/
public static function filter_media_library( $query ) {
if ( 'attachment' === $query->get( 'post_type' ) ) {
$meta_query = $query->get( 'meta_query' );
self::query_to_exclude_files( $meta_query );
$query->set( 'meta_query', $meta_query );
}
}
private static function query_to_exclude_files( &$meta_query ) {
if ( current_user_can( 'frm_edit_entries' ) ) {
$show = FrmAppHelper::get_param( 'frm-attachment-filter', '', 'get', 'absint' );
} else {
$show = false;
}
if ( ! is_array( $meta_query ) ) {
$meta_query = array();
} else {
$continue = self::nest_attachment_query( $meta_query );
if ( ! $continue ) {
return;
}
}
$meta_query[] = array(
'relation' => 'AND',
array(
'key' => '_frm_temporary',
'compare' => 'NOT EXISTS',
),
array(
'key' => '_frm_file',
'compare' => $show ? 'EXISTS' : 'NOT EXISTS',
),
);
}
/**
* If a query uses OR, adding to it will return unexpected results
* Move the OR query into a subquery
*
* @return boolean true to continue adding the extra query
*/
private static function nest_attachment_query( &$meta_query ) {
if ( ! isset( $meta_query['relation'] ) || 'or' !== strtolower( $meta_query['relation'] ) ) {
return true;
}
$temp_group = array();
foreach ( $meta_query as $k => $meta ) {
// if looking for a Formidable file, don't exclude it
if ( isset( $meta['value'] ) && strpos( $meta['value'], 'formidable' ) !== false ) {
return false;
}
$temp_group[] = $meta;
unset( $meta_query[ $k ] );
}
$meta_query[] = $temp_group;
return true;
}
public static function filter_api_attachments( $args ) {
add_action( 'pre_get_posts', 'FrmProFileField::filter_media_library', 99 );
return $args;
}
/**
* Validate a file upload field if file was not uploaded with Ajax
*
* @since 2.03.08
*
* @param array $errors
* @param stdClass $field
* @param array $values
* @param array $args
*
* @return array
*/
public static function no_js_validate( $errors, $field, $values, $args ) {
$field->temp_id = $args['id'];
$args['file_name'] = self::get_file_name( $field, $args );
if ( isset( $_FILES[ $args['file_name'] ] ) ) {
self::validate_file_upload( $errors, $field, $args, $values );
self::add_file_fields_to_global_variable( $field, $args );
}
return $errors;
}
/**
* @since 3.0
*
* @param object $field
* @param array $args
*/
private static function get_file_name( $field, $args ) {
$file_name = 'file' . $field->id;
if ( isset( $args['key_pointer'] ) && ( $args['key_pointer'] || $args['key_pointer'] === 0 ) ) {
$file_name .= '-' . $args['key_pointer'];
}
return $file_name;
}
/**
* Add file upload field information to global variable
*
* @since 2.03.08
*
* @param stdClass $field
* @param array $args
*/
private static function add_file_fields_to_global_variable( $field, $args ) {
global $frm_vars;
if ( ! isset( $frm_vars['file_fields'] ) ) {
$frm_vars['file_fields'] = array();
}
$frm_vars['file_fields'][ $field->temp_id ] = $args;
$frm_vars['file_fields'][ $field->temp_id ]['field_id'] = $field->id;
}
/**
* Upload files the uploaded files when no JS on page
*
* @since 2.03.08
*
* @param array $errors
*
* @return array
*/
public static function upload_files_no_js( $errors ) {
if ( ! empty( $errors ) ) {
return $errors;
}
global $frm_vars;
if ( isset( $frm_vars['file_fields'] ) ) {
foreach ( $frm_vars['file_fields'] as $unique_file_id => $file_args ) {
if ( isset( $_FILES[ $file_args['file_name'] ] ) ) {
$file_field = FrmField::getOne( $file_args['field_id'] );
$file_field->temp_id = $file_field->id;
self::maybe_upload_temp_file( $errors, $file_field, $file_args );
}
}
}
return $errors;
}
/**
* If blank errors are set, remove them if a file was uploaded in the field.
* It still needs some checks in case there are multiple file fields
*
* @since 3.0.03
*
* @param array $errors
* @param stdClass $field
* @param mixed $value unused in this function but always passed into the frm_validate_file_field_entry filter.
* @param array $args
* @return array
*/
public static function remove_error_message( $errors, $field, $value, $args ) {
if ( ! isset( $errors[ 'field' . $field->temp_id ] ) || $errors[ 'field' . $field->temp_id ] != FrmFieldsHelper::get_error_msg( $field, 'blank' ) ) {
return $errors;
}
$file_name = self::get_file_name( $field, $args );
if ( ! isset( $_FILES[ $file_name ] ) ) {
return $errors;
}
$file_uploads = $_FILES[ $file_name ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( self::file_was_selected( $file_uploads ) ) {
unset( $errors[ 'field' . $field->temp_id ] );
}
return $errors;
}
/**
* @param array $errors
* @param stdClass $field
* @param array $args
* @param array $values
* @return void
*/
public static function validate_file_upload( &$errors, $field, $args, $values = array() ) {
if ( ! isset( $_FILES[ $args['file_name'] ] ) ) {
return;
}
$file_uploads = $_FILES[ $args['file_name'] ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( self::file_was_selected( $file_uploads ) ) {
add_filter( 'frm_validate_file_field_entry', 'FrmProFileField::remove_error_message', 10, 4 );
self::validate_file_size( $errors, $field, $args );
self::validate_file_count( $errors, $field, $args, $values );
self::validate_file_type( $errors, $field, $args );
self::validate_file_spam( $errors, $field, $args );
$errors = apply_filters( 'frm_validate_file', $errors, $field, $args );
} elseif ( empty( $values ) ) {
$skip_required = FrmProEntryMeta::skip_required_validation( $field );
if ( $field->required && ! $skip_required ) {
$errors[ 'field' . $field->temp_id ] = FrmFieldsHelper::get_error_msg( $field, 'blank' );
}
}
}
/**
* @param array $file_uploads
* @return bool
*/
private static function file_was_selected( $file_uploads ) {
// if the field is a file upload, check for a file
if ( empty( $file_uploads['name'] ) ) {
return false;
}
$filled = true;
if ( is_array( $file_uploads['name'] ) ) {
$filled = false;
foreach ( $file_uploads['name'] as $n ) {
if ( ! empty( $n ) ) {
$filled = true;
break;
}
}
}
return $filled;
}
/**
* @since 2.02
*/
public static function validate_file_size( &$errors, $field, $args ) {
if ( ! isset( $_FILES[ $args['file_name'] ] ) ) {
return;
}
$mb_limit = FrmField::get_option( $field, 'size' );
$size_limit = self::get_max_file_size( $mb_limit );
$file_uploads = (array) $_FILES[ $args['file_name'] ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
foreach ( (array) $file_uploads['name'] as $k => $name ) {
// Check allowed file size
if ( ! empty( $file_uploads['error'] ) && in_array( 1, (array) $file_uploads['error'] ) ) {
/* translators: %sMB: File size limit (Megabytes). */
$errors[ 'field' . $field->temp_id ] = __( 'That file is too big. It must be less than %sMB.', 'formidable-pro' );
}
if ( empty( $name ) ) {
continue;
}
$this_file_size = is_array( $file_uploads['size'] ) ? $file_uploads['size'][ $k ] : $file_uploads['size'];
$this_file_size = $this_file_size / 1000000; // compare in MB
if ( $this_file_size > $size_limit ) {
/* translators: %sMB: File size limit (Megabytes). */
$errors[ 'field' . $field->temp_id ] = sprintf( __( 'That file is too big. It must be less than %sMB.', 'formidable-pro' ), $size_limit );
}
unset( $name );
}
}
/**
* @param int $mb_limit
* @return int
*/
public static function get_max_file_size( $mb_limit = 256 ) {
if ( ! $mb_limit || ! is_numeric( $mb_limit ) ) {
$mb_limit = 516;
}
$mb_limit = (float) $mb_limit;
$upload_max = wp_max_upload_size() / 1000000;
return round( min( $upload_max, $mb_limit ), 3 );
}
/**
* @since 2.02
*
* @param array $errors
* @param stdClass $field
* @param array $args
* @param array $values
* @return void
*/
private static function validate_file_count( &$errors, $field, $args, $values ) {
$multiple_files_allowed = FrmField::get_option( $field, 'multiple' );
$file_count_limit = (int) FrmField::get_option( $field, 'max' );
if ( ! $multiple_files_allowed || empty( $file_count_limit ) ) {
return;
}
$total_upload_count = self::get_new_and_old_file_count( $field, $args );
if ( $total_upload_count > $file_count_limit ) {
$errors[ 'field' . $field->temp_id ] = self::get_max_file_limit_error( $file_count_limit );
}
}
/**
* Count the number of new files uploaded
* along with any previously uploaded files
*
* @since 2.02
*
* @param stdClass $field
* @param array $args
* @return int
*/
private static function get_new_and_old_file_count( $field, $args ) {
if ( isset( $_FILES[ $args['file_name'] ] ) ) {
$file_uploads = (array) $_FILES[ $args['file_name'] ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$uploaded_count = count( array_filter( $file_uploads['tmp_name'] ) );
} else {
$uploaded_count = 0;
}
$previous_uploads = (array) self::get_file_posted_vals( $field->id, $args );
$previous_upload_count = count( array_filter( $previous_uploads ) );
$total_upload_count = $uploaded_count + $previous_upload_count;
return $total_upload_count;
}
/**
* @since 2.02
*/
public static function validate_file_type( &$errors, $field, $args ) {
if ( isset( $errors[ 'field' . $field->temp_id ] ) ) {
return;
}
if ( ! isset( $_FILES[ $args['file_name'] ] ) ) {
return;
}
$mimes = self::get_allowed_mimes( $field );
$file_uploads = $_FILES[ $args['file_name'] ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
foreach ( (array) $file_uploads['name'] as $name ) {
if ( empty( $name ) ) {
continue;
}
//check allowed mime types for this field
$file_type = wp_check_filetype( $name, $mimes );
unset($name);
if ( ! $file_type['ext'] ) {
break;
}
}
if ( isset( $file_type ) && ! $file_type['ext'] ) {
$errors[ 'field' . $field->temp_id ] = self::get_invalid_file_type_message( $field->name, $field->field_options['invalid'] );
}
}
private static function get_allowed_mimes( $field ) {
$mimes = FrmField::get_option( $field, 'ftypes' );
$restrict = FrmField::is_option_true( $field, 'restrict' ) && ! empty( $mimes );
if ( ! $restrict ) {
$mimes = null;
}
return $mimes;
}
/**
* @param string $field_name
* @param string $field_invalid_msg
* @return string
*/
private static function get_invalid_file_type_message( $field_name, $field_invalid_msg ) {
$default_invalid_messages = array( '' );
$default_invalid_messages[] = __( 'This field is invalid', 'formidable-pro' );
$default_invalid_messages[] = $field_name . ' ' . __( 'is invalid', 'formidable-pro' );
$is_default_message = in_array( $field_invalid_msg, $default_invalid_messages );
$invalid_type = __( 'Sorry, this file type is not permitted.', 'formidable-pro' );
$invalid_message = $is_default_message ? $invalid_type : $field_invalid_msg;
return $invalid_message;
}
/**
* @since 4.10.02
*
* @param array $errors
* @param object $field
* @param array $args
*/
private static function validate_file_spam( &$errors, $field, $args ) {
if ( isset( $errors[ 'field' . $field->temp_id ] ) || ! isset( $_FILES[ $args['file_name'] ] ) || ! isset( $_FILES[ $args['file_name'] ]['name'] ) ) {
return;
}
$file_names = array_map(
function( $file_name ) {
return sanitize_option( 'upload_path', $file_name );
},
(array) $_FILES[ $args['file_name'] ]['name'] // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
);
$file_is_spam = false;
$spam_keywords = apply_filters( 'frm_filename_spam_keywords', self::get_default_filename_spam_keywords() );
if ( $spam_keywords && is_array( $spam_keywords ) ) {
foreach ( $file_names as $file_name ) {
foreach ( $spam_keywords as $keyword ) {
if ( false !== strpos( $file_name, $keyword ) ) {
$file_is_spam = true;
break;
}
}
}
unset( $file_name );
}
$values = array(
'item_meta' => array( $field->id => $file_names ),
);
if ( FrmEntryValidate::blacklist_check( $values ) ) {
$file_is_spam = true;
}
if ( $file_is_spam ) {
$errors[ 'field' . $field->temp_id ] = __( 'File is spam', 'formidable-pro' );
}
}
/**
* @return array
*/
private static function get_default_filename_spam_keywords() {
return array( 'fortnite', 'vbucks', 'roblox', 'robux' );
}
/**
* Upload new files, delete removed files
*
* @since 2.0
* @param array|string $meta_value (the posted value)
* @param int $field_id
* @param int $entry_id
* @return array|string $meta_value
*/
public static function prepare_data_before_db( $meta_value, $field_id, $entry_id, $atts ) {
_deprecated_function( __FUNCTION__, '3.0', 'FrmFieldType::get_value_to_save' );
$atts['field_id'] = $field_id;
$atts['entry_id'] = $entry_id;
$field_obj = FrmFieldFactory::get_field_object( $atts['field'] );
return $field_obj->get_value_to_save( $meta_value, $atts );
}
/**
* Get media ID(s) to be saved to database and set global media ID values
*
* @since 2.0
* @param array|string $prev_value (posted value)
* @param object $field
* @param integer $entry_id
* @return array|string $meta_value
*/
public static function prepare_file_upload_meta( $prev_value, $field, $entry_id ) {
global $frm_vars;
if ( ! empty( $frm_vars['checking_duplicates'] ) ) {
// this function is called in the FrmEntry::is_duplicate check as well so it shouldn't always update the database when this function is called.
return $prev_value;
}
// remove temp tag on uploads
self::remove_meta_from_media( $prev_value, $field->form_id );
$last_saved_value = self::get_previous_file_ids( $field, $entry_id );
self::delete_removed_files( $last_saved_value, $prev_value, $field );
return $prev_value;
}
/**
* @param array $errors
* @param stdClass $field
* @param array $args
* @return void
*/
private static function maybe_upload_temp_file( &$errors, $field, $args ) {
if ( ! isset( $_FILES[ $args['file_name'] ] ) ) {
return;
}
$file_uploads = $_FILES[ $args['file_name'] ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
if ( self::file_was_selected( $file_uploads ) ) {
$response = array( 'errors' => array(), 'media_ids' => array() );
self::upload_temp_files( $args['file_name'], $response, $field );
if ( ! empty( $response['media_ids'] ) ) {
$previous_value = self::get_file_posted_vals( $field->id, $args );
$new_value = self::set_new_file_upload_meta_value( $field, $response['media_ids'], $previous_value );
self::set_file_posted_vals( $field->id, $new_value, $args );
}
if ( ! empty( $response['errors'] ) ) {
$errors[ 'field' . $field->temp_id ] = implode( ' ', $response['errors'] );
}
}
}
public static function ajax_upload() {
$response = array(
'errors' => array(),
'media_ids' => array(),
);
$field_id = FrmAppHelper::get_param( 'field_id', '', 'post', 'absint' );
if ( empty( $_FILES ) || ! $field_id ) {
return $response;
}
$field = FrmField::getOne( $field_id, true );
if ( ! self::should_allow_ajax_upload_for_field( $field ) ) {
return $response;
}
$field->temp_id = $field->id;
$is_spam = ! self::ajax_upload_includes_valid_antispam_token( $field, $response['errors'] );
if ( ! $is_spam ) {
foreach ( $_FILES as $file_name => $file ) {
$args = array( 'file_name' => $file_name );
self::validate_file_type( $response['errors'], $field, $args );
self::validate_file_size( $response['errors'], $field, $args );
self::validate_file_spam( $response['errors'], $field, $args );
$response['errors'] = apply_filters( 'frm_validate_file', $response['errors'], $field, $args );
if ( empty( $response['errors'] ) ) {
self::upload_temp_files( $file_name, $response, $field );
}
}
}
$response = apply_filters( 'frm_response_after_upload', $response, $field );
return $response;
}
/**
* @param object $field
* @return bool
*/
private static function should_allow_ajax_upload_for_field( $field ) {
if ( ! $field || 'file' !== $field->type ) {
return false;
}
$form_info = FrmDb::get_row( 'frm_forms', array( 'id' => $field->form_id ), 'status, logged_in' );
if ( ! $form_info || 'trash' === $form_info->status ) {
return false;
}
if ( $form_info->logged_in && ! is_user_logged_in() ) {
return false;
}
return true;
}
/**
* @since 4.11
*
* @param object $field
* @param array $errors passed by reference.
* @return bool True if a token is passed and it is valid, or if antispam is turned off or does not exist.
*/
private static function ajax_upload_includes_valid_antispam_token( $field, &$errors ) {
$valid = true;
if ( ! class_exists( 'FrmAntiSpam' ) ) {
return $valid;
}
$aspm = new FrmAntiSpam( $field->form_id );
$antispam_check = $aspm->validate();
$valid = ! is_string( $antispam_check );
if ( ! $valid ) {
$errors[ 'field' . $field->temp_id ] = esc_html__( 'File is spam', 'formidable' );
}
return $valid;
}
/**
* @param string $file_name
* @param array $response
* @param object $field
*/
private static function upload_temp_files( $file_name, &$response, $field ) {
self::$uploading_temporary_files = true;
self::$active_upload_field = $field;
self::$new_temporary_file_ids = array();
add_action( 'add_post_meta', 'FrmProFileField::add_frm_temporary_meta', 10, 3 );
$new_media_ids = self::upload_file( $file_name );
$new_media_ids = (array) $new_media_ids;
$errors = array_filter( $new_media_ids, 'is_wp_error' );
$new_media_ids = array_filter( $new_media_ids, 'is_numeric' );
if ( ! $new_media_ids ) {
if ( $errors ) {
$errors = array_map(
function( $error ) {
return $error->get_error_message();
},
$errors
);
$response['errors'] = array_merge( $response['errors'], $errors );
} else {
$response['errors'][] = __( 'File upload failed', 'formidable-pro' );
}
} else {
$missing_file_ids = array_diff( $new_media_ids, self::$new_temporary_file_ids );
if ( $missing_file_ids ) {
self::add_meta_to_media( $missing_file_ids, 'temporary', $field->id );
}
$response['media_ids'] = $response['media_ids'] + $new_media_ids;
self::sort_errors_from_ids( $response );
}
remove_action( 'add_post_meta', 'FrmProFileField::add_frm_temporary_meta', 10 );
self::$uploading_temporary_files = false;
self::$active_upload_field = null;
}
/**
* @param int $object_id
* @param string $meta_key
* @param string $meta_value
* @return void
*/
public static function add_frm_temporary_meta( $object_id, $meta_key, $meta_value ) {
if ( '_wp_attached_file' !== $meta_key || ! self::$uploading_temporary_files || empty( self::$active_upload_field ) ) {
return;
}
$field = self::$active_upload_field;
$upload_dir = self::get_upload_dir_for_form( $field->form_id );
$is_in_formidable_dir = 0 === strpos( $meta_value, $upload_dir );
if ( $is_in_formidable_dir ) {
self::$new_temporary_file_ids[] = $object_id;
self::add_meta_to_media( $object_id, 'temporary', $field->id );
}
}
/**
* Let WordPress process the uploads
*
* @param string|array $file_id Index (or array of Indices) of the $_FILES array that the file was sent.
* @param bool $sideload If True upload is handled with media_handle_sideload instead of media_handle_upload.
*/
public static function upload_file( $file_id, $sideload = false ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/image.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
$response = array( 'media_ids' => array(), 'errors' => array() );
add_filter( 'upload_dir', array( 'FrmProFileField', 'upload_dir' ) );
if ( ! $sideload && isset( $_FILES[ $file_id ] ) && isset( $_FILES[ $file_id ]['name'] ) && is_array( $_FILES[ $file_id ]['name'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
$file_keys = array_keys( $_FILES[ $file_id ]['name'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
foreach ( $file_keys as $k ) {
if ( empty( $_FILES[ $file_id ]['name'][ $k ] ) || ! isset( $_FILES[ $file_id ]['type'][ $k ] ) || ! isset( $_FILES[ $file_id ]['tmp_name'][ $k ] ) || ! isset( $_FILES[ $file_id ]['error'][ $k ] ) || ! isset( $_FILES[ $file_id ]['size'][ $k ] ) ) {
continue;
}
$f_id = $file_id . $k;
$_FILES[ $f_id ] = array(
'name' => self::maybe_truncate_long_file_name( sanitize_file_name( wp_unslash( $_FILES[ $file_id ]['name'][ $k ] ) ) ),
'type' => sanitize_mime_type( wp_unslash( $_FILES[ $file_id ]['type'][ $k ] ) ),
'tmp_name' => sanitize_option( 'upload_path', $_FILES[ $file_id ]['tmp_name'][ $k ] ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
'error' => absint( wp_unslash( $_FILES[ $file_id ]['error'][ $k ] ) ),
'size' => absint( wp_unslash( $_FILES[ $file_id ]['size'][ $k ] ) ),
);
unset( $k );
self::handle_upload( $f_id, $response );
}
} else {
if ( is_string( $file_id ) && isset( $_FILES[ $file_id ] ) && isset( $_FILES[ $file_id ]['name'] ) && is_string( $_FILES[ $file_id ]['name'] ) ) {
$_FILES[ $file_id ]['name'] = self::maybe_truncate_long_file_name( sanitize_file_name( wp_unslash( $_FILES[ $file_id ]['name'] ) ) );
}
self::handle_upload( $file_id, $response, $sideload );
}
remove_filter( 'upload_dir', array( 'FrmProFileField', 'upload_dir' ) );
self::prepare_upload_response( $response );
return $response;
}
/**
* @since 5.0.03
* @param string $name
* @return string
*/
private static function maybe_truncate_long_file_name( $name ) {
$max_filename_length = apply_filters( 'frm_max_filename_length', 100, compact( 'name' ) );
if ( strlen( $name ) < $max_filename_length ) {
return $name;
}
$split = explode( '.', $name );
$extension = array_pop( $split );
$name = implode( '.', $split );
$name = substr( $name, 0, $max_filename_length - strlen( $extension ) - 1 );
return $name . '.' . $extension;
}
/**
* @param string|array $file_id Index (or array of Indices) of the $_FILES array that the file was sent.
* @param array $response
* @param bool $sideload If True upload is handled with media_handle_sideload instead of media_handle_upload.
*/
private static function handle_upload( $file_id, &$response, $sideload = false ) {
add_filter( 'wp_insert_attachment_data', 'FrmProFileField::change_attachment_slug', 10, 2 );
$resize = false;
if ( 'frm_submit_dropzone' === FrmAppHelper::get_param( 'action' ) && ! empty( self::$active_upload_field->field_options['resize'] ) ) {
add_filter( 'wp_image_maybe_exif_rotate', 'FrmProFileField::disable_exif_rotation' );
add_filter( 'wp_image_editors', 'FrmProFileField::force_gd_editor' );
$resize = true;
}
$media_id = $sideload ? media_handle_sideload( $file_id, 0 ) : media_handle_upload( $file_id, 0 );
remove_filter( 'wp_insert_attachment_data', 'FrmProFileField::change_attachment_slug' );
if ( is_numeric( $media_id ) ) {
$response['media_ids'][] = $media_id;
$form_id = FrmAppHelper::get_param( 'form_id', '', 'post', 'absint' );
if ( $resize ) {
$file = get_attached_file( $media_id );
$editor = wp_get_image_editor( $file );
if ( ! is_wp_error( $editor ) ) {
$editor->save( $file );
}
remove_filter( 'wp_image_maybe_exif_rotate', 'FrmProFileField::disable_exif_rotation' );
remove_filter( 'wp_image_editors', 'FrmProFileField::force_gd_editor' );
}
self::maybe_set_chmod(
array(
'file_id' => $media_id,
'form_id' => $form_id,
'protected' => self::file_is_protected( $file_id, $form_id ),
)
);
self::add_meta_to_media( $media_id, 'file' );
} else {
$response['errors'][] = $media_id;
}
}
/**
* The wp_image_maybe_exif_rotate logic in WordPress has conflicts with the resize functionality in Dropzone.
* When uploading with dropzone, with resizae is on, disable the exif rotation. The file is saved again after it is inserted.
*
* @since 5.1
*
* @return false
*/
public static function disable_exif_rotation() {
return false;
}
/**
* On sites with ImageMagick installed the image still gets flipped, so force GD.
*
* @since 5.1
*
* @return array
*/
public static function force_gd_editor() {
return array( 'WP_Image_Editor_GD' );
}
/**
* Prevent attachments from using valuable top-level slug names
*
* @param array $data
* @param array $post
* @return array
*/
public static function change_attachment_slug( $data, $post ) {
$slug = 'frm-' . sanitize_title( $data['post_name'] );
$post_id = $post['ID'];
$post_status = $data['post_status'];
$post_type = $data['post_type'];
$post_parent = $data['post_parent'];
$data['post_name'] = wp_unique_post_slug( $slug, $post_id, $post_status, $post_type, $post_parent );
return $data;
}
private static function prepare_upload_response( &$response ) {
if ( empty( $response['media_ids'] ) ) {
$response = $response['errors'];
} else {
$response = $response['media_ids'];
if ( count( $response ) == 1 ) {
$response = reset( $response );
}
}
}
/**
* Get the final media IDs
*
* @since 2.0
* @param array $response
* @return array media ids.
*/
private static function sort_errors_from_ids( &$response ) {
$mids = array();
foreach ( (array) $response['media_ids'] as $media_id ) {
if ( is_numeric( $media_id ) ) {
$mids[] = $media_id;
} else {
foreach ( $media_id->errors as $error ) {
if ( ! is_array( $error[0] ) ) {
$response['errors'][] = $error[0];
}
unset( $error );
}
}
unset( $media_id );
}
$response['media_ids'] = array_filter( $mids );
}
/**
* Set _frm_temporary and _frm_file metas
* to use for media library filtering
*
* @param array $media_ids
* @param string $type
* @param mixed $value
*/
private static function add_meta_to_media( $media_ids, $type = 'temporary', $value = 1 ) {
foreach ( (array) $media_ids as $media_id ) {
if ( is_numeric( $media_id ) ) {
update_post_meta( $media_id, '_frm_' . $type, $value );
}
}
}
/**
* When an entry is saved, remove the temp flag
*
* @param int|array $media_ids
* @param int $form_id
* @return void
*/
private static function remove_meta_from_media( $media_ids, $form_id ) {
$form_is_protected = self::folder_is_protected( $form_id );
$unprotected_file_ids = array();
foreach ( (array) $media_ids as $media_id ) {
if ( ! is_numeric( $media_id ) ) {
continue;
}
if ( ! $form_is_protected ) {
$unprotected_file_ids[] = $media_id;
}
delete_post_meta( $media_id, '_frm_temporary' );
}
if ( $unprotected_file_ids ) {
self::maybe_set_chmod(
array(
'dir' => self::upload_dir_path( $form_id ),
'form_id' => $form_id,
'file_ids' => $unprotected_file_ids,
'protected' => false,
)
);
}
}
/**
* Upload files into "formidable" subdirectory
*/
public static function upload_dir( $uploads ) {
$form_id = FrmAppHelper::get_post_param( 'form_id', 0, 'absint' );
if ( ! $form_id ) {
$form_id = FrmAppHelper::simple_get( 'form', 'absint', 0 );
}
$relative_path = self::get_upload_dir_for_form( $form_id );
if ( ! empty( $relative_path ) ) {
$uploads['path'] = $uploads['basedir'] . '/' . $relative_path;
$uploads['url'] = $uploads['baseurl'] . '/' . $relative_path;
$uploads['subdir'] = '/' . $relative_path;
self::create_index( $uploads, $relative_path );
}
return $uploads;
}
/**
* Create an index.php in the folders where files are being uploaded.
*
* @since 3.06.01
* @param array $uploads Info about file locations.
* @param string $path The folder where files will be saved.
*/
private static function create_index( $uploads, $path ) {
if ( file_exists( $uploads['path'] . $uploads['subdir'] . '/index.php' ) ) {
return;
}
remove_filter( 'upload_dir', array( 'FrmProFileField', 'upload_dir' ) );
$file_atts = array(
'file_name' => 'index.php',
'folder_name' => $path,
);
$file_content = '<?php' . "\r\n";
$new_file = new FrmCreateFile( $file_atts );
$new_file->create_file( $file_content );
add_filter( 'upload_dir', array( 'FrmProFileField', 'upload_dir' ) );
}
public static function get_upload_dir_for_form( $form_id ) {
$base = 'formidable';
if ( $form_id ) {
$base .= '/' . $form_id;
}
$relative_path = apply_filters( 'frm_upload_folder', $base, compact( 'form_id' ) );
$relative_path = untrailingslashit( $relative_path );
return $relative_path;
}
/**
* Automatically delete files when an entry is deleted.
* If the "Delete all entries" button is used, entries will not be deleted
*
* @since 2.0.22
*/
public static function delete_files_with_entry( $entry_id, $entry = false ) {
if ( empty( $entry ) ) {
return;
}
$upload_fields = FrmField::getAll( array( 'fi.type' => 'file', 'fi.form_id' => $entry->form_id ) );
foreach ( $upload_fields as $field ) {
self::delete_files_from_field( $field, $entry );
unset( $field );
}
}
/**
* @since 2.0.22
*/
public static function delete_files_from_field( $field, $entry ) {
if ( self::should_delete_files( $field ) ) {
$media_ids = self::get_previous_file_ids( $field, $entry );
self::delete_files_now( $media_ids );
}
}
private static function should_delete_files( $field ) {
$auto_delete = FrmField::get_option_in_object( $field, 'delete' );
return ! empty( $auto_delete );
}
/**
* @since 2.0.22
*/
private static function get_previous_file_ids( $field, $entry_id ) {
return FrmProEntryMetaHelper::get_post_or_meta_value( $entry_id, $field );
}
private static function delete_removed_files( $old_value, $new_value, $field ) {
if ( self::should_delete_files( $field ) ) {
$media_ids = self::get_removed_file_ids( $old_value, $new_value );
self::delete_files_now( $media_ids );
}
}
/**
* @since 2.0.22
*/
private static function get_removed_file_ids( $old_value, $new_value ) {
$media_ids = array_diff( (array) $old_value, (array) $new_value );
return $media_ids;
}
/**
* @since 2.0.22
*/
private static function delete_files_now( $media_ids ) {
if ( empty( $media_ids ) ) {
return;
}
FrmProAppHelper::unserialize_or_decode( $media_ids );
foreach ( (array) $media_ids as $m ) {
if ( is_numeric( $m ) ) {
wp_delete_attachment( $m, true );
}
}
}
/**
* @since 2.02
*
* @param int $field_id
* @param array $args
* @return array|int
*/
private static function get_file_posted_vals( $field_id, $args ) {
if ( self::is_field_repeating( $field_id, $args ) ) {
if ( isset( $_POST['item_meta'][ $args['parent_field_id'] ][ $args['key_pointer'] ][ $field_id ] ) ) {
if ( is_array( $_POST['item_meta'][ $args['parent_field_id'] ][ $args['key_pointer'] ][ $field_id ] ) ) {
$value = array_map( 'absint', wp_unslash( $_POST['item_meta'][ $args['parent_field_id'] ][ $args['key_pointer'] ][ $field_id ] ) );
} else {
$value = absint( wp_unslash( $_POST['item_meta'][ $args['parent_field_id'] ][ $args['key_pointer'] ][ $field_id ] ) );
}
}
} elseif ( isset( $_POST['item_meta'][ $field_id ] ) ) {
if ( is_array( $_POST['item_meta'][ $field_id ] ) ) {
$value = array_map( 'absint', wp_unslash( $_POST['item_meta'][ $field_id ] ) );
} else {
$value = absint( wp_unslash( $_POST['item_meta'][ $field_id ] ) );
}
}
if ( ! isset( $value ) ) {
$value = array();
}
return $value;
}
/**
*
* @since 2.0
* @param int $field_id
* @param $new_value to set
* @param array $args array with repeating, key_pointer, and parent_field
*/
private static function set_file_posted_vals( $field_id, $new_value, $args ) {
if ( self::is_field_repeating( $field_id, $args ) ) {
$_POST['item_meta'][ $args['parent_field_id'] ][ $args['key_pointer'] ][ $field_id ] = $new_value;
} else {
$_POST['item_meta'][ $field_id ] = $new_value;
}
}
/**
* Get the final value for a file upload field
*
* @since 2.0.19
*
* @param object $field
* @param array $new_mids
* @param array|string $prev_value
* @return array|string $new_value
*/
private static function set_new_file_upload_meta_value( $field, $new_mids, $prev_value ) {
// If no media IDs to upload, end now
if ( empty( $new_mids ) ) {
$new_value = $prev_value;
} elseif ( FrmField::is_option_true( $field, 'multiple' ) ) {
// Multi-file upload fields
if ( $prev_value ) {
$new_value = array_merge( (array) $prev_value, $new_mids );
} else {
$new_value = $new_mids;
}
} else {
// Single file upload fields
$new_value = reset( $new_mids );
}
return $new_value;
}
/**
* @param int $field_id
* @param array $args
* @return bool
*/
private static function is_field_repeating( $field_id, $args ) {
// Assume this field is not repeating
$repeating = false;
if ( ! empty( $args['parent_field_id'] ) && isset( $args['key_pointer'] ) ) {
// Check if the current field is inside of the parent/pointer
$repeating = isset( $_POST['item_meta'][ $args['parent_field_id'] ][ $args['key_pointer'] ][ $field_id ] );
}
return $repeating;
}
/**
* @since 3.01.03
*/
public static function duplicate_files_with_entry( $entry_id, $form_id, $args ) {
$old_entry_id = ! empty( $args['old_id'] ) ? $args['old_id'] : 0;
$upload_fields = FrmField::getAll( array( 'fi.type' => 'file', 'fi.form_id' => $form_id ) );
if ( ! $old_entry_id || ! $upload_fields ) {
return;
}
include_once ABSPATH . 'wp-admin/includes/file.php';
$form_is_protected = self::folder_is_protected( $form_id );
foreach ( $upload_fields as $field ) {
$attachments = self::get_previous_file_ids( $field, $old_entry_id );
FrmProAppHelper::unserialize_or_decode( $attachments );
if ( empty( $attachments ) ) {
continue;
}
$new_media_ids = array();
foreach ( (array) $attachments as $attachment_id ) {
$orig_path = get_attached_file( $attachment_id );
if ( ! file_exists( $orig_path ) ) {
continue;
}
// Copy path to a temp location because wp_handle_sideload() deletes the original.
$tmp_path = wp_tempnam();
if ( ! $tmp_path ) {
continue;
}
if ( $form_is_protected ) {
// Temporarily allow access to file so it can be copied.
self::set_to_read_only( $orig_path );
}
$read_file = new FrmCreateFile(
array(
'new_file_path' => dirname( $orig_path ),
'file_name' => basename( $orig_path ),
)
);
$file_contents = $read_file->get_file_contents();
if ( ! $file_contents || false === file_put_contents( $tmp_path, $file_contents ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_file_put_contents,
@unlink( $tmp_path );
continue;
}
if ( $form_is_protected ) {
// Protect the file that was temporarily been readable.
self::set_to_write_only( $orig_path );
}
$file_arr = array(
'name' => basename( $orig_path ),
'size' => @filesize( $tmp_path ),
'tmp_name' => $tmp_path,
'error' => 0,
);
$response = self::upload_file( $file_arr, true );
foreach ( (array) $response as $r ) {
if ( is_numeric( $r ) ) {
$new_media_ids[] = $r;
}
}
}
if ( 1 === count( $new_media_ids ) ) {
$new_meta = reset( $new_media_ids );
} else {
$new_meta = $new_media_ids;
}
FrmEntryMeta::update_entry_meta( $entry_id, $field->id, null, $new_meta );
}
}
/**
* @since x.x
*
* @param string $path
*/
private static function set_to_read_only( $path ) {
self::chmod( $path, self::READ_ONLY );
}
/**
* @since x.x
*
* @param string $path
*/
private static function set_to_write_only( $path ) {
self::chmod( $path, self::WRITE_ONLY );
}
/**
* Check if a file is currently protected (true for protected forms and also temporary files).
*
* @since 5.0.09
*
* @param int $file_id
* @param int $form_id
* @return bool
*/
public static function file_is_protected( $file_id, $form_id ) {
if ( ! self::file_is_temporary( $file_id ) ) {
return self::folder_is_protected( $form_id );
}
/**
* By default files are uploaded as chmod 200 to prevent public access.
* This also can cause conflicts with other plugins that try to make updates to files immediately on upload.
* It is not recommended to turn this off as it will make files public. Only do this for forms where you can trust the uploaded files.
*
* @since 5.0.12
*/
return apply_filters( 'frm_protect_temporary_file', true, compact( 'file_id', 'form_id' ) );
}
/**
* @since 5.0.09
*
* @param int $file_id ID of the attachment.
* @return bool
*/
public static function file_is_temporary( $file_id ) {
return get_post_meta( $file_id, '_frm_temporary', true );
}
/**
* @since 5.0.09
*
* @param int $file_id ID of the attachment.
* @return bool
*/
public static function file_is_temporary_and_a_blocked_file_type( $file_id ) {
if ( ! self::file_is_temporary( $file_id ) ) {
return false;
}
// Allow access to images so previews do not break but block other file types.
return ! self::file_is_an_image( $file_id );
}
/**
* @since 5.0.09
*
* @param int $file_id
* @return bool
*/
public static function file_is_an_image( $file_id ) {
$file = get_attached_file( $file_id );
$file_type = wp_check_filetype( $file );
return self::file_type_matches_image( $file_type['type'] );
}
/**
* @since 5.0.09
*
* @param string $type
* @return bool
*/
public static function file_type_matches_image( $type ) {
return is_string( $type ) && 0 === strpos( $type, 'image/' );
}
/**
* @param int $file_id
* @return bool
*/
public static function is_formidable_file( $file_id ) {
$meta = get_post_meta( $file_id, '_frm_file', true );
return ! is_array( $meta ) && $meta;
}
/**
* @return bool
*/
public static function server_supports_htaccess() {
return strpos( FrmAppHelper::get_server_value( 'SERVER_SOFTWARE' ), 'nginx' ) === false && self::files_can_be_modified_on_server();
}
/**
* @return bool
*/
private static function files_can_be_modified_on_server() {
ob_start();
$credentials = request_filesystem_credentials( add_query_arg( array( 'page' => 'formidable-settings' ), admin_url( 'admin.php' ) ) );
ob_end_clean();
return $credentials !== false;
}
/**
* Check if the current user has permission to access a specific file
*
* @param int $id
* @return bool
*/
public static function user_has_permission( $id ) {
if ( ! self::check_temporary_file_access( $id ) ) {
return false;
}
$form_id = self::get_form_id_from_file_id( $id );
if ( ! self::folder_is_protected( $form_id ) ) {
return true;
}
$protect_files_roles = self::get_option( $form_id, 'protect_files_role', 0 );
if ( ! $protect_files_roles ) {
return true;
}
return FrmProFieldsHelper::user_has_permission( $protect_files_roles );
}
/**
* @since 5.0.09
*
* @param int $file_id
* @return bool false if the user fails the temporary access check. true if the file is not temporary.
*/
public static function check_temporary_file_access( $file_id ) {
return self::logged_in_user_can_access_temporary_files() || ! self::file_is_temporary_and_a_blocked_file_type( $file_id );
}
/**
* @since 5.0.09
*
* @return bool
*/
public static function logged_in_user_can_access_temporary_files() {
return current_user_can( 'frm_edit_entries' );
}
/**
* @param array $args
* @return int
*/
public static function get_chmod( $args ) {
if ( isset( $args['file'] ) ) {
$path = $args['file'];
} elseif ( isset( $args['file_id'] ) ) {
$path = get_attached_file( $args['file_id'] );
} else {
return -1;
}
clearstatcache();
return fileperms( $path ) & 0777;
}
/**
* @param array $args
*/
public static function maybe_set_chmod( $args ) {
$args = self::fill_missing_chmod_args( $args );
if ( ! $args ) {
return;
}
if ( isset( $args['file_id'] ) ) {
self::set_file_protection( get_attached_file( $args['file_id'] ), $args['protected'] );
return;
}
$dir = $args['dir'];
$file_ids = array_filter( array_map( 'absint', $args['file_ids'] ) );
$files_to_update = array();
foreach ( $file_ids as $file_id ) {
$files_to_update[] = basename( get_attached_file( $file_id ) );
$metadata = wp_get_attachment_metadata( $file_id );
if ( is_array( $metadata ) && isset( $metadata['sizes'] ) ) {
$files_to_update = array_merge( $files_to_update, array_column( $metadata['sizes'], 'file' ) );
}
}
foreach ( $files_to_update as $file ) {
$path = "$dir/$file";
if ( is_file( $path ) ) {
self::set_file_protection( $path, $args['protected'] );
}
}
}
/**
* Fill missing chmod keys with function calls based off of other data provided in $args
* Also performs some light clean up and validation. If data cannot be filled properly, $args returned will be false
*
* @param array $args
* @return array|false
*/
private static function fill_missing_chmod_args( $args ) {
$is_single_file = isset( $args['file_id'] ) && is_numeric( $args['file_id'] );
$is_folder = isset( $args['file_ids'] ) && is_array( $args['file_ids'] );
if ( ! $is_single_file && ! $is_folder ) {
return false;
}
$missing_args = ! isset( $args['protected'] );
if ( $is_folder ) {
$missing_args = $missing_args || ! isset( $args['dir'] );
}
if ( ! $missing_args ) {
return self::cleanup_chmod_args( $args );
}
if ( ! isset( $args['form_id'] ) ) {
$file_id = isset( $args['file_id'] ) ? $args['file_id'] : reset( $args['file_ids'] );
$args['form_id'] = self::get_form_id_from_file_id( $file_id );
}
if ( ! $args['form_id'] || -1 === $args['form_id'] ) {
return false;
}
if ( ! isset( $args['protected'] ) ) {
if ( isset( $args['file_id'] ) ) {
$args['protected'] = self::file_is_protected( $args['file_id'], $args['form_id'] );
} else {
$args['protected'] = self::folder_is_protected( $args['form_id'] );
}
}
if ( $is_folder && ! isset( $args['dir'] ) ) {
$args['dir'] = self::upload_dir_path( $args['form_id'] );
}
return self::cleanup_chmod_args( $args );
}
private static function cleanup_chmod_args( $args ) {
if ( isset( $args['dir'] ) ) {
$args['dir'] = untrailingslashit( $args['dir'] );
if ( ! file_exists( $args['dir'] ) ) {
return false;
}
}
if ( isset( $args['file_ids'] ) ) {
$args['file_ids'] = array_filter( array_map( 'absint', $args['file_ids'] ) );
if ( ! $args['file_ids'] ) {
return false;
}
}
return $args;
}
/**
* @param int $id Post attachment ID.
* @param string|int[]|bool $size
* @param array $args supported keys include "url" and "leave_size_out_of_payload"
* @return string a maybe-protected url to use for our specified file id and size.
*/
public static function get_file_url( $id, $size = false, $args = array() ) {
$form_id = self::get_form_id_from_file_id( $id );
$url = isset( $args['url'] ) ? $args['url'] : false;
$builder = new FrmProFilePayloadBuilder( $id, $size, $url );
if ( -1 === $form_id ) {
return $builder->get_url();
}
$protected = self::file_is_protected( $id, $form_id );
$chmod_params = array( 'file_id' => $id, 'form_id' => $form_id, 'protected' => $protected );
if ( false === $size && wp_attachment_is_image( $id ) && ! get_post_meta( $id, '_wp_attachment_metadata', true ) ) {
self::delay_file_protection_for_image( $chmod_params );
} else {
// Protect non-images immediately.
self::maybe_set_chmod( $chmod_params );
}
if ( ! $protected ) {
return $builder->get_url();
}
$leave_filesize_out_of_payload = ! empty( $args['leave_size_out_of_payload'] );
return $builder->get_protected_url( self::file_protocol(), $leave_filesize_out_of_payload );
}
/**
* Images are not protected immediately so that thumbnails may be generated.
*
* @param array $chmod_params {
* @type int $file_id
* @type int $form_id
* @type bool $protected
* }
* @return void
*/
private static function delay_file_protection_for_image( $chmod_params ) {
add_filter(
'wp_generate_attachment_metadata',
/**
* @param array $metadata
* @param int $attachment_id
* @param string $context
* @return array
*/
function( $metadata, $attachment_id, $context ) use ( $chmod_params ) {
if ( 'create' === $context && $attachment_id === $chmod_params['file_id'] ) {
self::maybe_set_chmod( $chmod_params );
}
return $metadata;
},
10,
3
);
}
/**
* @param int $form_id
* @param string $key
* @param mixed $default
* @return mixed
*/
public static function get_option( $form_id, $key, $default ) {
$options = FrmDb::get_var( 'frm_forms', array( 'id' => $form_id ), 'options' );
FrmProAppHelper::unserialize_or_decode( $options );
return isset( $options[ $key ] ) ? $options[ $key ] : $default;
}
/**
* Check REQUEST_URI for protected file download details
*
* @return void
*/
public static function check_for_download() {
$payload = self::get_file_payload();
if ( ! $payload ) {
return;
}
$download = self::get_download_filepath( $payload );
if ( ! isset( $download['code'] ) ) {
return;
}
if ( 200 !== $download['code'] ) {
self::handle_download_error( $download );
return;
}
// Temporary allow file access.
self::set_to_read_only( $download['path'] );
$mime_type = FrmProAppHelper::get_mime_type( $download['path'] );
$disposition = self::get_disposition( $mime_type );
header( FrmAppHelper::get_server_value('SERVER_PROTOCOL') . ' 200 OK');
header( 'Cache-Control: public' ); // needed for internet explorer
header( 'Content-Type: ' . $mime_type );
header( 'Content-Transfer-Encoding: Binary' );
header( 'Content-Length:' . filesize( $download['path'] ) );
header( 'Content-Disposition: ' . esc_attr( $disposition ) . '; filename=' . esc_attr( $download['name'] ) );
if ( ! empty( $download['is_temporary'] ) || self::noindex_setting_is_on_for_file( $download['form_id'] ) ) {
header( 'X-Robots-Tag: noindex' );
}
@readfile( $download['path'] ); // hide any errors to prevent issues with downloading an error message as a file
// Set the protection back after download.
self::set_to_write_only( $download['path'] );
die();
}
/**
* @since 5.2.02
*
* @param array $download
* @return void
*/
private static function handle_download_error( $download ) {
status_header( $download['code'] );
if ( 404 === $download['code'] ) {
$message = __( 'Oops! That file no longer exists', 'formidable-pro' );
} else {
$message = __( 'Oops! That file is protected', 'formidable-pro' );
}
$title = is_user_logged_in() ? $download['message'] : '';
wp_die(
'<h1>' . esc_html( $message ) . '</h1>',
'<p>' . esc_html( $title ) . '</p>',
absint( $download['code'] )
);
}
/**
* @since 5.0.09
*
* @param int $form_id
* @return int 1 or 0, 1 if the file in the form should not be indexed.
*/
private static function noindex_setting_is_on_for_file( $form_id ) {
return self::get_option( $form_id, 'noindex_files', 0 );
}
/**
* Determine Content-Disposition based on $mime_type
* We want to inline PDF and images
*
* @param string $mime_type
* @return string
*/
private static function get_disposition( $mime_type ) {
$is_pdf = 'application/pdf' === $mime_type;
$is_image = 0 === strpos( $mime_type, 'image/' );
if ( $is_pdf || $is_image ) {
return 'inline';
}
return 'attachment';
}
/**
* @param int $form_id
* @return string
*/
private static function upload_dir_url( $form_id ) {
return trailingslashit( wp_upload_dir()['baseurl'] ) . self::get_upload_dir_for_form( $form_id );
}
/**
* @param int $form_id
* @return string
*/
private static function upload_dir_path( $form_id ) {
return trailingslashit( wp_upload_dir()['basedir'] ) . self::get_upload_dir_for_form( $form_id );
}
/**
* @param int $file_id
* @return int
*/
private static function get_form_id_from_file_id( $file_id ) {
$meta = get_post_meta( $file_id, '_frm_file', true );
if ( ! $meta ) {
$path = get_attached_file( $file_id );
if ( self::file_is_in_the_formidable_uploads_dir( $path ) ) {
$meta = 1;
} else {
return -1;
}
}
if ( is_array( $meta ) ) {
return -1;
}
$meta = (int) $meta;
if ( $meta !== 1 ) {
return $meta;
}
$file_upload_field_ids = self::get_all_file_upload_field_ids();
$form_id_from_metas = self::search_item_meta_for_file( $file_id );
if ( false !== $form_id_from_metas ) {
update_post_meta( $file_id, '_frm_file', $form_id_from_metas );
return $form_id_from_metas;
}
$path = get_attached_file( $file_id );
if ( self::file_is_in_the_formidable_uploads_dir( $path ) ) {
$relative_path = str_replace( self::default_formidable_uploads_dir(), '', $path );
$split = explode( '/', $relative_path );
if ( 2 === count( $split ) && is_numeric( $split[0] ) ) {
$form_id = $split[0];
update_post_meta( $file_id, '_frm_file', $form_id );
return $form_id;
}
}
return -1;
}
/**
* @param int $file_id
* @return int|false form id
*/
private static function search_item_meta_for_file( $file_id ) {
$metas = self::get_all_file_upload_metas();
if ( ! $metas ) {
return false;
}
$string_file_id = "{$file_id}";
$string_check = ':"' . $file_id . '"';
$int_check = 'i:' . $file_id . ';';
foreach ( $metas as $meta ) {
if ( $string_file_id === $meta->meta_value || false !== strpos( $meta->meta_value, $string_check ) || false !== strpos( $meta->meta_value, $int_check ) ) {
return self::get_form_id_from_field_id( $meta->field_id );
}
}
return false;
}
private static function get_form_id_from_field_id( $field_id ) {
return FrmDb::get_var( 'frm_fields', array( 'id' => $field_id ), 'form_id' );
}
private static function get_all_file_upload_metas() {
$field_ids = self::get_all_file_upload_field_ids();
if ( ! $field_ids ) {
return array();
}
if ( ! isset( self::$all_file_upload_item_metas ) ) {
self::$all_file_upload_item_metas = FrmDb::get_results(
'frm_item_metas',
array(
'field_id' => $field_ids,
),
'field_id, meta_value'
);
}
return self::$all_file_upload_item_metas;
}
private static function get_all_file_upload_field_ids() {
if ( ! isset( self::$all_file_upload_field_ids ) ) {
self::$all_file_upload_field_ids = FrmDb::get_col( 'frm_fields', array( 'type' => 'file' ) );
}
return self::$all_file_upload_field_ids;
}
/**
* As a fallback, if the _frm_file meta is missing for whatever reason, still check for files in the formidable uploads dir
*
* @param string $path
* @return bool
*/
private static function file_is_in_the_formidable_uploads_dir( $path ) {
$file_folder = dirname( $path );
$formidable_uploads_dir = self::default_formidable_uploads_dir();
$dir_length = strlen( $formidable_uploads_dir );
if ( strlen( $file_folder ) < $dir_length ) {
return false;
}
return $formidable_uploads_dir === substr( $file_folder, 0, $dir_length );
}
public static function default_formidable_uploads_dir() {
return trailingslashit( wp_upload_dir()['basedir'] ) . 'formidable/';
}
/**
* Check if the current user has permission to access a specific form
*
* @param int $form_id
* @return bool
*/
private static function folder_is_protected( $form_id ) {
if ( ! $form_id || $form_id === -1 ) {
return false;
}
$form = FrmForm::getOne( $form_id );
if ( ! $form ) {
return false;
}
return self::get_option( $form->parent_form_id ? $form->parent_form_id : $form_id, 'protect_files', 0 );
}
/**
* @param string $file path
* @param bool protected
*/
private static function set_file_protection( $file, $protected ) {
if ( ! file_exists( $file ) ) {
return;
}
$chmod = $protected ? self::WRITE_ONLY : 0644;
$leave = array( $chmod );
if ( $protected ) {
$leave[] = self::READ_ONLY;
}
if ( ! in_array( self::get_chmod( array( 'file' => $file ) ), $leave, true ) ) {
self::chmod( $file, $chmod );
}
}
/**
* @param string $file
* @param int $mode
*/
public static function chmod( $file, $mode ) {
self::setup_wp_filesystem();
global $wp_filesystem;
if ( ! is_null( $wp_filesystem ) ) {
$wp_filesystem->chmod( $file, $mode );
}
}
private static function setup_wp_filesystem() {
new FrmCreateFile( array( 'file_name' => '' ) );
}
/**
* @return string
*/
private static function file_protocol() {
return get_option( 'permalink_structure' ) ? '/frm_file/' : '?frm_file=';
}
/**
* Attempt to get a protected file from a /frm_file/ url
*
* @param string $payload
* @return array
*/
private static function get_download_filepath( $payload ) {
/**
* $decoded needs to match id:|filename:|size: pattern (size is optional though)
* if it does not, return without a code (to ignore the request, just in case there is another /frm_file/ implementation on their website)
*/
$decoded = base64_decode( $payload );
$split = preg_split( '/[:|]+/', $decoded );
$count = count( $split );
$home_url = home_url();
if ( ! in_array( $count, array( 4, 6 ), true ) ) {
return array( 'message' => __( 'payload is not the right size', 'formidable-pro' ) );
}
if ( $split[0] !== 'id' || $split[2] !== 'filename' ) {
return array( 'message' => __( 'payload does not match the expected format', 'formidable-pro' ) );
}
$file_id = absint( $split[1] );
$filename = $split[3];
if ( empty( $file_id ) || empty( $filename ) ) {
return array(
'code' => 403,
'message' => __( 'if sanitized data is empty, do not try to download', 'formidable-pro' ),
);
}
$is_temporary = self::file_is_temporary( $file_id );
if ( $is_temporary && ! self::file_is_an_image( $file_id ) && ! self::logged_in_user_can_access_temporary_files() ) {
return array(
'code' => 403,
'message' => __( 'temporary files can only be accessed by privileged users', 'formidable-pro' ),
);
}
$form_id = self::get_form_id_from_file_id( $file_id );
$folder_is_protected = self::folder_is_protected( $form_id );
$require_login = $folder_is_protected;
// only do the referer checks if the user is a guest
if ( $require_login && ! is_user_logged_in() ) {
$referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
if ( ! $referer ) {
return array(
'code' => 403,
'message' => __( 'referer value either does not exist or it is unusable', 'formidable-pro' ),
);
}
$referer = wp_parse_url( $referer );
$home = wp_parse_url( $home_url );
if ( $referer['host'] !== $home['host'] ) {
return array(
'code' => 403,
'message' => __( 'referer check failed', 'formidable-pro' ),
);
}
}
$is_image = wp_attachment_is_image( $file_id );
if ( $is_image ) {
if ( $count === 6 && $split[4] !== 'size' ) {
return array( 'message' => __( 'payload does not match the expected format', 'formidable-pro' ) );
}
$size = $count === 6 ? $split[5] : 'full';
}
$get_file_url_args = array();
if ( 4 === $count ) {
$get_file_url_args['leave_size_out_of_payload'] = true;
}
$expected_url = self::get_file_url( $file_id, $is_image && $count === 6 ? $split[5] : false, $get_file_url_args );
$expected_request_uri = str_replace( $home_url, '', $expected_url );
if ( self::file_protocol() . $payload !== $expected_request_uri ) {
// prevent urls like /something/frm_file/ from triggering a download
// in this case, leave out the code so the url just continues gracefully
return array( 'message' => __( 'url is not an exact match', 'formidable-pro' ) );
}
$original_file = get_attached_file( $file_id );
if ( ! $original_file ) {
return array(
'code' => 404,
'message' => __( 'no file found', 'formidable-pro' ),
);
}
$original_filename = basename( $original_file );
if ( $original_filename !== $filename ) {
return array(
'code' => 403,
'message' => __( 'if the filename requested does not match our filename, do not return the file', 'formidable-pro' ),
);
}
if ( $form_id === -1 ) {
return array(
'code' => 403,
'message' => __( 'prevent downloads for other uploads. We only want to allow valid connected formidable data', 'formidable-pro' ),
);
}
if ( $folder_is_protected && ! self::user_has_permission( $file_id ) ) {
return array(
'code' => 403,
'message' => __( 'user does not fit any of the set roles, do not serve a file', 'formidable-pro' ),
);
}
$directory = dirname( get_attached_file( $file_id ) );
$final_path = trailingslashit( $directory );
self::remove_protected_file_filters();
if ( $is_image ) {
$split = array_filter( array_map( 'absint', explode( 'x', $size ) ) );
if ( 2 === count( $split ) ) {
$size = $split;
}
$image_src = wp_get_attachment_image_src( $file_id, $size );
}
$final_path .= ! empty( $image_src ) ? basename( reset( $image_src ) ) : $filename;
if ( ! file_exists( $final_path ) ) {
$url = apply_filters( 'wp_get_attachment_url', '', $file_id );
if ( self::file_might_be_on_another_server( $url ) ) {
wp_redirect( $url );
exit;
}
self::put_back_protected_file_filters();
$meta_path = self::check_post_meta_for_path( $file_id );
if ( $meta_path ) {
$filename = basename( $meta_path );
$final_path = $meta_path;
} else {
return array(
'code' => 404,
'message' => __( 'file does not exist', 'formidable-pro' ),
);
}
}
self::put_back_protected_file_filters();
return array(
'code' => 200,
'name' => $filename,
'path' => $final_path,
'is_temporary' => $is_temporary,
'form_id' => $form_id,
);
}
private static function remove_protected_file_filters() {
remove_filter( 'wp_get_attachment_url', 'FrmProFileField::filter_attachment_url' );
remove_filter( 'wp_get_attachment_image_src', 'FrmProFileField::filter_attachment_image_src' );
}
private static function put_back_protected_file_filters() {
add_filter( 'wp_get_attachment_url', 'FrmProFileField::filter_attachment_url', 10, 2 );
add_filter( 'wp_get_attachment_image_src', 'FrmProFileField::filter_attachment_image_src', 10, 4 );
}
/**
* It seems that get_attached_file doesn't always get the proper path to the file.
* As a fallback, check the post meta for _wp_attached_file, and use that if the file exists.
*
* @param int $file_id
* @return string|false
*/
private static function check_post_meta_for_path( $file_id ) {
// $post_meta is saved like formidable/form_id/filename.extension
$post_meta = get_post_meta( $file_id, '_wp_attached_file', true );
if ( ! $post_meta ) {
return false;
}
$meta_path = trailingslashit( wp_upload_dir()['basedir'] ) . $post_meta;
if ( ! file_exists( $meta_path ) ) {
return false;
}
return $meta_path;
}
/**
* If the url is not pointing to the uploads dir, it might be on another server (like S3, or Google's Cloud)
*
* @param string $url
* @return bool
*/
private static function file_might_be_on_another_server( $url ) {
return $url && false === strpos( $url, wp_upload_dir()['baseurl'] );
}
/**
* Check REQUEST_URI (or any uri) for protected file download details
*
* @param string|bool $uri
* @return string|bool false if no payload exists
*/
private static function get_file_payload( $uri = false ) {
if ( ! $uri ) {
$uri = FrmAppHelper::get_server_value('REQUEST_URI');
}
$pattern = self::file_protocol();
$position = strpos( $uri, $pattern );
if ( $position === false ) {
return false;
}
return substr( $uri, $position + strlen( $pattern ) );
}
/**
* @param string $url
* @param int $attachment_id
* @return bool
*/
private static function should_filter_url( $url, $attachment_id ) {
$form_id = self::get_form_id_from_file_id( $attachment_id );
if ( $form_id === -1 ) {
// do not touch non-formidable urls
return false;
}
if ( self::url_is_already_protected( $url ) ) {
return false;
}
return true;
}
/**
* @param string $url
* @return bool
*/
private static function url_is_already_protected( $url ) {
return strpos( $url, self::file_protocol() ) !== false;
}
/**
* @param string $url
* @param int $attachment_id
* @return string
*/
public static function filter_attachment_url( $url, $attachment_id ) {
if ( ! self::should_filter_url( $url, $attachment_id ) ) {
return $url;
}
return self::get_file_url( $attachment_id, false, compact( 'url' ) );
}
/**
* @param array|false $image
* @param int $attachment_id
* @param string|int[] $size
* @param bool $icon
* @return array
*/
public static function filter_attachment_image_src( $image, $attachment_id, $size, $icon ) {
if ( is_array( $image ) && isset( $image[0] ) && self::should_filter_url( $image[0], $attachment_id ) && ! $icon ) {
$image[0] = self::get_file_url( $attachment_id, $size, array( 'url' => $image[0] ) );
}
return $image;
}
/**
* @param array $attr
* @param WP_Post $attachment
* @param string|int[] $size
* @return array
*/
public static function filter_attachment_image_attributes( $attr, $attachment, $size = 'thumbnail' ) {
if ( self::should_filter_attachment_image_attributes( $attachment->ID ) ) {
$attr['src'] = self::get_file_url( $attachment->ID, $size );
}
return $attr;
}
/**
* @param int $attachment_id
* @return bool
*/
private static function should_filter_attachment_image_attributes( $attachment_id ) {
return self::is_formidable_file( $attachment_id ) && wp_attachment_is_image( $attachment_id );
}
/**
* Protect generated attachments that get generated for thumbnails and other image sizes
*
* @param mixed $metadata
* @param int $attachment_id
* @param string $context
* @return array
*/
public static function protect_metadata_attachments( $metadata, $attachment_id, $context = 'create' ) {
if ( 'create' === $context ) {
self::maybe_protect_metadata_file( $metadata, $attachment_id );
}
return $metadata;
}
/**
* @param array $metadata
* @param int $attachment_id
* @return void
*/
private static function maybe_protect_metadata_file( $metadata, $attachment_id ) {
$no_file_meta_exists = ! is_array( $metadata ) || ! isset( $metadata['file'] );
if ( $no_file_meta_exists ) {
return;
}
if ( self::is_formidable_file( $attachment_id ) ) {
$form_id = self::get_form_id_from_file_id( $attachment_id );
} else {
$form_id = self::get_request_form_id();
if ( ! $form_id || ! self::path_matches_form( $form_id, $metadata['file'] ) ) {
return;
}
}
if ( ! self::file_is_protected( $attachment_id, $form_id ) ) {
return;
}
$upload_directory = trailingslashit( self::upload_dir_path( $form_id ) );
foreach ( $metadata['sizes'] as $size_meta ) {
$path = $upload_directory . $size_meta['file'];
self::set_file_protection( $path, true );
}
}
/**
* @return int 0 if request has no form id
*/
private static function get_request_form_id() {
$form_id = FrmAppHelper::get_post_param( 'form_id', 0, 'absint' );
if ( ! $form_id ) {
$form_id = FrmAppHelper::simple_get( 'form', 'absint', 0 );
}
return $form_id;
}
/**
* @param int $form_id
* @param string $path
* @return bool
*/
private static function path_matches_form( $form_id, $path ) {
$form_directory = self::upload_dir_path( $form_id );
$path = wp_upload_dir()['basedir'] . '/' . $path;
return 0 === strpos( $path, $form_directory );
}
/**
* @deprecated 2.03.08
*/
public static function validate( $errors, $field, $values, $args ) {
_deprecated_function( __FUNCTION__, '2.03.08', 'FrmProFileField::no_js_validate' );
return self::no_js_validate( $errors, $field, $values, $args );
}
}