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 */ const SHORTCODE_TAG = 'icon'; /** * Default style prefix. * * @since 4.0.0 */ 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 */ 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 */ const CONFLICT_DETECTION_OPTIONS_KEY = 'font-awesome-conflict-detection'; /** * The unique WordPress plugin slug for this plugin. * * @since 4.0.0 */ const PLUGIN_NAME = 'font-awesome'; /** * The version of this WordPress plugin. * * @since 4.0.0 */ const PLUGIN_VERSION = '4.5.0'; /** * The namespace for this plugin's REST API. * * @internal * @deprecated * @ignore */ 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 */ const OPTIONS_PAGE = 'font-awesome'; /** * GET param used for linking to a particular starting tab in the admin UI. * * @ignore * @internal */ 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 */ const RESOURCE_HANDLE = 'font-awesome-official'; /** * The handle used when enqueuing the v4shim. * * @since 4.0.0 */ const RESOURCE_HANDLE_V4SHIM = 'font-awesome-official-v4shim'; /** * The handle used when enqueuing the conflict detector. * * @ignore * @internal */ const RESOURCE_HANDLE_CONFLICT_DETECTOR = 'font-awesome-official-conflict-detector'; /** * The source URL for the conflict detector, a feature introduced in Font Awesome 5.10.0. * * @ignore * @internal */ const CONFLICT_DETECTOR_SOURCE = 'https://use.fontawesome.com/releases/v6.1.1/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 */ 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 */ 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 */ const ADMIN_RESOURCE_LOCALIZATION_NAME = '__FontAwesomeOfficialPlugin__'; /** * @ignore * @deprecated */ const V3DEPRECATION_TRANSIENT = 'font-awesome-v3-deprecation-data'; /** * @ignore * @deprecated */ const V3DEPRECATION_EXPIRY = WEEK_IN_SECONDS; /** * 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 */ 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 */ 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 */ 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; /** * 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() { /* noop */ } /** * 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; } /** * Main entry point for running the plugin. Called automatically when the plugin is loaded. * * Internal only, not part of this plugin's public API. * * @internal * @ignore */ public function run() { $this->init(); $this->initialize_rest_api(); if ( is_admin() ) { $this->initialize_admin(); } } /** * Callback for init. * * Internal use only. * * @ignore * @internal */ public function init() { try { $this->try_upgrade(); add_shortcode( self::SHORTCODE_TAG, array( $this, 'process_shortcode' ) ); $this->validate_options( fa()->options() ); 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() ) { $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 ); } } /** * 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 ); $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 $this->using_kit_given_options( $options ); } /** * Internal use only. * * @internal * @ignore * @return bool */ private 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', ) ); add_action( 'rest_api_init', array( new FontAwesome_V3Deprecation_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(); } /** * 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; } } /** * Internal use only, not part of this plugin's public API. * * @internal * @ignore */ private function emit_v3_deprecation_admin_notice( $data ) { ?>

' . esc_html( $data['atts']['name'] ) . '', '[icon]' ); ?>

settings_page_url() ) . '&tab=ts">', '' ); ?>

get_v3deprecation_warning_data(); if ( $v3deprecation_warning_data && ! ( isset( $v3deprecation_warning_data['snooze'] ) && $v3deprecation_warning_data['snooze'] ) ) { $v3_deprecation_command = new FontAwesome_Command( function() use ( $v3deprecation_warning_data ) { $current_screen = get_current_screen(); if ( $current_screen && fa()->screen_id !== $current_screen->id ) { fa()->emit_v3_deprecation_admin_notice( $v3deprecation_warning_data ); } } ); add_action( 'admin_notices', array( $v3_deprecation_command, 'run' ) ); } $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, $status ) { 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 ) . '' ); ?>

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 = is_string( $version ) && 1 === preg_match( '/^[0-9]+\.[0-9]+\.[0-9]+/', $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 ) { throw PreferenceRegistrationException::with_thrown( $e ); } catch ( Error $e ) { 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() ); 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 ) { return array_key_exists( 'value', $preference ) ? $preference['value'] : $default; } /** * 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 ); 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 ) { $this->maybe_refresh_releases(); 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(), ), 'pluginVersion' => FontAwesome::PLUGIN_VERSION, 'preferenceConflicts' => $this->conflicts_by_option(), 'v3DeprecationWarning' => $this->get_v3deprecation_warning_data(), ) ) ); } elseif ( $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( 'enableIconChooser' => true, ) ) ); /** * 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 ( ! $this->is_gutenberg_page() ) { // These are needed for the Tiny MCE Classic Editor. add_action( 'media_buttons', function() { printf( /* translators: 1: open button tag and icon tag 2: close button tag */ esc_html__( '%1$sAdd Font Awesome%2$s', 'font-awesome' ), '' ); }, 99 ); add_action( 'before_wp_tiny_mce', function() { printf( '
' ); }, 99 ); } } else { wp_localize_script( self::ADMIN_RESOURCE_HANDLE, self::ADMIN_RESOURCE_LOCALIZATION_NAME, $this->common_data_for_js_bundle() ); } /** * There are some vendor dependencies in WP5 that create globals * as side effects. We might use those in our JS bundle and * we need to make sure that we don't accidently change the global * version that other themes or plugins might be depending upon. * * Here's the recommendation we're following here: * https://make.wordpress.org/core/2018/12/06/javascript-packages-and-interoperability-in-5-0-and-beyond/ */ $vendor_globals = array( '_', 'React', 'ReactDOM', 'moment' ); $originals_global = '__originalsBeforeFontAwesome'; $originals = array_map( function ( $var ) { return "$var: window.$var"; }, $vendor_globals ); $capture_vendor_global_originals_script = sprintf( 'window.%1$s = { %2$s }', $originals_global, implode( ',', $originals ) ); wp_add_inline_script( self::ADMIN_RESOURCE_HANDLE, $capture_vendor_global_originals_script, 'before' ); $original_restore_conditions = array_map( function ( $var ) { return "if(window.__originalsBeforeFontAwesome.$var){window.$var = window.__originalsBeforeFontAwesome.$var}"; }, $vendor_globals ); wp_add_inline_script( self::ADMIN_RESOURCE_HANDLE, implode( ' ', $original_restore_conditions ), 'after' ); } 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(); /** * If we're on a recent enough version of WordPress 5, then the supporting * libraries are adequate for us to use as externals. * * For earlier versions, we'll need to load our own compatibility bundle, * and disable Gutenberg integration, since our compatibility bundle * uses a newer version of React than what would be available in WordPress * Core in that earlier version. */ if ( $this->compat_js_required() ) { $wp4_compat_resource_handle = self::ADMIN_RESOURCE_HANDLE . '-compat'; wp_enqueue_script( $wp4_compat_resource_handle, trailingslashit( FONTAWESOME_DIR_URL ) . 'compat-js/build/compat.js', array(), self::PLUGIN_VERSION, true ); // We need our main bundle to depend on the compat bundle. array_push( $deps, $wp4_compat_resource_handle ); } else { $deps = array_merge( $deps, array( 'react', 'react-dom', 'wp-i18n', 'wp-element', 'wp-components', 'wp-api-fetch' ) ); /** * 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 && $this->is_gutenberg_page() ) { $deps = array_merge( $deps, array( 'wp-blocks', 'wp-editor', 'wp-rich-text', 'wp-block-editor' ) ); } } if ( $enable_icon_chooser ) { /** * TODO: re-enable the case where TinyMCE and Gutenberg are present on the same * page load. For now, we're eliminating that case because * some customers experienced Gutenberg failures on pages where both * editors were active. * * If we're not on a Gutenberg (as plugin) or Block Editor (as WP 5 Core editor), * then we want to enable our TinyMCE integration. We'll initialize it * on the wp_tiny_mce_init action hook. * * According to the docs: * "Fires after tinymce.js is loaded, but before any TinyMCE editor instances are created." * * So we expect this to only fire once, even if multiple instances of the editor * are added to a single page. * * If TinyMCE is not present or not active, then this action hook will * never be fired and thus our TinyMCE integration will never be setup, * which is what we want. */ add_action( 'wp_tiny_mce_init', array( $this, 'print_classic_editor_icon_chooser_setup_script' ) ); } 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 ), '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/', 'usingCompatJs' => $this->compat_js_required(), 'isGutenbergPage' => $this->is_gutenberg_page(), ); } /** * 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 { // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion wp_enqueue_script( FontAwesome::RESOURCE_HANDLE, trailingslashit( FONTAWESOME_KIT_LOADER_BASE_URL ) . $kit_token . '.js', array(), 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( '//', ' =' ); } /** * Internal use only, not part of this plugin's public API. * * This builds font-face rules to override the v4 font-family * name of "FontAwesome", pointing to current assets. This is only * needed for CDN setups in FA5. FA 5 kits have their own built-in * version of this. FA6 (since beta3) has improved compatibility handling * that does this and more, both in FA6 kits and Free CDN. * * @internal * @ignore */ private function build_legacy_font_face_overrides_for_v4( $license_subdomain, $version ) { return <<< EOT @font-face { font-family: "FontAwesome"; font-display: block; src: url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-brands-400.eot"), url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-brands-400.woff2") format("woff2"), url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-brands-400.woff") format("woff"), url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-brands-400.ttf") format("truetype"), url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-brands-400.svg#fontawesome") format("svg"); } @font-face { font-family: "FontAwesome"; font-display: block; src: url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-solid-900.eot"), url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-solid-900.woff2") format("woff2"), url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-solid-900.woff") format("woff"), url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-solid-900.ttf") format("truetype"), url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-solid-900.svg#fontawesome") format("svg"); } @font-face { font-family: "FontAwesome"; font-display: block; src: url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-regular-400.eot"), url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-regular-400.woff2") format("woff2"), url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-regular-400.woff") format("woff"), url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-regular-400.ttf") format("truetype"), url("https://{$license_subdomain}.fontawesome.com/releases/v{$version}/webfonts/fa-regular-400.svg#fontawesome") format("svg"); unicode-range: U+F004-F005,U+F007,U+F017,U+F022,U+F024,U+F02E,U+F03E,U+F044,U+F057-F059,U+F06E,U+F070,U+F075,U+F07B-F07C,U+F080,U+F086,U+F089,U+F094,U+F09D,U+F0A0,U+F0A4-F0A7,U+F0C5,U+F0C7-F0C8,U+F0E0,U+F0EB,U+F0F3,U+F0F8,U+F0FE,U+F111,U+F118-F11A,U+F11C,U+F133,U+F144,U+F146,U+F14A,U+F14D-F14E,U+F150-F152,U+F15B-F15C,U+F164-F165,U+F185-F186,U+F191-F192,U+F1AD,U+F1C1-F1C9,U+F1CD,U+F1D8,U+F1E3,U+F1EA,U+F1F6,U+F1F9,U+F20A,U+F247-F249,U+F24D,U+F254-F25B,U+F25D,U+F267,U+F271-F274,U+F279,U+F28B,U+F28D,U+F2B5-F2B6,U+F2B9,U+F2BB,U+F2BD,U+F2C1-F2C2,U+F2D0,U+F2D2,U+F2DC,U+F2ED,U+F328,U+F358-F35B,U+F3A5,U+F3D1,U+F410,U+F4AD; } EOT; } } /** * Iterates through each blog in the current network, switches to it, * and invokes the given callback function, restoring the current blog * after each callback invocation. * * Internal use only, not part of this plugin's public API. * * @internal * @ignore */ function for_each_blog( $cb ) { $network_id = get_current_network_id(); $site_count = get_sites( array( 'network_id' => $network_id, 'count' => true, ) ); $limit = 100; $offset = 0; while ( $offset < $site_count ) { $sites = get_sites( array( 'network_id' => $network_id, 'offset' => $offset, 'number' => $limit, ) ); foreach ( $sites as $site ) { $blog_id = $site->blog_id; switch_to_blog( $blog_id ); try { $cb( $blog_id ); } finally { restore_current_blog(); } } $offset = $offset + $limit; } } /** * Convenience global function to get a singleton instance of the main Font Awesome * class. * * @since 4.0.0 * * @see FontAwesome::instance() * @returns FontAwesome */ function fa() { return FontAwesome::instance(); } /** * This hook ensures that when we're in multisite mode, and a new site is activated * after an initial plugin activation, that the plugin is initialized for that newly * created site, but only if this plugin is otherwise network activated. * * If the plugin is only activated on a per-site basis, then creating a new site should * not result in this plugin automatically being activated for it. */ if ( is_multisite() ) { add_action( 'wp_initialize_site', function ( $site ) { if ( ! is_network_admin( FONTAWESOME_PLUGIN_FILE ) ) { return; } require_once trailingslashit( FONTAWESOME_DIR_PATH ) . 'includes/class-fontawesome-activator.php'; switch_to_blog( $site->blog_id ); try { FontAwesome_Activator::initialize_current_site( false ); } finally { restore_current_blog(); } }, 99, 1 ); }