File: /var/www/html/wp-content/plugins/wp-asset-clean-up/classes/OptimiseAssets/FontsLocal.php
<?php
namespace WpAssetCleanUp\OptimiseAssets;
use WpAssetCleanUp\Main;
/**
* Class LocalFonts
*
* @package WpAssetCleanUp\OptimiseAssets
*/
class FontsLocal
{
/**
*
*/
public function init()
{
add_filter('wpacu_local_fonts_display_css_output', array($this, 'updateCssOutputFontDisplay'), 10, 2);
add_filter('wpacu_local_fonts_display_style_inline', array($this, 'updateInlineCssOutputFontDisplay'), 10, 2); // alters $htmlSource
add_action('wp_head', array($this, 'preloadFontFiles'), 1);
}
/**
* @param $cssContent
* @param $enable
*
* @return mixed
*/
public function updateCssOutputFontDisplay($cssContent, $enable)
{
if (! $enable || ! preg_match('/@font-face(\s+|){/i', $cssContent)) {
return $cssContent;
}
// "font-display" is enabled in "Settings" - "Local Fonts"
return self::alterLocalFontFaceFromCssContent($cssContent);
}
/**
* @param $htmlSource
* @param $status
*
* @return mixed
*/
public function updateInlineCssOutputFontDisplay($htmlSource, $status)
{
if (! $status) {
return $htmlSource;
}
return self::alterLocalFontFaceFromInlineStyleTags($htmlSource);
}
/**
*
*/
public function preloadFontFiles()
{
// Don't apply any changes if not in the front-end view (e.g. Dashboard view)
// or test mode is enabled, and a guest user is accessing the page
if ( OptimizeCommon::preventAnyFrontendOptimization() ) {
return;
}
if ( ! $preloadFontFiles = trim(Main::instance()->settings['local_fonts_preload_files']) ) {
return;
}
$preloadFontFilesArray = array();
if (strpos($preloadFontFiles, "\n") !== false) {
foreach (explode("\n", $preloadFontFiles) as $preloadFontFile) {
$preloadFontFile = esc_attr(trim($preloadFontFile));
if ( ! $preloadFontFile ) {
continue;
}
$preloadFontFilesArray[] = $preloadFontFile;
}
} else {
$preloadFontFiles = esc_attr(trim($preloadFontFiles));
if ($preloadFontFiles) {
$preloadFontFilesArray[] = $preloadFontFiles;
}
}
$preloadFontFilesArray = array_unique($preloadFontFilesArray);
$preloadFontFilesOutput = '';
// Finally, go through the list
if ( ! empty($preloadFontFilesArray) ) {
foreach ($preloadFontFilesArray as $preloadFontFile) {
$preloadFontFilesOutput .= '<link rel="preload" as="font" href="' . $preloadFontFile . '" data-wpacu-preload-local-font="1" crossorigin>' . "\n";
}
}
echo apply_filters('wpacu_preload_local_font_files_output', $preloadFontFilesOutput);
}
/**
* It will append 'font-display' to the CSS @font-face
*
* @param $cssContent
*
* @return mixed
*/
public static function alterLocalFontFaceFromCssContent($cssContent)
{
if ( ! Main::instance()->settings['local_fonts_display'] || wpacuIsDefinedConstant('WPACU_ALLOW_ONLY_UNLOAD_RULES') ) {
return $cssContent;
}
$fontDisplayChosen = Main::instance()->settings['local_fonts_display'];
$fontDisplayOverwrite = Main::instance()->settings['local_fonts_display_overwrite'];
preg_match_all('#@font-face(|\s+){(.*?)}#si', $cssContent, $matchesFromCssCode, PREG_SET_ORDER);
if (! empty($matchesFromCssCode)) {
foreach ($matchesFromCssCode as $matchedFontFace) {
$fontFaceOuterContent = trim($matchedFontFace[0]);
if (PHP_VERSION_ID >= 70000) {
try {
// Some PHP RegEx warnings that won't affect the functionality might throw up for PHP >= 7; do not show them
$cleanFontFaceContent = $fontFaceOuterContentUpdated = self::cleanerCssContent($fontFaceOuterContent);
} catch (\Error $e) {
$cleanFontFaceContent = $fontFaceOuterContentUpdated = $fontFaceOuterContent;
}
} else {
$cleanFontFaceContent = $fontFaceOuterContentUpdated = self::cleanerCssContent($fontFaceOuterContent);
}
$cleanFontFaceContent = preg_replace('!\s+!', ' ', $cleanFontFaceContent);
$cleanFontFaceContent = preg_replace('/@font-face(|\s+){(|\s+)}/i', '', $cleanFontFaceContent);
if (! $cleanFontFaceContent) {
continue;
}
$cleanFontFaceContent = trim($cleanFontFaceContent);
// Add it if it's not there already
if (stripos($cleanFontFaceContent, 'font-display') === false) {
if (substr($cleanFontFaceContent, -1) === '}') {
// No room for errors; Check the syntax before the CSS property
$withoutCurlyBraces = trim(trim($cleanFontFaceContent, '}{'));
$appendBefore = (substr($withoutCurlyBraces, -1) !== ';') ? ';' : '';
$fontFaceOuterContentUpdated = substr_replace($fontFaceOuterContent, $appendBefore.' font-display:'.$fontDisplayChosen.';}', -1);
}
} elseif ($fontDisplayOverwrite && stripos($cleanFontFaceContent, 'font-display') !== false) {
// Already there? Overwrite it
preg_match_all('/font-display(|\s+):(|\s+)(?<value>.*?)(;|\s+;|}|\s+})/i', $cleanFontFaceContent, $matchesFontDisplay);
if (isset($matchesFontDisplay[0][0], $matchesFontDisplay['value'][0])) {
$totalMatches = count($matchesFontDisplay[0]);
for ($mI = 0; $mI < $totalMatches; $mI++) {
$currentDisplayProperty = trim($matchesFontDisplay[0][$mI]);
$currentDisplayPropertyRexExp = preg_replace('!\s+!', '(\s+|)', preg_quote($currentDisplayProperty, '/'));
$currentDisplayPropertyRexExp = '/'.$currentDisplayPropertyRexExp.'/i';
$currentDisplayPropertyValue = trim($matchesFontDisplay['value'][$mI]);
$newDisplayProperty = str_replace($currentDisplayPropertyValue, $fontDisplayChosen, $currentDisplayProperty);
$newDisplayProperty = preg_replace('!\s+!', ' ', $newDisplayProperty);
$fontFaceOuterContentUpdated = preg_replace($currentDisplayPropertyRexExp, $newDisplayProperty, $fontFaceOuterContentUpdated);
}
}
}
$fontFaceOuterContentNew = str_replace($fontFaceOuterContent, $fontFaceOuterContentUpdated, $fontFaceOuterContent);
$fontFaceOuterContentNew = preg_replace('/@font-face(|\s+){(|\s+)}/i', '', $fontFaceOuterContentNew);
$cssContent = str_replace($fontFaceOuterContent, $fontFaceOuterContentNew, $cssContent);
}
}
return $cssContent;
}
/**
* @param $cssOuterContent
*
* @return string
*/
public static function cleanerCssContent($cssOuterContent)
{
$regex = array(
"`^([\t\s]+)`ism" => '',
"`([:;}{]{1})([\t\s]+)(\S)`ism" => '$1$3',
"`(\S)([\t\s]+)([:;}{]{1})`ism" => '$1$3',
"`\/\*(.+?)\*\/`ism" => '',
"`(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+`ism" => "\n"
);
return trim(preg_replace(array_keys($regex), array_values($regex), $cssOuterContent));
}
/**
* It will scan all STYLE tags and alter any that contains @font-face
*
* @param $htmlSource
*
* @return mixed
*
* @noinspection MultiAssignmentUsageInspection
*/
public static function alterLocalFontFaceFromInlineStyleTags($htmlSource)
{
// Avoid further processing if there is no @font-face in the HTML source
if (! preg_match('/@font-face(\s+|){/i', $htmlSource)) {
return $htmlSource;
}
preg_match_all('#<\s*?style\b[^>]*>(.*?)</style\b[^>]*>#s', $htmlSource, $styleMatches, PREG_SET_ORDER);
// No STYLE tags? Stop here
if (empty($styleMatches)) {
return $htmlSource;
}
// Go through each STYLE tag
foreach ($styleMatches as $styleInlineArray) {
list($styleInlineTag, $styleInlineContent) = $styleInlineArray;
// Is the content relevant?
if (! preg_match('/@font-face(\s+|){/i', $styleInlineContent)) {
continue;
}
// Alter @font-face content
$newCssOutput = self::alterLocalFontFaceFromCssContent($styleInlineTag);
$htmlSource = str_replace($styleInlineTag, $newCssOutput, $htmlSource);
}
return $htmlSource;
}
}