Passed
Push — v3 ( cc2dc5...655881 )
by Andrew
38:15 queued 25:10
created

UrlHelper::getSiteUrlOverrideSetting()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
eloc 12
c 0
b 0
f 0
dl 0
loc 22
ccs 0
cts 13
cp 0
rs 9.5555
cc 5
nc 5
nop 1
crap 30
1
<?php
2
/**
3
 * SEOmatic plugin for Craft CMS 3.x
4
 *
5
 * A turnkey SEO implementation for Craft CMS that is comprehensive, powerful,
6
 * and flexible
7
 *
8
 * @link      https://nystudio107.com
9
 * @copyright Copyright (c) 2017 nystudio107
10
 */
11
12
namespace nystudio107\seomatic\helpers;
13
14
use Craft;
15
use craft\errors\SiteNotFoundException;
16
use craft\helpers\UrlHelper as CraftUrlHelper;
17
use nystudio107\seomatic\Seomatic;
18
use yii\base\Exception;
19
20
/**
21
 * @author    nystudio107
22
 * @package   Seomatic
23
 * @since     3.0.0
24
 */
25
class UrlHelper extends CraftUrlHelper
26
{
27
    // Public Static Properties
28
    // =========================================================================
29
30
    // Public Static Methods
31
    // =========================================================================
32
33
    /**
34
     * @inheritDoc
35
     */
36
    public static function siteUrl(string $path = '', $params = null, string $scheme = null, int $siteId = null): string
37
    {
38
        try {
39
            $siteUrl = self::getSiteUrlOverrideSetting($siteId);
40
        } catch (\Throwable $e) {
41
            // That's okay
42
        }
43
        if (!empty($siteUrl)) {
44
            $siteUrl = MetaValue::parseString($siteUrl);
45
            // Extract out just the path part
46
            $parts = self::decomposeUrl($path);
47
            $path = $parts['path'] . $parts['suffix'];
48
            $url = self::mergeUrlWithPath($siteUrl, $path);
49
            // Handle trailing slashes properly for generated URLs
50
            $generalConfig = Craft::$app->getConfig()->getGeneral();
51
            if ($generalConfig->addTrailingSlashesToUrls && !preg_match('/\.[^\/]+$/', $url)) {
52
                $url = rtrim($url, '/') . '/';
53
            }
54
            if (!$generalConfig->addTrailingSlashesToUrls) {
55
                $url = rtrim($url, '/');
56
            }
57
58
            return DynamicMeta::sanitizeUrl(parent::urlWithParams($url, $params ?? []), false, false);
59
        }
60
61
        return DynamicMeta::sanitizeUrl(parent::siteUrl($path, $params, $scheme, $siteId), false, false);
62
    }
63
64
    /**
65
     * Merge the $url and $path together, combining any overlapping path segments
66
     *
67
     * @param string $url
68
     * @param string $path
69
     * @return string
70
     */
71
    public static function mergeUrlWithPath(string $url, string $path): string
72
    {
73
        $overlap = 0;
74
        $urlOffset = strlen($url);
75
        $pathLength = strlen($path);
76
        $pathOffset = 0;
77
        while ($urlOffset > 0 && $pathOffset < $pathLength) {
78
            $urlOffset--;
79
            $pathOffset++;
80
            if (str_starts_with($path, substr($url, $urlOffset, $pathOffset))) {
81
                $overlap = $pathOffset;
82
            }
83
        }
84
85
        return rtrim($url, '/') . '/' . ltrim(substr($path, $overlap), '/');
86
    }
87
88
    /**
89
     * Return the page trigger and the value of the page trigger (null if it doesn't exist)
90
     *
91
     * @return array
92
     */
93
    public static function pageTriggerValue(): array
94
    {
95
        $pageTrigger = Craft::$app->getConfig()->getGeneral()->pageTrigger;
96
        if (!\is_string($pageTrigger) || $pageTrigger === '') {
97
            $pageTrigger = 'p';
98
        }
99
        // Is this query string-based pagination?
100
        if ($pageTrigger[0] === '?') {
101
            $pageTrigger = trim($pageTrigger, '?=');
102
        }
103
        // Avoid conflict with the path param
104
        $pathParam = Craft::$app->getConfig()->getGeneral()->pathParam;
105
        if ($pageTrigger === $pathParam) {
106
            $pageTrigger = $pathParam === 'p' ? 'pg' : 'p';
107
        }
108
        $pageTriggerValue = Craft::$app->getRequest()->getParam($pageTrigger);
109
110
        return [$pageTrigger, $pageTriggerValue];
111
    }
112
113
    /**
114
     * Return an absolute URL with protocol that curl will be happy with
115
     *
116
     * @param string $url
117
     *
118
     * @return string
119
     */
120
    public static function absoluteUrlWithProtocol($url): string
121
    {
122
        // Make this a full URL
123
        if (!self::isAbsoluteUrl($url)) {
124
            $protocol = 'http';
125
            if (isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1)
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (IssetNode && strcasecmp...PROTO'], 'https') === 0, Probably Intended Meaning: IssetNode && (strcasecmp...ROTO'], 'https') === 0)
Loading history...
126
                || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0
127
            ) {
128
                $protocol = 'https';
129
            }
130
            if (self::isProtocolRelativeUrl($url)) {
131
                try {
132
                    $url = self::urlWithScheme($url, $protocol);
133
                } catch (SiteNotFoundException $e) {
134
                    Craft::error($e->getMessage(), __METHOD__);
135
                }
136
            } else {
137
                try {
138
                    $url = self::siteUrl($url, null, $protocol);
139
                    if (self::isProtocolRelativeUrl($url)) {
140
                        $url = self::urlWithScheme($url, $protocol);
141
                    }
142
                } catch (Exception $e) {
143
                    Craft::error($e->getMessage(), __METHOD__);
144
                }
145
            }
146
        }
147
        // Ensure that any spaces in the URL are encoded
148
        $url = str_replace(' ', '%20', $url);
149
150
        // Handle trailing slashes properly for generated URLs
151
        $generalConfig = Craft::$app->getConfig()->getGeneral();
152
        if ($generalConfig->addTrailingSlashesToUrls && !preg_match('/\.[^\/]+$/', $url)) {
153
            $url = rtrim($url, '/') . '/';
154
        }
155
        if (!$generalConfig->addTrailingSlashesToUrls) {
156
            $url = rtrim($url, '/');
157
        }
158
159
        return DynamicMeta::sanitizeUrl($url, false, false);
160
    }
161
162
    /**
163
     * urlencode() just the query parameters in the URL
164
     *
165
     * @param string $url
166
     * @return string
167
     */
168 2
    public static function encodeUrlQueryParams(string $url): string
169
    {
170 2
        $urlParts = parse_url($url);
171 2
        $encodedUrl = "";
172 2
        if (isset($urlParts['scheme'])) {
173
            $encodedUrl .= $urlParts['scheme'] . '://';
174
        }
175 2
        if (isset($urlParts['host'])) {
176
            $encodedUrl .= $urlParts['host'];
177
        }
178 2
        if (isset($urlParts['port'])) {
179
            $encodedUrl .= ':' . $urlParts['port'];
180
        }
181 2
        if (isset($urlParts['path'])) {
182 2
            $encodedUrl .= $urlParts['path'];
183
        }
184 2
        if (isset($urlParts['query'])) {
185
            $query = explode('&', $urlParts['query']);
186
            foreach ($query as $j => $value) {
187
                $value = explode('=', $value, 2);
188
                if (count($value) === 2) {
189
                    $query[$j] = urlencode($value[0]) . '=' . urlencode($value[1]);
190
                } else {
191
                    $query[$j] = urlencode($value[0]);
192
                }
193
            }
194
            $encodedUrl .= '?' . implode('&', $query);
195
        }
196 2
        if (isset($urlParts['fragment'])) {
197
            $encodedUrl .= '#' . $urlParts['fragment'];
198
        }
199
200 2
        return $encodedUrl;
201
    }
202
203
    /**
204
     * Return whether this URL has a sub-directory as part of it
205
     *
206
     * @param string $url
207
     * @return bool
208
     */
209
    public static function urlHasSubDir(string $url): bool
210
    {
211
        return !empty(parse_url(trim($url, '/'), PHP_URL_PATH));
212
    }
213
214
    /**
215
     * Return the siteUrlOverride setting, which can be a string or an array of site URLs
216
     * indexed by the site handle
217
     *
218
     * @param int|null $siteId
219
     * @return string
220
     * @throws Exception
221
     * @throws SiteNotFoundException
222
     */
223
    public static function getSiteUrlOverrideSetting(?int $siteId = null): string
224
    {
225
        // If the override is a string, just return it
226
        $siteUrlOverride = Seomatic::$settings->siteUrlOverride;
227
        if (is_string($siteUrlOverride)) {
228
            return $siteUrlOverride;
229
        }
230
        // If the override is an array, pluck the appropriate one by handle
231
        if (is_array($siteUrlOverride)) {
0 ignored issues
show
introduced by
The condition is_array($siteUrlOverride) is always true.
Loading history...
232
            $sites = Craft::$app->getSites();
233
            $site = $sites->getCurrentSite();
234
            if ($siteId !== null) {
235
                $site = $sites->getSiteById($siteId, true);
236
                if (!$site) {
237
                    throw new Exception('Invalid site ID: ' . $siteId);
238
                }
239
            }
240
241
            return $siteUrlOverride[$site->handle] ?? '';
242
        }
243
244
        return '';
245
    }
246
247
    // Protected Methods
248
    // =========================================================================
249
250
    /**
251
     * Decompose a url into a prefix, path, and suffix
252
     *
253
     * @param $pathOrUrl
254
     *
255
     * @return array
256
     */
257
    protected static function decomposeUrl($pathOrUrl): array
258
    {
259
        $result = array();
260
261
        if (filter_var($pathOrUrl, FILTER_VALIDATE_URL)) {
262
            $url_parts = parse_url($pathOrUrl);
263
            $result['prefix'] = $url_parts['scheme'] . '://' . $url_parts['host'];
264
            $result['path'] = $url_parts['path'] ?? '';
265
            $result['suffix'] = '';
266
            $result['suffix'] .= empty($url_parts['query']) ? '' : '?' . $url_parts['query'];
267
            $result['suffix'] .= empty($url_parts['fragment']) ? '' : '#' . $url_parts['fragment'];
268
        } else {
269
            $result['prefix'] = '';
270
            $result['path'] = $pathOrUrl;
271
            $result['suffix'] = '';
272
        }
273
274
        return $result;
275
    }
276
}
277