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 ) {
?>
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
);
}