Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

URL Rewriting Module #16687

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
edcb175
Sync with upstream OrchardCMS/dev
arkadiuszwojcik Jul 10, 2020
ad39ce7
Merge pull request #3 from OrchardCMS/dev
arkadiuszwojcik Dec 17, 2020
67ff75e
Merge pull request #4 from OrchardCMS/dev
arkadiuszwojcik Dec 21, 2020
0c6761b
Merge pull request #5 from OrchardCMS/dev
arkadiuszwojcik Dec 27, 2020
f8ec4e2
Merge branch 'OrchardCMS:main' into dev
arkadiuszwojcik Sep 7, 2024
4661da6
Rewrite module
Sep 7, 2024
49f3939
Fix warning as errors
Sep 7, 2024
d7e6f61
Update OrchardCore.Rewrite.csproj
MikeAlhayek Sep 9, 2024
171c170
Code cleanup after code review
Sep 10, 2024
0f2914a
Rename module
Sep 11, 2024
3cbccfb
Rename classes to match module name
Sep 13, 2024
2eeefea
- Constant for module order
Sep 15, 2024
40bf27b
- Introduce ConfigureOptions
Sep 15, 2024
5a0a436
Merge branch 'OrchardCMS:main' into dev
arkadiuszwojcik Sep 16, 2024
3d86a84
Merge branch 'dev' into new_rewrite_module
Sep 16, 2024
6d1f4b4
UrlRewriting UI - part 1
Sep 18, 2024
ff64d23
Merge branch 'OrchardCMS:main' into dev
arkadiuszwojcik Sep 18, 2024
1735f9a
Merge branch 'dev' into new_rewrite_module
Sep 18, 2024
8080821
UrlRewriting UI - part 2
Sep 22, 2024
23546cb
UrlRewriting UI - part 3
Sep 25, 2024
060a19c
UrlRewriting UI - part 4
Sep 30, 2024
61e2ce7
Merge branch 'OrchardCMS:main' into dev
arkadiuszwojcik Sep 30, 2024
397ffa0
Merge branch 'dev' into new_rewrite_module
Sep 30, 2024
2963137
Add recipe step
Oct 2, 2024
7ffe421
Mark classes as sealed
Oct 2, 2024
806be17
Bug fixes
Oct 2, 2024
8b23ca1
UI fix
Oct 2, 2024
45d1547
Cleanup, add Id to Rules and other things
MikeAlhayek Oct 3, 2024
8fb929f
Merge branch 'main' into new_rewrite_module
MikeAlhayek Oct 3, 2024
78d7943
add site release warning
MikeAlhayek Oct 3, 2024
f5cd1f5
style
MikeAlhayek Oct 3, 2024
1f83775
Use Queries like UI and change everything
MikeAlhayek Oct 3, 2024
342d69b
remove old properties from RewriteStep
MikeAlhayek Oct 3, 2024
a1de614
missed method
MikeAlhayek Oct 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions OrchardCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Queries.Core",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Sms.Azure", "src\OrchardCore.Modules\OrchardCore.Sms.Azure\OrchardCore.Sms.Azure.csproj", "{013C8BBF-6879-4B47-80C9-A466923E45E5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.UrlRewriting", "src\OrchardCore.Modules\OrchardCore.UrlRewriting\OrchardCore.UrlRewriting.csproj", "{D0F8B342-BDA8-44CB-AA43-7A65C79636A2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1395,6 +1397,10 @@ Global
{013C8BBF-6879-4B47-80C9-A466923E45E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{013C8BBF-6879-4B47-80C9-A466923E45E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{013C8BBF-6879-4B47-80C9-A466923E45E5}.Release|Any CPU.Build.0 = Release|Any CPU
{D0F8B342-BDA8-44CB-AA43-7A65C79636A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0F8B342-BDA8-44CB-AA43-7A65C79636A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0F8B342-BDA8-44CB-AA43-7A65C79636A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0F8B342-BDA8-44CB-AA43-7A65C79636A2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1636,6 +1642,7 @@ Global
{4BAA08A2-878C-4B96-86BF-5B3DB2B6C2C7} = {F23AC6C2-DE44-4699-999D-3C478EF3D691}
{61B358F2-702C-40AA-9DF7-7121248FE6DE} = {F23AC6C2-DE44-4699-999D-3C478EF3D691}
{013C8BBF-6879-4B47-80C9-A466923E45E5} = {A066395F-6F73-45DC-B5A6-B4E306110DCE}
{D0F8B342-BDA8-44CB-AA43-7A65C79636A2} = {A066395F-6F73-45DC-B5A6-B4E306110DCE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {46A1D25A-78D1-4476-9CBF-25B75E296341}
Expand Down
29 changes: 29 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.UrlRewriting/AdminMenu.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.Extensions.Localization;
using OrchardCore.Navigation;

namespace OrchardCore.UrlRewriting;

public sealed class AdminMenu : AdminNavigationProvider
{
public readonly IStringLocalizer S;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public readonly IStringLocalizer S;
internal readonly IStringLocalizer S;


public AdminMenu(IStringLocalizer<AdminMenu> stringLocalizer)
{
S = stringLocalizer;
}

protected override ValueTask BuildAsync(NavigationBuilder builder)
{
builder
.Add(S["Configuration"], configuration => configuration
.Add(S["URL Rewriting"], S["URL Rewriting"].PrefixPosition(), rewriting => rewriting
.AddClass("urlrewriting").Id("urlrewriting")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.AddClass("urlrewriting").Id("urlrewriting")
.AddClass("urlrewriting")
.Id("urlrewriting")

.Permission(UrlRewritingPermissions.ManageUrlRewriting)
.Action("Index", "Admin", "OrchardCore.UrlRewriting")
.LocalNav()
)
);

return ValueTask.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Localization;
using OrchardCore.Admin;
using OrchardCore.DisplayManagement;
using OrchardCore.DisplayManagement.ModelBinding;
using OrchardCore.DisplayManagement.Notify;
using OrchardCore.Environment.Shell;
using OrchardCore.UrlRewriting.Helpers;
using OrchardCore.UrlRewriting.Models;
using OrchardCore.UrlRewriting.Services;
using OrchardCore.UrlRewriting.ViewModels;

namespace OrchardCore.UrlRewriting.Controllers;

[Admin("UrlRewriting/{action}/{id?}", "UrlRewriting{action}")]
public sealed class AdminController : Controller
{
private readonly IAuthorizationService _authorizationService;
private readonly INotifier _notifier;
private readonly RewriteRulesStore _rewriteRulesStore;
private readonly IDisplayManager<RewriteRule> _rewriteRuleDisplayManager;
private readonly IUpdateModelAccessor _updateModelAccessor;
private readonly IShellReleaseManager _shellReleaseManager;

internal readonly IStringLocalizer S;
internal readonly IHtmlLocalizer H;

public AdminController(
IDisplayManager<RewriteRule> rewriteRuleDisplayManager,
IAuthorizationService authorizationService,
RewriteRulesStore rewriteRulesStore,
INotifier notifier,
IShellReleaseManager shellReleaseManager,
IStringLocalizer<AdminController> stringLocalizer,
IHtmlLocalizer<AdminController> htmlLocalizer,
IUpdateModelAccessor updateModelAccessor)
{
_rewriteRuleDisplayManager = rewriteRuleDisplayManager;
_authorizationService = authorizationService;
_rewriteRulesStore = rewriteRulesStore;
_notifier = notifier;
_shellReleaseManager = shellReleaseManager;
_updateModelAccessor = updateModelAccessor;
S = stringLocalizer;
H = htmlLocalizer;
}

public async Task<IActionResult> Index()
{
if (!await _authorizationService.AuthorizeAsync(User, UrlRewritingPermissions.ManageUrlRewriting))
{
return Forbid();
}

var rules = await _rewriteRulesStore.GetRewriteRulesAsync();

var model = new RewriteRulesViewModel
{
Rules = rules.Rules
.Select(BuildViewModel)
.ToList()
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
};

return View(model);
}

[Admin("UrlRewriting/CreateRule", nameof(CreateRule))]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[Admin("UrlRewriting/CreateRule", nameof(CreateRule))]
[Admin("UrlRewriting/CreateRule", "CreateRewritingRule")]

public async Task<ActionResult> CreateRule()
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
{
if (!await _authorizationService.AuthorizeAsync(User, UrlRewritingPermissions.ManageUrlRewriting))
{
return Forbid();
}

var rule = new RewriteRule();

var shape = await _rewriteRuleDisplayManager.BuildEditorAsync(rule, updater: _updateModelAccessor.ModelUpdater, isNew: true);

return View(shape);
}

[HttpPost, ActionName(nameof(CreateRule))]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[HttpPost, ActionName(nameof(CreateRule))]
[HttpPost]
[ActionName(nameof(CreateRule))]

public async Task<ActionResult> CreateRulePOST()
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
{
if (!await _authorizationService.AuthorizeAsync(User, UrlRewritingPermissions.ManageUrlRewriting))
{
return Forbid();
}

var rule = new RewriteRule();

var shape = await _rewriteRuleDisplayManager.UpdateEditorAsync(rule, updater: _updateModelAccessor.ModelUpdater, isNew: true);

if (!ModelState.IsValid)
{
return View(shape);
}

await _rewriteRulesStore.SaveAsync(rule);

await _notifier.SuccessAsync(H["Rule created successfully."]);

return RedirectToAction(nameof(Index));
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
}

public async Task<ActionResult> EditRule(string id)
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
{
if (!await _authorizationService.AuthorizeAsync(User, UrlRewritingPermissions.ManageUrlRewriting))
{
return Forbid();
}

var currentRule = await _rewriteRulesStore.FindByIdAsync(id);
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved

if (currentRule == null)
{
return NotFound();
}

var shape = await _rewriteRuleDisplayManager.BuildEditorAsync(currentRule, updater: _updateModelAccessor.ModelUpdater, isNew: false);

return View(shape);
}

[HttpPost, ActionName(nameof(EditRule))]
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
public async Task<ActionResult> EditRulePOST(string id)
{
if (!await _authorizationService.AuthorizeAsync(User, UrlRewritingPermissions.ManageUrlRewriting))
{
return Forbid();
}

var currentRule = await _rewriteRulesStore.FindByIdAsync(id);

if (currentRule == null)
{
return NotFound();
}

var shape = await _rewriteRuleDisplayManager.UpdateEditorAsync(currentRule, updater: _updateModelAccessor.ModelUpdater, isNew: false);

if (ModelState.IsValid)
{
await _rewriteRulesStore.SaveAsync(currentRule);

await _notifier.SuccessAsync(H["Rule updated successfully."]);

return RedirectToAction(nameof(Index));
}

return View(shape);
}

[HttpPost]
public async Task<IActionResult> DeleteRule(string id)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public async Task<IActionResult> DeleteRule(string id)
public async Task<IActionResult> Delete(string id)

{
if (!await _authorizationService.AuthorizeAsync(User, UrlRewritingPermissions.ManageUrlRewriting))
{
return Forbid();
}

var currentRule = await _rewriteRulesStore.FindByIdAsync(id);

if (currentRule == null)
{
return NotFound();
}

await _rewriteRulesStore.DeleteAsync(currentRule);

await _notifier.SuccessAsync(H["Rule deleted successfully."]);

return RedirectToAction(nameof(Index));
}

[HttpPost]
public async Task<IActionResult> ReloadWebsite()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather just reload the shell every time a new rule is added or a rule edited with change instead of adding an action to reload the site. This is how to do it in other places like recipes and features. We typically add a warning from the display driver about the release.

{
if (!await _authorizationService.AuthorizeAsync(User, UrlRewritingPermissions.ManageUrlRewriting))
{
return Forbid();
}

_shellReleaseManager.RequestRelease();

return RedirectToAction(nameof(Index));
}


private static RewriteRuleViewModel BuildViewModel(RewriteRule rule)
{
var viewModel = new RewriteRuleViewModel();

viewModel.FromModel(rule);

return viewModel;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Localization;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.UrlRewriting.Models;
using OrchardCore.UrlRewriting.ViewModels;
using OrchardCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using OrchardCore.UrlRewriting.Services;
using OrchardCore.UrlRewriting.Rules;
using OrchardCore.UrlRewriting.Helpers;

namespace OrchardCore.UrlRewriting.Drivers;

internal sealed class RewriteRuleDisplayDriver : DisplayDriver<RewriteRule>
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly IAuthorizationService _authorizationService;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly RewriteRulesStore _rewriteRulesStore;

internal readonly IStringLocalizer S;

public RewriteRuleDisplayDriver(
IAuthorizationService authorizationService,
IHttpContextAccessor httpContextAccessor,
RewriteRulesStore rewriteRulesStore,
IStringLocalizer<RewriteRuleDisplayDriver> stringLocalizer)
{
_authorizationService = authorizationService;
_httpContextAccessor = httpContextAccessor;
_rewriteRulesStore = rewriteRulesStore;
S = stringLocalizer;
}

public override async Task<IDisplayResult> EditAsync(RewriteRule rule, BuildEditorContext context)
{
if (!await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, UrlRewritingPermissions.ManageUrlRewriting))
{
return null;
}

return Initialize<RewriteRuleViewModel>("RewriteRuleFields_Edit", viewModel => BuildViewModel(rule, viewModel, context.IsNew))
.Location("Content:1");
}

public override async Task<IDisplayResult> UpdateAsync(RewriteRule rule, UpdateEditorContext context)
{
if (!await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, UrlRewritingPermissions.ManageUrlRewriting))
{
return null;
}

var model = new RewriteRuleViewModel();

await context.Updater.TryUpdateModelAsync(model, Prefix);

var rules = await _rewriteRulesStore.LoadRewriteRulesAsync();

if (string.IsNullOrWhiteSpace(model.Name))
{
context.Updater.ModelState.AddModelError(Prefix, nameof(RewriteRuleViewModel.Name), "The rule name is required.");
}
else if (context.IsNew && rules.Rules.Any(x => string.Equals(x.Name, model.Name, StringComparison.OrdinalIgnoreCase)))
{
context.Updater.ModelState.AddModelError(Prefix, nameof(RewriteRuleViewModel.Name), "The rule name already exists.");
}
if (string.IsNullOrWhiteSpace(model.Pattern))
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
{
context.Updater.ModelState.AddModelError(Prefix, nameof(RewriteRuleViewModel.Pattern), "The url match pattern is required.");
}
if (model.RuleAction == RuleAction.Rewrite && string.IsNullOrWhiteSpace(model.RewriteAction.RewriteUrl))
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
{
context.Updater.ModelState.AddModelError(Prefix, "RewriteAction.RewriteUrl", "The rewrite url substitition is required.");
}
if (model.RuleAction == RuleAction.Redirect && string.IsNullOrWhiteSpace(model.RedirectAction.RedirectUrl))
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
{
context.Updater.ModelState.AddModelError(Prefix, "RedirectAction.RedirectUrl", "The redirect url substitition is required.");
}

if (context.Updater.ModelState.IsValid)
{
if (ApacheRuleValidator.ValidateRule(model, out var message) == false)
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
{
context.Updater.ModelState.AddModelError(Prefix, "RuleError", S["Rule error: {0}", message]);
}
}

rule.Name = model.Name;
rule.Pattern = model.Pattern;
rule.Substitution = model.RuleAction == RuleAction.Rewrite ? model.RewriteAction.RewriteUrl : model.RedirectAction.RedirectUrl;
rule.Flags = ApacheRules.FlagsFromViewModel(model);

return await EditAsync(rule, context);
}

private void BuildViewModel(RewriteRule model, RewriteRuleViewModel viewModel, bool isNew)
{
viewModel.FromModel(model);

viewModel.IsNew = isNew;

viewModel.AvailableActions.Add(new SelectListItem() { Text = S["Rewrite"], Value = ((int)RuleAction.Rewrite).ToString() });
viewModel.AvailableActions.Add(new SelectListItem() { Text = S["Redirect"], Value = ((int)RuleAction.Redirect).ToString() });

viewModel.RedirectAction.AvailableRedirectTypes.Add(new SelectListItem() { Text = S["Moved Permanently (301)"], Value = ((int)RedirectType.MovedPermanently301).ToString() });
viewModel.RedirectAction.AvailableRedirectTypes.Add(new SelectListItem() { Text = S["Found (302)"], Value = ((int)RedirectType.Found302).ToString() });
viewModel.RedirectAction.AvailableRedirectTypes.Add(new SelectListItem() { Text = S["Temporary Redirect (307)"], Value = ((int)RedirectType.TemporaryRedirect307).ToString() });
viewModel.RedirectAction.AvailableRedirectTypes.Add(new SelectListItem() { Text = S["Pernament Redirect (308)"], Value = ((int)RedirectType.PernamentRedirect308).ToString() });
}
}
Loading