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,304 @@
<?php
/**
* helper functions for cloud functionality
*/
/**
* Backwards compatibility - replace tve_editor with the new global selector
*/
add_filter( 'tcb_alter_cloud_template_meta', 'tve_replace_cloud_template_global_selector', 10, 2 );
/**
* Replace
*
* @param $data
* @param $meta
*
* @return mixed
*/
function tve_replace_cloud_template_global_selector( $data, $meta ) {
$data['head_css'] = tcb_custom_css( $data['head_css'] );
return $data;
}
/**
* Get cloud templates
*
* @param $tag
* @param array $args
*
* @return array|mixed|WP_Error
*/
function tve_get_cloud_content_templates( $tag, $args = [] ) {
$args = wp_parse_args( $args, [
'nocache' => false,
] );
$do_not_use_cache = ( defined( 'TCB_TEMPLATE_DEBUG' ) && TCB_TEMPLATE_DEBUG ) || $args['nocache'];
$transient = 'tcb_ct_' . $tag;
if ( ! empty( $args ) ) {
$transient .= '_' . md5( serialize( $args ) );
}
/**
* Filter the cache transient name.
*
* @param string $transient current name
* @param array function filters
*
* @return string new transient name
*/
$transient = apply_filters( 'tve_cloud_templates_transient_name', $transient, $args );
if ( $do_not_use_cache || ! ( $templates = get_transient( $transient ) ) ) {
delete_transient( $transient );
require_once tve_editor_path( 'inc/classes/content-templates/class-tcb-content-templates-api.php' );
try {
$templates = tcb_content_templates_api()->get_all( $tag, $args );
set_transient( $transient, $templates, 8 * HOUR_IN_SECONDS );
} catch ( Exception $e ) {
return new WP_Error( 'tcb_api_error', $e->getMessage() );
}
}
return $templates;
}
/**
* Get cloud templates data
*
* @param $tag
* @param array $args
*
* @return mixed
*/
function tve_get_cloud_template_data( $tag, $args = [] ) {
if ( isset( $args['id'] ) ) {
$id = $args['id'];
unset( $args['id'] );
} else {
return new WP_Error( 'tcb_download_err', __( 'Invalid template id.', 'thrive-cb' ) );
}
$args = wp_parse_args( $args, [
'nocache' => false,
'post_id' => null,
] );
$args = apply_filters( 'tcb_filter_cloud_template_data_args', $args, $tag );
$force_fetch = ( defined( 'TCB_TEMPLATE_DEBUG' ) && TCB_TEMPLATE_DEBUG ) || $args['nocache'];
require_once tve_editor_path( 'inc/classes/content-templates/class-tcb-content-templates-api.php' );
$api = tcb_content_templates_api();
/**
* check for newer versions - only download the template if there is a new version available
*/
$current_version = false;
if ( ! $force_fetch ) {
$all = apply_filters( 'tcb_filter_cloud_template_data', tve_get_cloud_content_templates( $tag ), $tag );
if ( is_wp_error( $all ) ) {
return $all;
}
foreach ( $all as $tpl ) {
if ( isset( $id ) && $tpl['id'] == $id ) {
$current_version = (int) ( isset( $tpl['v'] ) ? $tpl['v'] : 0 );
}
}
}
try {
$do_shortcode = empty( $args['skip_do_shortcode'] );
/**
* Download template if:
* $force_fetch OR
* template not downloaded OR
* template is downloaded but the version on the cloud has changed
*/
if ( $force_fetch || ! ( $data = $api->get_content_template( $id, $do_shortcode ) ) || ( $current_version !== false && $current_version > $data['v'] ) ) {
$template_id = $api->download( $id, $args, $do_shortcode );
$data = $api->get_content_template( $template_id, $do_shortcode );
}
} catch ( Exception $e ) {
$data = new WP_Error( 'tcb_download_err', $e->getMessage() );
}
return $data;
}
/**
* Returns the cloud landing pages transient name
*
* @param array $filters
*
* @return string
*/
function tve_get_cloud_templates_transient_name( $filters = [] ) {
$transient_name = 'tcb_lp';
if ( ! empty( $filters ) ) {
$transient_name .= '_' . md5( serialize( $filters ) );
}
/**
* Filter the LP cache transient name.
*
* @param string $transient_name current name
* @param array function filters
*
* @return string new transient name
*/
return apply_filters( 'tve_cloud_templates_transient_name', $transient_name, $filters );
}
/**
* get a list of templates from the cloud
* search first in a local wp_option (to avoid making too many requests to the templates server)
* cache the results for a set period of time
*
* default cache interval: 8h
*
*
* @param array $filters filtering options
* @param array $args
*
* @return array
*/
function tve_get_cloud_templates( $filters = [], $args = [] ) {
$transient_name = tve_get_cloud_templates_transient_name( $filters );
$args = wp_parse_args( $args, [
'nocache' => false,
] );
if ( ( defined( 'TCB_CLOUD_DEBUG' ) && TCB_CLOUD_DEBUG ) || $args['nocache'] ) {
delete_transient( $transient_name );
}
$cache_for = apply_filters( 'tcb_cloud_cache', 3600 * 8 );
$templates = get_transient( $transient_name );
if ( false === $templates ) {
try {
$templates = TCB_Landing_Page_Cloud_Templates_Api::getInstance()->get_template_list( $filters );
set_transient( $transient_name, $templates, $cache_for );
} catch ( Exception $e ) {
/* save the error message to display it in the LP modal */
$GLOBALS['tcb_lp_cloud_error'] = $e->getMessage();
$templates = [];
}
}
/**
* Check weather or not cloud templates should be filtered
*
* @param bool
*/
if ( apply_filters( 'tcb_filter_landing_page_templates', true ) ) {
/**
* Allow filtering for cloud templates
*
* @param array $templates
*/
$templates = apply_filters( 'tcb_landing_page_templates_list', $templates );
}
return $templates;
}
/**
* get the configuration stored in the wp_option table for this template (this only applies to templates downloaded from the cloud)
* if $validate === true => also perform validations of the files (ensure the required files exist in the uploads folder)
*
* @param string $lp_template
* @param bool $validate if true, causes the configuration to be validated
*
* @return array|bool false in case there is something wrong (missing files, invalid template name etc)
*/
function tve_get_cloud_template_config( $lp_template, $validate = true ) {
$templates = tve_get_downloaded_templates();
if ( ! isset( $templates[ $lp_template ] ) ) {
return false;
}
$config = $templates[ $lp_template ];
$config['cloud'] = true;
/**
* skip the validation process if $validate is falsy
*/
if ( ! $validate ) {
return $config;
}
$base_folder = tcb_get_cloud_base_path();
$required_files = [
'templates/' . $lp_template . '.tpl', // html contents
'templates/css/' . $lp_template . '.css', // css file
];
foreach ( $required_files as $file ) {
if ( ! is_readable( $base_folder . $file ) ) {
unset( $templates[ $lp_template ] );
tve_save_downloaded_templates( $templates );
return false;
}
}
return $config;
}
/**
* check if a landing page template is originating from the cloud (has been downloaded previously)
*
* @param string $lp_template
*
* @return bool
*/
function tve_is_cloud_template( $lp_template ) {
if ( ! $lp_template ) {
return false;
}
$templates = tve_get_downloaded_templates();
/**
* Filter - allows modifying cloud template behaviour
*
* @param bool $is_cloud_template whether or not the current page has a cloud template applied
*/
return apply_filters( 'tcb_is_cloud_template', array_key_exists( $lp_template, $templates ) );
}
/**
* Delete stored cloud templates & clear transients too
*/
function tve_delete_cloud_saved_data() {
tvd_reset_transient();
$query = new WP_Query( [
'post_type' => [ TCB_CT_POST_TYPE ],
'posts_per_page' => '-1',
'fields' => 'ids',
] );
$post_ids = $query->posts;
foreach ( $post_ids as $id ) {
wp_delete_post( $id, true );
}
}

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 );

View File

@@ -0,0 +1,145 @@
<?php
use TCB\inc\helpers\FormSettings;
/**
* Processes each form settings instance, saving it to the database
*
* @param array $forms array of form settings
* @param int $post_parent
*
* @return array map of replacements
*/
function tve_save_form_settings( $forms, $post_parent ) {
$replaced = [];
$post_title = 'Form settings' . ( $post_parent ? ' for content ' . $post_parent : '' );
foreach ( $forms as $form ) {
$id = ! empty( $form['id'] ) ? (int) $form['id'] : 0;
$instance = FormSettings::get_one( $form['id'], $post_parent )
->set_config( wp_unslash( $form['settings'] ) )
->save( $post_title, empty( $post_parent ) ? null : [ 'post_parent' => $post_parent ] );
if ( $instance->ID !== $id ) {
$replaced[ $form['id'] ] = $instance->ID;
}
}
return $replaced;
}
/**
* Delete one or multiple form settings
*
* @param array|int|string $id
*/
function tve_delete_form_settings( $id ) {
if ( empty( $id ) ) {
return;
}
if ( ! is_array( $id ) ) {
$id = [ $id ];
}
foreach ( $id as $form_id ) {
$form_id = (int) $form_id;
FormSettings::get_one( $form_id )->delete();
}
}
/**
* Delete all forms specific to a post
*
* @param $post_id
*
* @return void
*/
function tve_delete_post_form_settings( $post_id ) {
$query = new \WP_Query( [
'post_type' => [
FormSettings::POST_TYPE,
],
'fields' => 'ids',
'post_parent' => $post_id,
'posts_per_page' => '-1',
]
);
$post_ids = $query->posts;
foreach ( $post_ids as $id ) {
tve_delete_form_settings( $id );
}
}
/**
* Once a page is deleted remove also forms associated
*/
add_action( 'before_delete_post', static function ( $post_id, $post ) {
if ( $post->post_type !== FormSettings::POST_TYPE ) {
tve_delete_post_form_settings( $post_id );
}
}, 10, 2 );
add_action( 'tve_leads_delete_post', 'tve_delete_post_form_settings', 10, 1 );
add_action( 'after_delete_post', 'tve_delete_post_form_settings' );
/**
* On frontend contexts, always remove form settings from content
*/
add_filter( 'tve_thrive_shortcodes', static function ( $content, $is_editor_page ) {
if ( ! $is_editor_page && strpos( $content, FormSettings::SEP ) !== false ) {
$content = preg_replace( FormSettings::pattern( true ), '', $content );
}
return $content;
}, 10, 2 );
/**
* Process content pre-save
*/
add_filter( 'tcb.content_pre_save', static function ( $response, $post_data ) {
/**
* Allows skipping the process of saving form settings to database
*
* @param bool $skip whether or not to skip
*
* @return bool
*/
$process_form_settings = apply_filters( 'tcb_process_form_settings', true );
if ( $process_form_settings && ! empty( $post_data['forms'] ) ) {
/**
* save form settings to the database
*/
$post_id = isset( $post_data['post_id'] ) ? (int) $post_data['post_id'] : 0;
if ( ! empty( $post_data['ignore_post_parent'] ) ) {
$post_id = null;
}
$response['forms'] = tve_save_form_settings( $post_data['forms'], $post_id );
// generate response list of lead gen forms.
$lead_gen_forms = [];
foreach ( $post_data['forms'] as $form ) {
if ( $form['settings'] ) {
$form_attributes = ! empty( $form['settings'] ) ? json_decode( stripslashes( $form['settings'] ) ) : null;
if ( ! is_null( $form_attributes ) && 'lead_generation' === $form_attributes->form_type ) {
$lead_gen_forms[] = array(
'form_identifier' => $form_attributes->form_identifier,
'apis' => $form_attributes->apis ? array_keys( (array) $form_attributes->apis ) : array(),
'inputs' => $form_attributes->inputs,
);
}
}
}
$response['lead_gen_forms'] = $lead_gen_forms;
}
if ( $process_form_settings && ! empty( $post_data['deleted_forms'] ) ) {
tve_delete_form_settings( $post_data['deleted_forms'] );
}
return $response;
}, 10, 2 );

View File

@@ -0,0 +1,320 @@
<?php
namespace TCB\inc\helpers;
/**
* Class FormSettings
*
* @property-read array $apis configuration for the api connections
* @property-read int $captcha whether or not captcha is enabled (1 / 0)
* @property-read string $tool which spam prevention tool is connected
* @property-read string $sp_field custom honeypot field name for the current form
* @property-read array $extra extra configuration for each api connection
* @property-read array $custom_tags array of custom tags configuration (from radio, checkbox, dropdown)
*
* @package TCB\inc\helpers
*/
class FormSettings {
public $ID;
public $post_title;
const POST_TYPE = '_tcb_form_settings';
const SEP = '__TCB_FORM__';
protected $config = [];
/**
* Default configuration for forms
*
* @var array
*/
public static $defaults = [
'apis' => [],
'captcha' => 0,
'tool' => '',
'sp_field' => '',
'extra' => [],
'custom_tags' => [],
'form_identifier' => '',
'inputs' => [],
];
public function __construct( $config ) {
$this->set_config( $config );
}
/**
* Setter for config
*
* @param array|string $config
*
* @return $this
*/
public function set_config( $config ) {
if ( is_string( $config ) ) {
$config = json_decode( $config, true );
}
$this->config = wp_parse_args( $config, static::$defaults );
return $this;
}
/**
* Get the configuration
*
* @param bool $json get it as JSON
*
* @return array|false|string
*/
public function get_config( $json = true ) {
return $json ? json_encode( $this->config ) : $this->config;
}
/**
* Magic config getter
*
* @param string $name
*
* @return mixed
*/
public function __get( $name ) {
$default = isset( static::$defaults[ $name ] ) ? static::$defaults[ $name ] : null;
return isset( $this->config[ $name ] ) ? $this->config[ $name ] : $default;
}
/**
* Loads a file config from an ID, or directly with a configuration array
*
* @param string|int|null|array $id if array, it will act as a config. If empty, return a new instance with default settings
* @param int $post_parent if sent, it will also make sure the instance has the same post parent as this
*
* @return static
*/
public static function get_one( $id = null, $post_parent = null ) {
if ( is_array( $id ) ) {
$config = $id;
$id = null;
} elseif ( $id && is_numeric( $id ) ) {
$id = (int) $id;
$post = get_post( $id );
$post_parent_matches = true;
if ( $post && $post_parent ) {
$post_parent_matches = $post_parent && ( (int) $post_parent === (int) $post->post_parent );
}
if ( $post && $post->post_type === static::POST_TYPE && $post_parent_matches ) {
$config = json_decode( $post->post_content, true );
}
}
if ( empty( $config ) ) {
$id = null;
$config = [];
}
$instance = new static( $config );
$instance->ID = $id;
return $instance;
}
/**
* Save a File config to db
*
* @param string $post_title name to give to the post that's being saved
* @param array $post_data extra post data to save
*
* @return static|\WP_Error
*/
public function save( $post_title, $post_data = null ) {
/* preserve new lines for email fields */
array_walk_recursive( $this->config, function ( &$val, $key ) {
if ( is_string( $val ) && strpos( $key, 'email' ) !== false ) {
$val = nl2br( $val );
$val = preg_replace( "/[\n]+/", "", $val );
}
} );
$content = wp_json_encode( $this->config );
$content = wp_slash( $content );
$save_data = [
'post_type' => static::POST_TYPE,
'post_title' => $post_title,
'post_content' => $content,
];
if ( is_array( $post_data ) ) {
$save_data += $post_data;
}
remove_all_filters( 'wp_insert_post_data' );
remove_all_actions( 'edit_post' );
remove_all_actions( 'save_post' );
remove_all_actions( 'wp_insert_post' );
if ( $this->ID ) {
$save_data['ID'] = $this->ID;
$post_id = wp_update_post( $save_data );
} else {
$post_id = wp_insert_post( $save_data );
}
$this->ID = $post_id;
return is_wp_error( $post_id ) ? $post_id : $this;
}
/**
* Delete the current instance
*/
public function delete() {
if ( $this->ID ) {
wp_delete_post( $this->ID );
}
return $this;
}
/**
* Build the regex pattern for matching form json configuration
*
* @param bool $with_attribute whether or not to also match the `data-form-settings` attribute
*
* @return string
*/
public static function pattern( $with_attribute = false ) {
$regex = static::SEP . '(.+?)' . static::SEP;
if ( $with_attribute ) {
$regex = ' data-form-settings="' . $regex . '"';
}
return "#{$regex}#s";
}
/**
* Populate the $data that's sent to autoresponder based on stored settings for the form
*
* @param array $data
*/
public function populate_request( &$data ) {
/* mark the current request data as trusted */
$data['$$trusted'] = true;
/* captcha */
$data['_use_captcha'] = (int) $this->captcha;
$data['tool'] = ! empty( trim( $this->tool ) ) ? trim( $this->tool ) : '';
$data['sp_field'] = ! empty( trim( $this->sp_field ) ) ? trim( $this->sp_field ) : '';
/* add form identifier to keep track from which form the data is coming */
$data['form_identifier'] = ! empty( trim( $this->form_identifier ) ) ? trim( $this->form_identifier ) : '';
/* build custom tags list based on user-submitted values and form settings - these are set for radio, checkbox and dropdown form elements */
$taglist = [];
foreach ( $this->custom_tags as $field_name => $all_tags ) {
if ( ! isset( $data[ $field_name ] ) ) {
/* no POST data has been sent in $field_name - no tag associated*/
continue;
}
$value_as_array = is_array( $data[ $field_name ] ) ? $data[ $field_name ] : [ $data[ $field_name ] ];
foreach ( $value_as_array as $submitted_value ) {
if ( isset( $all_tags[ $submitted_value ] ) ) {
$taglist[] = str_replace( [ '"', "'" ], '', trim( $all_tags[ $submitted_value ] ) );
}
}
}
$taglist = implode( ',', array_filter( $taglist ) );
$has_custom_tags = ! empty( $taglist );
/* extra data for each api */
foreach ( $this->extra as $api_key => $data_array ) {
foreach ( $data_array as $field => $value ) {
parse_str( $field, $parsed_field );
/* parse array fields that are stored flat "field[custom_field]":"value" */
if ( is_array( $parsed_field ) && ! empty( $parsed_field ) ) {
$key = array_keys( $parsed_field )[0];
if ( is_array( $parsed_field[ $key ] ) && ! empty( $parsed_field[ $key ] ) ) {
$second_key = array_keys( $parsed_field[ $key ] )[0];
$data[ $api_key . '_' . $key ][ $second_key ] = $value;
} else {
$data[ $api_key . '_' . $key ] = $value;
}
} else {
$data[ $api_key . '_' . $field ] = $value;
}
}
$tags_key = $api_key . '_tags';
if ( isset( $data[ $tags_key ] ) ) {
/* append any tags from radio/checkboxes/dropdowns */
if ( $has_custom_tags ) {
$data[ $tags_key ] = trim( $data[ $tags_key ] . ',' . $taglist, ',' );
}
/**
* Filter the final list of tags that gets sent to the API
*
* @param string $tags list of tags, separated by comma
* @param string $api_key API connection identifier
*
* @return array
*/
$data[ $tags_key ] = apply_filters( 'tcb_form_api_tags', $data[ $tags_key ], $api_key );
}
}
}
/**
* On duplicate we need to re save the form settings on a new entry
*/
public static function save_form_settings_from_duplicated_content( $content, $post_parent ) {
/* pattern used to find the settings id from the content */
$pattern = '/data-settings-id="(.+?)"/';
/* Find if we have in content a form by searching for it's form settings id */
preg_match_all( $pattern, $content, $matches );
/* If we find a match we need to generate another entry for post settings and replace the new id in the content */
if ( ! empty( $matches[1] ) ) {
$forms = [];
if ( is_array( $matches[1] ) ) {
foreach ( $matches[1] as $form_settings_id ) {
$form_settings_instance = self::get_one( $form_settings_id, $post_parent );
$forms[] = $form_settings_instance->get_form_settings_array();
}
} else {
$form_settings_id = $matches[1];
$form_settings_instance = self::get_one( $form_settings_id, $post_parent );
$forms[] = $form_settings_instance->get_form_settings_array();
}
$replaced = tve_save_form_settings( $forms, $post_parent );
foreach ( $replaced as $old_id => $new_id ) {
$old_id = (int) $old_id;
$content = preg_replace( "/data-settings-id=\"$old_id\"/", "data-settings-id=\"$new_id\"", $content );
}
}
return $content;
}
/**
* Returns an array with the settings of a form, but without it's original ID so we can use this array to regenerate and save the settings for a cloned form
*
* @return array
*/
public function get_form_settings_array() {
/* We need to change the original ID so we won't find the initial instance of the settings, but to create a new one */
$temporary_id = $this->ID . '_temporary';
return [
'id' => $temporary_id,
'settings' => $this->get_config(),
];
}
}

View File

@@ -0,0 +1,225 @@
<?php
/**
* Migration script to convert Twitter social share elements to X
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Silence is golden..
}
defined( 'THRIVE_SOCIAL_OPTION_NAME' ) || define( 'THRIVE_SOCIAL_OPTION_NAME', 'thrive_social_urls' );
class TCB_Social_Migration {
/**
* Initialize the migration
*/
public static function init() {
add_action( 'admin_init', array( __CLASS__, 'maybe_run_migration' ) );
}
/**
* Check if migration needs to be run
*/
public static function maybe_run_migration() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$migration_completed = get_option( 'tcb_twitter_to_x_migration_completed' );
if ( ! $migration_completed ) {
self::run_migration();
}
}
/**
* Migrate user social media meta data from 't' to 'x'
*/
public static function migrate_user_social_meta() {
global $wpdb;
$batch_size = 50; // Process 50 users at a time
$offset = 0;
// Get total count of users with posts
$total_users = count_users();
$total_users = isset( $total_users['total_users'] ) ? $total_users['total_users'] : 0;
while ( $offset < $total_users ) {
// Get users in batches
$args = array(
'has_published_posts' => true,
'fields' => 'ID', // Only get IDs to reduce memory usage
'number' => $batch_size,
'offset' => $offset
);
$users = get_users( $args );
if ( empty( $users ) ) {
break;
}
foreach ( $users as $user ) {
try {
// Get user meta
$user_meta = get_user_meta( $user, THRIVE_SOCIAL_OPTION_NAME, true );
// Skip if no meta data
if ( empty( $user_meta ) ) {
continue;
}
// Skip if no 't' data to migrate
if ( empty( $user_meta['t'] ) ) {
continue;
}
// Migrate the value from t to x
$user_meta['x'] = $user_meta['t'];
unset( $user_meta['t'] );
// Update the user meta
update_user_meta( $user, THRIVE_SOCIAL_OPTION_NAME, $user_meta );
} catch ( Exception $e ) {
error_log( sprintf(
'Error processing user ID %d: %s',
$user,
$e->getMessage()
) );
}
}
$offset += $batch_size;
// Give the server a small break between batches
if ( function_exists( 'usleep' ) ) {
usleep( 100000 ); // 100ms delay
}
}
}
/**
* Run the migration
*/
public static function run_migration() {
global $wpdb;
// Get all posts that might contain Twitter social share elements
$posts = $wpdb->get_results( "
SELECT ID, post_content
FROM {$wpdb->posts}
WHERE post_content LIKE '%tve_s_t_share%'
AND post_status != 'trash'
" );
$updated = 0;
foreach ( $posts as $post ) {
$content = $post->post_content;
// Replace Twitter elements with X elements
$new_content = self::replace_twitter_elements( $content );
if ( $content !== $new_content ) {
wp_update_post( array(
'ID' => $post->ID,
'post_content' => $new_content
) );
$updated++;
}
}
// Update quiz variations
self::update_quiz_variations();
// Migrate user social meta
self::migrate_user_social_meta();
// Mark migration as completed
update_option( 'tcb_twitter_to_x_migration_completed', true );
}
/**
* Update quiz variations that contain Twitter elements
*/
private static function update_quiz_variations() {
global $wpdb;
$updated = 0;
// Get all quiz variations that contain Twitter elements
$variations = $wpdb->get_results( $wpdb->prepare( "
SELECT id, content
FROM {$wpdb->prefix}tqb_variations
WHERE content LIKE %s
", '%tve_s_t_share%' ) );
foreach ( $variations as $variation ) {
$content = $variation->content;
// Replace Twitter elements with X elements
$new_content = self::replace_twitter_elements( $content );
if ( $content !== $new_content ) {
$wpdb->update(
$wpdb->prefix . 'tqb_variations',
array( 'content' => $new_content ),
array( 'id' => $variation->id ),
array( '%s' ),
array( '%d' )
);
$updated++;
}
}
return $updated;
}
/**
* Replace Twitter elements with X elements in content
*/
private static function replace_twitter_elements( $content ) {
// Pattern to match Twitter social share elements
$pattern = '/<div[^>]*class="[^"]*tve_s_item[^"]*tve_s_t_share[^"]*"[^>]*data-s="t_share"[^>]*>.*?<\/div>/s';
// Replacement function
$replacement = function( $matches ) {
$element = $matches[0];
// Extract data attributes
preg_match( '/data-href="([^"]*)"/', $element, $href_matches );
$href = isset( $href_matches[1] ) ? $href_matches[1] : '{tcb_post_url}';
preg_match( '/data-via="([^"]*)"/', $element, $via_matches );
$via = isset( $via_matches[1] ) ? $via_matches[1] : '';
preg_match( '/<span class="tve_s_text">([^<]*)<\/span>/', $element, $label_matches );
$label = isset( $label_matches[1] ) ? $label_matches[1] : esc_html__( 'Post', 'thrive-cb' );
// Create new X element
return sprintf(
'<div class="tve_s_item tve_s_x_share" data-s="x_share" data-href="%s" data-post="%s" data-via="%s">
<a href="javascript:void(0)" class="tve_s_link">
<span class="tve_s_icon thrv-svg-icon">
<svg class="tcb-x" viewBox="0 0 512 512">
<path d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"></path>
</svg>
</span>
<span class="tve_s_text">%s</span>
<span class="tve_s_count">0</span>
</a>
</div>',
esc_attr( $href ),
esc_attr__( 'I got: %result%', 'thrive-cb' ), // translators: %result% is a placeholder for quiz result
esc_attr( $via ),
esc_html( $label )
);
};
return preg_replace_callback( $pattern, $replacement, $content );
}
}
// Initialize the migration
TCB_Social_Migration::init();

View File

@@ -0,0 +1,617 @@
<?php
/**
* Created by PhpStorm.
* User: radu
* Date: 18.06.2015
* Time: 16:46
*/
/**
* get the javascript sdk source link for a social network.
* For Pinterest network another logic has been applied: a script had to be executed
*
* @param string $handle
*
* @return string the link to the javascript sdk for the network
*/
function tve_social_get_sdk_link( $handle ) {
switch ( $handle ) {
case 'fb':
$app_id = tve_get_social_fb_app_id();
$app_id = $app_id ? '&appId=' . $app_id : '';
return '//connect.facebook.net/en_US/sdk.js#xfbml=0' . $app_id . '&version=v2.9';
case 'twitter':
case 'x':
return '//platform.x.com/widgets.js';
case 'linkedin':
return '//platform.linkedin.com/in.js';
case 'xing':
return '//www.xing-share.com/plugins/share.js';
}
}
/**
* all networks that have custom styles
*
* @return array
*/
function tve_social_get_custom_networks() {
return [
'fb_share',
'x_share',
'pin_share',
'in_share',
'xing_share',
'bluesky_share',
];
}
/**
* render the default social sharing buttons
* this is implemented as a shortcode (having a JSON configuration saved from the editor
*
* @param array $config
*
* @return string the rendered data
*/
function tve_social_render_default( $config ) {
$html = '<div class="thrive-shortcode-html"><div class="tve_social_items tve_clearfix">';
$custom_class = '';
if ( ! empty( $config['selected'] ) ) {
$buttons = tve_social_networks_default_html( null, $config );
foreach ( $config['selected'] as $item ) {
if ( $item === 'pin_share' && $config['btn_type'] === 'btn_count' ) {
$custom_class = 'tve_s_pin_share_count';
}
$html .= '<div class="tve_s_item ' . $custom_class . ' tve_s_' . $item . '" data-s="' . $item . '">' . $buttons[ $item ] . '</div>';
}
}
$html .= '</div></div>';
return $html;
}
/**
*
* this applied only to default share buttons
*
* get the default html for each of the social buttons
* usually, there would be a <div> element with some configuration for each social button
*
* @param string|null $network allows returning the html for just a single button
* @param array $config button configuration
* @param bool $editor_page whether or not we are on the editor page
*
* @return array|string
*/
function tve_social_networks_default_html( $network = null, $config = [], $editor_page = false ) {
$defaults = [
'btn_type' => 'btn',
];
$config = array_merge( $defaults, $config );
if ( ! empty( $network ) && function_exists( 'tve_social_network_default_' . $network ) ) {
return call_user_func( 'tve_social_network_default_' . $network, $config, $editor_page );
}
$networks = [
'fb_share',
'fb_like',
'x_share',
'x_follow',
'in_share',
'pin_share',
'xing_share',
'bluesky_share',
];
$html = [];
foreach ( $networks as $network ) {
$html[ $network ] = call_user_func( 'tve_social_network_default_' . $network, $config, $editor_page );
}
return $html;
}
/**
* default fb share button
*
* @param array $config
*
* @return string
*/
function tve_social_network_default_fb_share( $config ) {
//this function is also used when the control panel is loaded through AJAX request and we don't have access yet at get_the_ID() function
$post_id = get_the_ID();
if ( ! $post_id && ! empty( $_POST['post_id'] ) ) {
$post_id = (int) $_POST['post_id'];
}
return sprintf(
'<div class="fb-share-button" data-href="%s" data-layout="%s"></div>',
! empty( $config['fb_share']['href'] ) ? $config['fb_share']['href'] : get_permalink( $post_id ),
$config['btn_type'] == 'btn' ? 'button' : 'button_count'
);
}
/**
* default fb like button
*
* @param array $config
*
* @return string
*/
function tve_social_network_default_fb_like( $config ) {
return sprintf(
'<div class="fb-like" data-href="%s" data-layout="%s" data-send="false"></div>',
! empty( $config['fb_like']['href'] ) ? $config['fb_like']['href'] : '{tcb_post_url}',
$config['btn_type'] == 'btn' ? 'button' : 'button_count'
);
}
/**
* default twitter tweet button
*
* @param array $config
*
* @return string
*/
function tve_social_network_default_x_share( $config ) {
return sprintf(
'<a href="https://x.com/share" class="twitter-share-button" %s %s %s %s></a>',
! empty( $config['x_share']['href'] ) ? 'data-url="' . $config['x_share']['href'] . '"' : '',
! empty( $config['x_share']['tweet'] ) ? 'data-text="' . $config['x_share']['tweet'] . '"' : '',
! empty( $config['x_share']['via'] ) ? 'data-via="' . $config['x_share']['via'] . '"' : '',
$config['btn_type'] == 'btn' ? ' data-count="none"' : ''
);
}
/**
* default X (twitter follow button
*
* @param array $config
*
* @return string
*/
function tve_social_network_default_x_follow( $config ) {
$username = ! empty( $config['x_follow']['username'] ) ? trim( $config['x_follow']['username'], "@" ) : "";
return sprintf(
'<a href="https://x.com/%s" class="twitter-follow-button" %s %s>Follow</a>',
$username,
$config['btn_type'] == 'btn' ? 'data-show-count="false"' : '',
! empty( $config['x_follow']['hide_username'] ) ? 'data-show-screen-name="false"' : ''
);
}
/**
* default linkedin button
*
* @param array $config
*
* @return string
*/
function tve_social_network_default_in_share( $config ) {
return sprintf(
'<script type="IN/Share" data-showZero="true" %s data-url="%s"></script>',
$config['btn_type'] == 'btn_count' ? 'data-counter="right"' : '',
! empty( $config['in_share']['href'] ) ? $config['in_share']['href'] : '{tcb_post_url}'
);
}
/**
* default pinterest button
*
* @param array $config
* @param bool $editor_page
*
* @return string
*/
function tve_social_network_default_pin_share( $config, $editor_page ) {
$html = sprintf(
'<a href="//www.pinterest.com/pin/create/button/?url=%s&media=%s&description=%s" data-pin-do="buttonPin" %s data-pin-color="red"></a>',
! empty( $config['pin_share']['href'] ) ? urlencode( $config['pin_share']['href'] ) : '{tcb_encoded_post_url}',
! empty( $config['pin_share']['media'] ) ? $config['pin_share']['media'] : '{tcb_post_image}',
! empty( $config['pin_share']['description'] ) ? $config['pin_share']['description'] : '{tcb_post_title}',
$config['btn_type'] == 'btn_count' ? 'data-pin-config="beside" data-pin-zero="true"' : ''
);
if ( ! $editor_page ) {
$html .= <<< EOT
<script type="text/javascript">
(function () {
window.PinIt = window.PinIt || {loaded: false};
if (window.PinIt.loaded) {
return;
}
window.PinIt.loaded = true;
function async_load() {
var s = document.createElement("script");
s.type = "text/javascript";
s.async = true;
s.src = "//assets.pinterest.com/js/pinit.js";
s["data-pin-build"] = "parsePins";
var x = document.getElementsByTagName("script")[0];
x.parentNode.insertBefore(s, x);
}
if (window.attachEvent) {
window.attachEvent("onload", async_load);
} else {
window.addEventListener("load", async_load, false);
}
if(typeof TCB_Front !== 'undefined') {
ThriveGlobal.\$j(TCB_Front).on('tl-ajax-loaded', async_load);
}
})();
</script>
EOT;
}
return $html;
}
/**
* default xing button
*
* @param array $config
*
* @return string
*/
function tve_social_network_default_xing_share( $config ) {
return sprintf(
'<div data-type="xing/share" %s data-url="%s"></div>',
$config['btn_type'] == 'btn_count' ? 'data-counter="true"' : '',
! empty( $config['xing_share']['href'] ) ? $config['xing_share']['href'] : '{tcb_post_url}'
);
}
/**
* default bluesky button
*
* @param array $config
*
* @return string
*/
function tve_social_network_default_bluesky_share( $config ) {
return sprintf(
'<div data-type="bluesky/share" %s data-url="%s"></div>',
$config['btn_type'] == 'btn_count' ? 'data-counter="true"' : '',
! empty( $config['bluesky_share']['href'] ) ? $config['bluesky_share']['href'] : '{tcb_post_url}'
);
}
/**
* fetch and decode a JSON response from a URL
*
* @param string $url
* @param string $fn
*
* @return array
*/
function _tve_social_helper_get_json( $url, $fn = 'wp_remote_get' ) {
$response = $fn( $url, [ 'sslverify' => false ] );
if ( $response instanceof WP_Error ) {
return [];
}
$body = wp_remote_retrieve_body( $response );
if ( empty( $body ) ) {
return [];
}
$data = json_decode( $body, true );
return empty( $data ) ? [] : $data;
}
/**
* format big numbers in the form of 2.4K
*
* @param int $count
*/
function tve_social_count_format( $count ) {
$suffixes = [ '', 'K', 'M', 'G' ];
$suffixIndex = 0;
while ( $count >= 1000 ) {
$suffixIndex ++;
$count /= 1000;
}
return $suffixIndex ? number_format( $count, 1, '.', '' ) . $suffixes[ $suffixIndex ] : $count;
}
/**
* get the cached share count (or, if expired, fetch the count from the API for the network)
*
* @param mixed|null $post_id
* @param string $post_permalink optional, if passed in will be used instead of get_permalink
* @param array|string $networks the network to fetch the share count for (can also be an array)
* @param bool $force_fetch if true, it will bypass the cache and make the API request
*
* Allowed values for the network / network keys:
* 'fb_share',
* 'x_share',
* 'pin_share',
* 'in_share',
* 'xing_share'
*
* @return array
* [fb_share] => $count
* [_share] => $count
* ..
*/
function tve_social_get_share_count( $post_id, $post_permalink = null, $networks = null, $force_fetch = false ) {
$cache_lifetime = apply_filters( 'thrive_cache_shares_lifetime', 300 );
/* the share count is returned for a URL */
if ( null === $post_permalink ) {
$post_permalink = get_permalink( $post_id );
}
/**
* all possible networks
*/
$all_networks = [
'fb_share',
'x_share',
'pin_share',
'in_share',
'xing_share',
];
/* make sure the $networks will be an array */
if ( $networks !== null ) {
$networks = is_array( $networks ) ? $networks : [ $networks ];
$networks = array_intersect( $networks, $all_networks );
} else {
$networks = $all_networks;
}
$count = get_post_meta( $post_id, 'thrive_ss_count', true );
/* if no cache or if the URL has changed => re-fetch the whole thing */
if ( empty( $count ) || $count['url'] != $post_permalink ) {
$count = [];
$force_fetch = true;
}
/* cache expired => re-fetch */
if ( ! empty( $count['last_fetch'] ) && $count['last_fetch'] < time() - $cache_lifetime ) {
$force_fetch = true;
}
/* check to see if we have all of the required networks added to cache */
if ( ! $force_fetch && ! empty( $count ) ) {
foreach ( $networks as $network ) {
if ( ! isset( $count[ $network ] ) ) {
$force_fetch = true;
break;
}
}
}
if ( $force_fetch ) {
$count['url'] = $post_permalink;
$count['last_fetch'] = time();
foreach ( $networks as $network ) {
$shares = call_user_func( 'tve_social_fetch_count_' . $network, $post_permalink );
/* do not set the share count if it already exists and the value received from API is 0 */
if ( isset( $count[ $network ] ) && empty( $shares ) ) {
continue;
}
$count[ $network ] = $shares;
}
update_post_meta( $post_id, 'thrive_ss_count', $count );
}
return $count;
}
/**
* get the social share count for a range of networks (array param), for all (empty) or for an individual network (string)
*
* this triggers API calls to the network
*
* @param string $url the URL to get the shares for
* @param null|array|string $for
*
* @return array | int | false for error
*/
function tve_social_fetch_count( $url, $for = null ) {
$all = tve_social_get_custom_networks();
$response = [];
if ( $for === null ) {
foreach ( $all as $network ) {
$response[ $network ] = call_user_func( 'tve_social_fetch_count_' . $network, $url );
}
return $response;
}
if ( is_array( $for ) ) {
$for = array_intersect( $for, $all );
foreach ( $for as $network ) {
$response[ $network ] = call_user_func( 'tve_social_fetch_count_' . $network, $url );
}
return $response;
}
if ( is_string( $for ) && in_array( $for, $all ) ) {
return call_user_func( 'tve_social_fetch_count_' . $for, $url );
}
return false;
}
/**
* fetch the FB total number of shares for an url
*
* @param string $url
*
* @return int
*/
function tve_social_fetch_count_fb_share( $url ) {
/* not nice but the idea would be to use same function */
return tve_dash_fetch_share_count_facebook( $url );
}
/**
* fetch the total number of shares for an url from twitter
*
* @param string $url
*
* @return int
*/
function tve_social_fetch_count_x_share( $url ) {
return 0;
}
/**
* fetch the total number of shares for an url from Pinterest
*
* @param string $url
*
* @return int
*/
function tve_social_fetch_count_pin_share( $url ) {
$response = wp_remote_get( 'https://api.pinterest.com/v1/urls/count.json?callback=_&url=' . rawurlencode( $url ), [
'sslverify' => false,
] );
$body = wp_remote_retrieve_body( $response );
if ( empty( $body ) ) {
return 0;
}
$body = preg_replace( '#_\((.+?)\)$#', '$1', $body );
$data = json_decode( $body, true );
return empty( $data['count'] ) ? 0 : (int) $data['count'];
}
/**
* fetch the total number of shares for an url from LinkedIn
*
* @param string $url
*
* @return int
*/
function tve_social_fetch_count_in_share( $url ) {
$data = _tve_social_helper_get_json( 'http://www.linkedin.com/countserv/count/share?format=json&url=' . rawurlencode( $url ) );
return empty( $data['count'] ) ? 0 : (int) $data['count'];
}
/**
* fetch the total number of shares for an url from Xing
*
* @param string $url
*
* @return int
*/
function tve_social_fetch_count_xing_share( $url ) {
$response = _tve_social_helper_get_json( 'https://www.xing-share.com/spi/shares/statistics?url=' . rawurlencode( $url ), 'wp_remote_post' );
return isset( $response['share_counter'] ) ? $response['share_counter'] : 0;
}
/**
* entry point for the main ajax request - count social shares
*
* @param array $current no used
* @param array $post_data post data received from the main ajax request
*
* @see tve_dash_frontend_ajax_load
*
*/
function tve_social_dash_ajax_share_counts( $current, $post_data ) {
return tve_social_ajax_count( true, $post_data );
}
/**
* get the social count an array of networks
*
* POST['for'] each element is an object : a key-value pair - key=network value=url to get the counts for
*
* @param bool $return whether to return the counts or send them as json
* @param array $post_data allow overriding the default $_POST data
*/
function tve_social_ajax_count( $return = false, $post_data = null ) {
$response = [
'counts' => [],
'totals' => [],
];
$data = null !== $post_data ? $post_data : $_POST;
if ( empty( $data['for'] ) || ! is_array( $data['for'] ) ) {
if ( $return ) {
return $response;
}
wp_send_json( $response );
}
$url_cache = [];
$count_cache = [];
foreach ( $data['for'] as $index => $item ) {
if ( ! is_array( $item ) ) {
continue;
}
$default = tve_social_get_custom_networks();
$networks = array_intersect( $default, array_keys( $item ) );
$total = 0;
$post_permalink = empty( $data['post_id'] ) ? '' : get_permalink( $data['post_id'] );
$response['counts'][ $index ] = [];
foreach ( $networks as $network ) {
$url = $item[ $network ];
if ( $url == '{tcb_post_url}' ) {
$url = $post_permalink;
$post_id = $data['post_id'];
} else {
if ( ! isset( $url_cache[ $url ] ) ) {
$url_cache[ $url ] = url_to_postid( $url );
}
$post_id = $url_cache[ $url ];
}
if ( ! empty( $post_id ) ) {
/* get the count value from cache */
if ( ! isset( $count_cache[ $post_id ] ) ) {
$count_cache[ $post_id ] = tve_social_get_share_count( $post_id, $url, $networks );
}
$count = $count_cache[ $post_id ][ $network ];
} else {
$count = call_user_func( 'tve_social_fetch_count_' . $network, $url );
}
$total += $count;
$response['counts'][ $index ][ $network ] = array(
'value' => $count,
'formatted' => tve_social_count_format( $count ),
);
}
$response['totals'][ $index ] = array(
'value' => $total,
'formatted' => tve_social_count_format( $total ),
);
}
if ( $return ) {
return $response;
}
wp_send_json( $response );
}