/** * Dynamic functions class */ // If this file is called directly, abort.x if ( ! defined( 'WPINC' ) ) { die; } if ( ! class_exists( 'Jet_Engine_Dynamic_Functions' ) ) { class Jet_Engine_Dynamic_Functions { private $functions_list = array(); private $cache_group = 'jet_engine_dynamic_functions'; public function __construct() { $number_format_settings = array( 'decimal_point' => array( 'label' => __( 'Decimals Point', 'jet-engine' ), 'type' => 'text', 'default' => '.', ), 'thousands_separator' => array( 'label' => __( 'Thousands separator', 'jet-engine' ), 'type' => 'text', 'default' => ',', ), 'decimal_count' => array( 'label' => __( 'Decimals Count', 'jet-engine' ), 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 5, ), ); $this->functions_list = apply_filters( 'jet-engine/dynamic-functions/functions-list', array( 'sum' => array( 'label' => __( 'Summed value', 'jet-engine' ), 'type' => 'sql', // sql or raw 'query' => 'SUM( CAST( meta_value AS DECIMAL( 10, %decimal_count% ) ) )', 'cb' => array( $this, 'number_format' ), 'custom_settings' => $number_format_settings, ), 'avg' => array( 'label' => __( 'Average value', 'jet-engine' ), 'type' => 'sql', 'query' => 'ROUND( AVG( CAST( meta_value AS DECIMAL( 10, %decimal_count% ) ) ), %decimal_count% )', 'cb' => array( $this, 'number_format' ), 'custom_settings' => $number_format_settings, ), 'count' => array( 'label' => __( 'Count', 'jet-engine' ), 'type' => 'sql', 'query' => 'COUNT(meta_value)', 'cb' => false, ), 'max' => array( 'label' => __( 'Maximum value', 'jet-engine' ), 'type' => 'sql', 'query' => 'MAX( CAST( meta_value AS DECIMAL( 10, %decimal_count% ) ) )', 'cb' => array( $this, 'number_format' ), 'custom_settings' => $number_format_settings, ), 'min' => array( 'label' => __( 'Minimum value', 'jet-engine' ), 'type' => 'sql', 'query' => 'MIN( CAST( meta_value AS DECIMAL( 10, %decimal_count% ) ) )', 'cb' => array( $this, 'number_format' ), 'custom_settings' => $number_format_settings, ), 'query_var' => array( 'label' => __( 'SQL query results', 'jet-engine' ), 'type' => 'query_var', 'query' => false, 'cb' => array( $this, 'number_format' ), 'custom_settings' => $number_format_settings, 'custom_settings_cb' => array( $this, 'get_query_settings' ), ), ) ); add_filter( 'jet-engine/blocks-views/dynamic-content/data-sources', array( $this, 'register_blocks_dynamic_source' ) ); add_filter( 'jet-engine/blocks-views/editor-data', array( $this, 'register_blocks_controls' ) ); add_filter( 'jet-engine/blocks-views/dynamic-content/get-dynamic-value/dynamic_function', array( $this, 'get_block_dynamic_value' ), 10, 2 ); } /** * Register Dynamic function source for blocks dynamic data. * * @param [type] $sources [description] * @return [type] [description] */ public function register_blocks_dynamic_source( $sources ) { $sources[] = array( 'value' => 'dynamic_function', 'label' => __( 'Dynamic function', 'jet-engine' ), ); return $sources; } /** * Get block dyanmic value * * @param [type] $result [description] * @param [type] $data [description] * @return [type] [description] */ public function get_block_dynamic_value( $result, $data ) { $prepared_data = array(); $prefix = 'dynamic_functions_'; foreach ( $data as $key => $value ) { if ( false !== strpos( $key, $prefix ) ) { $prepared_data[ str_replace( $prefix, '', $key ) ] = $value; } } $function_name = \Jet_Engine_Tools::safe_get( 'function_name', $prepared_data ); $source = \Jet_Engine_Tools::safe_get( 'data_source', $prepared_data ); $field_name = \Jet_Engine_Tools::safe_get( 'field_name', $prepared_data ); if ( ! $source ) { $source = 'post_meta'; } $data_source = array( 'source' => $source ); if ( empty( $function_name ) ) { return; } if ( 'post_meta' === $source ) { $data_source['context'] = \Jet_Engine_Tools::safe_get( 'data_context', $prepared_data , 'all_posts' ); $data_source['context_tax'] = \Jet_Engine_Tools::safe_get( 'data_context_tax', $prepared_data ); $data_source['context_tax_term'] = \Jet_Engine_Tools::safe_get( 'data_context_tax_term', $prepared_data ); $data_source['context_user_id'] = \Jet_Engine_Tools::safe_get( 'data_context_user_id', $prepared_data ); $data_source['context_relation'] = \Jet_Engine_Tools::safe_get( 'data_context_relation', $prepared_data ); $data_source['post_status'] = \Jet_Engine_Tools::safe_get( 'data_post_status', $prepared_data ); $data_source['post_types'] = \Jet_Engine_Tools::safe_get( 'data_post_types', $prepared_data ); } $custom_settings = $this->get_custom_settings( $function_name, null, $prepared_data ); $result = $this->call_function( $function_name, $data_source, $field_name, $custom_settings ); return $result; } /** * Register denamic functions related controls for the blocks editor * * @param [type] $editor_config [description] * @return [type] [description] */ public function register_blocks_controls( $editor_config ) { $editor_config['dynamicFunctionsControls'] = $this->get_editor_controls( 'blocks' ); return $editor_config; } /** * Returns functions list for options */ public function functions_list() { $result = array(); foreach ( $this->functions_list as $func_key => $func_data ) { $result[ $func_key ] = ! empty( $func_data['label'] ) ? $func_data['label'] : $func_key; } return $result; } public function get_query_settings() { $queries = \Jet_Engine\Query_Builder\Manager::instance()->get_queries(); $options = array(); $sql_queries = array(); if ( ! empty( $queries ) ) { foreach( $queries as $query ) { if ( ! $query || ! is_object( $query ) ) { continue; } if ( 'sql' === $query->query_type ) { $sql_queries[] = $query; } } } if ( empty( $sql_queries ) ) { $options['no_query_notice'] = array( 'type' => 'raw_html', 'raw' => sprintf( esc_html__( 'With this function you can output any data from custom SQL query. To start, please %s', 'jet-elements' ), '' . esc_html__( 'create your query', 'jet-elements' ) . '' ) ); } $columns = array(); foreach ( $sql_queries as $query ) { $fields = $query->get_instance_fields(); $prepared_fields = array(); foreach ( $fields as $field ) { $prepared_fields[ $query->get_instance_id() . '::' . $field ] = $field; } $columns[] = array( 'label' => $query->get_instance_name(), 'options' => $prepared_fields, ); } $options['_query_field'] = array( 'label' => __( 'Query Column', 'jet-engine' ), 'description' => __( 'Select column from the query to show', 'jet-engine' ), 'type' => 'select', 'groups' => $columns, ); $options['_additional_function'] = array( 'label' => __( 'Additional Function', 'jet-engine' ), 'description' => __( 'Apply additional function to SQL query', 'jet-engine' ), 'type' => 'select', 'options' => array( '' => __( 'Select...', 'jet-engine' ), 'SUM' => __( 'Sum', 'jet-engine' ), 'AVG' => __( 'Average', 'jet-engine' ), 'COUNT' => __( 'Count', 'jet-engine' ), 'MIN' => __( 'Minimum', 'jet-engine' ), 'MAX' => __( 'Maximum', 'jet-engine' ), ), ); return $options; } public function get_relations_for_options() { $result = array( '' => __( 'Select...', 'jet-engine' ), ); $relations = jet_engine()->relations->get_relations_for_js(); if ( ! empty( $relations ) ) { foreach ( $relations as $relations ) { $result[ $relations['value'] ] = $relations['label']; } } return $result; } /** * Returns all taxonomies list for options * * @return [type] [description] */ public function get_taxonomies_for_options() { $result = array(); $taxonomies = get_taxonomies( array( 'public' => true ), 'objects' ); foreach ( $taxonomies as $taxonomy ) { if ( empty( $taxonomy->object_type ) || ! is_array( $taxonomy->object_type ) ) { continue; } foreach ( $taxonomy->object_type as $object ) { if ( empty( $result[ $object ] ) ) { $post_type = get_post_type_object( $object ); if ( ! $post_type ) { continue; } $result[ $object ] = array( 'label' => $post_type->labels->name, 'options' => array(), ); } $result[ $object ]['options'][ $taxonomy->name ] = $taxonomy->labels->name; }; } return $result; } /** * Returns editor controls for Elementor or Blocks editor * * @param string $for [description] * @return [type] [description] */ public function get_editor_controls( $for = 'elementor' ) { $controls = array(); $functions_list = array_reverse( $this->functions_list ); $controls['function_name'] = array( 'label' => __( 'Function', 'jet-engine' ), 'type' => 'select', 'add_placeholder' => __( 'Select...', 'jet-engine' ), 'options' => $this->functions_list(), ); $controls['data_source'] = array( 'label' => __( 'Data Source', 'jet-engine' ), 'type' => 'select', 'add_placeholder' => __( 'Select...', 'jet-engine' ), 'options' => array( 'post_meta' => __( 'Post Meta', 'jet-engine' ), 'term_meta' => __( 'Term Meta', 'jet-engine' ), 'user_meta' => __( 'User Meta', 'jet-engine' ), ), 'description' => __( 'Please note: if you used Custom Meta Storage feature for meta data you want to get with Dynamic Functions, you need to use SQL Query Results function type', 'jet-engine' ), 'condition' => array( 'function_name!' => 'query_var', ), ); $controls['field_name'] = array( 'label' => __( 'Field Name', 'jet-engine' ), 'type' => 'text', 'label_block' => true, 'condition' => array( 'function_name!' => 'query_var', ), ); $controls['data_context'] = array( 'label' => __( 'Data Context', 'jet-engine' ), 'type' => 'select', 'default' => 'all_posts', 'add_placeholder' => __( 'Select...', 'jet-engine' ), 'options' => array( 'all_posts' => __( 'All posts', 'jet-engine' ), 'current_term' => __( 'Posts from current term', 'jet-engine' ), 'current_user' => __( 'Posts by current user', 'jet-engine' ), 'queried_user' => __( 'Posts by queried user', 'jet-engine' ), 'related_posts' => __( 'Related posts for current post', 'jet-engine' ), ), 'condition' => array( 'data_source' => 'post_meta', 'function_name!' => 'query_var', ), ); $controls['data_context_relation'] = array( 'label' => __( 'Relation', 'jet-engine' ), 'type' => 'select', 'options' => $this->get_relations_for_options(), 'condition' => array( 'data_source' => 'post_meta', 'data_context' => 'related_posts', 'function_name!' => 'query_var', ), ); $controls['data_context_relation_object'] = array( 'label' => __( 'From Object (what to show)', 'jet-engine' ), 'type' => 'select', 'options' => array( 'parent_object' => __( 'Parent Object', 'jet-engine' ), 'child_object' => __( 'Child Object', 'jet-engine' ), ), 'default' => 'child_object', 'condition' => array( 'data_source' => 'post_meta', 'data_context' => 'related_posts', 'function_name!' => 'query_var', ), ); $controls['data_context_tax'] = array( 'label' => __( 'Taxonomy', 'jet-engine' ), 'type' => 'select', 'groups' => $this->get_taxonomies_for_options(), 'add_placeholder' => __( 'Select...', 'jet-engine' ), 'condition' => array( 'data_source' => 'post_meta', 'data_context' => 'current_term', 'function_name!' => 'query_var', ), ); $controls['data_context_tax_term'] = array( 'label' => __( 'Set term ID/slug', 'jet-engine' ), 'type' => 'text', 'label_block' => true, 'description' => __( 'Leave empty to get term dynamically. Use prefix slug:: to set term by slug instead of ID, for example - slug::term-slug', 'jet-engine' ), 'has_html' => true, 'condition' => array( 'data_source' => 'post_meta', 'data_context' => 'current_term', 'function_name!' => 'query_var', ), ); $controls['data_context_user_id'] = array( 'label' => __( 'Set user ID/login/email', 'jet-engine' ), 'type' => 'text', 'label_block' => true, 'description' => __( 'Leave empty to get user ID dynamically. Use prefixes login:: or email:: to set user by login or email instead of ID, for example - email::admin@demolink.org', 'jet-engine' ), 'has_html' => true, 'condition' => array( 'data_source' => 'post_meta', 'data_context' => 'queried_user', 'function_name!' => 'query_var', ), ); $controls['data_post_status'] = array( 'label' => __( 'Query by posts with status', 'jet-engine' ), 'description' => __( 'Leave empy to search anywhere', 'jet-engine' ), 'type' => 'select2', 'default' => '', 'label_block' => true, 'multiple' => true, 'add_placeholder' => __( 'Select...', 'jet-engine' ), 'options' => array( 'publish' => __( 'Publish', 'jet-engine' ), 'pending' => __( 'Pending', 'jet-engine' ), 'draft' => __( 'Draft', 'jet-engine' ), 'auto-draft' => __( 'Auto draft', 'jet-engine' ), 'future' => __( 'Future', 'jet-engine' ), 'private' => __( 'Private', 'jet-engine' ), 'trash' => __( 'Trash', 'jet-engine' ), 'any' => __( 'Any', 'jet-engine' ), ), 'condition' => array( 'data_source' => 'post_meta', 'function_name!' => 'query_var', ), ); $controls['data_post_types'] = array( 'label' => esc_html__( 'Query by posts with types', 'jet-engine' ), 'description' => __( 'Leave empy to search anywhere', 'jet-engine' ), 'type' => 'select2', 'label_block' => true, 'add_placeholder' => __( 'Select...', 'jet-engine' ), 'multiple' => true, 'options' => jet_engine()->listings->get_post_types_for_options(), 'condition' => array( 'data_source' => 'post_meta', 'function_name!' => 'query_var', ), ); foreach ( $functions_list as $func_name => $data ) { $custom_settings = array(); if ( ! empty( $data['custom_settings_cb'] ) && is_callable( $data['custom_settings_cb'] ) ) { $custom_settings = array_merge( $custom_settings, call_user_func( $data['custom_settings_cb'] ) ); } if ( ! empty( $data['custom_settings'] ) ) { $custom_settings = array_merge( $custom_settings, $data['custom_settings'] ); } if ( ! empty( $custom_settings ) ) { foreach ( $custom_settings as $setting_key => $setting_data ) { if ( empty( $controls[ $setting_key ] ) ) { $setting_data['condition'] = array( 'function_name' => array( $func_name ), ); $controls[ $setting_key ] = $setting_data; } else { $controls[ $setting_key ]['condition']['function_name'][] = $func_name; } } } } if ( 'blocks' === $for ) { $controls = \Jet_Engine_Tools::prepare_controls_for_js( $controls ); } return $controls; } /** * Allow to register custom settings to each function */ public function register_custom_settings( $tag ) { $controls = $this->get_editor_controls(); if ( ! empty( $controls ) ) { foreach ( $controls as $control_key => $control_data ) { $tag->add_control( $control_key, $control_data ); } } } /** * Allow to get custom settings to each function */ public function get_custom_settings( $function, $tag, $all_settings = array() ) { $settings = array(); $custom_settings = array(); $data = ! empty( $this->functions_list[ $function ] ) ? $this->functions_list[ $function ] : array(); if ( ! empty( $data['custom_settings_cb'] ) && is_callable( $data['custom_settings_cb'] ) ) { $custom_settings = array_merge( $custom_settings, call_user_func( $data['custom_settings_cb'] ) ); } if ( ! empty( $data['custom_settings'] ) ) { $custom_settings = array_merge( $custom_settings, $data['custom_settings'] ); } if ( empty( $this->functions_list[ $function ] ) || empty( $custom_settings ) ) { return $settings; } $required_sql_settings = array( 'decimal_count' ); foreach ( $custom_settings as $setting_key => $setting_data ) { if ( $tag ) { $settings[ $setting_key ] = $tag->get_settings( $setting_key ); } else { $settings[ $setting_key ] = isset( $all_settings[ $setting_key ] ) ? $all_settings[ $setting_key ] : false; } // Added to prevent SQL error if required sql setting is empty. if ( in_array( $setting_key, $required_sql_settings ) && Jet_Engine_Tools::is_empty( $settings[ $setting_key ] ) && isset( $setting_data['default'] ) ) { $settings[ $setting_key ] = $setting_data['default']; } } return $settings; } /** * Call function by function name and arguments */ public function call_function( $function_name = null, $data_source = array(), $field_name = null, $custom_settings = array() ) { if ( empty( $this->functions_list[ $function_name ] ) ) { return null; } $func_data = $this->functions_list[ $function_name ]; $func_data['function'] = $function_name; $func_data['data_source'] = $data_source; $func_data['field_name'] = $field_name; $func_data['custom_settings'] = $custom_settings; if ( ! empty( $func_data['type'] ) ) { switch ( $func_data['type'] ) { case 'sql': return $this->call_sql_function( $func_data ); case 'query_var': return $this->get_query_var( $func_data ); default: return $this->call_raw_function( $func_data ); } } } public function get_query_var( $func_data ) { if ( empty( $func_data['custom_settings'] ) || empty( $func_data['custom_settings']['_query_field'] ) ) { return; } $query_data = explode( '::', $func_data['custom_settings']['_query_field'] ); $query_id = str_replace( '_query_', '', $query_data[0] ); $column = $query_data[1]; $query = \Jet_Engine\Query_Builder\Manager::instance()->get_query_by_id( $query_id ); if ( ! $query || 'sql' !== $query->query_type ) { return; } $func = ! empty( $func_data['custom_settings']['_additional_function'] ) ? $func_data['custom_settings']['_additional_function'] : false; $decimal_count = isset( $func_data['custom_settings']['decimal_count'] ) ? $func_data['custom_settings']['decimal_count'] : 0; $callback = ! empty( $func_data['cb'] ) ? $func_data['cb'] : false; $result = $query->get_var( $column, $func, $decimal_count ); if ( $callback && is_callable( $callback ) ) { return call_user_func( $callback, array( 'result' => $result, 'custom_settings' => $func_data['custom_settings'] ) ); } else { return $result; } } /** * Call plain PHP function */ public function call_raw_function( $func_data ) { $function = ! empty( $func_data['cb'] ) ? $func_data['cb'] : false; if ( ! $function || ! is_callable( $function ) ) { return null; } unset( $func_data['cb'] ); return call_user_func( $function, $func_data ); } /** * Call sql-query function */ public function call_sql_function( $data ) { $query = ! empty( $data['query'] ) ? $data['query'] : false; $where = ! empty( $data['where'] ) ? $data['where'] : false; $data_source = ! empty( $data['data_source'] ) ? $data['data_source'] : false; $field_name = ! empty( $data['field_name'] ) ? $data['field_name'] : false; if ( ! $field_name || ! $query ) { return null; } $table = false; global $wpdb; $source = ! empty( $data_source['source'] ) ? $data_source['source'] : 'post_meta'; switch ( $source ) { case 'post_meta': $table = $wpdb->postmeta; break; case 'term_meta': $table = $wpdb->termmeta; break; case 'user_meta': $table = $wpdb->usermeta; break; default: $table = apply_filters( 'jet-engine/dynamic-functions/custom-sql-table', $table, $data ); break; } if ( ! $table ) { return null; } if ( ! $where ) { $where = $wpdb->prepare( "WHERE $table.meta_key ='%s'", $field_name ); } if ( ! empty( $data['custom_settings'] ) ) { foreach ( $data['custom_settings'] as $key => $value ) { $query = str_replace( '%' . $key . '%', $value, $query ); $where = str_replace( '%' . $key . '%', $value, $where ); } } $posts_table = $wpdb->posts; $posts_query_join = " INNER JOIN $posts_table ON $table.post_id = $posts_table.ID "; $posts_join = ""; $posts_where = ""; if ( ! empty( $data_source['post_status'] ) ) { $posts_join = $posts_query_join; $posts_where .= " AND $posts_table.post_status"; $post_status = is_array( $data_source['post_status'] ) ? $data_source['post_status'] : array( $data_source['post_status'] ); if ( 1 === count( $post_status ) ) { $status = $post_status[0]; $posts_where .= " = '$status'"; } else { $statuses = array(); foreach ( $post_status as $status ) { $statuses[] = sprintf( "'%s'", $status ); } $statuses = implode( ', ', $statuses ); $posts_where .= " IN ($statuses)"; } } if ( ! empty( $data_source['post_types'] ) ) { if ( ! $posts_join ) { $posts_join = $posts_query_join; } $posts_where .= " AND $posts_table.post_type"; $post_types = is_array( $data_source['post_types'] ) ? $data_source['post_types'] : array( $data_source['post_types'] ); if ( 1 === count( $post_types ) ) { $type = $post_types[0]; $posts_where .= " = '$type'"; } else { $types = array(); foreach ( $post_types as $type ) { $types[] = sprintf( "'%s'", $type ); } $types = implode( ', ', $types ); $posts_where .= " IN ($types)"; } } $final_query = "SELECT $query FROM $table $posts_join $where $posts_where;"; if ( 'post_meta' === $source ) { switch ( $data_source['context'] ) { case 'current_term': $term_id = $this->get_current_term_id( $data_source ); if ( $term_id ) { $term_relationships = $wpdb->term_relationships; $final_query = "SELECT $query FROM $table $posts_join INNER JOIN $term_relationships ON $table.post_id = $term_relationships.object_id $where AND $term_relationships.term_taxonomy_id = $term_id $posts_where"; } break; case 'related_posts': $relation = ! empty( $data_source['context_relation'] ) ? $data_source['context_relation'] : false; $post = get_post(); $posts = false; if ( $relation && $post ) { $post_type = $post->post_type; $relation_data = jet_engine()->relations->legacy->get_relation_info( $relation ); if ( $relation_data ) { $from = ( $post_type === $relation_data['post_type_1'] ) ? $relation_data['post_type_2'] : $relation_data['post_type_1']; $posts = jet_engine()->relations->legacy->get_related_posts( array( 'post_type_1' => $relation_data['post_type_1'], 'post_type_2' => $relation_data['post_type_2'], 'hash' => $relation, 'from' => $from, ) ); } else { $from = ! empty( $data_source['data_context_relation_object'] ) ? $data_source['data_context_relation_object'] : 'child_object'; $object_id = jet_engine()->listings->data->get_current_object_id(); $relation_instance = jet_engine()->relations->get_active_relations( $relation ); if ( ! $relation_instance ) { return null; } switch ( $from ) { case 'parent_object': $posts = $relation_instance->get_parents( $object_id, 'ids' ); break; default: $posts = $relation_instance->get_children( $object_id, 'ids' ); break; } } if ( ! empty( $posts ) ) { $posts = implode( ', ', $posts ); } else { // If related posts not found - return null to avoid incorrect calculations return null; } $final_query = str_replace( ";", " AND $table.post_id IN ( $posts );", $final_query ); } break; case 'current_user': $user_id = get_current_user_id(); if ( $user_id ) { $posts = $wpdb->posts; $final_query = "SELECT $query FROM $table INNER JOIN $posts ON $table.post_id = $posts.ID $where AND $posts.post_author = $user_id $posts_where"; } break; case 'queried_user': $user_id = false; if ( ! empty( $data_source['context_user_id'] ) ) { $user = $data_source['context_user_id']; if ( false === strpos( $user, '::' ) ) { $user_id = absint( $user ); } else { $user_data = explode( '::', $user ); if ( ! empty( $user_data[0] ) && ! empty( $user_data[1] ) ) { $user_obj = get_user_by( $user_data[0], $user_data[1] ); } if ( $user_obj ) { $user_id = $user_obj->ID; } } } else { if ( is_author() ) { $user_id = get_queried_object_id(); } elseif ( jet_engine()->modules->is_module_active( 'profile-builder' ) ) { $user_id = \Jet_Engine\Modules\Profile_Builder\Module::instance()->query->get_queried_user_id(); } } if ( ! $user_id ) { $user_id = get_current_user_id(); } if ( $user_id ) { $posts = $wpdb->posts; $final_query = "SELECT $query FROM $table INNER JOIN $posts ON $table.post_id = $posts.ID $where AND $posts.post_author = $user_id $posts_where"; } break; } } $final_query = apply_filters( 'jet-engine/dynamic-functions/final-query', $final_query, $data, $this ); $hash = md5( $final_query ); $result = wp_cache_get( $hash, $this->cache_group ); if ( ! $result ) { $result = $wpdb->get_var( $final_query ); if ( ! empty( $data['cb'] ) ) { $data['result'] = $result; $cb_result = $this->call_raw_function( $data ); $result = ( null !== $cb_result ) ? $cb_result : $result; } wp_cache_set( $hash, $result, $this->cache_group ); } return $result; } /** * Get term ID for current context and settings */ public function get_current_term_id( $data_source ) { if ( ! empty( $data_source['context_tax_term'] ) ) { $term = $data_source['context_tax_term']; $term_id = false; if ( false === strpos( $term, 'slug::' ) ) { $term_id = absint( $term ); } elseif ( ! empty( $data_source['context_tax'] ) ) { $term = str_replace( 'slug::', '', $term ); $term_obj = get_term_by( 'slug', $term, $data_source['context_tax'] ); if ( $term_obj ) { $term_id = $term_obj->term_id; } } if ( $term_id ) { return $term_id; } } $listing_object = jet_engine()->listings->data->get_current_object(); $term_id = false; if ( $listing_object && 'WP_Term' === get_class( $listing_object ) ) { $term_id = $listing_object->term_id; } else { $queried_object = get_queried_object(); if ( $queried_object && isset( $queried_object->term_id ) ) { $term_id = $queried_object->term_id; } elseif ( $listing_object && 'WP_Post' === get_class( $listing_object ) && ! empty( $data_source['context_tax'] ) ) { $post_id = $listing_object->ID; $terms = wp_get_post_terms( $post_id, $data_source['context_tax'], array( 'fields' => 'ids' ) ); if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) { $term_id = $terms[0]; } } } return $term_id; } /** * Number format. * * @param array $data * @return string|null */ public function number_format( $data = array() ) { if ( ! isset( $data['result'] ) ) { return null; } $decimal_point = isset( $data['custom_settings']['decimal_point'] ) ? $data['custom_settings']['decimal_point'] : '.'; $thousands_sep = isset( $data['custom_settings']['thousands_separator'] ) ? $data['custom_settings']['thousands_separator'] : ','; $decimal_count = isset( $data['custom_settings']['decimal_count'] ) ? $data['custom_settings']['decimal_count'] : 0; return number_format( $data['result'], $decimal_count, $decimal_point, $thousands_sep ); } } } Spins et innovations : l’évolution des casinos en ligne en France – homeserviceapp.noviindus.com

Spins et innovations : l’évolution des casinos en ligne en France

Les casinos en ligne ont connu une transformation majeure depuis leur apparition, passant d’une niche technologique à un secteur dominant de l’industrie du jeu. En France, cette transition a été marquée par une réglementation stricte, mais aussi par une explosion de l’offre, notamment avec des plateformes spécialisées dans les machines à sous virtuelles. Ces dernières années, les opérateurs ont dû s’adapter aux attentes des joueurs, en intégrant des fonctionnalités plus interactives et des mécanismes de jeu plus transparents. Le secteur est aujourd’hui en pleine mutation, avec des enjeux majeurs autour de la sécurité, de la responsabilité et de l’innovation technologique.

Un des aspects les plus marquants de cette évolution est l’introduction des spins automatiques, ou *slots*, qui ont redéfini les standards de divertissement en ligne. Ces machines, autrefois réservées aux casinos physiques, sont désormais disponibles en version numérique avec des graphismes ultra-réalistes, des animations fluides et des systèmes de bonus intégrés. Les opérateurs comme blazespins casino 2026 ont joué un rôle clé en proposant des jeux adaptés aux attentes des joueurs français, avec des thèmes variés, des mises en jeu progressives et des mécanismes de paiement sécurisés.

Les tendances technologiques qui redéfinissent les slots en ligne

L’une des innovations les plus visibles est l’utilisation de l’intelligence artificielle pour personnaliser l’expérience joueur. Les algorithmes analysent les préférences individuelles pour proposer des jeux adaptés, augmentant ainsi la rétention. Par ailleurs, l’adoption du *live dealer* a permis de mélanger l’expérience physique et numérique, avec des animateurs réels contrôlant les machines à distance. Cette approche, bien que controversée, a permis à certains opérateurs de se différencier en offrant une touche humaine dans un univers autrement automatisé.

La sécurité reste un pilier essentiel du secteur. Les plateformes doivent respecter des normes strictes, comme celles imposées par l’AMF (Autorité des Marchés Financiers), qui vérifient la transparence des gains et les mécanismes de protection des mineurs. Les solutions de *fair play*, telles que les systèmes de vérification tiers comme eCOGRA, sont désormais obligatoires pour rassurer les joueurs sur l’équité des jeux. Enfin, l’essor des cryptomonnaies a ouvert de nouvelles possibilités, bien que leur adoption reste limitée par les réglementations locales.

Le marché français : chiffres clés et perspectives

En 2023, le marché des casinos en ligne en France représentait environ 1,2 milliard d’euros de chiffre d’affaires, avec une croissance annuelle estimée à 8 %. Les slots occupent une part significative, représentant près de 40 % des transactions, devant les machines à sous classiques et les jeux de table virtuels. Les opérateurs étrangers, comme ceux présents sur blazespins casino 2026, bénéficient d’une forte présence grâce à leur capacité à innover rapidement et à s’adapter aux préférences locales.

Les perspectives pour 2026 devraient continuer cette dynamique, avec une augmentation des investissements dans le développement de jeux plus immersifs, comme les *virtual reality slots* ou les intégrations de blockchain pour des transactions plus rapides. Cependant, le secteur devra aussi faire face à des défis réglementaires croissants, notamment en matière de lutte contre le blanchiment et de protection des joueurs vulnérables. La France, en tant que leader européen du jeu en ligne, restera un terrain de compétition intense pour les opérateurs.

  • En 2023, les slots virtuels représentaient 40 % du volume de transactions dans les casinos en ligne français.
  • L’intelligence artificielle permet une personnalisation des jeux à un niveau jamais atteint auparavant.
  • Le marché français du jeu en ligne a connu une croissance annuelle de 8 % entre 2021 et 2023.
  • Les opérateurs doivent respecter des normes AMF strictes pour garantir la transparence des gains.
  • Les cryptomonnaies représentent moins de 5 % des transactions dans les casinos français, en raison des restrictions réglementaires.

Vers un avenir plus responsable

L’avenir des slots en ligne en France dépendra en grande partie de l’équilibre entre innovation et responsabilité. Les opérateurs devront continuer à investir dans des technologies qui limitent les risques de dépendance, comme les outils de détection des jeux excessifs ou les limites de dépense automatiques. Les régulateurs, quant à eux, devront adapter leurs cadres légaux pour répondre aux évolutions technologiques sans étouffer l’innovation. Une collaboration étroite entre les acteurs du secteur et les autorités devrait permettre de créer un écosystème plus équilibré, où le divertissement reste au cœur de l’expérience, sans négliger la protection des joueurs.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *