You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

307 lines
8.1 KiB

9 months ago
<?php
defined( 'ABSPATH' ) || die;
/**
* The icon field.
*/
class RWMB_Icon_Field extends RWMB_Select_Advanced_Field {
const CACHE_GROUP = 'meta-box-icon-field';
public static function admin_enqueue_scripts() {
parent::admin_enqueue_scripts();
wp_enqueue_style( 'rwmb-icon', RWMB_CSS_URL . 'icon.css', [], RWMB_VER );
wp_style_add_data( 'rwmb-icon', 'path', RWMB_CSS_DIR . 'icon.css' );
wp_enqueue_script( 'rwmb-icon', RWMB_JS_URL . 'icon.js', [ 'rwmb-select2', 'rwmb-select', 'underscore' ], RWMB_VER, true );
$args = func_get_args();
$field = $args[0];
self::enqueue_icon_font_style( $field );
}
private static function enqueue_icon_font_style( array $field ): void {
if ( is_string( $field['icon_css'] ) ) {
$handle = md5( $field['icon_css'] );
wp_enqueue_style( $handle, $field['icon_css'], [], RWMB_VER );
} elseif ( is_callable( $field['icon_css'] ) ) {
$field['icon_css']();
}
}
private static function get_icons( array $field ): array {
// Get from cache to prevent reading large files.
$params = [
'icon_file' => $field['icon_file'],
'icon_dir' => $field['icon_dir'],
'icon_css' => is_string( $field['icon_css'] ) ? $field['icon_css'] : '',
];
$cache_key = md5( serialize( $params ) ) . '-icons';
$icons = wp_cache_get( $cache_key, self::CACHE_GROUP );
if ( false !== $icons ) {
return $icons;
}
$data = self::parse_icon_data( $field );
// Reformat icons.
$icons = [];
foreach ( $data as $key => $icon ) {
$icon = self::normalize_icon( $field, $key, $icon );
if ( is_numeric( key( $icon ) ) ) {
$icons = array_merge( $icons, $icon );
continue;
}
$icons[] = $icon;
}
// Cache the result.
wp_cache_set( $cache_key, $icons, self::CACHE_GROUP );
return $icons;
}
private static function parse_icon_data( array $field ): array {
$keys = [
'icon_file',
'icon_css',
'icon_dir',
];
foreach ( $keys as $key ) {
if ( ! empty( $field[ $key ] ) && is_string( $field[ $key ] ) ) {
return call_user_func( [ __CLASS__, "parse_$key" ], $field );
}
}
return [];
}
private static function parse_icon_file( array $field ): array {
if ( ! file_exists( $field['icon_file'] ) ) {
return [];
}
$data = (string) file_get_contents( $field['icon_file'] );
$decoded = json_decode( $data, true );
// JSON file.
if ( JSON_ERROR_NONE === json_last_error() ) {
return $decoded;
}
// Text file: each icon on a line.
return array_map( 'trim', explode( "\n", $data ) );
}
private static function parse_icon_css( array $field ): array {
// Parse local CSS file only.
$file = self::url_to_path( $field['icon_css'] );
if ( ! file_exists( $file ) ) {
return [];
}
$css = (string) file_get_contents( $file );
preg_match_all( '/\.([^\s:]+):before/', $css, $matches );
if ( empty( $matches[1] ) ) {
preg_match_all( '/\.([^\s:]+)/', $css, $matches );
}
return $matches[1];
}
private static function parse_icon_dir( array $field ): array {
$dir = $field['icon_dir'];
if ( ! is_dir( $dir ) ) {
return [];
}
$icons = [];
$files = glob( trailingslashit( $dir ) . '*.svg' );
foreach ( $files as $file ) {
$filename = substr( basename( $file ), 0, -4 );
$icons[] = [
'value' => $filename,
'label' => $filename,
'svg' => file_get_contents( $file ),
];
}
return $icons;
}
private static function normalize_icon( array $field, $key, $icon ): array {
// Default: Font Awesome Free.
if ( $field['icon_set'] === 'font-awesome-free' ) {
$style = $icon['styles'][0];
return [
'value' => "fa-{$style} fa-{$key}",
'label' => $icon['label'],
'svg' => $icon['svg'][ $style ]['raw'],
];
}
// Font Awesome Pro.
if ( $field['icon_set'] === 'font-awesome-pro' ) {
$icons = [];
foreach ( $icon['styles'] as $style ) {
$icons[] = [
'value' => "fa-{$style} fa-{$key}",
'label' => "{$icon[ 'label' ]} ({$style})",
'svg' => $icon['svg'][ $style ]['raw'],
];
}
return $icons;
}
// JSON file: "icon-class": { "label": "Label", "svg": "<svg...>" }
if ( is_array( $icon ) ) {
$label = empty( $icon['label'] ) ? $key : $icon['label'];
$svg = empty( $icon['svg'] ) ? '' : $icon['svg'];
return [
'value' => $key,
'label' => $label,
'svg' => $svg,
];
}
// JSON file: "icon-class": "Label" or "icon-class": "<svg...>".
if ( is_string( $key ) ) {
$label = str_contains( $icon, '<svg' ) ? $key : $icon;
$svg = str_contains( $icon, '<svg' ) ? $icon : '';
return [
'value' => $key,
'label' => $label,
'svg' => $svg,
];
}
// Parse classes from CSS.
if ( $field['icon_css'] && ! $field['icon_file'] ) {
$icon = trim( $field['icon_base_class'] . ' ' . $icon );
}
// Text file: each icon on a line.
return [
'value' => $icon,
'label' => $icon,
'svg' => '',
];
}
private static function get_svg( array $field, string $value ): string {
$file = trailingslashit( $field['icon_dir'] ) . $value . '.svg';
return file_exists( $file ) ? file_get_contents( $file ) : '';
}
private static function get_options( array $field ): array {
$icons = self::get_icons( $field );
$options = [];
foreach ( $icons as $icon ) {
$svg = ! $icon['svg'] && $field['icon_dir'] ? self::get_svg( $field, $icon['value'] ) : $icon['svg'];
$options[] = [
'value' => $icon['value'],
'label' => $svg . $icon['label'],
];
}
return $options;
}
/**
* Normalize field settings.
*
* @param array $field Field settings.
* @return array
*/
public static function normalize( $field ) {
$field = wp_parse_args( $field, [
'placeholder' => __( 'Select an icon', 'meta-box' ),
'icon_css' => '',
'icon_set' => '',
'icon_file' => '',
'icon_dir' => '',
'icon_base_class' => '',
] );
// Ensure absolute paths and URLs.
$field['icon_file'] = self::ensure_absolute_path( $field['icon_file'] );
$field['icon_dir'] = self::ensure_absolute_path( $field['icon_dir'] );
if ( is_string( $field['icon_css'] ) && $field['icon_css'] ) {
$field['icon_css'] = self::ensure_absolute_url( $field['icon_css'] );
}
// Font Awesome Pro.
if ( $field['icon_set'] === 'font-awesome-pro' ) {
} elseif ( $field['icon_file'] || $field['icon_dir'] || $field['icon_css'] ) {
// Custom icon set.
$field['icon_set'] = 'custom';
} else {
// Font Awesome Free.
$field['icon_set'] = 'font-awesome-free';
$field['icon_file'] = RWMB_DIR . 'css/fontawesome/icons.json';
}
$field['options'] = self::get_options( $field );
$field = parent::normalize( $field );
return $field;
}
/**
* Format value for the helper functions.
*
* @param array $field Field parameters.
* @param string|array $value The field meta value.
* @param array $args Additional arguments. Rarely used. See specific fields for details.
* @param int|null $post_id Post ID. null for current post. Optional.
*
* @return string
*/
public static function format_value( $field, $value, $args, $post_id ) {
// SVG from file.
if ( $field['icon_dir'] ) {
return self::get_svg( $field, $value );
}
$icons = self::get_icons( $field );
$key = array_search( $value, array_column( $icons, 'value' ) );
if ( false === $key ) {
return '';
}
// Embed SVG.
if ( $icons[ $key ]['svg'] ) {
return $icons[ $key ]['svg'];
}
// Render with class and use css.
self::enqueue_icon_font_style( $field );
return sprintf( '<span class="%s"></span>', $value );
}
private static function url_to_path( string $url ): string {
return str_starts_with( $url, home_url( '/' ) ) ? str_replace( home_url( '/' ), trailingslashit( ABSPATH ), $url ) : '';
}
private static function ensure_absolute_path( string $path ): string {
if ( ! $path || file_exists( $path ) ) {
return $path;
}
$root = wp_normalize_path( ABSPATH );
$path = wp_normalize_path( $path );
return str_starts_with( $path, $root ) ? $path : trailingslashit( $root ) . ltrim( $path, '/' );
}
private static function ensure_absolute_url( string $url ): string {
return filter_var( $url, FILTER_VALIDATE_URL ) ? $url : home_url( $url );
}
}