Passed
Push — master ( 7a576b...99159c )
by zyt
04:01
created

GoogleFontsOptimizerUtils::buildGoogleFontsUrlFromFontsArray()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace ZWF;
4
5
use ZWF\GoogleFontsCollection as Collection;
6
7
class GoogleFontsOptimizerUtils
8
{
9
    /**
10
     * Replaces any occurences of un-encoded ampersands in the given string
11
     * with the value given in the `$amp` parameter (`&amp;` by default).
12
     *
13
     * @param string $url
14
     * @param string $amp
15
     *
16
     * @return string
17
     */
18 7
    public static function encodeUnencodedAmpersands($url, $amp = '&amp;')
19 7
    {
20 7
        $amp = trim($amp);
21 7
        if (empty($amp)) {
22 3
            $amp = '&amp;';
23
        }
24
25 7
        return preg_replace('/&(?!#?\w+;)/', $amp, $url);
26
    }
27
28
    /**
29
     * Turns protocol-relative or non-https URLs into their https versions.
30
     *
31
     * @param string $link
32
     *
33
     * @return string
34
     */
35 6
    public static function httpsify($link)
36 6
    {
37 6
        $is_protocol_relative = ('/' === $link{0} && '/' === $link{1});
38
39 6
        if ($is_protocol_relative) {
40 1
            $link = 'https:' . $link;
41
        } else {
42 5
            $link = preg_replace('/^(https?):\/\//mi', '', $link);
43 5
            $link = 'https://' . $link;
44
        }
45
46 6
        return $link;
47
    }
48
49
    /**
50
     * Returns true if a given url is a google font url.
51
     *
52
     * @param string $url
53
     *
54
     * @return bool
55
     */
56 12
    public static function isGoogleWebFontUrl($url)
57 12
    {
58 12
        return (substr_count($url, 'fonts.googleapis.com/css') > 0);
59
    }
60
61
    /**
62
     * Returns true if given `$string` contains the HTML5 doctype.
63
     *
64
     * @param string $string
65
     *
66
     * @return bool
67
     */
68 2
    public static function hasHtml5Doctype($string)
69 2
    {
70 2
        return (preg_match('/^<!DOCTYPE.+html>/i', $string) > 0);
71
    }
72
73
    /**
74
     * Returns true when given `$string` contains an XSL stylesheet element.
75
     *
76
     * @param string $string
77
     *
78
     * @return bool
79
     */
80 2
    public static function hasXslStylesheet($string)
81 2
    {
82 2
        return (false !== stripos($string, '<xsl:stylesheet'));
83
    }
84
85
    /**
86
     * Returns true when given `$string` contains the beginnings of an `<html>` tag.
87
     *
88
     * @param string $string
89
     * @return bool
90
     */
91 2
    public static function hasHtmlTag($string)
92 2
    {
93 2
        return (false !== stripos($string, '<html'));
94
    }
95
96
    /**
97
     * Given a key => value map in which the value is a single string or
98
     * a list of comma-separeted strings, it returns a new array with the
99
     * given keys, but the values are transformed into an array and any
100
     * potential duplicate values are removed.
101
     * If the $sort parameter is given, the list of values is sorted using
102
     * `sort()` and the $sort param is treated as a sort flag.
103
     *
104
     * @param array $data
105
     * @param bool|int $sort If false, no sorting, otherwise an int representing
106
     *                       sort flags. See http://php.net/sort
107
     *
108
     * @return array
109
     */
110 5
    public static function dedupValues(array $data, $sort = false)
111 5
    {
112 5
        foreach ($data as $key => $values) {
113 5
            if (is_array($values)) {
114 5
                $parts = $values;
115
            } else {
116 5
                $parts = explode(',', $values);
117
            }
118
119 5
            $parts = array_unique($parts);
120
121
            // Perform sort if specified
122 5
            if (false !== $sort) {
123 5
                sort($parts, (int) $sort);
124
            }
125
126
            // Store back
127 5
            $data[$key] = $parts;
128
        }
129
130 5
        return $data;
131
    }
132
133
    /**
134
     * Given a GoogleFontsCollection it builds needed `<link rel="stylesheet">` markup.
135
     *
136
     * @param Collection $fonts
137
     *
138
     * @return string
139
     */
140 2
    public static function buildFontsMarkupLinks(Collection $fonts)
141 2
    {
142 2
        $font_url = $fonts->getCombinedUrl();
143 2
        $href     = self::encodeUnencodedAmpersands($font_url);
144 2
        $markup   = '<link rel="stylesheet" type="text/css" href="' . $href . '">';
145
146 2
        foreach ($fonts->getTextUrls() as $url) {
147 1
            $markup .= '<link rel="stylesheet" type="text/css"';
148 1
            $markup .= ' href="' . self::encodeUnencodedAmpersands($url) . '">';
149
        }
150
151 2
        return $markup;
152
    }
153
154
    /**
155
     * Given a GoogleFontsCollection it builds the WebFont loader script markup.
156
     *
157
     * @param Collection $fonts
158
     *
159
     * @return string
160
     */
161 2
    public static function buildFontsMarkupScript(Collection $collection)
162 2
    {
163 2
        $families_array = [];
164
165 2
        list($names, $mapping) = $collection->getScriptData();
166
167 2
        foreach ($names as $name => $sizes) {
168 2
            $family = $name . ':' . implode(',', $sizes);
169 2
            if (isset($mapping[$name])) {
170 2
                $family .= ':' . implode(',', $mapping[$name]);
171
            }
172 2
            $families_array[] = $family;
173
        }
174 2
        $families = "'" . implode("', '", $families_array) . "'";
175
176
        // Load 'text' requests with the "custom" module
177 2
        $custom = '';
178 2
        if ($collection->hasText()) {
179 1
            $custom  = ",\n    custom: {\n";
180 1
            $custom .= "        families: [ '" . implode("', '", $collection->getTextNames()) . "' ],\n";
181 1
            $custom .= "        urls: [ '" . implode("', '", $collection->getTextUrls()) . "' ]\n";
182 1
            $custom .= '    }';
183
        }
184
185
        $markup = <<<MARKUP
186
<script type="text/javascript">
187
WebFontConfig = {
188 2
    google: { families: [ {$families} ] }{$custom}
189
};
190
(function() {
191
    var wf = document.createElement('script');
192
    wf.src = ('https:' == document.location.protocol ? 'https' : 'http') +
193
        '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
194
    wf.type = 'text/javascript';
195
    wf.async = 'true';
196
    var s = document.getElementsByTagName('script')[0];
197
    s.parentNode.insertBefore(wf, s);
198
})();
199
</script>
200
MARKUP;
201
202 2
        return $markup;
203
    }
204
205
    /**
206
     * Builds a combined Google Font URL for multiple font families/subsets.
207
     *
208
     * Usage examples:
209
     * ```
210
     * ZWF\GoogleFontsOptimizerUtils::buildGoogleFontsUrl(
211
     *     [
212
     *         'Open Sans' => [ '400', '400italic', '700', '700italic' ],
213
     *         'Ubuntu'    => [ '400', '400italic', '700', '700italic' ],
214
     *     ),
215
     *     [ 'latin', 'latin-ext' ]
216
     * );
217
     * ```
218
     *
219
     * or
220
     *
221
     * ```
222
     * ZWF\GoogleFontsOptimizerUtils::buildGoogleFontsUrl(
223
     *     [
224
     *         'Open Sans' => '400,400italic,700,700italic',
225
     *         'Ubuntu'    => '400,400italic,700,700italic',
226
     *     ],
227
     *     'latin,latin-ext'
228
     * );
229
     * ```
230
     *
231
     * @param array $fonts
232
     * @param array|string $subsets
233
     *
234
     * @return null|string
235
     */
236 6
    public static function buildGoogleFontsUrl(array $fonts, $subsets = [])
237 6
    {
238 6
        $base_url  = 'https://fonts.googleapis.com/css';
239 6
        $font_args = [];
240 6
        $family    = [];
241
242 6
        foreach ($fonts as $font_name => $font_weight) {
243 6
            if (is_array($font_weight)) {
244 6
                $font_weight = implode(',', $font_weight);
245
            }
246
            // Trimming end colon handles edge case of being given an empty $font_weight
247 6
            $family[] = trim(trim($font_name) . ':' . trim($font_weight), ':');
248
        }
249
250 6
        $font_args['family'] = implode('|', $family);
251
252 6
        if (! empty($subsets)) {
253 5
            if (is_array($subsets)) {
254 4
                $subsets = array_unique($subsets);
255 4
                $subsets = implode(',', $subsets);
256
            }
257 5
            $font_args['subset'] = trim($subsets);
258
        }
259
260 6
        $url = $base_url . '?' . http_build_query($font_args);
261
262 6
        return $url;
263
    }
264
265
    /**
266
     * Flatten an array without recursion.
267
     *
268
     * @param array $arr
269
     *
270
     * @return array
271
     */
272 3
    public static function arrayFlattenIterative(array $arr)
273 3
    {
274 3
        $flat  = [];
275 3
        $stack = array_values($arr);
276
277 3
        while ($stack) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stack of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
278 3
            $value = array_shift($stack);
279 3
            if (is_array($value)) {
280 3
                $stack = array_merge(array_values($value), $stack);
281
            } else {
282 2
                $flat[] = $value;
283
            }
284
        }
285
286 3
        return $flat;
287
    }
288
}
289