Skip to content

Commit 2e88b35

Browse files
committed
Added url settings
1 parent 6aff2d2 commit 2e88b35

File tree

7 files changed

+390
-65
lines changed

7 files changed

+390
-65
lines changed

Plugin.php

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,9 @@
88
use System\Classes\PluginBase;
99
use System\Classes\PluginManager;
1010
use System\Classes\SettingsManager;
11-
use Webmaxx\Seo\Classes\ContentTags\Presets\ComponentTagsPreset;
12-
use Webmaxx\Seo\Classes\ContentTags\Presets\GeneratorAdsTxtPreset;
13-
use Webmaxx\Seo\Classes\ContentTags\Presets\GeneratorAppAdsTxtPreset;
14-
use Webmaxx\Seo\Classes\ContentTags\Presets\GeneratorDefaultPreset;
15-
use Webmaxx\Seo\Classes\ContentTags\Presets\GeneratorHumansTxtPreset;
16-
use Webmaxx\Seo\Classes\ContentTags\Presets\GeneratorRobotsTxtPreset;
17-
use Webmaxx\Seo\Classes\ContentTags\Presets\GeneratorSecurityTxtPreset;
11+
use Webmaxx\Seo\Classes\ContentTags\Presets;
12+
use Webmaxx\Seo\Classes\UrlNormalizer;
13+
use Webmaxx\Seo\Middlewares\UrlNormalizeMiddleware;
1814

1915
/**
2016
* Seo Plugin Information File
@@ -40,13 +36,13 @@ public function pluginDetails(): array
4036
*/
4137
public function register(): void
4238
{
43-
App::singleton('wmSeoGeneratorDefaultContentTagsPreset', fn() => new GeneratorDefaultPreset);
44-
App::singleton('wmSeoGeneratorRobotsTxtContentTagsPreset', fn() => new GeneratorRobotsTxtPreset);
45-
App::singleton('wmSeoGeneratorHumansTxtContentTagsPreset', fn() => new GeneratorHumansTxtPreset);
46-
App::singleton('wmSeoGeneratorSecurityTxtContentTagsPreset', fn() => new GeneratorSecurityTxtPreset);
47-
App::singleton('wmSeoGeneratorAdsTxtContentTagsPreset', fn() => new GeneratorAdsTxtPreset);
48-
App::singleton('wmSeoGeneratorAppAdsTxtContentTagsPreset', fn() => new GeneratorAppAdsTxtPreset);
49-
App::singleton('wmSeoComponentTagsContentTagsPreset', fn() => new ComponentTagsPreset);
39+
App::singleton('wmSeoGeneratorDefaultContentTagsPreset', fn() => new Presets\GeneratorDefaultPreset);
40+
App::singleton('wmSeoGeneratorRobotsTxtContentTagsPreset', fn() => new Presets\GeneratorRobotsTxtPreset);
41+
App::singleton('wmSeoGeneratorHumansTxtContentTagsPreset', fn() => new Presets\GeneratorHumansTxtPreset);
42+
App::singleton('wmSeoGeneratorSecurityTxtContentTagsPreset', fn() => new Presets\GeneratorSecurityTxtPreset);
43+
App::singleton('wmSeoGeneratorAdsTxtContentTagsPreset', fn() => new Presets\GeneratorAdsTxtPreset);
44+
App::singleton('wmSeoGeneratorAppAdsTxtContentTagsPreset', fn() => new Presets\GeneratorAppAdsTxtPreset);
45+
App::singleton('wmSeoComponentTagsContentTagsPreset', fn() => new Presets\ComponentTagsPreset);
5046

5147
\Event::listen('webmaxx.seo.generators.extendDefaultContentTags', function(...$tags) {
5248
app('wmSeoGeneratorDefaultContentTagsPreset')->addTags($tags);
@@ -82,7 +78,9 @@ public function register(): void
8278
*/
8379
public function boot(): void
8480
{
81+
$this->extendMiddlewares();
8582
$this->extendPagesForms();
83+
$this->normalizeUrls();
8684
}
8785

8886
/**
@@ -147,6 +145,23 @@ public function registerSettings(): array
147145
];
148146
}
149147

148+
public function registerMarkupTags()
149+
{
150+
return [
151+
'filters' => [
152+
'urlNormalize' => function ($text, $force = false) {
153+
return UrlNormalizer::normalize($text, $force);
154+
},
155+
],
156+
];
157+
}
158+
159+
protected function extendMiddlewares()
160+
{
161+
$this->app['Illuminate\Contracts\Http\Kernel']
162+
->prependMiddleware(UrlNormalizeMiddleware::class);
163+
}
164+
150165
protected function extendPagesForms(): void
151166
{
152167
Event::listen('backend.form.extendFieldsBefore', function(\Backend\Widgets\Form $widget) {
@@ -190,4 +205,42 @@ protected function addSeoTabFieldsToWidget($widget, $key): void
190205

191206
$widget->tabs['fields'] = array_merge($widget->tabs['fields'], $halcyonFields);
192207
}
208+
209+
protected function normalizeUrls()
210+
{
211+
Event::listen('pages.menu.referencesGenerated', function (&$items) {
212+
$iterator = function ($menuItems) use (&$iterator) {
213+
$result = [];
214+
215+
foreach ($menuItems as $item) {
216+
if ($item->items) {
217+
$item->items = $iterator($item->items);
218+
}
219+
if (isset($item->normalized)) {
220+
continue;
221+
}
222+
223+
if (!empty($item->url) && substr($item->url, 0, 1) !== '#') {
224+
$originalUrl = $item->url;
225+
$normalizedUrl = UrlNormalizer::normalize($item->url);
226+
227+
if ($originalUrl !== $normalizedUrl) {
228+
$item->url = $normalizedUrl;
229+
$item->normalized = true;
230+
} else {
231+
$item->normalized = false;
232+
}
233+
} else {
234+
$item->normalized = true;
235+
}
236+
237+
$result[] = $item;
238+
}
239+
240+
return $result;
241+
};
242+
243+
$items = $iterator($items);
244+
});
245+
}
193246
}

classes/UrlNormalizer.php

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
<?php namespace Webmaxx\Seo\Classes;
2+
3+
use Webmaxx\Seo\Models\Settings;
4+
5+
class UrlNormalizer
6+
{
7+
use \Winter\Storm\Support\Traits\Singleton;
8+
9+
/**
10+
* Settings.
11+
*
12+
* @var array
13+
*/
14+
protected $settings = [];
15+
16+
/**
17+
* Paths to ignore
18+
*
19+
* @var array
20+
*/
21+
protected $pathsToIgnore = [];
22+
23+
/**
24+
* Paths to always ignore
25+
*
26+
* @var array
27+
*/
28+
protected $alwaysIgnore = [
29+
'backend/*'
30+
];
31+
32+
/**
33+
* Initialize this singleton.
34+
*
35+
* @return void
36+
*/
37+
protected function init()
38+
{
39+
$this->settings = Settings::instance();
40+
41+
if (!empty($this->settings->url_ignore)) {
42+
$this->pathsToIgnore = array_merge(
43+
$this->alwaysIgnore,
44+
preg_split(
45+
'/[\r\n]+/',
46+
$this->settings->url_ignore,
47+
-1,
48+
PREG_SPLIT_NO_EMPTY
49+
)
50+
);
51+
} else {
52+
$this->pathsToIgnore = $this->alwaysIgnore;
53+
}
54+
}
55+
56+
/**
57+
* Normalize a given URL using your URL normalisation settings.
58+
*
59+
* This will ignore any URLs that appear to be external. You may force the normalisation if you wish.
60+
*
61+
* @param string $url
62+
* @param bool $force
63+
* @return string The normalized URL
64+
*/
65+
public static function normalize(string $url, bool $force = false)
66+
{
67+
$instance = self::instance();
68+
69+
if (!$force && $instance->isExternal($url)) {
70+
return $url;
71+
}
72+
73+
$originalUrl = $url;
74+
$url = parse_url($url);
75+
76+
// If the link is not a HTTP or HTTPS link, return the URL as is
77+
if (!empty($url['scheme']) && !in_array($url['scheme'], ['http', 'https'])) {
78+
return \http_build_url($url);
79+
}
80+
81+
// Set default URL parts, if not provided
82+
if (empty($url['host'])) {
83+
$url['host'] = $instance->getHostname();
84+
85+
// If we cannot determine the hostname, return the URL as is
86+
if (empty($url['host'])) {
87+
return \http_build_url($url);
88+
}
89+
}
90+
91+
if (empty($url['scheme'])) {
92+
$url['scheme'] = ($instance->getPort() === 443) ? 'https' : 'http';
93+
}
94+
if (empty($url['port'])) {
95+
if (
96+
($url['scheme'] === 'http' && $instance->getPort() !== 80)
97+
|| ($url['scheme'] === 'https' && $instance->getPort() !== 443)
98+
) {
99+
$url['port'] = $instance->getPort();
100+
}
101+
}
102+
103+
// Check if path is ignored
104+
if (empty($url['path'])) {
105+
$url['path'] = '/';
106+
}
107+
108+
if (count($instance->pathsToIgnore) && $url['path'] !== '/') {
109+
foreach ($instance->pathsToIgnore as $ignorePath) {
110+
$ignorePath = (substr($ignorePath, 0, 1) !== '/')
111+
? '/' . $ignorePath
112+
: $ignorePath;
113+
$targetPath = (substr($url['path'], 0, 1) !== '/')
114+
? '/' . $url['path']
115+
: $url['path'];
116+
$wildcardPos = strpos($ignorePath, '*');
117+
118+
if ($wildcardPos !== false) {
119+
$ignorePath = substr($ignorePath, 0, $wildcardPos);
120+
$targetPath = substr($targetPath, 0, $wildcardPos);
121+
}
122+
123+
if ($ignorePath === $targetPath) {
124+
return $originalUrl;
125+
}
126+
}
127+
}
128+
129+
// Add or remove trailing slash if preferenced
130+
if ($instance->settings->url_trailing_slash !== 'none') {
131+
// Do not apply trailing slash rules if the URL has an extension
132+
$extension = pathinfo($url['path'], PATHINFO_EXTENSION);
133+
$hasSlash = (preg_match('/\/$/', $url['path']) === 1);
134+
135+
if (empty($extension) && $url['path'] !== '/') {
136+
if ($instance->settings->url_trailing_slash === 'use' && $hasSlash === false) {
137+
$url['path'] = $url['path'] . '/';
138+
}
139+
if ($instance->settings->url_trailing_slash === 'unuse' && $hasSlash === true) {
140+
$url['path'] = preg_replace('/\/+$/', '', $url['path']);
141+
}
142+
}
143+
}
144+
145+
// Add or remove www prefix if preferenced
146+
if ($instance->settings->url_www_prefix !== 'none') {
147+
$hasPrefix = (preg_match('/^www./i', $url['host']) === 1);
148+
149+
if ($instance->settings->url_www_prefix === 'use' && $hasPrefix === false) {
150+
$url['host'] = 'www.' . $url['host'];
151+
}
152+
if ($instance->settings->url_www_prefix === 'unuse' && $hasPrefix === true) {
153+
$url['host'] = preg_replace('/^www./i', '', $url['host']);
154+
}
155+
}
156+
157+
// Add HTTPS if it is forced
158+
if (empty($url['scheme'])) {
159+
$url['scheme'] = 'https';
160+
}
161+
if (boolval($instance->settings->url_force_https) === true && $url['scheme'] === 'http') {
162+
$url['scheme'] = 'https';
163+
}
164+
165+
return \http_build_url($url);
166+
}
167+
168+
public static function doRedirect()
169+
{
170+
return self::instance()->settings->url_use_redirect;
171+
}
172+
173+
/**
174+
* Determines if a URL is external, based on the server name.
175+
*
176+
* @param string $url
177+
* @return bool
178+
*/
179+
protected function isExternal(string $url)
180+
{
181+
$urlHostname = parse_url($url, PHP_URL_HOST) ?? null;
182+
183+
if (empty($urlHostname)) {
184+
return false;
185+
}
186+
187+
$serverName = $this->getHostname();
188+
189+
if (empty($serverName)) {
190+
return true;
191+
}
192+
193+
$urlHostname = preg_replace('/www\./i', '', $urlHostname);
194+
$serverName = preg_replace('/www\./i', '', $serverName);
195+
196+
return (strtolower($serverName) !== strtolower($urlHostname));
197+
}
198+
199+
/**
200+
* Get the hostname, either from the server variables or from the current URL.
201+
*
202+
* @return string|null
203+
*/
204+
protected function getHostname()
205+
{
206+
return
207+
$_SERVER['X_FORWARDED_HOST']
208+
?? $_SERVER['SERVER_NAME']
209+
?? parse_url(url()->current(), PHP_URL_HOST)
210+
?? null;
211+
}
212+
213+
/**
214+
* Get the port, either from the server variables or from the current URL.
215+
*
216+
* @return int
217+
*/
218+
protected function getPort()
219+
{
220+
return intval(
221+
$_SERVER['X_FORWARDED_PORT']
222+
?? $_SERVER['SERVER_PORT']
223+
?? parse_url(url()->current(), PHP_URL_PORT)
224+
?? 80
225+
);
226+
}
227+
}

classes/metatags/MetaCanonicalTag.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Url;
88
use Webmaxx\Seo\Classes\Metatags\BaseMetaTag;
9+
use Webmaxx\Seo\Classes\UrlNormalizer;
910
use Webmaxx\Seo\Models\Settings;
1011

1112
class MetaCanonicalTag extends BaseMetaTag
@@ -15,12 +16,14 @@ class MetaCanonicalTag extends BaseMetaTag
1516

1617
protected function content(): ?string
1718
{
18-
return data_get($this->context->modelData, 'meta_canonical_url')
19+
$canonical = data_get($this->context->modelData, 'meta_canonical_url')
1920
?: $this->context->page->meta_canonical_url
2021
?: (
2122
Settings::get('current_url_as_canonical')
2223
? Url::current()
2324
: null
2425
);
26+
27+
return $canonical ? UrlNormalizer::normalize($canonical) : null;
2528
}
2629
}

classes/metatags/OgImageTag.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use System\Classes\MediaLibrary;
88
use Webmaxx\Seo\Classes\Metatags\BaseMetaTag;
9+
use Webmaxx\Seo\Classes\UrlNormalizer;
910
use Webmaxx\Seo\Models\Settings;
1011

1112
class OgImageTag extends BaseMetaTag
@@ -19,6 +20,6 @@ protected function content(): ?string
1920
?: $this->context->page->og_image
2021
?: Settings::get('og_image');
2122

22-
return $imageUrl ? url(MediaLibrary::url($imageUrl)) : null;
23+
return $imageUrl ? UrlNormalizer::normalize(url(MediaLibrary::url($imageUrl))) : null;
2324
}
2425
}

0 commit comments

Comments
 (0)