Action Hooks * * Fires the following WordPress action hooks: * * - `font_awesome_preferences` * * Fired when the plugin is ready for clients to register their preferences. * * Client plugins and themes should normally use this action hook to call {@see FontAwesome::register()} * with their preferences. * * The hook passes no parameters to its callbacks. * * - `font_awesome_enqueued` * * Called when a version of Font Awesome has been successfully prepared for enqueuing. * * Clients should register a callback on this action to be notified when it is valid to query the FontAwesome * plugin's metadata using methods such as: * - {@see FontAwesome::version()} to discover the version of Font Awesome being loaded * - {@see FontAwesome::pro()} to discover whether a version with Pro icons is being loaded * - {@see FontAwesome::pseudo_elements()} to discover whether Font Awesome is being loaded with support for svg pseudo-elements * *

Internal Use vs. Public API

* * Developers should take care to notice which functions, methods, classes, * constants, defines, REST routes, or data structures are indicated as part of * this plugin's public API and which are not. * * A method, for example, being declared in PHP with `public` visibility does not * indicate its inclusion in the plugin's _public API_. * * A method may be declared with public visibility in PHP in order to satisfy * the language's requirements for access across code modules, or for callbacks. * Yet this does not mean it can be relied upon as a stable interface by client * code. * * A method that is part of _this plugin's public API_ can be relied upon to * change, or not change, according to [semantic versioning best practices](https://semver.org/). * No such conventions apply to a method that is for internal use only, even * if it is declared `public` in PHP. * * Generally, public API members are accessed only from this `FontAwesome` class. * * For example, the {@see FontAwesome::releases_refreshed_at()} method provides a way * to find out when releases metadata were last fetched from `api.fontawesome.com`. * It delegates to another class internally. But that other class and its methods * are not part of this plugin's public API. They may change significantly from * one patch release to another, but no breaking changes would be made to * {@see FontAwesome::releases_refreshed_at()} without a major version change. * * References to "API" in this section refer to this plugin's PHP code or REST * routes, not to the Font Awesome GraphQL API at `api.fontawesome.com`. * * @since 4.0.0 */ class FontAwesome { /** * Name of this plugin's shortcode tag. * * @since 4.0.0 */ public const SHORTCODE_TAG = 'icon'; /** * Default style prefix. * * @since 4.0.0 */ public const DEFAULT_PREFIX = 'fas'; /** * Key where this plugin's saved options data are stored in the WordPress options table. * * Internal use only, not part of this plugin's public API. * * @ignore * @internal */ public const OPTIONS_KEY = 'font-awesome'; /** * Key where this plugin stores conflict detection data in the WordPress options table. * * Internal use only, not part of this plugin's public API. * * @internal * @ignore */ public const CONFLICT_DETECTION_OPTIONS_KEY = 'font-awesome-conflict-detection'; /** * The unique WordPress plugin slug for this plugin. * * @since 4.0.0 */ public const PLUGIN_NAME = 'font-awesome'; /** * The version of this WordPress plugin. * * @since 4.0.0 */ public const PLUGIN_VERSION = '5.1.4'; /** * The namespace for this plugin's REST API. * * @internal * @deprecated * @ignore */ public const REST_API_NAMESPACE = self::PLUGIN_NAME . '/v1'; /** * The name of this plugin's options page, or WordPress admin dashboard page. * * @since 4.0.0 */ public const OPTIONS_PAGE = 'font-awesome'; /** * GET param used for linking to a particular starting tab in the admin UI. * * @ignore * @internal */ public const ADMIN_TAB_QUERY_VAR = 'tab'; /** * The handle used when enqueuing this plugin's resulting resource. * Used when this plugin calls either `wp_enqueue_script` or `wp_enqueue_style` to enqueue Font Awesome assets. * * @since 4.0.0 */ public const RESOURCE_HANDLE = 'font-awesome-official'; /** * The handle used when enqueuing the v4shim. * * @since 4.0.0 */ public const RESOURCE_HANDLE_V4SHIM = 'font-awesome-official-v4shim'; /** * The handle used when enqueuing the conflict detector. * * @ignore * @internal */ public const RESOURCE_HANDLE_CONFLICT_DETECTOR = 'font-awesome-official-conflict-detector'; /** * The handle used when enqueuing the icon chooser. * * @ignore * @internal */ public const RESOURCE_HANDLE_ICON_CHOOSER = 'font-awesome-official-icon-chooser'; /** * The handle used when enqueuing the bundle for supporting the Classic Editor. * * @ignore * @internal */ public const RESOURCE_HANDLE_CLASSIC_EDITOR = 'font-awesome-official-classic-editor'; /** * The handle used when enqueuing block editor assets. * * @ignore * @internal */ public const RESOURCE_HANDLE_FA_BLOCKS = 'font-awesome-official-blocks'; /** * The source URL for the conflict detector, a feature introduced in Font Awesome 5.10.0. * * @ignore * @internal */ public const CONFLICT_DETECTOR_SOURCE = 'https://use.fontawesome.com/releases/v6.6.0/js/conflict-detection.js'; /** * The custom data attribute added to script, link, and style elements enqueued * by this plugin when conflict detection is enabled, in order for them to be * ignored by the conflict detector. * * @internal * @ignore */ public const CONFLICT_DETECTION_IGNORE_ATTR = 'data-fa-detection-ignore'; /** * The base name of the handle used for enqueuing this plugin's admin assets, those required for running * the admin settings page. * * @ignore * @internal */ public const ADMIN_RESOURCE_HANDLE = self::RESOURCE_HANDLE . '-admin'; /** * Name used for inline data attached to the JavaScript admin bundle. * Not part of this plugin's public API. * * @internal * @ignore */ public const ADMIN_RESOURCE_LOCALIZATION_NAME = '__FontAwesomeOfficialPlugin__'; /** * Refresh the ReleaseProvider automatically no more often than this * number of seconds. * * Internal use only. Not part of this plugin's public API. * * @ignore * @internal */ public const RELEASES_REFRESH_INTERVAL = 10 * 60; /** * We will not use a default for version, since we want the version stored in the options * to always be resolved to an actual version number, which requires that the release * provider successfully runs at least once. We'll do that upon plugin activation. * * @ignore * @internal */ public const DEFAULT_USER_OPTIONS = array( 'usePro' => false, 'compat' => true, 'technology' => 'webfont', 'pseudoElements' => true, 'kitToken' => null, // whether the token is present, not the token's value. 'apiToken' => false, 'dataVersion' => 4, ); /** * Default conflict detection options. * * @ignore * @internal */ public const DEFAULT_CONFLICT_DETECTION_OPTIONS = array( 'detectConflictsUntil' => 0, 'unregisteredClients' => array(), ); /** * @internal * @ignore */ protected static $instance = null; /** * @internal * @ignore */ protected $client_preferences = array(); /** * @internal * @ignore */ protected $icon_chooser_screens = array( 'post.php', 'post-new.php', 'site-editor.php' ); /** * @internal * @ignore */ protected $conflicts_by_client = null; /** * @internal * @ignore */ protected $screen_id = null; /** * This tracks the state of whether, when we process options after the * plugin upgrades from using the v1 options schema to v2, the former * removeUnregisteredClients option was set. If so we use some automatic * conflict detection and resolution, like that old feature worked. * * Internal use only, not part of this plugin's public API. * * @deprecated * @internal * @ignore */ protected $old_remove_unregistered_clients = false; /** * If true, features required for Block Editor support will be disabled. * * Internal use only, not part of this plugin's public API. * * @deprecated * @internal * @ignore */ protected $disable_block_editor_support = false; /** * Returns the singleton instance of the FontAwesome plugin. * * @since 4.0.0 * * @see fa() * @return FontAwesome */ public static function instance() { if ( is_null( self::$instance ) ) { self::$instance = new self(); } return self::$instance; } /** * Internal use only, not part of this plugin's public API. * * @internal * @ignore */ private function __construct() { /** * Determines whether to disable features required for supporting the Block Editor. * * @return bool if `true`, disable Block Editor support * @since 5.0.2 */ $this->disable_block_editor_support = apply_filters( 'font_awesome_disable_block_editor_support', false ); } /** * Returns this plugin's admin page's screen_id. Only valid after the admin_menu hook has run. * * Internal only, not part of this plugin's public API. * * @ignore * @internal */ public function admin_screen_id() { return $this->screen_id; } /** * Callback for init. * * Main entry point for running the plugin. Called automatically when the plugin is loaded. * * Internal use only. * * @ignore * @internal */ public function init() { try { $this->try_upgrade(); $this->validate_options( fa()->options() ); $this->initialize_rest_api(); if ( is_admin() ) { $this->initialize_admin(); } add_shortcode( self::SHORTCODE_TAG, array( $this, 'process_shortcode' ) ); if ( $this->is_block_editor_support_enabled() ) { FontAwesome_SVG_Styles_Manager::register_svg_styles( $this ); block_init(); add_action( 'enqueue_block_assets', function () { wp_enqueue_style( FontAwesome_SVG_Styles_Manager::RESOURCE_HANDLE_SVG_STYLES ); } ); } try { $this->gather_preferences(); // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch } catch ( PreferenceRegistrationException $e ) { /** * Ignore this on normal page loads. * If something seems amiss, the site owner may try to look * into it on the plugin settings page where some additional * diagnostic information may be found. */ } $this->maybe_enqueue_admin_assets(); // Setup JavaScript internationalization if we're on WordPress 5.0+. if ( function_exists( 'wp_set_script_translations' ) ) { wp_set_script_translations( self::ADMIN_RESOURCE_HANDLE, 'font-awesome' ); } if ( $this->using_kit() ) { if ( $this->skip_enqueue_kit() ) { // Normally, conflict detection is built into a kit. // However, when not enqueuing the kit, we must enqueue conflict detection separately. $this->maybe_enqueue_conflict_detection(); } else { $this->enqueue_kit( $this->options()['kitToken'] ); } } else { $resource_collection = $this->cdn_resource_collection_for_current_options(); $this->enqueue_cdn( $this->options(), $resource_collection ); } } catch ( Exception $e ) { notify_admin_fatal_error( $e ); } catch ( Error $e ) { notify_admin_fatal_error( $e ); } } /** * Indicates whether to enqueue the kit. * * Internal use only, not part of the plugin's public API. * * However, this depends on the `font_awesome_skip_enqueue_kit` filter * which is part of the public API. * * @internal * @ignore */ protected function skip_enqueue_kit() { /** * Determines whether to skip the kit enqueue. * * When the plugin is configured to use a kit, the normal behavior is * to use `wp_enqueue_script()` to enqueue the kit's embed code, a JavaScript * loaded from the Font Awesome Kits CDN. The kit being loaded on front end page * renderings enables rendering `` tags as Font Awesome icons, for example. * * By setting this to `true`, that `wp_enqueue_script()` will be skipped. Thus, the * kit will not be loaded from the Font Awesome CDN on front end page loads. As a * consequence, this plugin will not render `` tags as Font Awesome icons. * * You may prefer to skip loading the kit if: * * 1. You only use the block editor. * * As of version 5.0.0 of this plugin, icons are added in the * block editor as `` elements and require no further rendering like `` tags. * * 2. You want to avoid using a CDN for front end page loads. * * Even when disabling the use of the CDN for front end page loads, the CDN is still used * when editing pages on the back with the icon chooser. The icon chooser loads SVG icons * from the CDN. When you choose one, the SVG content for that icon is added to your page. * * Default: false (that is, by default, enqueue the kit to be loaded from the CDN) * * @since 5.0.0 */ return apply_filters( 'font_awesome_skip_enqueue_kit', false ); } /** * Not part of this plugin's public API. * * @ignore * @internal * @throws ConfigCorruptionException * @return array */ public function cdn_resource_collection_for_current_options() { return FontAwesome_Release_Provider::get_resource_collection( $this->options()['version'], array( 'use_pro' => $this->pro(), 'use_svg' => 'svg' === $this->technology(), 'use_compatibility' => $this->v4_compatibility(), ) ); } /** * Detects whether upgrade is necessary and performs upgrade if so. * * Internal use only. * * @throws UpgradeException * @throws ApiRequestException * @throws ApiResponseException * @throws ReleaseProviderStorageException * @throws ReleaseMetadataMissingException * @throws ConfigCorruptionException if options are invalid * @internal * @ignore */ public function try_upgrade() { $options = get_option( self::OPTIONS_KEY ); if ( ! $options || ! is_array( $options ) ) { throw new ConfigCorruptionException(); } $should_upgrade = false; // Upgrade from v1 schema: 4.0.0-rc13 or earlier. if ( isset( $options['lockedLoadSpec'] ) || isset( $options['adminClientLoadSpec'] ) ) { if ( isset( $options['removeUnregisteredClients'] ) && $options['removeUnregisteredClients'] ) { $this->old_remove_unregistered_clients = true; } $upgraded_options = $this->convert_options_from_v1( $options ); /** * If the version is still not set for some reason, set it to a * default of the latest available version. */ if ( ! isset( $upgraded_options['version'] ) ) { $upgraded_options['version'] = fa()->latest_version_6(); } $should_upgrade = true; $options = $upgraded_options; } if ( ! isset( $options['dataVersion'] ) || $options['dataVersion'] < 4 ) { if ( ! isset( $options['compat'] ) && isset( $options['v4Compat'] ) ) { $v4_compat = boolval( $options['v4Compat'] ); $options['compat'] = $v4_compat; unset( $options['v4Compat'] ); } else { $options['compat'] = self::DEFAULT_USER_OPTIONS['compat']; } if ( isset( $options['v4Compat'] ) ) { unset( $options['v4Compat'] ); } $options['dataVersion'] = 4; $should_upgrade = true; } if ( $should_upgrade ) { $this->validate_options( $options ); $this->maybe_update_last_used_release_schema_for_upgrade(); $this->maybe_move_release_metadata_for_upgrade(); /** * Delete the main option to make sure it's removed entirely, including * from the autoload cache. * * Function delete_option() returns false when it fails, including when the * option does not exist. We know the option exists, because we just * queried it above. So any other failure should halt the upgrade * process to avoid inconsistent states. */ if ( ! delete_option( self::OPTIONS_KEY ) ) { throw UpgradeException::main_option_delete(); } update_option( self::OPTIONS_KEY, $options ); } } /** * Some upgrades have involved changing how we store release metadata. * * If the plugin's backing data was in a valid sate before upgrade, then * it should always be possible to apply any fixups to how the release * metadata are stored without issuing a request to the API server for * fresh metadata. Since issuing such a blocking request upon upgrade * is known to cause load problems and request timeouts, let's never * do it on upgrade. * * Internal use only. * * @throws ReleaseMetadataMissingException * @ignore * @internal */ private function maybe_move_release_metadata_for_upgrade() { if ( boolval( get_option( FontAwesome_Release_Provider::OPTIONS_KEY ) ) ) { // If this option is set, then we're all caught up. return; } // It used to be stored in one of these. $release_metadata = get_site_transient( 'font-awesome-releases' ); if ( ! $release_metadata ) { $release_metadata = get_transient( 'font-awesome-releases' ); } // Move it into where it belongs now. if ( boolval( $release_metadata ) ) { update_option( FontAwesome_Release_Provider::OPTIONS_KEY, $release_metadata, false ); } /** * Delete the old release metadata transient, if it exists. * It's no longer stored as a transient. */ delete_transient( 'font-awesome-releases' ); /** * Delete the old font-awesome-last-used-release site transient, if it exists. * It's no longer stored as a site (network-wide) transient. */ delete_site_transient( 'font-awesome-last-used-release' ); /** * Now we'll reset the release provider. * * If we've fallen through to this point, and we haven't found the release * metadata stored in one of the previous locations, then this will throw an * exception. */ FontAwesome_Release_Provider::reset(); } /** * With 4.1.0, the name of one of the keys in the LAST_USED_RELEASE_TRANSIENT changed. * We can fix it up. * * Internal use only. * * @throws ReleaseMetadataMissingException * @ignore * @internal */ private function maybe_update_last_used_release_schema_for_upgrade() { $last_used_transient = get_site_transient( FontAwesome_Release_Provider::LAST_USED_RELEASE_TRANSIENT ); if ( ! $last_used_transient ) { $last_used_transient = get_transient( FontAwesome_Release_Provider::LAST_USED_RELEASE_TRANSIENT ); } if ( $last_used_transient && isset( $last_used_transient['use_shim'] ) ) { $compat = $last_used_transient['use_shim']; unset( $last_used_transient['use_shim'] ); $last_used_transient['use_compatibility'] = $compat; set_site_transient( FontAwesome_Release_Provider::LAST_USED_RELEASE_TRANSIENT, $last_used_transient, FontAwesome_Release_Provider::LAST_USED_RELEASE_TRANSIENT_EXPIRY ); } } /** * Returns boolean indicating whether the plugin is currently configured * to run the client-side conflict detection scanner. * * @since 4.0.0 * @return bool */ public function detecting_conflicts() { $conflict_detection = get_option( self::CONFLICT_DETECTION_OPTIONS_KEY ); if ( isset( $conflict_detection['detectConflictsUntil'] ) && is_integer( $conflict_detection['detectConflictsUntil'] ) ) { return time() < $conflict_detection['detectConflictsUntil']; } else { return false; } } /** * Returns boolean indicating whether a kit is configured. * * It normally shouldn't make a difference to other theme's or plugins * as to whether Font Awesome is configured to use the standard CDN or a kit. * Yet this is a valid way to determine that. * * @since 4.0.0 * @throws ConfigCorruptionException * @return bool */ public function using_kit() { $options = $this->options(); $this->validate_options( $options ); return self::using_kit_given_options( $options ); } /** * Internal use only, not part of this plugin's public API. * * @internal * @ignore * @return bool */ public static function using_kit_given_options( $options ) { return isset( $options['kitToken'] ) && isset( $options['apiToken'] ) && $options['apiToken'] && is_string( $options['kitToken'] ); } /** * Internal use only, not part of this plugin's public API. * * @internal * @ignore */ protected function stringify_constraints( $constraints ) { $flipped_concat_each = array_map( function ( $constraint ) { return "$constraint[1] $constraint[0]"; }, $constraints ); return implode( ' and ', $flipped_concat_each ); } /** * Internal use only, not part of this plugin's public API. * * @internal * @ignore */ private function initialize_rest_api() { add_action( 'rest_api_init', array( new FontAwesome_API_Controller( self::PLUGIN_NAME, self::REST_API_NAMESPACE ), 'register_routes', ) ); add_action( 'rest_api_init', array( new FontAwesome_Config_Controller( self::PLUGIN_NAME, self::REST_API_NAMESPACE ), 'register_routes', ) ); add_action( 'rest_api_init', array( new FontAwesome_Preference_Check_Controller( self::PLUGIN_NAME, self::REST_API_NAMESPACE ), 'register_routes', ) ); add_action( 'rest_api_init', array( new FontAwesome_Conflict_Detection_Controller( self::PLUGIN_NAME, self::REST_API_NAMESPACE ), 'register_routes', ) ); } /** * Returns the latest available full release version of Font Awesome 5 as a string, * or null if the releases metadata has not yet been successfully retrieved from the * API server. * * As of the release of Font Awesome 6.0.0-beta1, this API is being deprecated, * because the symbolic version "latest" is being deprecated. It now just means * "the latest full release of Font Awesome with major version 5." Therefore, * it may not be very useful any more as Font Awesome 6 is released. * * The recommended way to resolve the symbolic versions 'latest', * '5.x', or '6.x' into their current concrete values is to query the GraphQL * API like this: * * ``` * query { release(version: "5.x") { version } } * ``` * * The `version` argument on the `release` field can accept any of these symbolic * version values. So that release's `version` field will be the corresponding * current concrete version value at the time the query is run. * * This query could be issued from a front-end script through `FontAwesome_API_Controller` * like this, assuming `@wordpress/api-fetch` is at `wp.apiFetch`, * and you've [setup a nonce](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-api-fetch/#built-in-middlewares) correctly, * and the logged in user has the appropriate permissions. * * ``` * wp.apiFetch( { * path: '/font-awesome/v1/api', * method: 'POST', * headers: {'Content-Type': 'application/json'}, * body: '{ "query": "query Version5x($ver: String!) { release(version: $ver){ version } }", "variables": {"ver": "5.x"} }' * } ).then( res => { * console.log( res ); * } ) * ``` * * Or you could issue your own `POST` request directly `api.fontawesome.com`. * [See the Font Awesome GraphQL API reference here](https://fontawesome.com/v5.15/how-to-use/graphql-api/intro/getting-started). * * @since 4.0.0 * @deprecated * * @return null|string */ public function latest_version() { return $this->release_provider()->latest_version(); } /** * Returns the latest available full release version of Font Awesome 5 as a string, * or null if the releases metadata has not yet been successfully retrieved from the * API server. * * @since 4.2.0 * * @return null|string */ public function latest_version_5() { return $this->release_provider()->latest_version_5(); } /** * Returns the latest available full release version of Font Awesome 6 as a string, * or null if the releases metadata has not yet been successfully retrieved from the * API server. * * @since 4.2.0 * * @return null|string */ public function latest_version_6() { return $this->release_provider()->latest_version_6(); } /** * Returns the latest available version of Font Awesome 7 as a string, * or null if the releases metadata has not yet been successfully retrieved from the * API server. * * @since 5.1.0 * * @return null|string */ public function latest_version_7() { return $this->release_provider()->latest_version_7(); } /** * Queries the Font Awesome API to load releases metadata. Results are stored * in the wp database. * * This is the metadata that supports API * methods like {@see FontAwesome::latest_version()} * and all other metadata required to enqueue Font Awesome when configured * to use the standard CDN (non-kits). * * This has been deprecated to discourage themes or plugins from invoking * it as a blocking network request during front-end page loads. If we find * that functionality like this is still needed for some use cases, let's * design an alternative API that encourages best-practice use while * discouraging anti-patterns. * * @since 4.0.0 * @deprecated * @ignore * @throws ApiRequestException * @throws ApiResponseException * @throws ReleaseProviderStorageException */ public function refresh_releases() { $this->release_provider()->load_releases(); } /** * Returns the time when releases metadata was last * refreshed. * * @since 4.0.0 * @return integer|null the time in unix epoch seconds or null if never */ public function releases_refreshed_at() { return $this->release_provider()->refreshed_at(); } /** * Refreshes releases only if it's a been a while. * * Internal use only, not part of this plugin's public API. * * @ignore * @internal * @return WP_Error|1 error if there was a problem, otherwise 1. */ protected function maybe_refresh_releases() { $refreshed_at = $this->releases_refreshed_at(); /** * If we've just upgraded from an older plugin version that didn't have this metadata value, * then we should refresh to get it. */ $latest_version_6 = $this->latest_version_6(); if ( is_null( $latest_version_6 ) || is_null( $refreshed_at ) || ( time() - $refreshed_at ) > self::RELEASES_REFRESH_INTERVAL ) { return FontAwesome_Release_Provider::load_releases(); } else { return 1; } } /** * URL for this plugin's admin settings page. * * Internal use only, not part of this plugin's public API. * * @ignore * @internal */ private function settings_page_url() { return admin_url( 'admin.php?page=' . self::OPTIONS_PAGE ); } /** * The value of the "ts" GET param given for this page request, or null if none. * * Internal use only, not part of this plugin's public API. * * We'll be super-strict validating what values we'll accept, insead of passing * through whatever is on the query string. * * @ignore * @internal * @return string|null */ private function active_admin_tab() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! isset( $_REQUEST[ self::ADMIN_TAB_QUERY_VAR ] ) || empty( $_REQUEST[ self::ADMIN_TAB_QUERY_VAR ] ) ) { return null; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $value = $_REQUEST[ self::ADMIN_TAB_QUERY_VAR ]; // These values are defined in the Redux reducer module of the admin JS React app. switch ( $value ) { case 'ts': return 'ADMIN_TAB_TROUBLESHOOT'; case 's': return 'ADMIN_TAB_SETTINGS'; default: return null; } } /** * Initalizes everything about the admin environment except the React app * bundle, which is handled in maybe_enqueue_admin_assets(). * * Internal use only, not part of this plugin's public API. * * @ignore * @internal */ public function initialize_admin() { $admin_menu_command = new FontAwesome_Command( function () { fa()->screen_id = add_options_page( /* translators: add_options_page page_title */ esc_html__( 'Font Awesome Settings', 'font-awesome' ), /* translators: add_options_page menu_title */ esc_html__( 'Font Awesome', 'font-awesome' ), 'manage_options', self::OPTIONS_PAGE, array( fa(), 'create_admin_page' ) ); } ); add_action( 'admin_menu', array( $admin_menu_command, 'run' ) ); $plugin_action_links_command = new FontAwesome_Command( function ( $links ) { $mylinks = array( /* translators: label for link to settings page on plugin listing */ '' . esc_html__( 'Settings', 'font-awesome' ) . '', ); return array_merge( $links, $mylinks ); } ); add_filter( 'plugin_action_links_' . FONTAWESOME_PLUGIN_FILE, array( $plugin_action_links_command, 'run' ) ); $multi_version_warning_command = new FontAwesome_Command( function ( $plugin_file, $plugin_data ) { if ( version_compare( FontAwesome::PLUGIN_VERSION, $plugin_data['Version'], 'ne' ) ) { $loader_version = FontAwesome_Loader::instance()->loaded_path(); ?>  

' . esc_html( $loader_version ) . '', 'ver. ' . esc_html( FontAwesome::PLUGIN_VERSION ) . '' ); ?>

screen_id ) { $this->maybe_refresh_releases(); } } catch ( Exception $e ) { notify_admin_warning( $e ); } catch ( Error $e ) { notify_admin_warning( $e ); } } ); if ( $this->is_block_editor_support_enabled() ) { try { if ( ! FontAwesome_SVG_Styles_Manager::is_svg_stylesheet_present( $this ) ) { FontAwesome_SVG_Styles_Manager::ensure_svg_styles_with_admin_notice_warning( $this, $this->release_provider() ); } } catch ( Exception $e ) { notify_admin_warning( $e ); } catch ( Error $e ) { notify_admin_warning( $e ); } } } /** * Returns current options. * * Internal use only. Not part of this plugin's public API. * * @throws ConfigCorruptionException * @internal * @ignore * @return array */ public function options() { $options = get_option( self::OPTIONS_KEY ); if ( ! $options ) { throw new ConfigCorruptionException(); } return $options; } /** * Validates options. * * Internal use only. Not part of this plugin's public API. * * @ignore * @internal * @throws ConfigCorruptionException if options are invalid */ public function validate_options( $options ) { $using_kit = self::using_kit_given_options( $options ); $kit_token = isset( $options['kitToken'] ) ? $options['kitToken'] : null; $api_token = isset( $options['apiToken'] ) ? $options['apiToken'] : null; $version = isset( $options['version'] ) ? $options['version'] : null; if ( ! isset( $options['usePro'] ) || ! is_bool( $options['usePro'] ) ) { throw new ConfigCorruptionException(); } if ( $using_kit ) { if ( ! boolval( $api_token ) ) { throw new ConfigCorruptionException(); } if ( ! is_string( $kit_token ) ) { throw new ConfigCorruptionException(); } if ( ! is_string( $version ) ) { throw new ConfigCorruptionException(); } } else { /** * If we're not using a kit, then the version cannot be "latest", * "5.x", or "6.x" at this point. It must have already been resolved * into a concrete version. */ $version_is_concrete = self::version_is_concrete( $version ); if ( ! $version_is_concrete ) { throw new ConfigCorruptionException(); } $version_is_v6 = is_string( $version ) && 1 === preg_match( '/^6\./', $version ); $is_pro = boolval( $options['usePro'] ); /** * Pro Version 6 CDN is not supported. */ if ( $version_is_v6 && $is_pro ) { throw new ConfigCorruptionException(); } } if ( ! isset( $options['compat'] ) || ! is_bool( $options['compat'] ) ) { throw new ConfigCorruptionException(); } if ( ! isset( $options['pseudoElements'] ) || ! is_bool( $options['pseudoElements'] ) ) { throw new ConfigCorruptionException(); } if ( ! isset( $options['technology'] ) || ! is_string( $options['technology'] ) || false === array_search( $options['technology'], array( 'svg', 'webfont' ), true ) ) { throw new ConfigCorruptionException(); } } /** * An array of md5 hashes that identify detected conflicting versions of * Font Awesome that the site owner has chosen to block from being enqueued. * * It is managed through the plugin's settings page. * * @since 4.0.0 * @return array */ public function blocklist() { $conflict_detection = get_option( self::CONFLICT_DETECTION_OPTIONS_KEY ); $unregistered_clients = ( isset( $conflict_detection['unregisteredClients'] ) && is_array( $conflict_detection['unregisteredClients'] ) ) ? $conflict_detection['unregisteredClients'] : array(); $blocklist = array_reduce( array_keys( $unregistered_clients ), function ( $carry, $md5 ) use ( $unregistered_clients ) { if ( isset( $unregistered_clients[ $md5 ]['blocked'] ) && boolval( $unregistered_clients[ $md5 ]['blocked'] ) ) { array_push( $carry, $md5 ); } return $carry; }, array() ); return $blocklist; } /** * Gets the current value of detectConflictsUntil from the conflict detection * option key in the database. * * Returns 0 if that value is unset in the db. * * Internal use only, not part of this plugin's public API. * * @ignore * @internal * @return integer */ protected function detect_conflicts_until() { $conflict_detection = get_option( self::CONFLICT_DETECTION_OPTIONS_KEY, self::DEFAULT_CONFLICT_DETECTION_OPTIONS ); return isset( $conflict_detection['detectConflictsUntil'] ) ? $conflict_detection['detectConflictsUntil'] : 0; } /** * Converts a given options array with a v1 schema to one with a v2 schema. * There are significant changes from the schema used by 4.0.0-rc9 and before. * * Internal use only, not part of this plugin's public API. * * @internal * @ignore * @param $options * @return array */ public function convert_options_from_v1( $options ) { $converted_options = self::DEFAULT_USER_OPTIONS; if ( isset( $options['usePro'] ) ) { $converted_options['usePro'] = $options['usePro']; } else { $converted_options['usePro'] = self::DEFAULT_USER_OPTIONS['usePro']; } if ( isset( $options['version'] ) ) { $converted_options['version'] = $options['version']; } if ( isset( $options['lockedLoadSpec'] ) ) { $converted_options['technology'] = isset( $options['lockedLoadSpec']['method'] ) ? $options['lockedLoadSpec']['method'] : 'webfont'; /** * If technology is webfont, always coerce pseudo-elements to true. * Otherwise, carry over whatever value it had before. */ $converted_options['pseudoElements'] = 'webfont' === $converted_options['technology'] ? true : ( isset( $options['lockedLoadSpec']['pseudoElements'] ) ? $options['lockedLoadSpec']['pseudoElements'] : false ); $converted_options['compat'] = $options['lockedLoadSpec']['v4shim']; } elseif ( isset( $options['adminClientLoadSpec'] ) ) { $converted_options['technology'] = $options['adminClientLoadSpec']['method']; $converted_options['pseudoElements'] = 'svg' === $options['adminClientLoadSpec']['method'] && $options['adminClientLoadSpec']['pseudoElements']; $converted_options['compat'] = $options['adminClientLoadSpec']['v4shim']; } return $converted_options; } /** * Callback function for creating the plugin's admin page. * * Internal use only, not part of this plugin's public API. * * @ignore * @internal */ public function create_admin_page() { include_once FONTAWESOME_DIR_PATH . 'admin/views/main.php'; } /** * Resets the singleton instance referenced by this class. * * Internal use only, not part of this plugin's public API. * * @ignore * @internal * @return FontAwesome */ public static function reset() { self::$instance = null; return fa(); } /** * Triggers the font_awesome_preferences action to gather preferences from clients. * * Internal use only, not part of this plugin's public API. * * @internal * @ignore * @throws PreferenceRegistrationException */ public function gather_preferences() { /** * Fired when the plugin is ready for clients to register their preferences. * * @since 4.0.0 */ try { do_action( 'font_awesome_preferences' ); } catch ( Exception $e ) { // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped throw PreferenceRegistrationException::with_thrown( $e ); } catch ( Error $e ) { // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped throw PreferenceRegistrationException::with_thrown( $e ); } } /** * Returns current preferences conflicts, keyed by option name. * * Internal use only, not part of this plugin's public API. * * Should normally only be called after the `font_awesome_enqueued` action has triggered, indicating that all * client preferences have been registered and processed. * * The returned array includes all conflicts between the options configured for this plugin by the site owner * and any preferences registered by themes or plugins. * * The presence of conflicts will not stop this plugin from loading Font Awesome according to its * configured options, but they will be presented to the site owner in the plugin's admin settings page to * aid in troubleshooting. * * @ignore * @internal * @param $options options to use for comparison. Uses $this->options() by default. * @see FontAwesome::register() register() documents all client preference keys * @return array */ public function conflicts_by_option( $options = null ) { $conflicts = array(); $options_for_comparison = is_null( $options ) ? $this->options() : $options; foreach ( $this->conflicts_by_client( $options_for_comparison ) as $client_name => $client_conflicts ) { foreach ( $client_conflicts as $conflicted_option ) { // Initialize the key with an empty array if it doesn't already have something in it. $conflicts[ $conflicted_option ] = isset( $conflicts[ $conflicted_option ] ) ? $conflicts[ $conflicted_option ] : array(); // Push the current client onto that array. array_push( $conflicts[ $conflicted_option ], $client_name ); } } return $conflicts; } /** * Returns current preferences conflicts, keyed by client name. * * Internal use only, not part of this plugin's public API. * * Should normally only be called after the `font_awesome_enqueued` action has triggered, indicating that all * client preferences have been registered and processed. * * The returned array includes all conflicts between the given options and any preferences registered * by themes or plugins. * * The presence of conflicts will not stop this plugin from loading Font Awesome according to its * configured options, but they will be presented to the site owner in the plugin's admin settings page to * aid in troubleshooting. * * @ignore * @internal * @param $options options to use for comparison. Uses $this->options() by default. * @see FontAwesome::register() register() documents all client preference keys * @return array */ public function conflicts_by_client( $options = null ) { if ( is_null( $this->conflicts_by_client ) ) { $conflicts = array(); $options_for_comparison = is_null( $options ) ? $this->options() : $options; foreach ( $this->client_preferences as $client_name => $client_preferences ) { $current_conflicts = FontAwesome_Preference_Conflict_Detector::detect( $options_for_comparison, $client_preferences, $this->latest_version_5(), $this->latest_version_6(), $this->latest_version_7() ); if ( count( $current_conflicts ) > 0 ) { $conflicts[ $client_name ] = $current_conflicts; } } $this->conflicts_by_client = $conflicts; return $conflicts; } else { return $this->conflicts_by_client; } } /** * Return current client preferences for all registered clients. * * Internal use only, not part of this plugin's public API. * * The website owner (i.e. the one who uses the WordPress admin dashboard) is considered a registered client. * So that owner's preferences will be represented here. But note that these preferences do not include * the `options`, as returned by {@see FortAwesome\FontAwesome::options()} which also help determine the * final result of how the Font Awesome assets are loaded. * * Each element of the array has the same shape as the preferences given to {@see FortAwesome\FontAwesome::register()}. * * @ignore * @internal * @see FortAwesome\FontAwesome::register() * @return array */ public function client_preferences() { return $this->client_preferences; } /** * Return unregistered clients that have been detected and stored in the WordPress db. * * Internal use only, not part of this plugin's public API. * * Unregistered clients are those for which the in-browser conflict detector * detects the presence of a Font Awesome version that is not being loaded by * this plugin, and therefore is likely causing a conflict. * * Client-side conflict detection is enabled in this plugin's setting page in WP admin. * * @ignore * @internal * @return array */ public function unregistered_clients() { $conflict_detection = get_option( self::CONFLICT_DETECTION_OPTIONS_KEY ); if ( isset( $conflict_detection['unregisteredClients'] ) && is_array( $conflict_detection['unregisteredClients'] ) ) { return $conflict_detection['unregisteredClients']; } else { return array(); } } /** * Indicates whether Font Awesome Pro is being loaded. * * It's a handy way to toggle the use of Pro icons in client theme or plugin template code. * * @since 4.0.0 * @throws ConfigCorruptionException * * @return boolean */ public function pro() { $options = $this->options(); $this->validate_options( $options ); return $options['usePro']; } /** * Indicates which Font Awesome technology is configured: 'webfont' or 'svg'. * * @since 4.0.0 * @throws ConfigCorruptionException * * @return string */ public function technology() { $options = $this->options(); $this->validate_options( $options ); return $options['technology']; } /** * Reports the version of Font Awesome assets being loaded, which may have * a symbolic value like "latest" (deprecated), "5.x", or "6.x" if configured * for a kit. If not configured for a kit, the version is guaranteed to be * a concrete, semver parseable value, like 5.15.3. * * Your theme or plugin can call this method in order to determine * whether all of the icons used in your templates will be available, * especially if you tend to use newer icons. * * It should be really easy for site owners to update to a new Font Awesome * version to accommodate your templates--just a simple dropdown selection * on the Font Awesome plugin settings page. You might need to show an admin * notice to nudge them to do so if you detect that the current version of * Font Awesome being loaded is older than you'd like. * * When Font Awesome is configured to use a kit, that kit may be configured * to load the "latest" version. The resolution of that symoblic "latest" * version happens internal to the kit's own loading logic, which is * outside the scope of this plugin. * * Before the release of Font Awesome 6.0.0-beta1, the symbolic version "latest" * on a kit always meant: "the latest stable release with major version 5." * Using "latest" for kits has been deprecated, but where it is still present * on a kit, it will continue to mean just "the latest stable release with * major version 5." * * New symbolic major version ranges have been introduced instead "5.x" and "6.x". * These mean, respectively: "the latest stable release with major version 5", * and "the latest stable release with major version 6, when available, otherwise * the latest pre-release with major version 6." * * So if this function does not return a semver parseable version, then * it must be one of these symbolic versions. * * The recommended way to resolve the symbolic versions 'latest', * '5.x', or '6.x' into their current concrete values is to query the GraphQL * API like this: * * ``` * query { release(version: "5.x") { version } } * ``` * * @since 4.0.0 * @see FontAwesome::latest_version() * @see FontAwesome::releases_refreshed_at() * @throws ConfigCorruptionException * @return string|null null if no version has yet been saved in the options * in the db. Otherwise, 5.x, or 6.x, or a semantic version string. */ public function version() { $options = $this->options(); $this->validate_options( $options ); return $options['version']; } /** * Returns the currently configured kit token, if the plugin is currently * configured to load a kit. * * @since 4.0.0 * @throws ConfigCorruptionException * @return string|null kit token if present, or null */ public function kit_token() { $options = $this->options(); $this->validate_options( $options ); return isset( $options['kitToken'] ) ? $options['kitToken'] : null; } /** * Indicates whether Font Awesome is being loaded with version 4 compatibility. * * Its result is valid only after the `font_awesome_enqueued` has been triggered. * * @since 4.0.0 * @throws ConfigCorruptionException * @deprecated * * @return boolean */ public function v4_compatibility() { return $this->compatibility(); } /** * Indicates whether Font Awesome is being loaded with older version compatibility. * * Its result is valid only after the `font_awesome_enqueued` has been triggered. * * @since 4.1.0 * @throws ConfigCorruptionException * * @return boolean */ public function compatibility() { $options = $this->options(); $this->validate_options( $options ); return $options['compat']; } /** * Indicates whether Font Awesome is being loaded with support for pseudo-elements. * * Its results are only valid after the `font_awesome_enqueued` action has been triggered. * * There are known performance problems with this SVG and pseudo-elements, * but it is provided for added compatibility where pseudo-elements must be used. * * Always returns true if technology() === 'webfont', because pseudo-elements * are always inherently supported by the CSS/Webfont technology. * * @since 4.0.0 * @link https://fontawesome.com/how-to-use/on-the-web/advanced/css-pseudo-elements CSS Pseudo-Elements and Font Awesome * @throws ConfigCorruptionException * @return boolean */ public function pseudo_elements() { $options = $this->options(); $this->validate_options( $options ); return $options['pseudoElements']; } /** * Internal use only, not part of this plugin's public API. * * @internal * @ignore */ protected function specified_preference_or_default( $preference, $default_value ) { return array_key_exists( 'value', $preference ) ? $preference['value'] : $default_value; } /** * Enqueues the JavaScript bundle that is the React app for the admin * settings page as well as the conflict detection reporter. * * The same bundle will be enqueued for both purposes. When enqueued, it * must be configured to indicate which React components to mount in the DOM, * which may be either, both, or neither. * * Internal use only, not part of this plugin's public API. * * @internal * @ignore */ public function maybe_enqueue_admin_assets() { add_action( 'admin_enqueue_scripts', function ( $hook ) { $should_enable_icon_chooser = $this->should_icon_chooser_be_enabled( $hook ); wp_register_script( self::RESOURCE_HANDLE_ICON_CHOOSER, trailingslashit( FONTAWESOME_DIR_URL ) . 'icon-chooser/build/index.js', array( self::ADMIN_RESOURCE_HANDLE ), self::PLUGIN_VERSION, true ); /** While this plugin's Classic Editor support does depend on tinymce * being loaded, the wp-tinymce dependency has intentionally been omitted * here due to compatibility problems with Toolset. * See: https://wordpress.org/support/topic/cant-edit-post_content-in-classic-editor-if-plugin-v5-0-1-or-v5-0-2-is-active/ */ wp_register_script( self::RESOURCE_HANDLE_CLASSIC_EDITOR, trailingslashit( FONTAWESOME_DIR_URL ) . 'classic-editor/build/index.js', array( self::ADMIN_RESOURCE_HANDLE, self::RESOURCE_HANDLE_ICON_CHOOSER, 'jquery', ), self::PLUGIN_VERSION, true ); try { if ( $this->detecting_conflicts() || $hook === $this->screen_id || $should_enable_icon_chooser ) { $this->enqueue_admin_js_assets( $should_enable_icon_chooser ); } if ( $hook === $this->screen_id ) { wp_localize_script( self::ADMIN_RESOURCE_HANDLE, self::ADMIN_RESOURCE_LOCALIZATION_NAME, array_merge( $this->common_data_for_js_bundle(), array( 'showAdmin' => true, 'onSettingsPage' => true, 'clientPreferences' => $this->client_preferences(), 'releases' => array( 'available' => $this->release_provider()->versions(), 'latest_version_5' => $this->latest_version_5(), 'latest_version_6' => $this->latest_version_6(), 'latest_version_7' => $this->latest_version_7(), ), 'pluginVersion' => FontAwesome::PLUGIN_VERSION, 'preferenceConflicts' => $this->conflicts_by_option(), ) ) ); } elseif ( $should_enable_icon_chooser ) { wp_localize_script( self::ADMIN_RESOURCE_HANDLE, self::ADMIN_RESOURCE_LOCALIZATION_NAME, $this->common_data_for_js_bundle() ); wp_enqueue_script( self::RESOURCE_HANDLE_ICON_CHOOSER ); /** * TODO: re-enable the possibility of integrating with TinyMCE * even on pages where Gutenberg is also present. * This is an initial fix for GitHub Issue: #133 * https://github.com/FortAwesome/wordpress-fontawesome/issues/133 * * UPATE: Now that the bundles are building differently * and external dependencies are working correctly, this * is close to working (try loading a new post in Gutenberg * with integration plugin-nu enabled). It requires disabling * the dynamic import of the components style.css. In the * case where we're already on Gutenberg page, it is not * necessary to load it, so it should be easy to check for that. * * The remaining issue seems to be just determining into * which editor the shortcode should be inserted. When * running plugin-nu, activating the Icon Chooser from either * Gutenberg or the plugin-nu's TinyMCE editor, the shortcode * is always inserted into to the TinyMCE editors. Seems * like that should be easy to resolve when there's time * and priority to continue investigating. */ if ( ! is_gutenberg_page() ) { add_action( 'media_buttons', function ( $editor_id ) { printf( /* translators: 1: open button tag, 2: editor id value, 3: data attribute for editor id, 4: editor id value, 5: closing data attribute value quote, 6: remaining button tag open and icon svg tag, 7: close button tag */ esc_html__( '%1$s%2$s%3$s%4$s%5$s%6$sAdd Font Awesome%7$s', 'font-awesome' ), '' ); }, 99, 1 ); add_action( 'after_wp_tiny_mce', function ( $mce_settings ) { $editor_ids = array_keys( $mce_settings ); ?> common_data_for_js_bundle() ); } } catch ( Exception $e ) { notify_admin_fatal_error( $e ); } catch ( Error $e ) { notify_admin_fatal_error( $e ); } } ); if ( $this->detecting_conflicts() && current_user_can( 'manage_options' ) ) { foreach ( array( 'wp_enqueue_scripts', 'login_enqueue_scripts' ) as $action ) { add_action( $action, function () { try { $current_screen = get_current_screen(); $should_enable_icon_chooser = $this->should_icon_chooser_be_enabled( $current_screen ); $this->enqueue_admin_js_assets( $should_enable_icon_chooser ); wp_localize_script( self::ADMIN_RESOURCE_HANDLE, self::ADMIN_RESOURCE_LOCALIZATION_NAME, array_merge( $this->common_data_for_js_bundle(), array( 'onSettingsPage' => false, 'showAdmin' => false, 'showConflictDetectionReporter' => true, ) ) ); } catch ( Exception $e ) { notify_admin_fatal_error( $e ); } catch ( Error $e ) { notify_admin_fatal_error( $e ); } } ); } } } /** * Enqueues the entrypoint JavaScript index.js, and declares relevant js * dependencies. * * @param bool $with_icon_chooser * @ignore * @internal * @return string $main_js_handle */ private function enqueue_admin_js_assets( $with_icon_chooser ) { global $wp_version; $enable_icon_chooser = boolval( $with_icon_chooser ); $deps = array(); $deps = array_merge( $deps, array( 'react', 'react-dom', 'wp-i18n', 'wp-element', 'wp-components', 'wp-api-fetch', 'lodash' ) ); /** * We don't need these Gutenberg dependencies unless we're on a Gutenberg * page. Declaring them unnecessarily (when not on a Gutenberg page) * has resulted in conflict for at least one other plugin: RankMath. * * See: https://wordpress.org/support/topic/plugin-conflicts-with-rankmath */ if ( $enable_icon_chooser && is_gutenberg_page() && $this->is_block_editor_support_enabled() ) { $deps = array_merge( $deps, array( 'wp-blocks', 'wp-editor', 'wp-rich-text', 'wp-block-editor' ) ); } wp_enqueue_script( self::ADMIN_RESOURCE_HANDLE, trailingslashit( $this->get_webpack_asset_url_base() ) . 'index.js', $deps, self::PLUGIN_VERSION, true ); } /** * Internal use only, not part of this plugin's public API. * * @ignore * @internal */ public function common_data_for_js_bundle() { return array( 'apiNonce' => wp_create_nonce( 'wp_rest' ), 'apiUrl' => rest_url( self::REST_API_NAMESPACE ), 'faApiUrl' => FONTAWESOME_API_URL, 'restApiNamespace' => self::REST_API_NAMESPACE, 'rootUrl' => rest_url(), 'detectConflictsUntil' => $this->detect_conflicts_until(), 'unregisteredClients' => $this->unregistered_clients(), 'showConflictDetectionReporter' => $this->detecting_conflicts(), 'settingsPageUrl' => $this->settings_page_url(), 'activeAdminTab' => $this->active_admin_tab(), 'options' => $this->options(), 'webpackPublicPath' => trailingslashit( FONTAWESOME_DIR_URL ) . 'admin/build/', 'disableRichTextIcons' => $this->disable_rich_text_icons(), 'assetsBaseUrlOverride' => FONTAWESOME_ASSETS_BASE_URL_OVERRIDE, ); } /** * Enqueues a kit loader * * where deadbeef00 is the kitToken * * @ignore * @internal * @throws ConfigCorruptionException if the kit_token is not a string */ public function enqueue_kit( $kit_token ) { if ( ! is_string( $kit_token ) ) { throw new ConfigCorruptionException(); } foreach ( array( 'wp_enqueue_scripts', 'admin_enqueue_scripts', 'login_enqueue_scripts' ) as $action ) { $enqueue_command = new FontAwesome_Command( function () use ( $kit_token ) { try { wp_enqueue_script( FontAwesome::RESOURCE_HANDLE, trailingslashit( FONTAWESOME_KIT_LOADER_BASE_URL ) . $kit_token . '.js', array(), // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion null, false ); /** * Kits have built-in support for detecting conflicts, but we need to * inject some configuration to turn it on. We will do that by manipulating * the FontAwesomeKitConfig global property. */ if ( fa()->detecting_conflicts() ) { /** * Kits Conflict Detection expects this value to be in milliseconds * since the unix epoch. */ $detect_conflicts_until = fa()->detect_conflicts_until() * 1000; $script_content = <<< EOT window.__FontAwesome__WP__KitConfig__ = { detectConflictsUntil: {$detect_conflicts_until} } Object.defineProperty(window, 'FontAwesomeKitConfig', { enumerable: true, configurable: false, get() { return window.__FontAwesome__WP__KitConfig__ }, set( newValue ) { var newValueCopy = Object.assign({}, newValue) window.__FontAwesome__WP__KitConfig__ = Object.assign(newValueCopy, window.__FontAwesome__WP__KitConfig__) } }) EOT; wp_add_inline_script( FontAwesome::RESOURCE_HANDLE, $script_content, 'before' ); } } catch ( Exception $e ) { notify_admin_fatal_error( $e ); } catch ( Error $e ) { notify_admin_fatal_error( $e ); } } ); add_action( $action, array( $enqueue_command, 'run' ) ); } $script_loader_tag_command = new FontAwesome_Command( function ( $html, $handle ) { $revised_html = $html; /** * Set the crossorigin attr to ensure that the Origin header is * by the browser when the kit loader script is loaded. * Needed for authorization. */ if ( self::RESOURCE_HANDLE === $handle ) { $revised_html = preg_replace( '//', '