Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

View File

@@ -0,0 +1,598 @@
<?php
namespace TCB\inc\helpers;
/**
* Class FileUploadConfig
*
* @package TCB\inc\helpers
*
* @property int $max_size
* @property int $max_files
* @property array $file_types
* @property array $custom_file_types custom extensions
* @property string $api key of the connected file upload API service
* @property string $name Filename template
* @property string $folder identification for the destination folder
* @property string $required Whether or not the field is required
*/
class FileUploadConfig extends FormSettings {
const EMAIL_FILENAME_TEMPLATE = '___T_USR_EMAIL___';
const POST_TYPE = '_tcb_file_upload';
/**
* Default configuration for file uploads
*
* @var array
*/
public static $defaults = [
'max_files' => 1,
'max_size' => 1, // in MB
'file_types' => [ 'documents' ],
'custom_file_types' => [],
'name' => '{match}', // by default, match the filename with the one uploaded by the visitor
];
/**
* Get a list of each file group, it's title, icon and associated extensions
*
* @return array[]
*/
public static function get_allowed_file_groups() {
return array(
'documents' => array(
'name' => __( 'Documents', 'thrive-cb' ),
'icon' => 'file',
'extensions' => [ 'pdf', 'doc', 'docx', 'ppt', 'pptx', 'pps', 'ppsx', 'odt', 'xls', 'xlsx', 'psd', 'txt' ],
),
'images' => array(
'name' => __( 'Images', 'thrive-cb' ),
'icon' => 'image2',
'extensions' => [ 'jpg', 'jpeg', 'png', 'gif', 'ico' ],
),
'audio' => array(
'name' => __( 'Audio files', 'thrive-cb' ),
'icon' => 'audio',
'extensions' => [ 'mp3', 'm4a', 'ogg', 'wav' ],
),
'video' => array(
'name' => __( 'Video files', 'thrive-cb' ),
'icon' => 'video',
'extensions' => [ 'mp4', 'm4v', 'mov', 'wmv', 'avi', 'mpg', 'ogv', '3gp', '3g2' ],
),
'zip' => array(
'name' => __( 'Zip archives', 'thrive-cb' ),
'icon' => 'archive',
'extensions' => [ 'zip', 'tar', 'gzip', 'gz' ],
),
'custom' => array(
'name' => __( 'Custom', 'thrive-cb' ),
'icon' => 'wrench',
),
);
}
/**
* Get a nonce key to use for file uploads
*
* @param string $file_id
*
* @return string
*/
public static function get_nonce_key( $file_id ) {
return "tcb_file_api_{$file_id}";
}
/**
* Generate a unique nonce for each file_id
*
* @param string $file_id
*
* @return string
*/
public static function create_nonce( $file_id ) {
return wp_create_nonce( static::get_nonce_key( $file_id ) );
}
/**
* @param string $nonce user-submitted nonce key
* @param string $file_id
*
* @return boolean|int
*/
public static function verify_nonce( $nonce, $file_id ) {
return wp_verify_nonce( $nonce, static::get_nonce_key( $file_id ) );
}
/**
* Setter for config
*
* @param array $config
*
* @return $this
*/
public function set_config( $config ) {
parent::set_config( $config );
/* normalize required value to integer */
$this->config['required'] = ! empty( $this->config['required'] ) ? 1 : 0;
return $this;
}
/**
* Get a subset of the configuration, that only applies for frontend
*
* @param bool $json get it as JSON
*
* @return string|array|false
*/
public function get_frontend_config( $json = true ) {
$config = array(
'max_size' => $this->max_size,
'max_files' => $this->max_files,
'allowed' => $this->get_allowed_extensions(),
'required' => $this->required,
);
return $json ? json_encode( $config ) : $config;
}
/**
* Get a list of allowed file extensions
*
* @return array
*/
public function get_allowed_extensions() {
$file_types = $this->file_types;
if ( ! is_array( $file_types ) ) {
$file_types = [];
}
$extensions = [];
if ( is_array( $this->custom_file_types ) && in_array( 'custom', $file_types, true ) ) {
$extensions = $this->custom_file_types;
}
$extension_groups = [ $extensions ];
foreach ( static::get_allowed_file_groups() as $key => $group ) {
if ( isset( $group['extensions'] ) && in_array( $key, $file_types, true ) ) {
$extension_groups [] = $group['extensions'];
}
}
return call_user_func_array( 'array_merge', $extension_groups );
}
/**
* Check if the extension is allowed
*
* @param string $extension
*
* @return bool
*/
public function extension_allowed( $extension ) {
$extension = strtolower( $extension );
/* if blacklisted, don't allow this extension */
if ( in_array( $extension, static::get_extensions_blacklist(), true ) ) {
return false;
}
return in_array( $extension, $this->get_allowed_extensions(), true );
}
/**
* Check if the filesize is allowed (if it's lower than max accepted file size)
*
* @param int $uploaded_file_size
*
* @return bool
*/
public function size_allowed( $uploaded_file_size ) {
/* $this->max_size = maximum file size in MB */
return $uploaded_file_size <= wp_convert_hr_to_bytes( $this->max_size . 'm' );
}
/**
* Calculate the filename used to store this on the API service
*
* @param string $original_name original filename, without extension
*
* @return string filename without extension
*/
public function get_upload_filename( $original_name ) {
$templates = array(
'{match}' => sanitize_file_name( $original_name ),
'{date}' => current_time( 'm-d-Y' ),
'{time}' => current_time( 'Hi' ),
/**
* email is not known at the time of file upload.
* Solution is to use this template and rename the files via API when the form is submitted
*/
'{email}' => static::EMAIL_FILENAME_TEMPLATE,
);
return str_replace( array_keys( $templates ), $templates, $this->name ) . '_' . mt_rand( 1000000, 9999999 );
}
/**
* Gets the service API that's been setup with this file upload configuration
*
* @return \Thrive_Dash_List_Connection_Abstract|\WP_Error
*/
public function get_service() {
$api_connection = $this->api;
if ( ! $api_connection ) {
return new \WP_Error( 'Missing file upload service. Please contact site owner' );
}
return \Thrive_Dash_List_Manager::connection_instance( $api_connection );
}
/**
* Validates the main LG submitted form
*
* @param array $data
*
* @return string|bool
*/
public function validate_form_submit( $data ) {
$files = ! empty( $data['_tcb_files'] ) && is_array( $data['_tcb_files'] ) ? $data['_tcb_files'] : [];
$min_files = $this->required ? 1 : 0;
$file_count = count( $files );
$result = true;
if ( ! $this->ID ) {
$result = __( 'Invalid file configuration', 'thrive-cb' );
} elseif ( $file_count < $min_files || $file_count > $this->max_files ) {
$result = __( 'Invalid number of files', 'thrive-cb' );
}
/* validate nonces */
foreach ( $files as $nonce => $file_id ) {
if ( ! static::verify_nonce( $nonce, $file_id ) ) {
$result = __( 'Invalid request', 'thrive-cb' );
}
}
return $result;
}
/**
* Checks the configuration to see if any files need to be renamed
* This can happen if the user adds '{email}' to the file name settings
*
* @return bool
*/
public function needs_file_rename() {
return strpos( $this->name, '{email}' ) !== false;
}
/**
* Returns a list of file extensions that should always be forbidden, regardless of user settings
*
* @return array
*/
public static function get_extensions_blacklist() {
return [
'0xe',
'a6p',
'action',
'app',
'applescript',
'bash',
'bat',
'cgi',
'cod',
'com',
'dek',
'dex',
'dmg',
'ebm',
'elf',
'es',
'esh',
'ex4',
'exe',
'exopc',
'fpi',
'gpe',
'gpu',
'hms',
'hta',
'ipa',
'isu',
'jar',
'jsx',
'kix',
'mau',
'mel',
'mem',
'mrc',
'exe',
'pex',
'pif',
'plsc',
'pkg',
'prg',
'ps1',
'pwc',
'qit',
'rbx',
'rox',
'rxe',
'scar',
'scb',
'scpt',
'sct',
'seed',
'sh',
'u3p',
'vb',
'vbe',
'vbs',
'vbscript',
'vlx',
'widget',
'workflow',
'ws',
'xbe',
'xex',
'xys',
];
}
}
/**
* Parse the content and replace file_upload shortcode configuration IDs with the actual config
*
*/
add_filter( 'tve_thrive_shortcodes', static function ( $content, $is_editor_page ) {
$content = preg_replace_callback( '#__FILE_SETUP__(\d+)__FILE_SETUP__#', static function ( $matches ) use ( $is_editor_page ) {
$file_config = FileUploadConfig::get_one( $matches[1] );
$replacement = $is_editor_page ? $file_config->get_config() : $file_config->get_frontend_config();
return esc_attr( $replacement );
}, $content );
return $content;
}, 10, 2 );
/**
* Sends an error response to be picked up by the plupload library
*
* @param string $error
* @param int $code
*/
function upload_error_handler( $error, $code = 400 ) {
status_header( $code );
echo esc_html( $error );
die();
}
/**
* Handle file uploads submitted through the Lead Gen element
*/
function handle_upload() {
if ( empty( $_REQUEST['id'] ) ) {
upload_error_handler( 'Missing required parameter' );
}
// this must mean that the uploaded file is too large
if ( empty( $_FILES ) ) {
upload_error_handler( 'Missing file, or file is too large' );
}
if ( ! empty( $_FILES['file']['error'] ) ) {
upload_error_handler( 'Error uploading file', 500 );
}
$config = FileUploadConfig::get_one( isset( $_REQUEST['id'] ) ? sanitize_text_field( $_REQUEST['id'] ) : '' );
if ( ! $config->ID ) {
upload_error_handler( 'Missing / Invalid request parameter' );
}
$info = pathinfo( $_FILES['file']['name'] );
if ( empty( $info['extension'] ) ) {
/* something is wrong here */
upload_error_handler( 'Invalid file name' );
}
if ( ! $config->extension_allowed( $info['extension'] ) ) {
upload_error_handler( 'This type of file is not accepted' );
}
if ( ! $config->size_allowed( filesize( $_FILES['file']['tmp_name'] ) ) ) { // phpcs:ignore
upload_error_handler( 'File is too big' );
}
$api = $config->get_service();
if ( is_wp_error( $api ) ) {
upload_error_handler( 'Could not determine API' );
}
$file_meta = array(
'originalName' => $_FILES['file']['name'], // phpcs:ignore
'name' => $config->get_upload_filename( $info['filename'] ) . '.' . $info['extension'],
);
/** @var string|\WP_Error $result */
$result = $api->upload( file_get_contents( $_FILES['file']['tmp_name'] ), $config->folder, $file_meta ); // phpcs:ignore
if ( is_wp_error( $result ) ) {
/**
* Log API Error
*/
global $wpdb;
$log_data = array(
'date' => date( 'Y-m-d H:i:s' ),
'error_message' => sanitize_text_field( $result->get_error_message() ),
'api_data' => serialize( array(
'email' => ! empty( $_REQUEST['email'] ) ? sanitize_email( $_REQUEST['email'] ) : '-',
'file_name' => $file_meta['name'],
) ),
'connection' => $config->api,
'list_id' => '',
);
$wpdb->insert( $wpdb->prefix . 'tcb_api_error_log', $log_data );
upload_error_handler( $result->get_error_message() );
}
/**
* This looks like a successful operation
*/
wp_send_json( array(
'success' => true,
'nonce' => FileUploadConfig::create_nonce( $result ), // generate nonce so that it can be used in file delete requests and validate the subsequent POST
'file_id' => $result,
'file_info' => $api->get_file_data( $result ),
) );
}
/**
* Handle file removal
*/
function handle_remove() {
if ( empty( $_POST['nonce'] ) || empty( $_POST['file_id'] ) || empty( $_POST['id'] ) ) {
/* don't generate any error messages */
exit();
}
if ( ! FileUploadConfig::verify_nonce( sanitize_text_field( $_POST['nonce'] ), sanitize_text_field( $_POST['file_id'] ) ) ) {
exit();
}
$api = FileUploadConfig::get_one( sanitize_text_field( $_POST['id'] ) )->get_service();
if ( is_wp_error( $api ) ) {
exit();
}
$api->delete( sanitize_text_field( $_POST['file_id'] ) );
exit();
}
/**
* Process the form data before sending it to various APIs
* Checks if the form data contains any files and, if a mapping is defined for the file field, make sure data contains a field populated with mapping data
* Mapped data must contain URLs to uploaded files
*
* @param array $data submitted post data
*
* @return array
*/
function process_form_data( $data ) {
if ( empty( $data['tcb_file_id'] ) ) {
return $data;
}
$config = FileUploadConfig::get_one( $data['tcb_file_id'] );
$api = $config->get_service();
if ( is_wp_error( $api ) ) {
return $data;
}
$file_data = [];
$file_urls = [];
$file_ids = [];
/* transform storage file IDs into URLs */
if ( ! empty( $data['_tcb_files'] ) ) {
/* if email has been submitted, and the filenames should include the email, we need to trigger a file rename for each submitted file */
if ( ! empty( $data['email'] ) && $config->needs_file_rename() ) {
foreach ( $data['_tcb_files'] as $file_id ) {
$file_data[ $file_id ] = $api->rename_file( $file_id, function ( $filename ) use ( $data, $api ) {
// "@" is allowed in filenames in Dropbox. Untested in other services.
if ( 'Thrive_Dash_List_Connection_FileUpload_Dropbox' === get_class( $api ) ) {
$email = sanitize_file_name( $data['email'] );
} else {
$email = str_replace( '@', '__A-Round__', sanitize_email( $data['email'] ) );
$email = str_replace( '__A-Round__', '+', sanitize_file_name( $email ) );
}
return str_replace( FileUploadConfig::EMAIL_FILENAME_TEMPLATE, $email, $filename );
} );
}
}
foreach ( $data['_tcb_files'] as $index => $file_id ) {
/* filedata gets populated if the file needs to be renamed. Using it to avoid extra API calls */
$data['_tcb_files'][ $index ] = isset( $file_data[ $file_id ] ) ? $file_data[ $file_id ] : $api->get_file_data( $file_id );
$file_urls[] = $data['_tcb_files'][ $index ]['url'];
$file_ids[ $index ] = $file_id;
}
}
/**
* Mapped field: needs the file URL to be sent to the autoresponder
*/
$data[ $data['tcb_file_field'] ] = $file_urls;
/**
* Preserve the file IDs in the form data so that they can be used later on
*/
$data['_tcb_files_upload_ids'] = $file_ids;
return $data;
}
/**
* Build an HTML list of file URLs based on the submitted files
*
* @param array $data post data
*
* @return string
*/
function build_html_file_list( $data ) {
if ( empty( $data['_tcb_files'] ) || ! is_array( $data['_tcb_files'] ) ) {
return '';
}
return sprintf(
'<ul style="margin:0">%s</ul>',
array_reduce( $data['_tcb_files'], static function ( $carry, $file ) {
return $carry . sprintf( '<li><a href="%s">%s</a></li>', esc_attr( $file['url'] ), esc_html( $file['name'] ) );
}, '' )
);
}
/**
* Search and replace the [uploaded_files] shortcode with an HTML list of file links
*
* @param string $message
* @param array $data
*
* @return string
*/
function process_email_message( $message, $data ) {
return str_replace( '[uploaded_files]', build_html_file_list( $data ), $message );
}
/**
* Build the HTML to be displayed in [all_fields] shortcode inside the email message.
* Outputs the file list a HTML <ul> list element containing links to files and file names.
*
* @param string $value current value
* @param string $field field name. Only process if it starts with `mapping_file_`
* @param array $data form submission data
*
* @return string
*/
function process_email_field( $value, $field, $data ) {
if ( strpos( $field, 'mapping_file_' ) === 0 ) {
$value = build_html_file_list( $data );
}
return $value;
}
add_action( 'wp_ajax_nopriv_tcb_file_upload', 'TCB\inc\helpers\handle_upload' );
add_action( 'wp_ajax_tcb_file_upload', 'TCB\inc\helpers\handle_upload' );
add_action( 'wp_ajax_nopriv_tcb_file_remove', 'TCB\inc\helpers\handle_remove' );
add_action( 'wp_ajax_tcb_file_remove', 'TCB\inc\helpers\handle_remove' );
add_filter( 'tcb_before_api_subscribe_data', 'TCB\inc\helpers\process_form_data' );
add_filter( 'thrive_api_email_message', 'TCB\inc\helpers\process_email_message', 10, 2 );
add_filter( 'thrive_email_message_field', 'TCB\inc\helpers\process_email_field', 10, 3 );