<?php namespace Mai\Builder; use WP_HTML_Tag_Processor; use WP_REST_Request; use WP_REST_Response; use RecursiveIteratorIterator; use RecursiveDirectoryIterator; defined( 'ABSPATH' ) || exit; /** * Icons Block class. * * @since 0.1.0 * * @version 0.1.0 * * @uses Icons Block from Nick Diego. * @link https://wordpress.org/plugins/icon-block/ * * @return void */ class IconBlock { /** * Icons. * * @since TBD * * @var array */ protected $icons; /** * Categories. * * @since TBD * * @var array */ protected $categories; /** * Instance of the class. * * @var IconBlock|null */ private static ?IconBlock $instance = null; /** * Constructor. * * @since TBD */ function __construct() { $this->icons = []; $this->categories = []; $this->hooks(); } /** * Get instance of the class. * * @since 0.1.0 * * @return IconBlock */ public static function get_instance(): IconBlock { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Hooks. * * @since TBD * * @return void */ function hooks() { add_action( 'rest_api_init', [ $this, 'register_rest_route' ] ); add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_script' ] ); add_filter( 'render_block_outermost/icon-block', [ $this, 'render_block' ], 10, 2 ); } /** * Register REST route. * * @since 0.1.0 * * @return void */ function register_rest_route() { register_rest_route( 'mai/v1', '/icons(?:/(?P<path>.+))?', [ 'methods' => 'GET', 'callback' => [ $this, 'get_icons' ], 'permission_callback' => function() { return current_user_can( 'edit_posts' ); }, 'args' => [ 'path' => [ 'required' => false, 'type' => 'string', 'description' => __( 'The subpath within the icons directory to filter by.', 'mai-builder' ), ], ], ]); } /** * Get icons. * * @since 0.1.0 * * @param WP_REST_Request $request The REST API request. * * @return WP_REST_Response */ function get_icons( WP_REST_Request $request ) { $base_dir = get_template_directory() . '/mai/icons'; $path = $request->get_param( 'path' ); $target_dir = $path ? realpath( $base_dir . '/' . $path ) : $base_dir; // Ensure the target directory is within the base icons directory. if ( ! str_contains( $target_dir, $base_dir ) || ! is_dir( $target_dir ) ) { return new WP_REST_Response( [ 'error' => __( 'Invalid directory path.', 'mai-builder' ) ], 400 ); } // Start icons and categories. $this->icons = []; $this->categories = []; // If no path, check for root level SVGs. if ( ! $path ) { $root_svgs = glob( $base_dir . '/*.svg' ); // If root level SVGs. if ( ! empty( $root_svgs ) ) { $cat_slug = '_theme'; $cat_title = __( 'Theme', 'mai-builder' ); // Add category. $this->categories[] = [ 'name' => $cat_slug, 'title' => $cat_title, ]; // Add icons. foreach ( $root_svgs as $svg ) { $filename = basename( $svg, '.svg' ); $this->icons[] = [ 'name' => $filename, 'title' => $this->format_title( $filename ), 'icon' => file_get_contents( $svg ), 'categories' => [ $cat_slug ], ]; } } } // Get iterator. $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_dir, RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::SELF_FIRST ); // Loop through the iterator. foreach ( $iterator as $file ) { // Skip if not a directory. if ( ! $file->isDir() ) { continue; } // Get SVGs in this directory. $svg_files = glob( $file->getPathname() . '/*.svg' ); // Skip if no SVGs. if ( empty( $svg_files ) ) { continue; } // Set category name. $cat_base = str_replace( $base_dir . '/', '', $file->getPathname() ); $cat_slug = str_replace( '/', '-', $cat_base ); $cat_title = str_replace( '-', ' ', $cat_base ); // Add category. $this->categories[] = [ 'name' => $cat_slug, 'title' => $cat_title, ]; // Add icons. foreach ( $svg_files as $svg ) { $filename = basename( $svg, '.svg' ); $this->icons[] = [ 'name' => sanitize_title( $cat_slug . '-' . $filename ), 'title' => $this->format_title( $filename ), 'icon' => file_get_contents( $svg ), 'categories' => [ $cat_slug ], ]; } } // Sort categories. usort( $this->categories, function( $a, $b ) { return strcmp( $a['title'], $b['title'] ); } ); // Return the icons and categories. return new WP_REST_Response( [ 'icons' => $this->icons, 'categories' => $this->categories, ], 200 ); } /** * Format title. * * @since 0.1.0 * * @param string $title Title to format. * * @return string */ function format_title( $title ) { return ucwords( str_replace( ['-', '_', '/'], ' ', $title ) ); } /** * Enqueue script. * * @since 0.1.0 * * @return void */ function enqueue_script() { $icons_asset = require( plugin_dir_path( __DIR__ ) . 'build/icon-block-icons.asset.php' ); $divider_asset = require( plugin_dir_path( __DIR__ ) . 'build/icon-block-divider.asset.php' ); wp_enqueue_script( 'mai-icon-block-icons', plugins_url( 'build/icon-block-icons.js', dirname( __FILE__ ) ), $icons_asset['dependencies'], $icons_asset['version'], true // Very important, otherwise the filter is called too early. ); wp_enqueue_script( 'mai-icon-block-divider', plugins_url( 'build/icon-block-divider.js', dirname( __FILE__ ) ), $divider_asset['dependencies'], $divider_asset['version'], true // Very important, otherwise the filter is called too early. ); } /** * Convert viewport units to pixels in icon block height. * This is for the divider functionality so we don't get partial pixel gaps. * * @since 0.1.0 * * @param string $block_content The block content. * @param array $block The block data. * * @return string */ function render_block( $block_content, $block ) { // Bail if no height. if ( ! isset( $block['attrs']['height'] ) ) { return $block_content; } // Bail if height string does not end with 'vh' or 'vw'. if ( ! ( str_ends_with( $block['attrs']['height'], 'vh' ) || str_ends_with( $block['attrs']['height'], 'vw' ) ) ) { return $block_content; } // Use WP_HTML_Tag_Processor to modify the style attribute. $processor = new WP_HTML_Tag_Processor( $block_content ); // Get tag. if ( $processor->next_tag( [ 'class_name' => 'icon-container' ] ) ) { // Get the style attribute and replace the height value with the rounded value. $style = $processor->get_attribute( 'style' ); $style = str_replace( 'height:' . $block['attrs']['height'], 'height:round(' . $block['attrs']['height'] . ', 1px)', $style ); // Set the style attribute. $processor->set_attribute( 'style', $style ); } // Get updated HTML. $block_content = $processor->get_updated_html(); return $block_content; } }