'', 'icon_set_dir_name' => '', 'service' => 'icomoon', 'css_prefix' => '', 'icons' => [], ]; /** * Reserved CSS prefixes. * * @access private * @since 6.2 * @var array */ private $reserved_css_prefixes = [ 'fusiona-', ]; /** * The class constructor. * * @access private * @since 6.2 * @return void */ private function __construct() { $this->wp_filesystem = Fusion_Helper::init_filesystem(); // Register custom post type. add_action( 'init', [ $this, 'register_post_type' ] ); // Front end styles. add_filter( 'fusion_dynamic_css_final', [ $this, 'combine_stylesheets' ] ); // Live Builders scripts. add_action( 'fusion_enqueue_live_scripts', [ $this, 'enqueue_scripts' ] ); // Dashboard scripts. add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts' ] ); add_action( 'admin_head', [ $this, 'admin_head' ] ); add_action( 'wp_ajax_fusion-icons-uploader-action', [ $this, 'handle_upload' ] ); if ( is_admin() ) { // Add meta box. add_action( 'add_meta_boxes_' . $this->post_type, [ $this, 'add_meta_box' ] ); // Save post meta. add_action( 'save_post_' . $this->post_type, [ $this, 'save_post_meta' ], 10, 3 ); // Cleanup when post is deleted (trash emptied). add_action( 'before_delete_post', [ $this, 'delete_icon_set' ], 10, 1 ); add_action( 'do_meta_boxes', [ $this, 'remove_revolution_slider_meta_box' ], 10, 3 ); // Display 'duplicate css prefix note' if needed. add_action( 'admin_notices', [ $this, 'add_duplicate_prefix_notice' ], 1, 1 ); // Process icon package when post is saved. add_action( $this->post_type . '_post_saved', [ $this, 'process_upload' ], 10, 1 ); // Add admin page. add_action( 'admin_action_fusion_custom_icons_new', [ $this, 'add_new_custom_icon_set' ] ); } } /** * Creates or returns an instance of this class. * * @static * @access public * @since 6.2 */ public static function get_instance() { // If an instance hasn't been created and set to $instance create an instance and set it to $instance. if ( null === self::$instance ) { self::$instance = new Fusion_Custom_Icon_Set(); } return self::$instance; } /** * Register custom post type. * * @since 6.2 * @return void */ public function register_post_type() { $labels = [ 'name' => _x( 'Custom Icons', 'Avada Icon', 'Avada' ), 'singular_name' => _x( 'Icon Set', 'Avada Icon', 'Avada' ), 'add_new' => _x( 'Add New', 'Avada Icon', 'Avada' ), 'add_new_item' => _x( 'Add New Icon Set', 'Avada Icon', 'Avada' ), 'edit_item' => _x( 'Edit Icon Set', 'Avada Icon', 'Avada' ), 'new_item' => _x( 'New Icon Set', 'Avada Icon', 'Avada' ), 'all_items' => _x( 'All Icon Sets', 'Avada Icon', 'Avada' ), 'view_item' => _x( 'View Icon Set', 'Avada Icon', 'Avada' ), 'search_items' => _x( 'Search Icon Sets', 'Avada Icon', 'Avada' ), 'not_found' => _x( 'No Icon Sets found', 'Avada Icon', 'Avada' ), 'not_found_in_trash' => _x( 'No Icon Sets found in Trash', 'Avada Icon', 'Avada' ), 'parent_item_colon' => '', 'menu_name' => _x( 'Custom Icons', 'Avada Icon', 'Avada' ), ]; $args = [ 'labels' => $labels, 'public' => false, 'rewrite' => false, 'show_ui' => true, 'show_in_menu' => false, 'show_in_nav_menus' => false, 'exclude_from_search' => true, 'capability_type' => 'post', 'hierarchical' => false, 'supports' => [ 'title' ], ]; register_post_type( $this->post_type, apply_filters( 'fusion_custom_icons_args', $args ) ); // phpcs:ignore WPThemeReview.PluginTerritory.ForbiddenFunctions.plugin_territory_register_post_type } /** * Removes Slider Revolution metabox from new / edit screen. * * @access public * @since 6.2 * * @param string $screen Screen identifier. * @param string $context The screen context for which to display meta boxes. * @param object $post Post object. * @return void */ public function remove_revolution_slider_meta_box( $screen, $context, $post ) { if ( 'normal' !== $context ) { return; } remove_meta_box( 'mymetabox_revslider_0', $this->post_type, 'normal' ); } /** * Adds assets to the compiled CSS. * * @access public * @since 3.4 * @param string $original_styles The compiled styles. * @return string The compiled styles with any additional CSS appended. */ public function combine_stylesheets( $original_styles ) { $icon_style = ''; $icon_styles = ''; $icon_sets = fusion_get_custom_icons_array(); foreach ( $icon_sets as $key => $icon_set ) { if ( isset( $icon_set['icon_set_dir_name'] ) && '' !== $icon_set['icon_set_dir_name'] ) { $icon_style_path = FUSION_ICONS_BASE_DIR . $icon_set['icon_set_dir_name'] . '/style.css'; if ( file_exists( $icon_style_path ) ) { $icon_style = fusion_file_get_contents( $icon_style_path ); $icon_style = str_replace( 'fonts/', FUSION_ICONS_BASE_URL . $icon_set['icon_set_dir_name'] . '/fonts/', $icon_style ); $icon_styles .= $icon_style; } } } if ( 'swap-all' === fusion_library()->get_option( 'font_face_display' ) ) { $icon_styles = str_replace( 'font-display: block', 'font-display: swap', $icon_styles ); } return $icon_styles . $original_styles; } /** * Enqueue front end scripts, used in Live Editor. * * @since 2.2.0 * @return void */ public function enqueue_scripts() { global $fusion_library_latest_version; $icon_sets = fusion_get_custom_icons_array(); foreach ( $icon_sets as $key => $icon_set ) { if ( isset( $icon_set['css_url'] ) && '' !== $icon_set['css_url'] ) { wp_enqueue_style( 'fusion-custom-icons-' . $key, $icon_set['css_url'], [], $fusion_library_latest_version, 'all' ); } } } /** * Enqueue admin scripts. * * @since 2.2.0 * @param string $hook_suffix The current admin page. * @return void */ public function enqueue_admin_scripts( $hook_suffix ) { global $fusion_library_latest_version, $typenow, $post; $current_screen_id = null; $allowed_screens = [ 'nav-menus', 'appearance_page_avada_options' ]; if ( function_exists( 'get_current_screen' ) ) { $current_screen = get_current_screen(); $current_screen_id = $current_screen->id; } if ( 'post-new.php' !== $hook_suffix && 'post.php' !== $hook_suffix && ! in_array( $current_screen_id, $allowed_screens, true ) ) { return; } if ( get_post_type() === $this->post_type ) { $min = ''; if ( ( ! defined( 'FUSION_LIBRARY_DEV_MODE' ) || ! FUSION_LIBRARY_DEV_MODE ) ) { $min = 'min/'; } // Enqueue WP media. wp_enqueue_media(); // Scripts. wp_enqueue_script( 'fusion-custom-icons', trailingslashit( FUSION_LIBRARY_URL ) . 'assets/' . $min . 'js/general/fusion-custom-icons.js', [ 'jquery' ], $fusion_library_latest_version, false ); wp_enqueue_script( 'plupload-handlers' ); // Styles. wp_enqueue_style( 'fusion-custom-icons', trailingslashit( FUSION_LIBRARY_URL ) . 'assets/css/fusion-custom-icons.css', [], $fusion_library_latest_version, 'all' ); // Icon set is already saved. if ( 'post.php' === $hook_suffix ) { $css_url = fusion_get_custom_icons_css_url(); if ( $css_url ) { wp_enqueue_style( 'fusion-custom-icons-style', $css_url, [], get_the_ID(), 'all' ); } } } // Enqueue custom icon's styles. if ( isset( $typenow ) && class_exists( 'FusionBuilder' ) && in_array( $typenow, FusionBuilder::allowed_post_types(), true ) || in_array( $current_screen_id, $allowed_screens, true ) ) { $icon_sets = fusion_get_custom_icons_array(); foreach ( $icon_sets as $key => $icon_set ) { if ( isset( $icon_set['css_url'] ) && '' !== $icon_set['css_url'] ) { wp_enqueue_style( 'fusion-custom-icons-' . $key, $icon_set['css_url'], [], $fusion_library_latest_version, 'all' ); } } } } /** * Adds the fusionUploaderOptions global var. * * @access public * @since 2.2.0 * @return void */ public function admin_head() { $uploader_options = [ 'runtimes' => 'html5,silverlight,flash,html4', 'browse_button' => 'fusion-icons-uploader-button', 'container' => 'fusion-icons-uploader-wrapper', 'drop_element' => 'fusion-icons-uploader-drop-zone', 'file_data_name' => 'async-upload', 'multiple_queues' => true, 'max_file_size' => wp_max_upload_size() . 'b', 'url' => admin_url( 'admin-ajax.php' ), 'flash_swf_url' => includes_url( 'js/plupload/plupload.flash.swf' ), 'silverlight_xap_url' => includes_url( 'js/plupload/plupload.silverlight.xap' ), 'filters' => [ [ 'title' => __( 'Allowed Files' ), 'extensions' => 'zip', ], ], 'multipart' => true, 'urlstream_upload' => true, 'multi_selection' => true, 'multipart_params' => [ '_ajax_nonce' => '', 'action' => 'fusion-icons-uploader-action', ], ]; ?> true, 'action' => 'fusion-icons-uploader-action', ] ); // Send the file' url as response. if ( is_wp_error( $id ) ) { $response['status'] = 'error'; $response['error'] = $id->get_error_messages(); } else { $response['status'] = 'success'; $response['attachment'] = []; $response['attachment']['id'] = $id; } } echo wp_json_encode( $response ); die(); } /** * Display 'duplicate css prefix note' if needed. * * @since 6.2 * @return void */ public function add_duplicate_prefix_notice() { global $post, $pagenow; if ( ! is_admin() || 'post.php' !== $pagenow || $post->post_type !== $this->post_type ) { return; } $icon_set = fusion_data()->post_meta( $post->ID )->get( 'custom_icon_set' ); if ( isset( $icon_set['css_prefix'] ) && true === $this->is_duplicate_prefix( $icon_set['css_prefix'] ) && class_exists( 'Fusion_Admin_Notice' ) ) { new Fusion_Admin_Notice( 'fusion-custom-icons-notice', '

' . esc_html__( 'Icon set with same CSS prefix already exists!', 'Avada' ) . '

' . esc_html__( 'Icon set with same CSS prefix already exists! Please use unique prefix in order to avoid conflicts.', 'Avada' ) . '

', true, 'error', true, 'user_meta', 'the-meta-custom-icons', [ 'fusion_icons' ] ); } } /** * Display 'duplicate css prefix note' if needed. * * @since 6.2 * @param int $post_id The post-ID. * @return void */ protected function change_post_status_to_draft( $post_id ) { $duplicate_prefix = fusion_data()->post_meta( $post_id )->get( 'duplicate_css_prefix' ); if ( true === $duplicate_prefix ) { wp_update_post( [ 'ID' => $post_id, 'post_status' => 'draft', ] ); } } /** * Add metaboxes. * * @since 6.2 * @return void */ public function add_meta_box() { add_meta_box( 'fusion-custom-icons-metabox', __( 'Icon Set', 'Avada' ), [ $this, 'render_metabox' ], $this->post_type, 'normal', 'default' ); } /** * Meta box callback, outputs metabox content. * * @since 6.2 * @return void */ public function render_metabox() { global $post; $icon_set = fusion_data()->post_meta( $post->ID )->get( 'custom_icon_set' ); $icon_set = wp_parse_args( $icon_set, $this->post_meta_defaults ); $is_new_icon_set = empty( $icon_set['icon_set_dir_name'] ) ? true : false; $buton_label = $is_new_icon_set ? __( 'Browse Files', 'Avada' ) : __( 'Update Custom Icon Set', 'Avada' ); $wrapper_class = $is_new_icon_set ? 'fusion-no-custom-icons-uploaded' : 'fusion-custom-icons-uploaded'; ?>

. %3$s Note: */ esc_html__( 'Supported Icon Tool - %1$s %2$s %3$s Every uploaded custom icon set needs a unique font name and CSS class prefix. For more info, see the %4$s.', 'Avada' ), 'Icomoon', '
', '' . esc_html__( 'NOTE:', 'Avada' ) . '', 'Custom Icon documentation' ); ?>

wp_filesystem, 'exists' ) && $this->wp_filesystem->exists( FUSION_ICONS_BASE_DIR . $icon_set['icon_set_dir_name'] . '/selection.json' ); if ( $json_exists ) : ?>
get_icons_html(); // phpcs:ignore WordPress.Security.EscapeOutput ?>
post_type . '_post_saved', $post_id ); } /** * Processes icon package, exctracts files and saves post meta. * Separate method as it might be needed to be called as AJAX callback. * * @since 6.2 * @param int $post_id Post ID. * @return void */ public function process_upload( $post_id ) { // Early exit if post ID is not valid. if ( ! $post_id || ( isset( $_POST['fusion-custom-icons']['icon_set_update'] ) && 'true' !== $_POST['fusion-custom-icons']['icon_set_update'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification return; } // Remove icon set files if we're updating. if ( isset( $_POST['fusion-custom-icons']['icon_set_update'] ) && 'true' === $_POST['fusion-custom-icons']['icon_set_update'] ) { // phpcs:ignore WordPress.Security.NonceVerification $this->delete_icon_set( $post_id ); } $icon_set = []; // Get $_POST values and set defaults. foreach ( $this->post_meta_defaults as $key => $value ) { $icon_set[ $key ] = isset( $_POST['fusion-custom-icons'][ $key ] ) ? sanitize_text_field( wp_unslash( $_POST['fusion-custom-icons'][ $key ] ) ) : $this->post_meta_defaults[ $key ]; // phpcs:ignore WordPress.Security.NonceVerification } // Return if attachment ID is not set. if ( empty( $icon_set['attachment_id'] ) ) { return; } // Create base directory if it's not there. if ( ! file_exists( FUSION_ICONS_BASE_DIR ) ) { wp_mkdir_p( FUSION_ICONS_BASE_DIR ); } // Get package path. $package_path = get_attached_file( $icon_set['attachment_id'] ); $status = false; if ( $package_path && file_exists( $package_path ) ) { // Create icon set path. $icon_set_dir_name = $this->get_unique_dir_name( pathinfo( $package_path, PATHINFO_FILENAME ), FUSION_ICONS_BASE_DIR ); $icon_set_path = FUSION_ICONS_BASE_DIR . $icon_set_dir_name; // Attempt to manually extract the zip file first. Required for fptext method. if ( class_exists( 'ZipArchive' ) ) { $zip = new ZipArchive(); $to_extract = []; if ( true === $zip->open( $package_path ) ) { // Check if icomoon file extensions are allowed. for ( $i = 0; $i < $zip->numFiles; $i++ ) { $filename = $zip->getNameIndex( $i ); if ( preg_match( '/(\.json$|\.js$|\.css$|\.txt$|\.ttf$|\.woff$|\.woff2$|\.otf$|\.eot$|\.scss$|\.less$|\.styl$|\/$)/', $filename ) ) { $to_extract[] = $filename; } } if ( ! empty( $to_extract ) ) { wp_mkdir_p( $icon_set_path ); // Create icon set directory. $status = $zip->extractTo( $icon_set_path, $to_extract ); } $zip->close(); } } } // Update post meta if extract didn't fail. if ( true === $status ) { $icon_set['icon_set_dir_name'] = $icon_set_dir_name; // Parse package. $parsed_package = $this->parse_icons_package( $icon_set_dir_name ); // Update post meta with package data. foreach ( $parsed_package as $key => $value ) { $icon_set[ $key ] = $parsed_package[ $key ]; } $icon_set['icon_set_id'] = md5( $post_id . $icon_set['attachment_id'] ); // Finally save post meta. fusion_data()->post_meta( $post_id )->set( 'custom_icon_set', $icon_set ); // Add icon set id to attachment as well. update_post_meta( $icon_set['attachment_id'], '_fusion_icon_set_id', $icon_set['icon_set_id'] ); } } /** * Processes icon package, extracts files and saves post meta. * Separate method as it might be needed to be called as AJAX callback. * * @since 6.2 * @param int $post_id Post ID. * @return void */ public function regenerate_icon_files( $post_id ) { // Early exit if post ID is not valid. if ( ! $post_id ) { return; } // Remove icon set files. $this->delete_icon_set( $post_id ); $icon_set = fusion_data()->post_meta( $post_id )->get( 'custom_icon_set' ); // Return if attachment ID is not set. if ( empty( $icon_set['attachment_id'] ) ) { return; } // Create base directory if it's not there. if ( ! file_exists( FUSION_ICONS_BASE_DIR ) ) { wp_mkdir_p( FUSION_ICONS_BASE_DIR ); } // Get package path. $package_path = get_attached_file( $icon_set['attachment_id'] ); $status = false; if ( $package_path && file_exists( $package_path ) ) { // Create icon set path. $icon_set_dir_name = $this->get_unique_dir_name( pathinfo( $package_path, PATHINFO_FILENAME ), FUSION_ICONS_BASE_DIR ); $icon_set_path = FUSION_ICONS_BASE_DIR . $icon_set_dir_name; // Attempt to manually extract the zip file first. Required for fptext method. if ( class_exists( 'ZipArchive' ) ) { $zip = new ZipArchive(); $to_extract = []; if ( true === $zip->open( $package_path ) ) { // Check if icomoon file extensions are allowed. for ( $i = 0; $i < $zip->numFiles; $i++ ) { $filename = $zip->getNameIndex( $i ); if ( preg_match( '/(\.json$|\.js$|\.css$|\.txt$|\.ttf$|\.woff$|\.woff2$|\.otf$|\.eot$|\.scss$|\.less$|\.styl$|\/$)/', $filename ) ) { $to_extract[] = $filename; } } if ( ! empty( $to_extract ) ) { wp_mkdir_p( $icon_set_path ); // Create icon set directory. $status = $zip->extractTo( $icon_set_path, $to_extract ); } $zip->close(); } } } // Update post meta if extract didn't fail. if ( true === $status ) { $icon_set['icon_set_dir_name'] = $icon_set_dir_name; // Parse package. $parsed_package = $this->parse_icons_package( $icon_set_dir_name ); // Update post meta with package data. foreach ( $parsed_package as $key => $value ) { $icon_set[ $key ] = $parsed_package[ $key ]; } // Update post meta. fusion_data()->post_meta( $post_id )->set( 'custom_icon_set', $icon_set ); } } /** * Checks if icon set with same prefix already exists. * * @since 6.2 * @param string $css_prefix Icon set prefix. * @return bool */ protected function is_duplicate_prefix( $css_prefix ) { global $post; if ( $css_prefix ) { // Check if there is conflict with our icon fonts. if ( in_array( $css_prefix, $this->reserved_css_prefixes, true ) ) { return true; } // Exclude currently edited post. $custom_icon_sets = fusion_get_custom_icons_array( [ 'post__not_in' => [ $post->ID ] ] ); foreach ( $custom_icon_sets as $custom_set ) { if ( $css_prefix === $custom_set['css_prefix'] ) { return true; } } } return false; } /** * Delete icon set directory and do general cleanup. * * @since 6.2 * @param int $post_id Post ID. * @return void */ public function delete_icon_set( $post_id ) { if ( get_post_type( $post_id ) !== $this->post_type ) { return; } $icon_set = fusion_data()->post_meta( $post_id )->get( 'custom_icon_set' ); if ( isset( $icon_set['icon_set_dir_name'] ) ) { $icon_set_path = FUSION_ICONS_BASE_DIR . $icon_set['icon_set_dir_name']; // Delete directory. $this->wp_filesystem->rmdir( $icon_set_path, true ); } } /** * Get unique directory name for passed parent directory. * * @since 6.2 * @param string $dir_name Name of the directory. * @param string $parent_dir_path Path of the parent directory. * @return string Unique directory name. */ protected function get_unique_dir_name( $dir_name, $parent_dir_path ) { $parent_dir_path = trailingslashit( $parent_dir_path ); $dir_path = $parent_dir_path . $dir_name; $counter = 0; $tmp_name = $dir_name; while ( file_exists( $dir_path ) ) { $counter++; $dir_name = $tmp_name . '-' . $counter; $dir_path = $parent_dir_path . $dir_name; } return $dir_name; } /** * Get package config. * * @since 6.2 * @param string $icon_set_dir_name Icon set dir name. * @return array Config array. */ protected function get_package_config( $icon_set_dir_name ) { if ( ! isset( $this->package_config[ $icon_set_dir_name ] ) ) { $json_file = $this->wp_filesystem->get_contents( FUSION_ICONS_BASE_DIR . '/' . $icon_set_dir_name . '/selection.json' ); $this->package_config[ $icon_set_dir_name ] = json_decode( $json_file, true ); } return $this->package_config[ $icon_set_dir_name ]; } /** * Parse package. * * @since 6.2 * @param string $icon_set_dir_name Icon set dir name. * @return array Post meta array. */ protected function parse_icons_package( $icon_set_dir_name ) { // Get icons config file. $icons_config = $this->get_package_config( $icon_set_dir_name ); $parsed_package = []; $parsed_package['icons'] = []; // Add icons. foreach ( $icons_config['icons'] as $icon ) { $parsed_package['icons'][] = $icon['properties']['name']; } // Set icon prefix. $parsed_package['css_prefix'] = $icons_config['preferences']['fontPref']['prefix']; // Set icon count. $parsed_package['icon_count'] = count( $parsed_package['icons'] ); return $parsed_package; } /** * Get icon HTML code, for example to be used in a meta box. * * @since 6.2 * @param int $post_id Post ID. * @return string HTML code. */ public function get_icons_html( $post_id = 0 ) { if ( ! $post_id ) { $post_id = get_the_ID(); } $icon_set = fusion_data()->post_meta( $post_id )->get( 'custom_icon_set' ); $html = ''; foreach ( $icon_set['icons'] as $icon ) { $html .= '' . esc_html( $icon ) . ''; } return $html; } /** * Create a new library element, fired from library page. */ public function add_new_custom_icon_set() { check_admin_referer( 'fusion_new_custom_icon_set' ); if ( ! current_user_can( apply_filters( 'awb_role_manager_access_capability', 'edit_posts', 'fusion_icons' ) ) ) { return; } $custom_icon_set = [ 'post_title' => isset( $_GET['name'] ) ? sanitize_text_field( wp_unslash( $_GET['name'] ) ) : '', 'post_status' => current_user_can( 'publish_posts' ) ? 'publish' : 'pending', 'post_type' => $this->post_type, ]; $set_id = wp_insert_post( $custom_icon_set, true ); if ( is_wp_error( $set_id ) ) { $error_string = $set_id->get_error_message(); wp_die( esc_html( $error_string ) ); } // Just redirect to back-end editor. In future tie it to default editor option. wp_safe_redirect( get_edit_post_link( $set_id, false ) ); die(); } } /* Omit closing PHP tag to avoid "Headers already sent" issues. */