Passed
Push — master ( e3de86...7a576b )
by zyt
03:30
created

GoogleFontsOptimizerUtils::buildFontsMarkupLinks()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 2
nop 1
dl 0
loc 14
ccs 10
cts 10
cp 1
crap 4
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
namespace ZWF;
4
5
class GoogleFontsOptimizerUtils
6
{
7
    /**
8
     * Replaces any occurences of un-encoded ampersands in the given string
9
     * with the value given in the `$amp` parameter (`&amp;` by default).
10
     *
11
     * @param string $url
12
     * @param string $amp
13
     *
14
     * @return string
15
     */
16 7
    public static function encodeUnencodedAmpersands($url, $amp = '&amp;')
17 7
    {
18 7
        $amp = trim($amp);
19 7
        if (empty($amp)) {
20 3
            $amp = '&amp;';
21
        }
22
23 7
        return preg_replace('/&(?!#?\w+;)/', $amp, $url);
24
    }
25
26
    /**
27
     * Turns protocol-relative or non-https URLs into their https versions.
28
     *
29
     * @param string $link
30
     *
31
     * @return string
32
     */
33 6
    public static function httpsify($link)
34 6
    {
35 6
        $is_protocol_relative = ('/' === $link{0} && '/' === $link{1});
36
37 6
        if ($is_protocol_relative) {
38 1
            $link = 'https:' . $link;
39
        } else {
40 5
            $link = preg_replace('/^(https?):\/\//mi', '', $link);
41 5
            $link = 'https://' . $link;
42
        }
43
44 6
        return $link;
45
    }
46
47
    /**
48
     * Returns true if a given url is a google font url.
49
     *
50
     * @param string $url
51
     *
52
     * @return bool
53
     */
54 12
    public static function isGoogleWebFontUrl($url)
55 12
    {
56 12
        return (substr_count($url, 'fonts.googleapis.com/css') > 0);
57
    }
58
59
    /**
60
     * Returns true if given `$string` contains the HTML5 doctype.
61
     *
62
     * @param string $string
63
     *
64
     * @return bool
65
     */
66 2
    public static function hasHtml5Doctype($string)
67 2
    {
68 2
        return (preg_match('/^<!DOCTYPE.+html>/i', $string) > 0);
69
    }
70
71
    /**
72
     * Returns true when given `$string` contains an XSL stylesheet element.
73
     *
74
     * @param string $string
75
     *
76
     * @return bool
77
     */
78 2
    public static function hasXslStylesheet($string)
79 2
    {
80 2
        return (false !== stripos($string, '<xsl:stylesheet'));
81
    }
82
83
    /**
84
     * Returns true when given `$string` contains the beginnings of an `<html>` tag.
85
     *
86
     * @param string $string
87
     * @return bool
88
     */
89 2
    public static function hasHtmlTag($string)
90 2
    {
91 2
        return (false !== stripos($string, '<html'));
92
    }
93
94
    /**
95
     * Given a key => value map in which the value is a single string or
96
     * a list of comma-separeted strings, it returns a new array with the
97
     * given keys, but the values are transformed into an array and any
98
     * potential duplicate values are removed.
99
     * If the $sort parameter is given, the list of values is sorted using
100
     * `sort()` and the $sort param is treated as a sort flag.
101
     *
102
     * @param array $data
103
     * @param bool|int $sort If false, no sorting, otherwise an int representing
104
     *                       sort flags. See http://php.net/sort
105
     *
106
     * @return array
107
     */
108 5
    public static function dedupValues(array $data, $sort = false)
109 5
    {
110 5
        foreach ($data as $key => $values) {
111 5
            $parts = explode(',', $values);
112 5
            $parts = array_unique($parts);
113 5
            if (false !== $sort) {
114 5
                sort($parts, (int) $sort);
115
            }
116 5
            $data[$key] = $parts;
117
        }
118
119 5
        return $data;
120
    }
121
122
    /**
123
     * Given a "raw" getFontsArray(), deduplicate and sort the data
124
     * and return a new array with three keys:
125
     * - the first key contains sorted list of fonts/sizes
126
     * - the second key contains the global list of requested subsets
127
     * - the third key contains the map of requested font names and their subsets
128
     *
129
     * @param array $fonts_array
130
     *
131
     * @return array
132
     */
133 5
    public static function consolidateFontsArray(array $fonts_array)
134 5
    {
135 5
        $fonts         = [];
136 5
        $subsets       = [];
137 5
        $fonts_to_subs = [];
138
139 5
        foreach ($fonts_array['complete'] as $font_string) {
140 5
            $parts = explode(':', $font_string);
141 5
            $name  = $parts[0];
142 5
            $size  = isset($parts[1]) ? $parts[1] : '';
143
144 5
            if (isset($fonts[$name])) {
145
                // If a name already exists, append the new size
146 1
                $fonts[$name] .= ',' . $size;
147
            } else {
148
                // Create a new key for the name and size
149 5
                $fonts[$name] = $size;
150
            }
151
152
            // Check if subset is specified as the third element
153 5
            if (isset($parts[2])) {
154 4
                $subset = $parts[2];
155
                // Collect all the subsets defined into a single array
156 4
                $elements = explode(',', $subset);
157 4
                foreach ($elements as $sub) {
158 4
                    $subsets[] = $sub;
159
                }
160
                // Keeping a separate map of names => requested subsets for
161
                // webfontloader purposes
162 4
                if (isset($fonts_to_subs[$name])) {
163 1
                    $fonts_to_subs[$name] .= ',' . $subset;
164
                } else {
165 5
                    $fonts_to_subs[$name] = $subset;
166
                }
167
            }
168
        }
169
170
        // Remove duplicate subsets
171 5
        $subsets = array_unique($subsets);
172
173
        // Sanitize and de-dup name/sizes pairs
174 5
        $fonts = self::dedupValues($fonts, SORT_REGULAR); // sorts values (sizes)
175
176
        // Sorts by font names alphabetically
177 5
        ksort($fonts);
178
179
        // Sanitize and de-dup $fonts_to_subs mapping
180 5
        $fonts_to_subs = self::dedupValues($fonts_to_subs, false); // no sort
181
182 5
        return [$fonts, $subsets, $fonts_to_subs];
183
    }
184
185
    /**
186
     * Given data from `getFontsArray()` builds `<link rel="stylesheet">` markup.
187
     *
188
     * @param array $fonts
189
     *
190
     * @return string
191
     */
192 2
    public static function buildFontsMarkupLinks(array $fonts)
193 2
    {
194 2
        $font_url = self::buildGoogleFontsUrlFromFontsArray($fonts);
195 2
        $href     = self::encodeUnencodedAmpersands($font_url);
196 2
        $markup   = '<link rel="stylesheet" type="text/css" href="' . $href . '">';
197
198 2
        if (isset($fonts['partial']) && is_array($fonts['partial']['url'])) {
199 1
            foreach ($fonts['partial']['url'] as $other) {
200 1
                $markup .= '<link rel="stylesheet" type="text/css"';
201 1
                $markup .= ' href="' . self::encodeUnencodedAmpersands($other) . '">';
202
            }
203
        }
204
205 2
        return $markup;
206
    }
207
208
    /**
209
     * Given data from `GoogleFontsOptimizer::getFontsArray()` builds
210
     * WebFont loader script markup.
211
     *
212
     * @param array $fonts
213
     *
214
     * @return string
215
     */
216 2
    public static function buildFontsMarkupScript(array $fonts)
217 2
    {
218 2
        $families_array = [];
219
220 2
        list($names, $subsets, $mapping) = self::consolidateFontsArray($fonts);
221 2
        foreach ($names as $name => $sizes) {
222 2
            $family = $name . ':' . implode(',', $sizes);
223 2
            if (isset($mapping[$name])) {
224 2
                $family .= ':' . implode(',', $mapping[$name]);
225
            }
226 2
            $families_array[] = $family;
227
        }
228 2
        $families = "'" . implode("', '", $families_array) . "'";
229
230
        // Load 'text' requests with the "custom" module
231 2
        $custom = '';
232 2
        if (isset($fonts['partial'])) {
233 1
            $custom  = ",\n    custom: {\n";
234 1
            $custom .= "        families: [ '" . implode("', '", $fonts['partial']['name']) . "' ],\n";
235 1
            $custom .= "        urls: [ '" . implode("', '", $fonts['partial']['url']) . "' ]\n";
236 1
            $custom .= '    }';
237
        }
238
239
        $markup = <<<MARKUP
240
<script type="text/javascript">
241
WebFontConfig = {
242 2
    google: { families: [ {$families} ] }{$custom}
243
};
244
(function() {
245
    var wf = document.createElement('script');
246
    wf.src = ('https:' == document.location.protocol ? 'https' : 'http') +
247
        '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
248
    wf.type = 'text/javascript';
249
    wf.async = 'true';
250
    var s = document.getElementsByTagName('script')[0];
251
    s.parentNode.insertBefore(wf, s);
252
})();
253
</script>
254
MARKUP;
255
256 2
        return $markup;
257
    }
258
259
    /**
260
     * Builds a combined Google Font URL for multiple font families/subsets.
261
     *
262
     * Usage examples:
263
     * ```
264
     * ZWF\GoogleFontsOptimizerUtils::buildGoogleFontsUrl(
265
     *     [
266
     *         'Open Sans' => [ '400', '400italic', '700', '700italic' ],
267
     *         'Ubuntu'    => [ '400', '400italic', '700', '700italic' ],
268
     *     ),
269
     *     [ 'latin', 'latin-ext' ]
270
     * );
271
     * ```
272
     *
273
     * or
274
     *
275
     * ```
276
     * ZWF\GoogleFontsOptimizerUtils::buildGoogleFontsUrl(
277
     *     [
278
     *         'Open Sans' => '400,400italic,700,700italic',
279
     *         'Ubuntu'    => '400,400italic,700,700italic',
280
     *     ],
281
     *     'latin,latin-ext'
282
     * );
283
     * ```
284
     *
285
     * @param array $fonts
286
     * @param array|string $subsets
287
     *
288
     * @return null|string
289
     */
290 6
    public static function buildGoogleFontsUrl(array $fonts, $subsets = [])
291 6
    {
292 6
        $base_url  = 'https://fonts.googleapis.com/css';
293 6
        $font_args = [];
294 6
        $family    = [];
295
296 6
        foreach ($fonts as $font_name => $font_weight) {
297 6
            if (is_array($font_weight)) {
298 6
                $font_weight = implode(',', $font_weight);
299
            }
300
            // Trimming end colon handles edge case of being given an empty $font_weight
301 6
            $family[] = trim(trim($font_name) . ':' . trim($font_weight), ':');
302
        }
303
304 6
        $font_args['family'] = implode('|', $family);
305
306 6
        if (! empty($subsets)) {
307 5
            if (is_array($subsets)) {
308 4
                $subsets = array_unique($subsets);
309 4
                $subsets = implode(',', $subsets);
310
            }
311 5
            $font_args['subset'] = trim($subsets);
312
        }
313
314 6
        $url = $base_url . '?' . http_build_query($font_args);
315
316 6
        return $url;
317
    }
318
319
    /**
320
     * Creates a single google fonts url from data returned by `getFontsArray()`.
321
     *
322
     * @param array $fonts_array
323
     *
324
     * @return string
325
     */
326 3
    public static function buildGoogleFontsUrlFromFontsArray(array $fonts_array)
327 3
    {
328 3
        list($fonts, $subsets) = self::consolidateFontsArray($fonts_array);
329
330 3
        return self::buildGoogleFontsUrl($fonts, $subsets);
331
    }
332
}
333