WordPress enqueues don't support Google Fonts with more than one font-family.
Tags: wordpress, google, fonts
Published: 2020-05-22 14:26:22

The current Google Fonts stylesheet URLs syntax looks like this:

`https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,700;1,400;1,700&family=Montserrat:wght@700;800&display=swap`

If you use more than one font-family, the "family" parameter is reused. If you're using `wp_register_style` and `wp_enqueue_style` to add this stylesheet link to your site, you'll get this:

`https://fonts.googleapis.com/css2?family=Montserrat:wght@700;800&display=swap&ver=5.4.1`

The code that adds the version query string parameter uses [parse_str](https://www.php.net/manual/en/function.parse-str.php), which returns code like this:

```php
<?php
$str = "family=Lora:ital,wght@0,400;0,700;1,400;1,700&family=Montserrat:wght@700;800&display=swap";

parse_str($str, $output);
echo $output['family'];  // Montserrat:wght@700;800
echo $output['display']; // swap
```

I'm not sure why `add_query_arg` *has* to deconstruct the entire query string and rebuild it in order to add another query string parameter. In my opinion, WordPress also does a bad job of giving devs the opportunity to prevent this behavior with hooks. Consider this snippet from `do_item` in `class.wp-styles.php`:

```php
$href = $this->_css_href( $src, $ver, $handle );
....
$tag = apply_filters( 'style_loader_tag', $tag, $handle, $href, $media );
```
The original `$src` variable is never accessible through hooks after being changed into `$href`. Consider `_css_href`, which `do_item` calls. This is where the query string arg is added:

```php
if ( ! empty( $ver ) ) {
    $src = add_query_arg( 'ver', $ver, $src );
}

$src = apply_filters( 'style_loader_src', $src, $handle );
return esc_url( $src );
```
We're given a filter, but it doesn't include the original `$src`, because it's been overwritten by `add_query_arg`. If the original variables were preserved and passed in these hooks, then we'd be able to prevent this behavior.

Another way to do it is to change the behavior of `wp_parse_str`. There's a [good comment on php.net outlining the issue from Evan K from **12 years ago**](https://www.php.net/manual/en/function.parse-str.php#76792):

> It bears mentioning that the parse_str builtin does NOT process a query string in the CGI standard way, when it comes to duplicate fields.  If multiple fields of the same name exist in a query string, every other web processing language would read them into an array, but PHP silently overwrites them:

```php
<?php
# silently fails to handle multiple values
parse_str('foo=1&foo=2&foo=3');

# the above produces:
$foo = array('foo' => '3');
?>
```

> Instead, PHP uses a non-standards compliant practice of including brackets in fieldnames to achieve the same effect.

```php
<?php
# bizarre php-specific behavior
parse_str('foo[]=1&foo[]=2&foo[]=3');

# the above produces:
$foo = array('foo' => array('1', '2', '3') );
?>
```

> This can be confusing for anyone who's used to the CGI standard, so keep it in mind.

But of course, changing the Google Fonts URL to `https://fonts.googleapis.com/css2?family[]=Lora:ital,wght@0,400;0,700;1,400;1,700&family[]=Montserrat:wght@700;800&display=swap` results in a 404, so there's no easy fix there.

## How to Fix

There are a few ways to fix this. I grabbed Evan's `proper_parse_str` function and plugged it into `wp_parse_str` and it worked fine for this case, but I don't know where all else `wp_parse_str` is used, so it makes me hesitant to propose that as a solution. 

For the meantime, we can:

1. Set `$ver` to `null` when registering and/or enqueueing the stylesheet.
1. Enqueue fonts in separate files, rather than one.
1. Petition WordPress to change the behavior of `wp_parse_str`.