Duplicate Posts and Add a Duplicate Bu...
Learn how to easily duplicate posts and add a convenient duplicate button in WordPres...

WPCodeBox
398

Effortlessly integrate and select custom fonts in Oxygen Builder and other platforms with this powerful snippet.
<?php
/*
Plugin Name: MA Oxygen Custom Fonts
Description: Load custom fonts and inject to Oxygen
Author: <a href="https://www.altmann.de/">Matthias Altmann</a>
Project: Code Snippet: Load custom fonts and inject to Oxygen
Version: 3.2.4
Plugin URI: https://www.altmann.de/en/blog-en/code-snippet-integrate-custom-fonts-into-oxygen-en/
Description: en: https://www.altmann.de/en/blog-en/code-snippet-integrate-custom-fonts-into-oxygen-en/
de: https://www.altmann.de/blog/code-snippet-eigene-schriftarten-in-oxygen-integrieren/
Copyright: © 2020-2021, Matthias Altmann
Version History:
Date Version Description
--------------------------------------------------------------------------------------------------------------
2021-10-26 3.2.4 Changes:
- Deferred initialization to hook wp_loaded, after incompatibility checks
- Gutenberg: Apply custom fonts for new posts also
- Renamed IDs (for scripts and styles) and CSS classes (for font test) for consistency
Performance:
- Avoid unneccessary init by separating admin/frontend and more detailed checks
Fixes:
- Fixed an issue comparing hashes for code and file
(Thanks to David Sitniansky for reporting)
- Emit styles for Gutenberg correctly if $cssoutput is configured as 'html'
2021-10-15 3.2.3 Changes:
- Gutenberg: Use display font for post title
(Thanks to Sunny Trochaniak for reporting)
- Fonts preview: Changed sample text size from 15 to 16 px which is browser standard
- Fonts preview: Shortcode output uses WP system fonts for UI instead of custom fonts
- CSS file link now contains ?ver=... instead of ?hash only
Performance:
- Only create CSS file if contents (font configuration) have changed
Fixes:
- Removed itemprop="stylesheet" from <link rel="stylesheet" ...>
(Thanks to Max Gottschalk for reporting and testing)
- Proper quoting for font families
2021-08-02 3.2.2 Changes:
- Using scheme-less URL to avoid issues with wrong WordPress URL configuration
- Added admin notice if folder wp-content/uploads/fonts is not writable.
Fixes:
- Fixed issue with uppercase font file extensions.
2021-06-18 3.2.1 Fixes:
- Fixed typo in CSS for Gutenberg
2021-06-18 3.2.0 New Features:
- Display Custom Fonts in Gutenberg (enqueue ma_customfonts.css for font definitions,
add custom style for display and text font from Oxygen global settings)
Changes:
- Auto-create folder /wp-content/uploads/fonts
2021-05-17 3.1.3 Changes:
- Optimized init sequence
- Emit implementation and version in CSS
- Reversed Version History order
2021-05-16 3.1.2 Changes:
- Avoid font swap: Load ma-customfonts.css early; default font-display now "block"
New Features:
- Allow space in addition to dashes to detect font weights and styles
2021-03-21 3.1.1 Fixes:
- Fixed font loading in Gutenberg editor (with Oxygen Gutenberg Integration)
2021-03-20 3.1.0 New Features:
- "Oblique" in font file name is now detected as italic style
- Custom Fonts test: Option to show font weights/styles without files as browser would
simulate.
Changes:
- Output Custom Font CSS in head instead of footer to prevent font swap
- Custom Fonts test: Changed logic for output font samples and related file info
Fixes:
- Custom Fonts test: Fixed font file count for fonts provided by Web Font Loader
2021-03-08 3.0.2 Fix:
- Compatibility with Windows server and local dev environments.
(Thanks to Franz Müller for reporting and testing)
2021-02-23 3.0.1 Fixes:
- Compatibility with WordPress 5.6.2 (doesn't set REQUEST::action anymore)
- Compatibility check with Swiss Knife's Font Manager feature
- Compatibility with Swiss Knife (font lists did not display custom fonts light blue)
2021-02-18 3.0.0 New Features:
- Support for font packages from Web Font Loader (https://webfontloader.altmann.de/)
- New configuration option: CSS output as inline CSS or external CSS file (cacheable)
- New configuration option: CSS minimize (was controlled by debug switch before)
- Changed configuration option: font-display may now be specified as desired,
default is now 'auto'
2021-01-24 2.5.2 New Features:
- Custom Fonts test (via Admin panel and shortcode) now allows custom sample text
2021-01-23 2.5.1 Fix:
- Changed compatibility check process:
Changed Hook for plugin compatibility check from plugins_loaded to init
Check only if admin and function is_plugin_active exists
(Thanks to Sebastian Albert for reporting and testing)
2021-01-23 2.5.0 New features:
- WP Admin Menu: Appearance > Custom Fonts
Shows a list of all registered custom fonts, including samples, weights, formats
with adaptable sample font size
- Detect font weight terms "Book" (400) and "Demi" (600)
Changes:
- Redesign of classes (MA_CustomFonts, ECF_Plugin)
- Font swap is now a configuration option
- Cut "-webfont" from font name
2020-12-08 2.2.5 Changes:
- In CSS, font sources are now listed in a prioritized order (eot,woff2,woff,ttf,otf,svg)
(Thanks to Viorel Cosmin Miron for reporting)
- Test shortcode now also displays available font formats
2020-11-27 2.2.4 Fix:
- Corrected typo in variable name (2 occurrences) that could cause repeated search
for font files. (Thanks to Viorel Cosmin Miron for reporting)
2020-11-25 2.2.3 Changes:
- In Oxygen font selectors the custom fonts are now displayed in lightblue
to distinguish from default, websafe and Google Fonts
2020-11-25 2.2.2 New features:
- Partial support for fonts with variable weights, detected by "VariableFont" in
filename. CSS output as font-weight:100 900;
2020-11-24 2.2.1 New features:
- Shortcode [ maltmann_custom_font_test ] for listing all custom fonts with their weights
and styles
Changes:
- Fonts are now sorted alphabetically for e.g. CSS output
- Added more request rules to skipping code execution when not needed
2020-11-23 2.2.0 New features:
- Detection of font weight from number values
- CSS now contains font-display:swap;
2020-10-03 2.1.1 Fix:
- Handle empty fonts folder correctly.
(Thanks to Mario Peischl for reporting)
- Corrected title and file name (typo "cutsom") of Code Snippet
2020-09-16 2.1.0 New features:
- Detection of font weight and style from file name
Fixes:
- EOT: Typo in extension detection
- EOT: Missing quote in style output
2020-09-15 2.0.0 Improved version
- Finds all font files (eot, otf, svg, ttf, woff, woff2) in directory wp-content/uploads/fonts/
- Optionally recursive
- Takes font name from file name
- Emits optimized CSS with alternative font formats
- Special handling for EOT for Internet Explorer
2020-04-10 1.0.0 Initial Release for customer project
--------------------------------------------------------------------------------------------------------------
*/
if (!class_exists('MA_CustomFonts')) :
class MA_CustomFonts {
const TITLE = 'MA Custom Fonts';
const VERSION = '3.2.4';
// ===== CONFIGURATION =====
public static $recursive = true; // enables recursive file scan
public static $parsename = true; // enables parsing font weight and style from file name
public static $fontdisplay = 'block'; // set font-display to auto, block, swap, fallback, optional or '' (disable)
public static $cssoutput = 'file'; // set to 'html' to output CSS inline into page,
// set to 'file' to create and reference a CSS file (cacheable by browser)
public static $cssminimize = true; // minimize CSS (true) or pretty print (false)
public static $timing = false; // write timing info (a lot!) to wordpress debug.log if WP_DEBUG enabled
public static $debug = false; // write debug info (a lot!) to wordpress debug.log if WP_DEBUG enabled
public static $sample_text = 'The quick brown fox jumps over the lazy dog.';
// ===== INTERNAL =====
public static $prioritized_formats = ['eot','woff2','woff','ttf','otf','svg'];
private static $fonts = null; // will be populated with fonts and related files we found
private static $fonts_details_cache = []; // cache for already parsed font details
private static $font_files_cnt = 0; // number of font files parsed
private static $font_css = null; // temp storage for custom font css
private static $guten_oxy_font_css = null; // temp storage for Gutenberg css defining oxygen fonts
//-------------------------------------------------------------------------------------------------------------------
static function init() {
if (!defined('MA_CustomFonts_Version')) define('MA_CustomFonts_Version',self::VERSION);
// Pre-fill font definitions
self::get_font_families();
// Pre-fill custom font css, and optionally write file
self::$font_css = self::get_font_css();
// Frontend: Emit custom font css in head
add_action( 'wp_head', function(){
echo self::$font_css;
},5);
// Backend
if (is_admin()) {
// Load CSS for calls using Gutenberg Editor.
// Requires ma_customfont.css (for font loading) and some Oxygen settings (for font assignment)
global $pagenow;
if ( ($pagenow === 'post-new.php') || ($pagenow === 'post.php' && @$_REQUEST['action']==='edit') ) {
// set up fonts dir and url
$fonts_base = self::get_fonts_base();
if (!$fonts_base) {return false;}
// enqueue or embed ma_customfonts.css
if (self::$cssoutput == 'file') {
wp_enqueue_style('ma-customfonts', $fonts_base->url.'/ma_customfonts.css');
} else {
$plain_css = self::get_font_css(true);
add_action( 'admin_head', function(){
echo self::get_font_css();
},5);
}
if (defined("CT_VERSION")) { // Oxygen installed and active?
// create custom style for body, h1-h6 from Oxygen global settings
$ct_global_settings = ct_get_global_settings();
self::$guten_oxy_font_css = sprintf(
'body .editor-styles-wrapper {font-family:%1$s;}'.
'body .editor-styles-wrapper :is(h1,h2,h3,h4,h5,h6,.editor-post-title) {font-family:%2$s;}',
self::quote_font_names($ct_global_settings['fonts']['Text']), // text font
self::quote_font_names($ct_global_settings['fonts']['Display']) // heading font
);
// add custom style to overwrite Gutenberg's default font
if (self::$cssoutput == 'file') {
wp_add_inline_style('ma-customfonts',self::$guten_oxy_font_css);
} else {
add_action( 'admin_head', function(){
echo '<style id="ma-customfonts-oxygen">'.self::$guten_oxy_font_css.'</style>';
},5);
}
}
}
}
// Shortcode for testing custom fonts (listing all fonts with their formats, weights, styles)
add_shortcode('maltmann_custom_font_test', function( $atts, $content, $shortcode_tag ) {
return self::get_font_samples('shortcode');
});
}
//-------------------------------------------------------------------------------------------------------------------
static function init_admin_menu() {
$st = microtime(true);
// Add submenu page to the Appearance menu.
add_action('admin_menu', function(){
add_submenu_page( 'themes.php', // parent slug of "Appearance"
_x('Custom Fonts','page title','ma_customfonts'), // page title
_x('Custom Fonts','menu title','ma_customfonts'), // menu title
'manage_options', // capabilitiy
'ma_customfonts', // menu slug
[__CLASS__, 'admin_customfonts'] // function
);
});
$et = microtime(true);
if (WP_DEBUG && self::$timing) {error_log(sprintf('%s::%s() Timing: %.5f sec.',__CLASS__,__FUNCTION__,$et-$st));}
}
//-------------------------------------------------------------------------------------------------------------------
// Proper quoting for font families: Detect multiple families and quote individually if required
static function quote_font_names($font_spec) {
$fonts = preg_split('/,s*/', $font_spec, -1, PREG_SPLIT_NO_EMPTY);
$fonts = array_map(function($name){
return preg_match('/[^A-Za-z-]/',$name) ? '"'.$name.'"' : $name;
},$fonts);
$retval = implode(', ',$fonts);
return $retval;
}
//-------------------------------------------------------------------------------------------------------------------
static function get_script_version() {
$implementation = basename(__FILE__) == 'ma-oxygen-custom-fonts.php' ? 'Plugin' : 'Code Snippet';
return sprintf('%s, %s', $implementation, self::VERSION);
}
//-------------------------------------------------------------------------------------------------------------------
// Admin function Appearance > Custom Fonts to display samples of all detected fonts
static function admin_customfonts() {
$output = '<div class="wrap">'.
'<h1>' . esc_html(get_admin_page_title()) . '</h1>'.
self::get_font_samples('admin').
'</div>';
echo $output;
echo self::get_font_css();
}
//-------------------------------------------------------------------------------------------------------------------
// parses weight from a font file name (not used for Web Font Loader packages)
static function parse_font_name($name) {
// already in cache?
if (array_key_exists($name,self::$fonts_details_cache)) {return self::$fonts_details_cache[$name];}
$retval = (object)['name'=>$name, 'weight'=>400, 'style'=>'normal'];
if (!self::$parsename) {return $retval;}
$st = microtime(true);
if (WP_DEBUG && self::$debug) {error_log(sprintf('%s::%s() parsing font file name: "%s"',__CLASS__,__FUNCTION__, $retval->name));}
$weights = (object)[ // must match from more to less specific !!
// more specific
200 => '/[ -]?(200|((extra|ultra)-?light))/i',
800 => '/[ -]?(800|((extra|ultra)-?bold))/i',
600 => '/[ -]?(600|([ds]emi(-?bold)?))/i',
// less specific
100 => '/[ -]?(100|thin)/i',
300 => '/[ -]?(300|light)/i',
400 => '/[ -]?(400|normal|regular|book)/i',
500 => '/[ -]?(500|medium)/i',
700 => '/[ -]?(700|bold)/i',
900 => '/[ -]?(900|black|heavy)/i',
'var' => '/[ -]?(VariableFont)/i',
];
$count = 0;
// detect & cut style
$new_name = preg_replace('/[ -]?(italic|oblique)/i', '', $retval->name, -1, $count);
if ($new_name && $count) {
$retval->name = $new_name;
$retval->style = 'italic';
if (WP_DEBUG && self::$debug) {error_log(sprintf('%s::%s() detected italic, new font family name: "%s"',__CLASS__,__FUNCTION__, $retval->name));}
}
// detect & cut weight
foreach ($weights as $weight => $pattern) {
$new_name = preg_replace($pattern, '', $retval->name, -1, $count);
if ($new_name && $count) {
$retval->name = $new_name;
$retval->weight = $weight;
if (WP_DEBUG && self::$debug) {error_log(sprintf('%s::%s() detected weight %s, new font family name: "%s"',__CLASS__,__FUNCTION__, $retval->weight, $retval->name));}
break;
}
}
// cut -webfont
$retval->name = preg_replace('/[ -]?webfont$/i', '', $retval->name);
// variable font: detect & cut specifica
if ($retval->weight == 'var') {
$retval->name = preg_replace('/_(opsz,wght|opsz|wght)$/i', '', $retval->name);
}
if (WP_DEBUG && self::$debug) {error_log(sprintf('%s::%s() retval: [name:"%s", weigh:%d, style:%s]',__CLASS__,__FUNCTION__, $retval->name, $retval->weight, $retval->style));}
// store to cache
self::$fonts_details_cache[$name] = $retval;
$et = microtime(true);
if (WP_DEBUG && self::$timing) {error_log(sprintf(' %s::%s() Timing: %.5f sec.',__CLASS__,__FUNCTION__,$et-$st));}
return $retval;
}
//-------------------------------------------------------------------------------------------------------------------
// construct CSS block from CSS properties stored in JSON from Web Font Loader
static function create_css_from_ruleset($css_ruleset) {
$retval = '';
if (isset($css_ruleset)) {
if (isset($css_ruleset->{'comment'})) {$retval .= sprintf("/* %s */n",$css_ruleset->{'comment'});}
$retval .= "@font-face {n";
$retval .= sprintf("tfont-family: '%s';n",$css_ruleset->{'font-family'});
$retval .= sprintf("tfont-style: %s;n",$css_ruleset->{'font-style'});
$retval .= sprintf("tfont-weight: %s;n",$css_ruleset->{'font-weight'});
$retval .= sprintf("tsrc: url('%s') format('%s');n",$css_ruleset->{'url'}, $css_ruleset->{'format'});
if (isset($css_ruleset->{'unicode-range'})) {$retval .= sprintf("tunicode-range: %s;n", $css_ruleset->{'unicode-range'});}
if (self::$fontdisplay) {
$retval .= sprintf("tfont-display: %s;n",self::$fontdisplay);
}
$retval .= '}';
}
return $retval;
}
//-------------------------------------------------------------------------------------------------------------------
// return base dir/url for fonts. Create directory if necessary
private static function get_fonts_base() {
$retval = (object)['dir'=>null,'url'=>''];
$fonts_dir_info = wp_get_upload_dir();
$retval->dir = $fonts_dir_info['basedir'].'/fonts';
$retval->url = $fonts_dir_info['baseurl'].'/fonts';
// create fonts folder if not exists
if (!file_exists($retval->dir)) {
if (!@mkdir($retval->dir)) {
add_action('admin_notices', function(){
echo '<div class="notice notice-error"><p>['.self::TITLE.'] Error creating fonts base folder <code>wp-content/uploads/fonts</code>.</p></div>';
});
error_log(sprintf('%s::%s() Error creating fonts base folder.', __CLASS__, __FUNCTION__));
return null;
}
}
if (!is_writable($retval->dir)) {
add_action('admin_notices', function(){
echo '<div class="notice notice-error"><p>['.self::TITLE.'] Folder <code>wp-content/uploads/fonts</code> is not writable. Please correct folder permissions.</p></div>';
});
}
// V3.2.2, create scheme-less URL
$retval->url = preg_replace('/^https?:/','',$retval->url);
return $retval;
}
//-------------------------------------------------------------------------------------------------------------------
// find font files in font folder
static function find_fonts() {
$st = microtime(true);
if (isset(self::$fonts)) return;
self::$fonts = [];
// set up fonts dir and url
$fonts_base = self::get_fonts_base();
if (!$fonts_base) {return false;}
// property $recursive either recursive or flat file scan
if (self::$recursive) {
// recursive scan for font files (including subdirectories)
$directory_iterator = new RecursiveDirectoryIterator($fonts_base->dir, RecursiveDirectoryIterator::SKIP_DOTS | RecursiveDirectoryIterator::UNIX_PATHS);
$file_iterator = new RecursiveIteratorIterator($directory_iterator);
} else {
// flat scan for font files (no subdirectories)
$file_iterator = new FilesystemIterator($fonts_base->dir);
}
// loop through files and collect font and JSON files
$font_splfiles = [];
$json_splfiles = [];
foreach( $file_iterator as $file) {
// V3: A JSON file might be available from Web Font Loader
if ($file->getExtension() == 'json') {
$json_splfiles[] = $file;
}
if (in_array(strtolower($file->getExtension()), self::$prioritized_formats)) {
$font_splfiles[] = $file;
}
}
// V3: check JSON files. If it defines "family" read the font name and CSS
$json_font_families = [];
foreach ($json_splfiles as $json_splfile) {
if ($font_details = @json_decode(@file_get_contents($json_splfile->getPathname()))) {
// It's a JSON from Web Font Loader?
if (isset($font_details->creator) && (strpos($font_details->creator, 'Web Font Loader')=== 0)) {
// store font family name
$json_font_families[$json_splfile->getBasename('.json')] = $font_details->family;
// drop all collected font files for that font since they are listed in JSON file
$font_path = $json_splfile->getPath().'/';
foreach ($font_splfiles as $idx => $font_splfile) {
if (strpos($font_splfile->getPath().'/',$font_path) === 0) {
self::$font_files_cnt ++;
unset($font_splfiles[$idx]);
}
}
$font_path = str_replace($fonts_base->dir,'',$font_path);
// encode every single path element since we might have spaces or special chars
$font_path = implode('/',array_map('rawurlencode',explode('/',$font_path)));
// add CSS blocks (could be multiple unicode ranges) to fonts list
$font_baseurl = $fonts_base->url . $font_path;
foreach ($font_details->css as $css_ruleset) {
self::$fonts[$css_ruleset->{'font-family'}][$css_ruleset->{'font-weight'}.'/'.$css_ruleset->{'font-style'}]['has_css'] = true;
// only formats woff and woff2, so just use format as file extension slot
if (!isset(self::$fonts[$css_ruleset->{'font-family'}][$css_ruleset->{'font-weight'}.'/'.$css_ruleset->{'font-style'}][$css_ruleset->{'format'}])) {
self::$fonts[$css_ruleset->{'font-family'}][$css_ruleset->{'font-weight'}.'/'.$css_ruleset->{'font-style'}][$css_ruleset->{'format'}] = [];
}
$css_ruleset->url = $font_baseurl . $css_ruleset->url;
$css_block = self::create_css_from_ruleset($css_ruleset);
self::$fonts[$css_ruleset->{'font-family'}][$css_ruleset->{'font-weight'}.'/'.$css_ruleset->{'font-style'}][$css_ruleset->{'format'}][] = $css_block;
}
}
}
}
// collect font definitions
foreach ($font_splfiles as $font_splfile) {
self::$font_files_cnt ++;
$font_ext = $font_splfile->getExtension();
$font_details = self::parse_font_name($font_splfile->getbasename('.'.$font_ext));
$font_name = $font_details->name;
if (in_array($font_name,array_values($json_font_families))) {
// already found this font from Web Font Loader. Skip.
continue;
}
$font_weight = $font_details->weight;
$font_style = $font_details->style;
$font_path = str_replace($fonts_base->dir,'',$font_splfile->getPath());
// encode every single path element since we might have spaces or special chars
$font_path = implode('/',array_map('rawurlencode',explode('/',$font_path)));
// create entry for this font name
if (!array_key_exists($font_name,self::$fonts)) {self::$fonts[$font_name] = [];}
// create entry for this font weight/style
if (!array_key_exists($font_weight.'/'.$font_style,self::$fonts[$font_name])) {self::$fonts[$font_name][$font_weight.'/'.$font_style] = [];}
// store font details for this file
self::$fonts[$font_name][$font_weight.'/'.$font_style][strtolower($font_ext)] = $fonts_base->url . $font_path . '/' . rawurlencode($font_splfile->getBasename());
}
ksort(self::$fonts, SORT_NATURAL | SORT_FLAG_CASE);
if (WP_DEBUG && self::$debug) {error_log(sprintf('%s::%s() final fonts: %s]',__CLASS__,__FUNCTION__, print_r(self::$fonts,true)));}
$et = microtime(true);
if (WP_DEBUG && self::$timing) {error_log(sprintf('%s::%s() %d font files, %d font families.',__CLASS__,__FUNCTION__, self::$font_files_cnt, count(self::$fonts)));}
if (WP_DEBUG && self::$timing) {error_log(sprintf('%s::%s() Timing: %.5f sec.',__CLASS__,__FUNCTION__,$et-$st));}
}
//-------------------------------------------------------------------------------------------------------------------
// returns a list of font families
static function get_font_families() {
if (!isset(self::$fonts)) self::find_fonts();
$st = microtime(true);
$font_family_list = [];
foreach (array_keys(self::$fonts) as $font_name) {
$font_family_list[] = $font_name;
}
$et = microtime(true);
if (WP_DEBUG && self::$timing) {error_log(sprintf('%s::%s() Timing: %.5f sec.',__CLASS__,__FUNCTION__,$et-$st));}
return $font_family_list;
}
//-------------------------------------------------------------------------------------------------------------------
// we call this function from footer emitter to get font definitions for emitting required files
static function get_font_definitions() {
return self::$fonts;
}
//-------------------------------------------------------------------------------------------------------------------
// creates CSS for custom fonts.
// For $cssoutput 'file', return <link rel="stylesheet" ...>, for 'html' <style>...</style>
// If $plaincss is true, return plain css instead
static function get_font_css($plaincss = false) {
// emit CSS for fonts in footer
$version = self::get_script_version();
$style = '';
// set up fonts dir and url
$fonts_base = self::get_fonts_base();
if (!$fonts_base) {return false;}
$st = microtime(true);
foreach (self::$fonts as $font_name => $font_details) {
ksort($font_details);
foreach ($font_details as $weight_style => $file_list) {
list ($font_weight,$font_style) = explode('/',$weight_style);
if (isset($file_list['has_css'])) {
// V3: Google Font package CSS from Web Font Loader already has CSS
foreach (array_reverse(self::$prioritized_formats) as $font_ext) {
// we only have woff and woff2
if (!isset($file_list[$font_ext])) { continue; }
foreach ($file_list[$font_ext] as $css) {
$style .= trim($css).PHP_EOL;
}
}
} else {
// V2: Only have font info and file names. Build CSS
if ($font_weight == 'var') {
$font_weight_output = '100 900';
} else {
$font_weight_output = $font_weight;
}
$style .= '@font-face{'.PHP_EOL.
' font-family:"'.$font_name.'";'.PHP_EOL.
' font-weight:'.$font_weight_output.';'.PHP_EOL.
' font-style:'.$font_style.';'.PHP_EOL;
// .eot needs special handling for IE9 Compat Mode
if (array_key_exists('eot',$file_list)) {$style .= ' src:url("'.$file_list['eot'].'");'.PHP_EOL;}
$urls = [];
// output font sources in prioritized order
foreach (self::$prioritized_formats as $font_ext) {
if (array_key_exists($font_ext,$file_list)) {
$font_url = $file_list[$font_ext];
$format = '';
switch ($font_ext) {
case 'eot': $format = 'embedded-opentype'; break;
case 'otf': $format = 'opentype'; break;
case 'ttf': $format = 'truetype'; break;
// others have same format as extension (svg, woff, woff2)
default: $format = strtolower($font_ext);
}
if ($font_ext == 'eot') {
// IE6-IE8
$urls[] = 'url("'.$font_url.'?#iefix") format("'.$format.'")';
} else {
$urls[] = 'url("'.$font_url.'") format("'.$format.'")';
}
}
}
$style .= ' src:' . join(','.PHP_EOL.' ',$urls) . ';'.PHP_EOL;
if (self::$fontdisplay) {
$style .= sprintf(' font-display: %s;'.PHP_EOL,self::$fontdisplay);
}
$style .= '}'.PHP_EOL;
}
}
}
// if Oxygen Builder is active, emit CSS to show custom fonts in light blue.
$builder_style = defined('SHOW_CT_BUILDER') ? 'div.oxygen-select-box-option.ng-binding.ng-scope[ng-repeat*="elegantCustomFonts"] {color:lightblue !important;}' : '';
if (WP_DEBUG && self::$debug) {error_log(sprintf('%s::%s() style: %s]',__CLASS__,__FUNCTION__, $style));}
// minimize string if configured
if (self::$cssminimize) {
$style = preg_replace('/r?n */','',$style);
}
$retval = '';
if (self::$cssoutput == 'file') {
// option: write CSS to file
$css_path = $fonts_base->dir.'/ma_customfonts.css';
$css_code = '/* Version: '.$version.' */'.PHP_EOL.$style;
$css_hash_code = hash('CRC32', $css_code, false);
$css_hash_file = file_exists($css_path) ? hash_file('CRC32', $css_path, false) : 0;
if ($css_hash_code !== $css_hash_file) {
if (WP_DEBUG && (self::$timing || self::$debug)) {error_log(sprintf('%s::%s() Writing CSS file "%s"',__CLASS__,__FUNCTION__,$css_path));}
$status = file_put_contents($css_path, $css_code);
if ($status === false) {error_log(sprintf('%s::%s() Error writing CSS file "%s"',__CLASS__,__FUNCTION__,$css_path));}
$css_hash_file = file_exists($css_path) ? hash_file('CRC32', $css_path, false) : 0;
}
$css_url = str_replace($fonts_base->dir,$fonts_base->url ,$css_path);
$retval = sprintf('<link id="ma-customfonts" href="%s?ver=%s" rel="stylesheet" type="text/css" />%s',$css_url, $css_hash_file, $builder_style?'<style>'.$builder_style.'</style>':'');
}
if (self::$cssoutput == 'html') {
// option: write CSS to html
$retval = '<style id="ma-customfonts">'.'/* Version: '.$version.' */'.PHP_EOL.$style.PHP_EOL.$builder_style.'</style>';
}
if ($plaincss) {
$retval = $style.PHP_EOL.$builder_style;
}
$et = microtime(true);
if (WP_DEBUG && self::$timing) {error_log(sprintf('%s::%s() Timing: %.5f sec.',__CLASS__,__FUNCTION__,$et-$st));}
return $retval;
}
//-------------------------------------------------------------------------------------------------------------------
// parses font file url from CSS block
static function get_font_file_info_from_css($css) {
$retval = [];
if (!is_array($css)) {$css = [$css];}
foreach($css as $css_block) {
if (preg_match('/url('(.*?)')/',$css_block,$matches)) {
$retval[] = $matches[1];
}
}
$retval = array_unique($retval);
return $retval;
}
//-------------------------------------------------------------------------------------------------------------------
// returns HTML code to display all registered custom fonts
// $mode 'admin': formatting to be displayed on WP Admin > Appearance
// $mode 'shortcode': formatting to be displayed as shortcode output
static function get_font_samples($mode = null) {
$st = microtime(true);
$output = '';
$script_version = self::get_script_version();
$output = '<style>'.
'.ma-customfonts-test {font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;}'.
'#ma-customfonts-input-font-size {width:60px;text-align:center;min-height:1em;line-height:1em;padding:0;}'.
'#ma-customfonts-input-sample-text {width:400px;text-align:left;}'.
'.ma-customfonts-label {display:inline-block;width:150px;line-height:2em;}'.
'.ma-customfonts-font-row {display:flex;flex-direction:row;justify-content:space-between;align-items:center;padding:0;line-height:20px;border-bottom:1px solid #e0e0e0;margin:0 1em;}'.
'.ma-customfonts-font-row:hover {background-color:lightgray;}'.
'.ma-customfonts-font-info {font-size:10px;line-height:1em;width:100px;}'.
'.ma-customfonts-font-sample {font-size:16px;line-height:1em;flex-grow:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;}'.
'.ma-customfonts-format-info {font-size:10px;cursor:help;margin-left:1em;}'.
'.ma-customfonts-simulated {display: none;}'.
'</style>'.
'<div '.($mode=='shortcode'?'class="ma-customfonts-test" style="display:inline-block;border:1px dashed darkgray;padding:10px;"':'').'>'.
($mode=='shortcode'?'<h2>MA Custom Fonts</h2>':'').
'<div style="display:inline-block;border:1px solid darkgray;border-radius:10px;padding:10px;">'.
'<span class="ma-customfonts-label">Version:</span> '.$script_version.'<br/>'.
'<span class="ma-customfonts-label">Font Families:</span> '.count(self::$fonts).'<br/>'.
'<span class="ma-customfonts-label">Font Files:</span> '.self::$font_files_cnt.'<br/>'.
'<span class="ma-customfonts-label">Sample Font Size:</span> '.
'<input id="ma-customfonts-input-font-size" type="number" value="16" onchange="ma_customfonts_change_font_size();"> px<br/>'.
'<span class="ma-customfonts-label">Sample Text:</span> '.
'<input id="ma-customfonts-input-sample-text" value="'.self::$sample_text.'" onkeyup="ma_customfonts_change_sample_text();"><br/>'.
'<span class="ma-customfonts-label">Simulated:</span> '.
'<input id="ma-customfonts-input-simulated" type="checkbox" value="simulated" onchange="ma_customfonts_toggle_simulated();"> Show font weights/styles without files as browser would simulate.<br/>'.
'</div>';
// controls
$controls_script = <<<'END_OF_CONTROLS_SCRIPT'
<script>
function changeCss(className, classValue) {
// we need invisible container to store additional css definitions
var cssMainContainer = jQuery('#css-modifier-container');
if (cssMainContainer.length == 0) {
cssMainContainer = jQuery('<div id="css-modifier-container"></div>');
cssMainContainer.hide().appendTo(jQuery('body'));
}
// we need one div for each class
var classContainer = cssMainContainer.find('div[data-class="' + className + '"]');
if (classContainer.length == 0) {
classContainer = jQuery('<div data-class="' + className + '"></div>');
classContainer.appendTo(cssMainContainer);
}
// append additional style
classContainer.html('<style>' + className + ' {' + classValue + '}</style>');
}
function ma_customfonts_change_font_size() {
var $val = jQuery('#ma-customfonts-input-font-size').val();
changeCss('.ma-customfonts-font-sample','font-size: '+$val+'px;');
}
function ma_customfonts_change_sample_text() {
var $val = jQuery('#ma-customfonts-input-sample-text').val();
jQuery('.ma-customfonts-font-sample').text($val);
}
function ma_customfonts_toggle_simulated() {
var $simulated = jQuery('#ma-customfonts-input-simulated').is(':checked');
jQuery('.ma-customfonts-simulated').css('display',$simulated?'flex':'none');
}
</script>
END_OF_CONTROLS_SCRIPT;
$output .= $controls_script;
// prepare tags for every weight/style combination
$weights = [100,200,300,400,500,600,700,800,900];
$styles = ['normal','italic'];
$weights_styles = [];
foreach ($weights as $weight) { foreach ($styles as $style) { $weights_styles[] = $weight.'/'.$style; } }
// display fonts in each weight/style combination
$sample_text = self::$sample_text;
// build output
foreach (self::$fonts as $font_name => $font_details) {
$output .= sprintf('<h3 style="padding-top: 20px;">%1$s</h3>',$font_name);
ksort($font_details);
foreach ($weights_styles as $weight_style) {
list ($weight,$style) = explode('/',$weight_style);;
$font_file_info = '';
$font_file_list = [];
if (isset($font_details[$weight_style])) {
// walk through possible file formats
foreach (self::$prioritized_formats as $font_ext) {
// details available for this specific file format?
if (isset($font_details[$weight_style][$font_ext])) {
// details content type
if (isset($font_details[$weight_style]['has_css'])) {
// CSS
$font_file_list[$font_ext] = self::get_font_file_info_from_css($font_details[$weight_style][$font_ext]);
} else {
// just file name
$font_file_list[$font_ext] = [$font_details[$weight_style][$font_ext]];
}
}
}
// build font file info output
foreach ($font_file_list as $format => $files) {
// cut leading path/url from file info
$files = str_replace(wp_get_upload_dir(),'',$files);
// decode html entities (e.g. %20) in file path
foreach ($files as &$file) {$file = implode('/',array_map('rawurldecode',explode('/',$file)));;}
// convert array to html
$font_file_list[$format] = sprintf('<span title="%2$s">%1$s</span>', strtoupper($format), implode("n",$files));
}
$font_file_info = '<span class="ma-customfonts-format-info">(' . implode(', ',array_values($font_file_list)) . ')</span>';
}
$output .= sprintf( '<div class="ma-customfonts-font-row '.($font_file_info?'':'ma-customfonts-simulated').'">'.
'<span class="ma-customfonts-font-info">%2$s %3$s</span>'.
'<span class="ma-customfonts-font-sample" style="font-family:'%1$s';font-weight:%2$d;font-style:%3$s">%4$s</span>%5$s'.
'</div>',$font_name, $weight, $style, $sample_text, $font_file_info?$font_file_info:'<span class="ma-customfonts-format-info"><em>(simulated)</em></span>');
}
}
$output .= '</div>';
$et = microtime(true);
if (WP_DEBUG && self::$timing) {error_log(sprintf('%s::%s() Timing: %.5f sec.',__CLASS__,__FUNCTION__,$et-$st));}
return $output;
}
} // end of class MA_CustomFonts
endif; // end of conditional implementations
//===================================================================================================================
// Warn about incompatibilities
add_action('wp_loaded',function(){
$GLOBALS['ma_customfonts_incompatibilities'] = [];
if (is_admin()) {
// Plugin "Elegant Custom Fonts"
if (function_exists('is_plugin_active') && is_plugin_active('elegant-custom-fonts/elegant-custom-fonts.php'))
{$GLOBALS['ma_customfonts_incompatibilities'][] = '"Elegant Custom Fonts"';}
// Plugin "Use Any Font"
if (function_exists('is_plugin_active') && is_plugin_active('use-any-font/use-any-font.php'))
{$GLOBALS['ma_customfonts_incompatibilities'][] = '"Use Any Font"';}
// Plugin "Swiss Knife" with feature "Font Manager" active
if (function_exists('is_plugin_active') && is_plugin_active('swiss-knife/swiss-knife.php') && (get_option('swiss_font_manager')=='yes'))
{$GLOBALS['ma_customfonts_incompatibilities'][] = '"Swiss Knife" with feature "Font Manager" enabled';}
if (count($GLOBALS['ma_customfonts_incompatibilities'])) {
add_action('admin_notices', function(){
if (WP_DEBUG ) {error_log('MA_CustomFonts / Incompatibilities: '.print_r($GLOBALS['ma_customfonts_incompatibilities'],true));}
echo '<div class="notice notice-warning is-dismissible">
<p>The Code Snippet "Oxygen: Custom Fonts" is not compatible with the Plugin '.implode(' or ',$GLOBALS['ma_customfonts_incompatibilities']).'.<br/>
Please deactivate either the Code Snippet or the Plugin (feature).</p>
</div>';
});
}
}
if (count($GLOBALS['ma_customfonts_incompatibilities'])) return;
//-------------------------------------------------------------------------------------------------------------------
// create a primitive ECF_Plugin class if plugin "Elegant Custom Fonts" is not installed
if (!count($GLOBALS['ma_customfonts_incompatibilities']) && !class_exists('ECF_Plugin')) {
class ECF_Plugin {
static function get_font_families() {
$st = microtime(true);
$font_family_list = MA_CustomFonts::get_font_families();
$et = microtime(true);
if (WP_DEBUG && MA_CustomFonts::$timing) {error_log(sprintf('MA_CustomFonts/%s::%s() Timing: %.5f sec.',__CLASS__,__FUNCTION__,$et-$st));}
return $font_family_list;
}
}
}
},1000); // hook late to check other plugins!
//===================================================================================================================
// Initialize
add_action('wp_loaded',function(){
if (count($GLOBALS['ma_customfonts_incompatibilities'])) return;
if (wp_doing_ajax()) return; // don't run for AJAX requests
if (wp_doing_cron()) return; // don't run for CRON requests
if (wp_is_json_request()) return; // don't run for JSON requests
if (is_favicon()) return; // don't run for favicon request
if (WP_DEBUG && (MA_CustomFonts::$debug || MA_CustomFonts::$timing)) {error_log(sprintf('MA_CustomFonts: Request URI="%s" action="%s"', @$_SERVER['REQUEST_URI'], @$_REQUEST['action']));}
if (is_admin()) {
// initialize admin menu Appearance > Custom Fonts
MA_CustomFonts::init_admin_menu();
// init custom font functionality only when needed
global $pagenow;
if ( ($pagenow === 'themes.php' && @$_REQUEST['page'] === 'ma_customfonts') // custom font test
|| ($pagenow === 'post-new.php') // new post
|| ($pagenow === 'post.php' && @$_REQUEST['action'] === 'edit') // edit post
) {
MA_CustomFonts::init();
}
} else {
// frontend
MA_CustomFonts::init();
}
},1001); // hook after incompatibility check





