: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* This file defines Custom Fonts
* Themify_Custom_Fonts class register post type for Custom Fonts and load them
* @package Themify_Builder
* @subpackage Themify_Builder/classes
if (!class_exists('Themify_Custom_Fonts',false)) :
* The Custom Fonts class.
* This class register post type for Custom Fonts and load them.
* @package Themify_Builder
* @subpackage Themify_Builder/classes
class Themify_Custom_Fonts {
* slug Custom Fonts Object.
public static function init() {
self::$api_url = site_url('/?tb_load_cf=');
add_filter('themify_metaboxes', array(__CLASS__, 'meta_box'));
add_filter('themify_metabox/fields/tm-cf', array(__CLASS__, 'meta_box_fields'), 10, 2);
add_filter('upload_mimes', array(__CLASS__, 'upload_mimes'), 99, 1);
add_action('admin_head', array(__CLASS__, 'clean_admin_listing_page'));
add_action('admin_footer', array(__CLASS__, 'admin_footer'));
add_action('wp_enqueue_scripts', array(__CLASS__, 'enqueue_custom_fonts'), 30);
* Register Custom Font Custom Post Type
private static function register_cpt() {
if (!class_exists('CPT',false)) {
include THEMIFY_DIR . '/CPT.php';
// create a template custom post type
'post_type_name' => self::SLUG,
'singular' => __('Custom Font', 'themify'),
'plural' => __('Custom Fonts', 'themify')
'supports' => array('title'),
'exclude_from_search' => true,
'show_in_nav_menus' => false,
// define the columns to appear on the admin edit screen
'cb' => '<input type="checkbox" />',
'title' => __('Title', 'themify'),
'font_preview' => __('Preview', 'themify')
public static function meta_box(array $meta_boxes):array {
$meta_boxes['tm-cf'] = array(
'title' => __('Manage Font Files', 'themify'),
'screen' => array(self::SLUG)
* Setup the custom fields
public static function meta_box_fields(array $fields):array {
$fields['tm-cf'] = array(
'name' => __('Font Variations', 'themify'),
'name' => 'custom_fonts_notice',
'html' => '<div class="themify-info-link">' . __('To add custom fonts: select the font weight/style and upload the font files accordingly. You don\'t need to upload all formats. The common support format is .ttf or .woff (just upload either .tff or .woff file is fine, depending on the font files you have). The custom fonts will appear on Builder and Customizer font select.', 'themify') . '</div>'
'add_new_label' => __('Add New Variation', 'themify'),
'title' => __('Weight', 'themify'),
'type' => 'fontweight_dropdown',
array('value' => '100', 'name' => __('100', 'themify')),
array('value' => '200', 'name' => __('200', 'themify')),
array('value' => '300', 'name' => __('300', 'themify')),
array('value' => '400', 'name' => __('400', 'themify')),
array('value' => '500', 'name' => __('500', 'themify')),
array('value' => '600', 'name' => __('600', 'themify')),
array('value' => '700', 'name' => __('700', 'themify')),
array('value' => '800', 'name' => __('800', 'themify')),
array('value' => '900', 'name' => __('900', 'themify')),
'title' => __('Style', 'themify'),
array('value' => 'normal', 'name' => __('Normal', 'themify')),
array('value' => 'italic', 'name' => __('Italic', 'themify')),
array('value' => 'oblique', 'name' => __('Oblique', 'themify'))
'title' => __('WOFF File', 'themify'),
'mime' => 'application/x-font-woff',
'title' => __('WOFF2 File', 'themify'),
'mime' => 'application/x-font-woff2',
'title' => __('TTF / OTF File', 'themify'),
'mime' => 'application/x-font-ttf',
'title' => __('SVG File', 'themify'),
'mime' => 'image/svg+xml',
'title' => __('EOT File', 'themify'),
'mime' => 'application/x-font-eot',
public static function upload_mimes(array $mime_types):array {
'woff' => 'application/x-font-woff',
'woff2' => 'application/x-font-woff2',
'svg' => 'image/svg+xml',
'eot' => 'application/x-font-eot',
foreach ($ext as $type => $mime) {
if (!isset($mime_types[$type])) {
$mime_types[$type] = $mime;
* Clean up admin Font manager admin listing
public static function clean_admin_listing_page() {
if (self::SLUG === $typenow) {
add_filter('months_dropdown_results', '__return_empty_array');
add_action('manage_' . self::SLUG . '_posts_custom_column', array(__CLASS__, 'render_columns'), 10, 2);
add_filter('screen_options_show_screen', '__return_false');
* Render preview column in font manager admin listing
public static function render_columns($column, $post_id) {
if ('font_preview' === $column) {
$variations = get_post_meta($post_id, 'variations', true);
if (empty($variations)) {
'eot' => 'embedded-opentype',
$font_family = get_the_title($post_id);
foreach ($variations as $var):
foreach ($fonts as $type => $format) {
$src .= !empty($var[$type]) ? 'url(\'' . $var[$type] . '\') format(\'' . $format . '\'),' : '';
@font-face{font-family:'<?php echo $font_family; ?>';font-weight:<?php echo $var['weight']; ?>;font-style:<?php echo $var['style']; ?>;src:<?php echo rtrim($src, ','); ?>; }
$font_face = ob_get_clean();
printf('<style>%s</style><span style="font-family: \'%s\';">%s</span>', trim($font_face), $font_family, __('Themify makes your dreams come true.', 'themify'));
* Returns a list of Custom Fonts
public static function get_list($env = 'builder') {
$list = 'builder' !== $env ? array() : array(
array('value' => '', 'name' => ''),
'name' => '--- ' . __('Custom Fonts', 'themify') . ' ---'
$fonts = self::get_posts(array('limit' => -1));
foreach ($fonts as $slug => $font) {
if (!empty($font['family']) && !empty($font['variants'])) {
'name' => $font['family'],
'variant' => $font['variants']
* Returns a list of variants
private static function get_variants($variants) {
if (!empty($variants) && is_array($variants)) {
foreach ($variants as $var) {
/* 400 is "normal" weight, always show first */
if ($var['weight'] === '400') {
array_unshift($vars, '400');
$vars[] = $var['weight'];
$vars = array_keys(array_flip($vars));
* Get a list of Custom Fonts CPT
* @param array $args arguments of get posts
private static function get_posts(array $args = array()):array {
$limit = empty($args['limit']) ? 10 : $args['limit'];
$post_names = empty($args['post_names']) ? array() : $args['post_names'];
'post_type' => self::SLUG,
'posts_per_page' => $limit,
'ignore_sticky_posts' => true,
'post_name__in' => $post_names
$posts = get_posts($posts_args);
foreach ($posts as $post) {
$data = get_post_meta($post_id, 'variations', true);
$cf_posts[$post->post_name] = array(
'family' => $post->post_title,
'variants' => self::get_variants($data),
* Generate @font-face CSS for list of fonts in $_GET['tb_load_cf']
* This is used only in Builder live preview. On frontend self::load_fonts()
* adds the necessary code as inline CSS to the page.
private static function load_fonts_api() {
if (!empty($_GET['tb_load_cf'])) {
header('Content-Type: text/css');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Cache-Control: private', false);
header('Content-Transfer-Encoding: binary');
$fonts = $_GET['tb_load_cf'];
$fonts = preg_replace('/\.css$/', '', $fonts);
$fonts = explode('|', $fonts);
echo self::get_font_face_css($fonts);
* Returns @font-face CSS for all the custom fonts used in a given $post_id
* @return string Generated CSS code to load the fonts
public static function load_fonts($post_id):string {
$fonts = explode('|', self::get_fonts($post_id));
return self::get_font_face_css($fonts, true);
* Generate @font-family CSS code from an array of fonts
private static function get_font_face_css($fonts, $builder = false):string {
$customizer_fonts = apply_filters('themify_custom_fonts', array());
foreach ($customizer_fonts as $font) {
$f = explode(':', $font);
$v = isset($f[1]) ? explode(',', $f[1]) : array('normal');
$cf[$f[0]] = !isset($cf[$f[0]]) ? $v : array_keys(array_flip(array_merge($cf[$f[0]], $v)));
foreach ($fonts as $font) {
$font = explode(':', $font);
$variations = empty($font[1]) ? array() : explode(',', $font[1]);
// Skip loading duplicate fonts
if (true === $builder && isset($customizer_fonts[$font_family])) {
$variations = array_diff($variations, $customizer_fonts[$font_family]);
$api_fonts[$font_family] = !isset($api_fonts[$font_family]) ? $variations : array_keys(array_flip(array_merge($api_fonts[$font_family], $variations)));
if (!empty($api_fonts)) {
$cf_fonts = self::get_posts(array('post_names' => array_keys($api_fonts), 'limit' => -1));
foreach ($api_fonts as $font_family => $variations) {
if ( !empty($cf_fonts[$font_family]['data'])) {
if (empty($variations) && isset($cf_fonts[$font_family]['variants'])) {
$variations = $cf_fonts[$font_family]['variants'];
if (!empty($variations)) {
foreach ($variations as $var) {
$var = str_replace(['normal', 'bold'], ['400', '700'], $var);
foreach ($cf_fonts[$font_family]['data'] as $k => $v) {
$v['weight'] = str_replace(['normal', 'bold'], ['400', '700'], $v['weight']);
if ($v['weight'] === $var) {
$font_css .= self::get_font_face_from_data($font_family, $cf_fonts[$font_family]['data'][$k]) . PHP_EOL;
unset($cf_fonts[$font_family]['data'][$k]);
* @param string $font_family
* @param array $data variations data
private static function get_font_face_from_data($font_family, $data) {
$types = array('woff2', 'woff', 'eot', 'ttf', 'svg');
foreach ($types as $type) {
if (empty($data[$type])) {
$data[$type] .= '#' . str_replace(' ', '', $font_family);
$src[] = self::get_font_src_per_type($type, $data[$type]);
$font_face = '@font-face{' . PHP_EOL;
$font_face .= "\tfont-family:'" . $font_family . "';" . PHP_EOL;
$font_face .= "\tfont-style:" . $data['style'] . ';' . PHP_EOL;
$font_face .= "\tfont-weight:" . $data['weight'] . ';' . PHP_EOL;
$font_face .= "\tfont-display:swap;" . PHP_EOL;
if (!empty($data['eot'])) {
$font_face .= "\tsrc:url('" . esc_attr($data['eot']) . "');" . PHP_EOL;
$font_face .= "\tsrc:" . implode(',' . PHP_EOL . "\t\t", $src) . PHP_EOL . '}';
* Generate font file src base on file type
* @param string $type font file type
* @param string $url font file url
private static function get_font_src_per_type($type, $url) {
$src = 'url(\'' . esc_attr($url) . '\') ';
$src .= 'format(\'' . $type . '\')';
$src .= 'format(\'truetype\')';