Merge pull request #1610 from ultimatemember/development/2.9.x

Version 2.9.2 - 2025 year patch
This commit is contained in:
Mykyta Synelnikov
2025-01-14 15:42:05 +02:00
committed by GitHub
19 changed files with 744 additions and 1982 deletions
+5
View File
@@ -482,6 +482,11 @@ p.um-notice.warning a {
padding-right: 40px !important;
}
.um-form .um-field-area-password input[type="password"]::-ms-reveal,
.um-form .um-field-area-password input[type="password"]::-ms-clear {
display: none;
}
.um-form input[type="number"] {
width: auto;
padding: 0 0 0 5px !important;
+1 -1
View File
File diff suppressed because one or more lines are too long
+3
View File
@@ -57,6 +57,7 @@ UM.frontend = {
zoomable: false,
rotatable: false,
dashed: false,
scalable: false
};
} else if ( 'cover' === crop_data ) {
if ( Math.round( min_width / ratio ) > 0 ) {
@@ -70,6 +71,7 @@ UM.frontend = {
zoomable: false,
rotatable: false,
dashed: false,
scalable: false
};
} else if ( 'user' === crop_data ) {
opts = {
@@ -80,6 +82,7 @@ UM.frontend = {
zoomable: false,
rotatable: false,
dashed: false,
scalable: false
};
}
+1 -1
View File
@@ -1 +1 @@
"object"!=typeof window.UM&&(window.UM={}),"object"!=typeof UM.frontend&&(UM.frontend={}),UM.frontend={cropper:{obj:null,init:function(){var o=jQuery(".um-modal .um-single-image-preview img").first();if(o.length&&""!==o.attr("src")){UM.frontend.cropper.obj&&UM.frontend.cropper.destroy();var t=jQuery(".um-modal .um-single-image-preview"),r=o.parent().data("crop"),n=o.parent().data("min_width"),i=o.parent().data("min_height"),a=o.parent().data("ratio"),d=jQuery(".um-modal").find("#um_upload_single").data("ratio"),d=(d&&(a=d.split(":")[0]),jQuery(window).height()-(jQuery(".um-modal-footer a").height()+20)-50-jQuery(".um-modal-header:visible").height());o.css({height:"auto"}),t.css({height:"auto"}),jQuery(window).height()<=400?(t.css({height:d+"px","max-height":d+"px"}),o.css({height:"auto"})):(o.css({height:"auto","max-height":d+"px"}),t.css({height:o.height(),"max-height":d+"px"}));let e;"square"===r?e={minWidth:n,minHeight:i,dragCrop:!1,aspectRatio:1,zoomable:!1,rotatable:!1,dashed:!1}:"cover"===r?(0<Math.round(n/a)&&(i=Math.round(n/a)),e={minWidth:n,minHeight:i,dragCrop:!1,aspectRatio:a,zoomable:!1,rotatable:!1,dashed:!1}):"user"===r&&(e={minWidth:n,minHeight:i,dragCrop:!0,aspectRatio:"auto",zoomable:!1,rotatable:!1,dashed:!1}),e&&(UM.frontend.cropper.obj=new Cropper(o[0],e))}},destroy:function(){0<jQuery(".cropper-container").length&&UM.frontend.cropper.obj&&(UM.frontend.cropper.obj.destroy(),UM.frontend.cropper.obj=null)}}},wp.hooks.addAction("um_remove_modal","um_common_frontend",function(){UM.frontend.cropper.destroy()}),wp.hooks.addAction("um_after_removing_preview","um_common_frontend",function(){UM.frontend.cropper.destroy()}),wp.hooks.addAction("um_window_resize","um_common_frontend",function(){UM.frontend.cropper.destroy()});
"object"!=typeof window.UM&&(window.UM={}),"object"!=typeof UM.frontend&&(UM.frontend={}),UM.frontend={cropper:{obj:null,init:function(){var o=jQuery(".um-modal .um-single-image-preview img").first();if(o.length&&""!==o.attr("src")){UM.frontend.cropper.obj&&UM.frontend.cropper.destroy();var t=jQuery(".um-modal .um-single-image-preview"),r=o.parent().data("crop"),n=o.parent().data("min_width"),a=o.parent().data("min_height"),i=o.parent().data("ratio"),d=jQuery(".um-modal").find("#um_upload_single").data("ratio"),d=(d&&(i=d.split(":")[0]),jQuery(window).height()-(jQuery(".um-modal-footer a").height()+20)-50-jQuery(".um-modal-header:visible").height());o.css({height:"auto"}),t.css({height:"auto"}),jQuery(window).height()<=400?(t.css({height:d+"px","max-height":d+"px"}),o.css({height:"auto"})):(o.css({height:"auto","max-height":d+"px"}),t.css({height:o.height(),"max-height":d+"px"}));let e;"square"===r?e={minWidth:n,minHeight:a,dragCrop:!1,aspectRatio:1,zoomable:!1,rotatable:!1,dashed:!1,scalable:!1}:"cover"===r?(0<Math.round(n/i)&&(a=Math.round(n/i)),e={minWidth:n,minHeight:a,dragCrop:!1,aspectRatio:i,zoomable:!1,rotatable:!1,dashed:!1,scalable:!1}):"user"===r&&(e={minWidth:n,minHeight:a,dragCrop:!0,aspectRatio:"auto",zoomable:!1,rotatable:!1,dashed:!1,scalable:!1}),e&&(UM.frontend.cropper.obj=new Cropper(o[0],e))}},destroy:function(){0<jQuery(".cropper-container").length&&UM.frontend.cropper.obj&&(UM.frontend.cropper.obj.destroy(),UM.frontend.cropper.obj=null)}}},wp.hooks.addAction("um_remove_modal","um_common_frontend",function(){UM.frontend.cropper.destroy()}),wp.hooks.addAction("um_after_removing_preview","um_common_frontend",function(){UM.frontend.cropper.destroy()}),wp.hooks.addAction("um_window_resize","um_common_frontend",function(){UM.frontend.cropper.destroy()});
+14 -1
View File
@@ -1,25 +1,38 @@
== Changelog ==
= 2.9.2 December 17, 2024 =
= 2.9.2 January 14, 2025 =
* Enhancements:
- Added: Compatibility with the new [Ultimate Member - Zapier](https://ultimatemember.com/extensions/zapier/) extension
- Added: Only approved user Reset Password setting defined as true by default
- Added: `UM()->is_new_ui()` function for future enhancements related to new UI
- Added: Filter hook `um_before_user_submitted_registration_data`
- Tweak: Changed hook's priority for initialization of email templates paths
- Tweak: Removed `load_plugin_textdomain` due to (article)[https://make.wordpress.org/core/2024/10/21/i18n-improvements-6-7/#Enhanced-support-for-only-using-PHP-translation-files]
* Bugfixes:
- Fixed: Security issue CVE ID: CVE-2025-0308
- Fixed: Security issue CVE ID: CVE-2025-0318
- Fixed: Using placeholders in email templates when Action Scheduler is active. Using `fetch_user_id` attribute for fetching necessary user before sending email
- Fixed: PHP 8.4 compatibility. Using WordPress native `wp_is_mobile()` instead of MobileDetect library
- Fixed: PHP errors related to `UM()->localize()` function
- Fixed: PHP errors in user meta header when `last_update` meta is empty
- Fixed: Small CSS changes and avoid duplicates
- Fixed: Removed ms-native show password button for type="password" field in UM forms
- Fixed: Define scalable attribute for cropper
* Deprecated:
- Fully deprecated `UM()->mobile()` function
- Fully deprecated `UM()->localize()` function
- Fully deprecated `um_language_textdomain` filter hook
* Templates required update:
- account.php
* Cached and optimized/minified assets(JS/CSS) must be flushed/re-generated after upgrade
= 2.9.1 November 15, 2024 =
+10 -6
View File
@@ -123,7 +123,7 @@ class Site_Health {
'title' => get_the_title( $form_id ),
'link' => get_edit_post_link( $form_id ),
);
$forms_count++;
++$forms_count;
continue 2;
}
}
@@ -652,23 +652,27 @@ class Site_Health {
'value' => UM()->options()->get( 'reset_password_limit_number' ),
);
}
$access_other_settings['um-change_password_request_limit'] = array(
$access_other_settings['um-change_password_request_limit'] = array(
'label' => __( 'Change Password request limit ', 'ultimate-member' ),
'value' => UM()->options()->get( 'change_password_request_limit' ),
);
$access_other_settings['um-blocked_emails'] = array(
$access_other_settings['um-only_approved_user_reset_password'] = array(
'label' => __( 'Only approved user Reset Password', 'ultimate-member' ),
'value' => UM()->options()->get( 'only_approved_user_reset_password' ),
);
$access_other_settings['um-blocked_emails'] = array(
'label' => __( 'Blocked Email Addresses', 'ultimate-member' ),
'value' => stripslashes( $blocked_emails ),
);
$access_other_settings['um-blocked_words'] = array(
$access_other_settings['um-blocked_words'] = array(
'label' => __( 'Blacklist Words', 'ultimate-member' ),
'value' => stripslashes( $blocked_words ),
);
$access_other_settings['um-allowed_choice_callbacks'] = array(
$access_other_settings['um-allowed_choice_callbacks'] = array(
'label' => __( 'Allowed Choice Callbacks', 'ultimate-member' ),
'value' => stripslashes( $allowed_callbacks ),
);
$access_other_settings['um-allow_url_redirect_confirm'] = array(
$access_other_settings['um-allow_url_redirect_confirm'] = array(
'label' => __( 'Allow external link redirect confirm ', 'ultimate-member' ),
'value' => UM()->options()->get( 'allow_url_redirect_confirm' ) ? $labels['yes'] : $labels['no'],
);
File diff suppressed because it is too large Load Diff
+1
View File
@@ -691,6 +691,7 @@ if ( ! class_exists( 'um\Config' ) ) {
'enable_reset_password_limit' => true,
'reset_password_limit_number' => 3,
'change_password_request_limit' => false,
'only_approved_user_reset_password' => true,
'blocked_emails' => '',
'blocked_words' => 'admin' . "\r\n" . 'administrator' . "\r\n" . 'webmaster' . "\r\n" . 'support' . "\r\n" . 'staff',
'allowed_choice_callbacks' => '',
+8 -17
View File
@@ -520,12 +520,10 @@ if ( ! class_exists( 'UM' ) ) {
$this->form()->hooks();
$this->permalinks();
$this->cron();
$this->mobile();
$this->external_integrations();
$this->gdpr();
$this->member_directory();
$this->blocks();
$this->secure();
// If multisite networks active
if ( is_multisite() ) {
@@ -1334,21 +1332,6 @@ if ( ! class_exists( 'UM' ) ) {
return $this->classes['templates'];
}
/**
* @since 2.0
*
* @return um\lib\mobiledetect\Um_Mobile_Detect
*/
function mobile() {
if ( empty( $this->classes['mobile'] ) ) {
$this->classes['mobile'] = new um\lib\mobiledetect\Um_Mobile_Detect();
}
return $this->classes['mobile'];
}
/**
* @since 2.0.44
*
@@ -1474,6 +1457,14 @@ if ( ! class_exists( 'UM' ) ) {
_deprecated_function( __METHOD__, '2.1.0', 'UM()->member_directory()' );
return UM()->member_directory();
}
/**
* @since 2.0
* @deprecated 2.9.2
*/
public function mobile() {
_deprecated_function( __METHOD__, '2.9.2', 'wp_is_mobile' );
}
}
}
+2 -2
View File
@@ -751,13 +751,13 @@ if ( ! class_exists( 'um\core\Fields' ) ) {
$output .= '</label>';
if ( ! empty( $data['help'] ) && false === $this->viewing && false === strpos( $key, 'confirm_user_pass' ) ) {
if ( ! UM()->mobile()->isMobile() ) {
if ( ! wp_is_mobile() ) {
if ( false === $this->disable_tooltips ) {
$output .= '<span class="um-tip um-tip-' . ( is_rtl() ? 'e' : 'w' ) . '" title="' . esc_attr__( $data['help'], 'ultimate-member' ) . '"><i class="um-icon-help-circled"></i></span>';
}
}
if ( false !== $this->disable_tooltips || UM()->mobile()->isMobile() ) {
if ( false !== $this->disable_tooltips || wp_is_mobile() ) {
$output .= '<span class="um-tip-text">' . __( $data['help'], 'ultimate-member' ) . '</span>';
}
}
@@ -1027,7 +1027,7 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) {
$this->sql_order = apply_filters( 'um_modify_sortby_parameter_meta', $this->sql_order, $sortby );
$profiles_per_page = $directory_data['profiles_per_page'];
if ( UM()->mobile()->isMobile() && isset( $directory_data['profiles_per_page_mobile'] ) ) {
if ( wp_is_mobile() && isset( $directory_data['profiles_per_page_mobile'] ) ) {
$profiles_per_page = $directory_data['profiles_per_page_mobile'];
}
@@ -1105,7 +1105,7 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) {
$sizes = UM()->options()->get( 'cover_thumb_sizes' );
$this->cover_size = UM()->mobile()->isTablet() ? $sizes[1] : end( $sizes );
$this->cover_size = wp_is_mobile() ? $sizes[1] : end( $sizes );
$avatar_size = UM()->options()->get( 'profile_photosize' );
$this->avatar_size = str_replace( 'px', '', $avatar_size );
+13 -7
View File
@@ -1374,7 +1374,7 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
function pagination_options( $directory_data ) {
// number of profiles for mobile
$profiles_per_page = $directory_data['profiles_per_page'];
if ( UM()->mobile()->isMobile() && isset( $directory_data['profiles_per_page_mobile'] ) ) {
if ( wp_is_mobile() && isset( $directory_data['profiles_per_page_mobile'] ) ) {
$profiles_per_page = $directory_data['profiles_per_page_mobile'];
}
@@ -1716,6 +1716,9 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
// Make the search line empty if it contains the mySQL query statements.
$regexp_map = array(
'/select(.*?)from/im',
'/select(.*?)sleep/im',
'/select(.*?)database/im',
'/select(.*?)where/im',
'/update(.*?)set/im',
'/delete(.*?)from/im',
);
@@ -1727,15 +1730,15 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
break;
}
}
return $search;
// Early escape of the search line. The same as `$wpdb->prepare()`.
return esc_sql( $search );
}
/**
* Handle general search line request
*/
public function general_search() {
//general search
// General search
if ( ! empty( $_POST['search'] ) ) {
// complex using with change_meta_sql function
$search = $this->prepare_search( $_POST['search'] );
@@ -1873,8 +1876,11 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
$search_where = preg_replace( '/ AND \((.*?)\)/im', "$1 OR", $search_where );
// str_replace( '/', '\/', wp_slash( $search ) ) means that we add backslashes to special symbols + add backslash to slash(/) symbol for proper regular pattern.
$pattern = $wpdb->prepare( $meta_join_for_search . '.meta_value = %s', $search );
$pattern = '/(' . str_replace( '/', '\/', wp_slash( $pattern ) ) . ')/im';
$sql['where'] = preg_replace(
'/(' . $meta_join_for_search . '.meta_value = \'' . str_replace( '/', '\/', wp_slash( $search ) ) . '\')/im',
$pattern,
trim( $search_where ) . " $1",
$sql['where'],
1
@@ -2547,7 +2553,7 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
// number of profiles for mobile
$profiles_per_page = $directory_data['profiles_per_page'];
if ( UM()->mobile()->isMobile() && isset( $directory_data['profiles_per_page_mobile'] ) ) {
if ( wp_is_mobile() && isset( $directory_data['profiles_per_page_mobile'] ) ) {
$profiles_per_page = $directory_data['profiles_per_page_mobile'];
}
@@ -2992,7 +2998,7 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
$sizes = UM()->options()->get( 'cover_thumb_sizes' );
$this->cover_size = UM()->mobile()->isTablet() ? $sizes[1] : end( $sizes );
$this->cover_size = wp_is_mobile() ? $sizes[1] : end( $sizes );
$this->cover_size = apply_filters( 'um_member_directory_cover_image_size', $this->cover_size, $directory_data );
+5 -2
View File
@@ -454,10 +454,13 @@ if ( ! class_exists( 'um\core\Password' ) ) {
if ( isset( $data ) && is_a( $data, '\WP_User' ) ) {
um_fetch_user( $data->ID );
UM()->user()->password_reset();
if ( false === UM()->options()->get( 'only_approved_user_reset_password' ) || UM()->common()->users()->has_status( $data->ID, 'approved' ) ) {
UM()->user()->password_reset();
}
}
wp_redirect( um_get_core_page('password-reset', 'checkemail' ) );
wp_safe_redirect( um_get_core_page( 'password-reset', 'checkemail' ) );
exit;
}
+12 -13
View File
@@ -933,20 +933,19 @@ function um_submit_form_errors_hook_( $submitted_data, $form_data ) {
} elseif ( ! UM()->validation()->safe_username( $submitted_data[ $key ] ) ) {
UM()->form()->add_error( $key, __( 'Your email contains invalid characters', 'ultimate-member' ) );
}
break;
}
if ( '' === $submitted_data[ $key ] ) {
UM()->form()->add_error( $key, __( 'You must provide your email', 'ultimate-member' ) );
} elseif ( ! is_email( $submitted_data[ $key ] ) || email_exists( $submitted_data[ $key ] ) ) {
UM()->form()->add_error( $key, __( 'The email you entered is incorrect', 'ultimate-member' ) );
} else {
if ( '' !== $submitted_data[ $key ] && ! is_email( $submitted_data[ $key ] ) ) {
UM()->form()->add_error( $key, __( 'The email you entered is incorrect', 'ultimate-member' ) );
} elseif ( '' !== $submitted_data[ $key ] && email_exists( $submitted_data[ $key ] ) ) {
UM()->form()->add_error( $key, __( 'The email you entered is incorrect', 'ultimate-member' ) );
} elseif ( '' !== $submitted_data[ $key ] ) {
$users = get_users( 'meta_value=' . $submitted_data[ $key ] );
foreach ( $users as $user ) {
if ( $user->ID !== $submitted_data['user_id'] ) {
UM()->form()->add_error( $key, __( 'The email you entered is incorrect', 'ultimate-member' ) );
}
// There we have valid and unique user_email. But need to check in usermeta table for other users.
$users = get_users( 'meta_value=' . $submitted_data[ $key ] );
foreach ( $users as $user ) {
if ( $user->ID !== $submitted_data['user_id'] ) {
UM()->form()->add_error( $key, __( 'The email you entered is incorrect', 'ultimate-member' ) );
}
}
}
+3 -6
View File
@@ -917,12 +917,9 @@ function um_profile_header_cover_area( $args ) {
$size = $get_cover_size;
}
if ( UM()->mobile()->isMobile() ) {
// set for mobile width = 300 by default but can be changed via filter
if ( ! UM()->mobile()->isTablet() ) {
$size = 300;
}
if ( wp_is_mobile() ) {
// Set for mobile width = 300 by default but can be changed via filter
$size = 300;
/**
* UM hook
File diff suppressed because one or more lines are too long
+25
View File
@@ -693,6 +693,29 @@ function um_user_submitted_registration_formatted( $style = false ) {
$output .= um_user_submited_display( 'use_gdpr_agreement', __( 'GDPR Applied', 'ultimate-member' ), $submitted_data );
}
/**
* Filters the custom HTML before user registration submitted and formatted data.
*
* @param {string} $before_html Custom HTML before submitted data.
* @param {string} $output Prepared submitted data in HTML format.
* @param {array} $submitted_data Submitted user data during registration.
*
* @return {string} Custom HTML before submitted data.
*
* @since 2.9.2
* @hook um_before_user_submitted_registration_data
*
* @example <caption>Change custom HTML before user registration submitted and formatted data.</caption>
* function my_user_submitted_registration_data( $before_html, $output, $submitted_data ) {
* if ( ! empty( $submitted_data['custom_data'] ) ) {
* $before_html .= 'Custom HTML here';
* }
* return $before_html;
* }
* add_filter( 'um_before_user_submitted_registration_data', 'my_user_submitted_registration_data', 10, 3 );
*/
$output .= apply_filters( 'um_before_user_submitted_registration_data', '', $output, $submitted_data );
if ( isset( $submitted_data ) && is_array( $submitted_data ) ) {
if ( isset( $submitted_data['form_id'] ) ) {
@@ -810,6 +833,8 @@ function um_user_submitted_registration_formatted( $style = false ) {
}
}
$output .= apply_filters( 'um_after_user_submitted_registration_data', '', $output, $submitted_data );
if ( $style ) {
$output .= '</div>';
}
+17 -1
View File
@@ -167,26 +167,39 @@ No specific extensions are needed. But we highly recommended keep active these P
IMPORTANT: PLEASE UPDATE THE PLUGIN TO AT LEAST VERSION 2.6.7 IMMEDIATELY. VERSION 2.6.7 PATCHES SECURITY PRIVILEGE ESCALATION VULNERABILITY. PLEASE SEE [THIS ARTICLE](https://docs.ultimatemember.com/article/1866-security-incident-update-and-recommended-actions) FOR MORE INFORMATION
= 2.9.2 2024-12-17 =
= 2.9.2 2025-01-14 =
**Enhancements**
* Added: Compatibility with the new [Ultimate Member - Zapier](https://ultimatemember.com/extensions/zapier/) extension
* Added: Only approved user Reset Password setting defined as true by default
* Added: `UM()->is_new_ui()` function for future enhancements related to new UI
* Added: Filter hook `um_before_user_submitted_registration_data`
* Tweak: Changed hook's priority for initialization of email templates paths
* Tweak: Removed `load_plugin_textdomain` due to (article)[https://make.wordpress.org/core/2024/10/21/i18n-improvements-6-7/#Enhanced-support-for-only-using-PHP-translation-files]
**Bugfixes**
* Fixed: Security issue CVE ID: CVE-2025-0308
* Fixed: Security issue CVE ID: CVE-2025-0318
* Fixed: Using placeholders in email templates when Action Scheduler is active. Using `fetch_user_id` attribute for fetching necessary user before sending email
* Fixed: PHP 8.4 compatibility. Using WordPress native `wp_is_mobile()` instead of MobileDetect library
* Fixed: PHP errors related to `UM()->localize()` function
* Fixed: PHP errors in user meta header when `last_update` meta is empty
* Fixed: Small CSS changes and avoid duplicates
* Fixed: Removed ms-native show password button for type="password" field in UM forms
* Fixed: Define scalable attribute for cropper
**Deprecated**
* Fully deprecated `UM()->mobile()` function
* Fully deprecated `UM()->localize()` function
* Fully deprecated `um_language_textdomain` filter hook
**Templates required update**
* account.php
**Cached and optimized/minified assets(JS/CSS) must be flushed/re-generated after upgrade**
= 2.9.1 2024-11-15 =
@@ -224,6 +237,9 @@ IMPORTANT: PLEASE UPDATE THE PLUGIN TO AT LEAST VERSION 2.6.7 IMMEDIATELY. VERSI
== Upgrade Notice ==
= 2.9.2 =
This version fixes a security related bug. Upgrade immediately.
= 2.9.0 =
This version fixes a security related bug. Upgrade immediately.
+3 -3
View File
@@ -6,7 +6,7 @@
*
* Page: "Account"
*
* @version 2.7.0
* @version 2.9.2
*
* @var string $mode
* @var int $form_id
@@ -75,7 +75,7 @@ if ( ! defined( 'ABSPATH' ) ) {
</a>
</div>
<?php if ( UM()->mobile()->isMobile() ) { ?>
<?php if ( wp_is_mobile() ) { ?>
<div class="um-account-meta-img-b uimob800-show" title="<?php echo esc_attr( um_user( 'display_name' ) ); ?>">
<a href="<?php echo esc_url( um_user_profile_url() ); ?>">
@@ -114,7 +114,7 @@ if ( ! defined( 'ABSPATH' ) ) {
?>
<li>
<a data-tab="<?php echo esc_attr( $id )?>" href="<?php echo esc_url( UM()->account()->tab_link( $id ) ); ?>" class="um-account-link <?php if ( $id == UM()->account()->current_tab ) echo 'current'; ?>">
<?php if ( UM()->mobile()->isMobile() ) { ?>
<?php if ( wp_is_mobile() ) { ?>
<span class="um-account-icontip uimob800-show" title="<?php echo esc_attr( $info['title'] ); ?>">
<i class="<?php echo esc_attr( $info['icon'] ); ?>"></i>
</span>