Compare commits

..

14 Commits

Author SHA1 Message Date
Benedek Farkas
8bb939cb91 Merge branch '1.10.x' into issue/6748 2024-04-18 13:52:35 +02:00
Benedek Farkas
e43e3d9b55 Updating MediaLibrary not to log an error when a file can't be uploaded due to invalid characters 2024-04-12 13:03:45 +02:00
Benedek Farkas
08ae5ddccf Adding InvalidNameCharacterException to be able to handle invalid characters precisely at various user-facing components 2024-04-12 12:58:58 +02:00
Benedek Farkas
531deba838 Code styling 2024-04-12 11:26:05 +02:00
Benedek Farkas
15dc19ce9c Simplifying invalid character detection 2024-04-12 11:20:13 +02:00
Benedek Farkas
71b2ccd0ff Merge branch '1.10.x' into issue/6748 2024-04-12 10:48:29 +02:00
Benedek Farkas
c3c70ec8f1 Code styling and fixes in AzureFileSystem, MediaLibrary and IStorageProvider 2024-04-08 17:48:11 +02:00
Benedek Farkas
248ccb5939 Applying the same file and folder name validation to AzureFileSystem too 2024-04-08 17:47:20 +02:00
Benedek Farkas
7aa946d53b Adding string file and folder name validation to FileSystemStorageProvider, so that MediaLibrary components don't need to do it separately 2024-04-08 17:45:25 +02:00
Benedek Farkas
d3e02cdc46 Code styling in FileSystemStorageProvider 2024-04-08 12:01:46 +02:00
Benedek Farkas
c72dc2ba41 Resetting MediaLibraryService changes to 1.10.x 2024-04-08 11:41:42 +02:00
Benedek Farkas
cd313f9301 Merge branch '1.10.x' into issue/6748 2024-04-08 11:35:51 +02:00
Benedek Farkas
475000c690 Merge branch '1.10.x' into issue/6748
# Conflicts:
#	src/Orchard.Web/Modules/Orchard.MediaLibrary/Services/MediaLibraryService.cs
2024-03-26 13:58:44 +01:00
Lombiq
c744388ef1 Media Library: More strict file and folder name validation, fixes #6748 2016-04-18 00:48:30 +02:00
29 changed files with 221 additions and 423 deletions

View File

@@ -1,5 +1,4 @@
using System;
using System.Net;
using System.Net;
using NUnit.Framework;
using Orchard.Warmup.Services;
@@ -21,7 +20,7 @@ namespace Orchard.Tests.Modules.Warmup {
[Test]
public void StatusCodeShouldBe404ForUnexistingResources() {
var download = _webDownloader.Download("https://orchardcore.net/" + Guid.NewGuid());
var download = _webDownloader.Download("http://orchardproject.net/yepyep");
Assert.That(download, Is.Not.Null);
Assert.That(download.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
Assert.That(download.Content, Is.Null);
@@ -29,7 +28,7 @@ namespace Orchard.Tests.Modules.Warmup {
[Test]
public void StatusCodeShouldBe200ForValidRequests() {
var download = _webDownloader.Download("https://orchardcore.net/");
var download = _webDownloader.Download("http://orchardproject.net/");
Assert.That(download, Is.Not.Null);
Assert.That(download.StatusCode, Is.EqualTo(HttpStatusCode.OK));
Assert.That(download.Content, Is.Not.Empty);

View File

@@ -1,93 +0,0 @@
using System.Linq;
using Orchard.ContentManagement;
using Orchard.Core.Containers.Models;
using Orchard.Core.Containers.Services;
using Orchard.Localization;
using Orchard.Security;
using Orchard.UI.Navigation;
using Orchard.Utility.Extensions;
namespace Orchard.Core.Containers {
public class AdminMenu : INavigationProvider {
private readonly IContainerService _containerService;
private readonly IContentManager _contentManager;
private readonly IAuthorizationService _authorizationService;
private readonly IWorkContextAccessor _workContextAccessor;
public AdminMenu(
IContainerService containerService,
IContentManager contentManager,
IAuthorizationService authorizationService,
IWorkContextAccessor workContextAccessor) {
_containerService = containerService;
_contentManager = contentManager;
_authorizationService = authorizationService;
_workContextAccessor = workContextAccessor;
}
public Localizer T { get; set; }
public string MenuName { get { return "admin"; } }
public void GetNavigation(NavigationBuilder builder) {
builder.AddImageSet("container");
var containers = _containerService
.GetContainersQuery(VersionOptions.Latest)
.Where<ContainerPartRecord>(x => x.ShowOnAdminMenu)
.List()
.Where(content => _authorizationService.TryCheckAccess(
Contents.Permissions.EditContent,
_workContextAccessor.GetContext().CurrentUser,
content))
.ToList();
foreach (var container in containers) {
var closureContainer = container;
if (!string.IsNullOrWhiteSpace(container.AdminMenuImageSet)) {
builder.AddImageSet(container.AdminMenuImageSet.Trim());
}
builder.Add(T(container.AdminMenuText), container.AdminMenuPosition, item => {
var containedItems = _containerService.GetContentItems(closureContainer.Id, VersionOptions.Latest).ToList();
var actualContainer = closureContainer;
var position = 0;
// If the list has just a single item that happens to be a container itself,
// we will treat that one as the actual container to provide a nice & quick way to manage that list.
if (containedItems.Count == 1) {
var containedItem = containedItems.First().As<ContainerPart>();
if (containedItem != null) {
actualContainer = containedItem;
foreach (var itemContentType in containedItem.ItemContentTypes) {
var closureItemContentType = itemContentType;
item.Add(T("New {0}", itemContentType.DisplayName), string.Format("1.{0}", position++), subItem => subItem
.Action("Create", "Admin", new {
id = closureItemContentType.Name,
containerid = containedItem.Id,
area = "Contents"
}));
}
}
}
item.Action(_contentManager.GetItemMetadata(actualContainer).AdminRouteValues)
.AddClass("section-container")
.AddClass(closureContainer.AdminMenuText.HtmlClassify())
.LinkToFirstChild(false);
foreach (var itemContentType in closureContainer.ItemContentTypes) {
var closureItemContentType = itemContentType;
item.Add(T("New {0}", itemContentType.DisplayName), string.Format("1.{0}", position++), subItem => subItem
.Action("Create", "Admin", new {
id = closureItemContentType.Name,
containerid = container.Id,
area = "Contents"
}));
}
});
}
}
}
}

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="7.00:00:00" />
</staticContent>
<handlers accessPolicy="Script,Read">
<!-- For any request to a file exists on disk, return it via native http module. AccessPolicy="Script" above is to allow for a managed 404 page. -->
<add name="StaticFile" path="*" verb="*" modules="StaticFileModule" preCondition="integratedMode" resourceType="File" requireAccess="Read" />
</handlers>
</system.webServer>
</configuration>

View File

@@ -1,7 +0,0 @@
.menu-admin > .section-container > h3 > a {
background-image: url(images/menu.container.png) !important;
}
.menu-admin > .section-container > h3 > a:hover {
background-position: 0 -30px !important;
}

View File

@@ -119,7 +119,6 @@
<Compile Include="Common\ViewModels\DateTimeEditor.cs" />
<Compile Include="Common\ViewModels\TextFieldDriverViewModel.cs" />
<Compile Include="Common\ViewModels\TextFieldSettingsEventsViewModel.cs" />
<Compile Include="Containers\AdminMenu.cs" />
<Compile Include="Containers\Controllers\ItemController.cs" />
<Compile Include="Containers\Drivers\ContainablePartDriver.cs" />
<Compile Include="Containers\Drivers\ContainerPartDriver.cs" />
@@ -319,8 +318,6 @@
<Content Include="Common\Views\Parts.Common.Metadata.cshtml" />
<Content Include="Common\Views\CommonMetadataLastModified.cshtml" />
<Content Include="Containers\Module.txt" />
<Content Include="Containers\Styles\images\menu.container.png" />
<Content Include="Containers\Styles\menu.container-admin.css" />
<Content Include="Shapes\Scripts\admin-localnavigation.js" />
<Content Include="Contents\Styles\images\menu.content.png" />
<Content Include="Contents\Styles\menu.content-admin.css" />
@@ -613,9 +610,6 @@
<Content Include="Navigation\Views\Admin\Edit.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Containers\Styles\Web.config">
<SubType>Designer</SubType>
</Content>
<None Include="packages.config" />
<Content Include="Title\Views\DefinitionTemplates\TitlePartSettings.cshtml" />
</ItemGroup>

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Web;
namespace Orchard.DynamicForms.Helpers {
public static class NameValueCollectionExtensions {
public static string ToQueryString(this NameValueCollection nameValues) {
return String.Join("&", (from string name in nameValues select String.Concat(name, "=", HttpUtility.UrlEncode(nameValues[name]))).ToArray());
}
}
}

View File

@@ -340,6 +340,7 @@
<Compile Include="Migrations.cs" />
<Compile Include="Handlers\ReadFormValuesHandler.cs" />
<Compile Include="Services\FormElementEventHandlerBase.cs" />
<Compile Include="Helpers\NameValueCollectionExtensions.cs" />
<Compile Include="Models\Submission.cs" />
<Compile Include="Permissions.cs" />
<Compile Include="Services\FormService.cs" />

View File

@@ -467,4 +467,4 @@ namespace Orchard.DynamicForms.Services {
return validatorElementType == elementType || validatorElementType.IsAssignableFrom(elementType);
}
}
}
}

View File

@@ -4,41 +4,46 @@ using Orchard.ContentManagement.Handlers;
using Orchard.Fields.Fields;
using Orchard.Fields.Settings;
using Orchard.Localization;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Orchard.Fields.Drivers {
public class EnumerationFieldDriver : ContentFieldDriver<EnumerationField> {
public IOrchardServices Services { get; set; }
private const string TemplateName = "Fields/Enumeration.Edit";
public EnumerationFieldDriver(IOrchardServices services) {
Services = services;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
private static string GetPrefix(ContentField field, ContentPart part) =>
part.PartDefinition.Name + "." + field.Name;
private static string GetPrefix(ContentField field, ContentPart part) {
return part.PartDefinition.Name + "." + field.Name;
}
private static string GetDifferentiator(EnumerationField field) => field.Name;
private static string GetDifferentiator(EnumerationField field, ContentPart part) {
return field.Name;
}
protected override DriverResult Display(ContentPart part, EnumerationField field, string displayType, dynamic shapeHelper) {
return ContentShape("Fields_Enumeration", GetDifferentiator(field), () => shapeHelper.Fields_Enumeration());
return ContentShape("Fields_Enumeration", GetDifferentiator(field, part),
() => shapeHelper.Fields_Enumeration());
}
protected override DriverResult Editor(ContentPart part, EnumerationField field, dynamic shapeHelper) {
return ContentShape("Fields_Enumeration_Edit", GetDifferentiator(field), () => {
if (part.IsNew() && string.IsNullOrEmpty(field.Value)) {
var settings = field.PartFieldDefinition.Settings.GetModel<EnumerationFieldSettings>();
if (!string.IsNullOrWhiteSpace(settings.DefaultValue)) {
field.SelectedValues = new string[] { settings.DefaultValue };
return ContentShape("Fields_Enumeration_Edit", GetDifferentiator(field, part),
() => {
if (part.IsNew() && String.IsNullOrEmpty(field.Value)) {
var settings = field.PartFieldDefinition.Settings.GetModel<EnumerationFieldSettings>();
if (!String.IsNullOrWhiteSpace(settings.DefaultValue)) {
field.Value = settings.DefaultValue;
}
}
}
return shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: field, Prefix: GetPrefix(field, part));
});
return shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: field, Prefix: GetPrefix(field, part));
});
}
protected override DriverResult Editor(ContentPart part, EnumerationField field, IUpdateModel updater, dynamic shapeHelper) {

View File

@@ -6,7 +6,6 @@ using Orchard.Fields.Settings;
using Orchard.Localization;
using System;
using System.Collections.Generic;
using System.Security.Policy;
namespace Orchard.Fields.Drivers {
public class LinkFieldDriver : ContentFieldDriver<LinkField> {
@@ -58,33 +57,11 @@ namespace Orchard.Fields.Drivers {
if (settings.Required && String.IsNullOrWhiteSpace(field.Value)) {
updater.AddModelError(GetPrefix(field, part), T("Url is required for {0}", T(field.DisplayName)));
}
else if (!String.IsNullOrWhiteSpace(field.Value) && !Uri.IsWellFormedUriString(field.Value, UriKind.RelativeOrAbsolute)) {
updater.AddModelError(GetPrefix(field, part), T("{0} is an invalid url.", field.Value));
}
else if (settings.LinkTextMode == LinkTextMode.Required && String.IsNullOrWhiteSpace(field.Text)) {
updater.AddModelError(GetPrefix(field, part), T("Text is required for {0}.", T(field.DisplayName)));
} else if (!String.IsNullOrWhiteSpace(field.Value)) {
// Check if it's a valid uri, considering that there may be the link to an anchor only
// e.g.: field.Value = "#divId"
// Take everything before the first "#" character and check if it's a valid uri.
// If there is no character before the first "#", consider the value as a valid one (because it is a reference to a div inside the same page)
if (field.Value.StartsWith("#")) {
// The field value is a tag id reference
// For html 5, a tag id is valid as long as it doesn't contain white spaces
if (field.Value.IndexOf(' ') >= 0) {
updater.AddModelError(GetPrefix(field, part), T("{0} is an invalid url.", field.Value));
}
} else {
var urlAndRef = field.Value.Split(new char[] { '#' }, 2);
// Since field value is a proper url and not a tag id only, assume the first part of the array is the actual url to link to
if (!String.IsNullOrWhiteSpace(urlAndRef[0]) && !Uri.IsWellFormedUriString(urlAndRef[0], UriKind.RelativeOrAbsolute)) {
updater.AddModelError(GetPrefix(field, part), T("{0} is an invalid url.", field.Value));
} else if (urlAndRef.Length > 1) {
// The second part of the url is the id reference
// For html 5, a tag id is valid as long as it doesn't contain white spaces
if (urlAndRef[1].IndexOf(' ') >= 0) {
updater.AddModelError(GetPrefix(field, part), T("{0} is an invalid url.", field.Value));
}
}
}
}
}

View File

@@ -7,16 +7,28 @@ namespace Orchard.Fields.Fields {
private const char Separator = ';';
public string Value {
get => Storage.Get<string>()?.Trim(Separator) ?? "";
set => Storage.Set(string.IsNullOrWhiteSpace(value)
? string.Empty
// It is now the responsibility of this field to (re-)add the separators.
: Separator + value.Trim(Separator) + Separator);
get { return Storage.Get<string>(); }
set { Storage.Set(value ?? String.Empty); }
}
public string[] SelectedValues {
get => Value?.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];
set => Value = value?.Length > 0 ? string.Join(Separator.ToString(), value) : "";
get {
var value = Value;
if(string.IsNullOrWhiteSpace(value)) {
return new string[0];
}
return value.Split(new [] { Separator }, StringSplitOptions.RemoveEmptyEntries);
}
set {
if (value == null || value.Length == 0) {
Value = String.Empty;
}
else {
Value = Separator + string.Join(Separator.ToString(), value) + Separator;
}
}
}
}
}

View File

@@ -1,27 +1,22 @@
@model Orchard.Fields.Fields.EnumerationField
@using Orchard.Fields.Settings;
@{
var settings = Model.PartFieldDefinition.Settings.GetModel<EnumerationFieldSettings>();
string[] options = (!String.IsNullOrWhiteSpace(settings.Options)) ? settings.Options.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.None) : new string[] { T("Select an option").ToString() };
}
<fieldset>
<label for="@Html.FieldIdFor(m => m.Value)" @if (settings.Required) { <text> class="required" </text> }>@Model.DisplayName</label>
<label for="@Html.FieldIdFor(m => m.Value)" @if (settings.Required) { <text> class="required" </text> }>@Model.DisplayName</label>
@switch (settings.ListMode) {
case ListMode.Dropdown:
@Html.DropDownListFor(m => m.Value, new SelectList(options, Model.SelectedValues.FirstOrDefault()), settings.Required ? new { required = "required" } : null)
@Html.DropDownListFor(m => m.Value, new SelectList(options, Model.Value), settings.Required ? new { required = "required" } : null)
break;
case ListMode.Radiobutton:
foreach (var option in options) {
if (string.IsNullOrWhiteSpace(option)) {
<label>@Html.RadioButton("Value", "", string.IsNullOrWhiteSpace(Model.SelectedValues.FirstOrDefault()), settings.Required ? new { required = "required" } : null)<i>@T("unset")</i></label>
}
<label>@Html.RadioButton("Value", "", string.IsNullOrWhiteSpace(Model.Value), settings.Required ? new { required = "required" } : null)<i>@T("unset")</i></label> }
else {
<label>@Html.RadioButton("Value", option, (option == Model.SelectedValues.FirstOrDefault()), settings.Required ? new { required = "required" } : null)@option</label>
}
<label>@Html.RadioButton("Value", option, (option == Model.Value), settings.Required ? new { required = "required" } : null)@option</label> }
}
break;

View File

@@ -25,7 +25,7 @@
</div>
<div class="editor-field">
@(settings.Required ? Html.TextBoxFor(m => m.Value, new { @class = "text large", required = "required" }) : Html.TextBoxFor(m => m.Value, new { @class = "text large" }))
<span class="hint">@T("A valid url, i.e. http://orchardproject.net, /content/file.pdf, #some_id, ...")</span>
<span class="hint">@T("A valid url, i.e. http://orchardproject.net, /content/file.pdf, ...")</span>
</div>
@if (settings.LinkTextMode == LinkTextMode.Optional || settings.LinkTextMode == LinkTextMode.Required) {
<div class="editor-label">

View File

@@ -1,15 +1,98 @@
using Orchard.Localization;
using System;
using System.Linq;
using Orchard.ContentManagement;
using Orchard.Core.Containers.Models;
using Orchard.Core.Containers.Services;
using Orchard.Localization;
using Orchard.Security;
using Orchard.UI.Navigation;
using Orchard.Utility.Extensions;
namespace Orchard.Lists {
public class AdminMenu : INavigationProvider {
private readonly IContainerService _containerService;
private readonly IContentManager _contentManager;
private readonly IAuthorizationService _authorizationService;
private readonly IWorkContextAccessor _workContextAccessor;
public AdminMenu(
IContainerService containerService,
IContentManager contentManager,
IAuthorizationService authorizationService,
IWorkContextAccessor workContextAccessor
) {
_containerService = containerService;
_contentManager = contentManager;
_authorizationService = authorizationService;
_workContextAccessor = workContextAccessor;
}
public Localizer T { get; set; }
public string MenuName { get { return "admin"; } }
public void GetNavigation(NavigationBuilder builder) =>
builder
.AddImageSet("lists")
.Add(T("Lists"), "11", item => item
.Action("Index", "Admin", new { area = "Orchard.Lists" }).Permission(Permissions.ManageLists));
public void GetNavigation(NavigationBuilder builder) {
builder.AddImageSet("list");
CreateListManagementMenuItem(builder);
CreateListMenuItems(builder);
}
private void CreateListManagementMenuItem(NavigationBuilder builder) {
builder.Add(T("Lists"), "11", item => item
.Action("Index", "Admin", new {area = "Orchard.Lists"}).Permission(Permissions.ManageLists)
);
}
private void CreateListMenuItems(NavigationBuilder builder) {
var containers = _containerService
.GetContainersQuery(VersionOptions.Latest)
.Where<ContainerPartRecord>(x => x.ShowOnAdminMenu)
.List()
.Where(x => _authorizationService.TryCheckAccess(Orchard.Core.Contents.Permissions.EditContent, _workContextAccessor.GetContext().CurrentUser, x))
.ToList();
foreach (var container in containers) {
var closureContainer = container;
if (!String.IsNullOrWhiteSpace(container.AdminMenuImageSet)) {
builder.AddImageSet(container.AdminMenuImageSet.Trim());
}
builder.Add(T(container.AdminMenuText), container.AdminMenuPosition, item => {
var containedItems = _containerService.GetContentItems(closureContainer.Id, VersionOptions.Latest).ToList();
var actualContainer = closureContainer;
var position = 0;
// If the list has just a single item that happens to be a container itself,
// we will treat that one as the actual container to provide a nice & quick way to manage that list.
if (containedItems.Count == 1) {
var containedItem = containedItems.First().As<ContainerPart>();
if (containedItem != null) {
actualContainer = containedItem;
foreach (var itemContentType in containedItem.ItemContentTypes) {
var closureItemContentType = itemContentType;
item.Add(T("New {0}", itemContentType.DisplayName), String.Format("1.{0}", position++), subItem => subItem
.Action("Create", "Admin", new { id = closureItemContentType.Name, containerid = containedItem.Id, area = "Contents" }));
}
}
}
var containerMetadata = _contentManager.GetItemMetadata(actualContainer);
item.Action(containerMetadata.AdminRouteValues);
item.Action(containerMetadata.AdminRouteValues);
item.AddClass("nav-list");
item.AddClass(closureContainer.AdminMenuText.HtmlClassify());
item.LinkToFirstChild(false);
foreach (var itemContentType in closureContainer.ItemContentTypes) {
var closureItemContentType = itemContentType;
item.Add(T("New {0}", itemContentType.DisplayName), String.Format("1.{0}", position++), subItem => subItem
.Action("Create", "Admin", new { id = closureItemContentType.Name, containerid = container.Id, area = "Contents" }));
}
});
}
}
}
}
}

View File

@@ -115,7 +115,6 @@
<Content Include="Styles\images\icons.png" />
<Content Include="Styles\images\view.default.png" />
<Content Include="Styles\images\view.condensed.png" />
<Content Include="Styles\menu.lists-admin.css" />
<Content Include="Styles\nprogress.css" />
<Content Include="Scripts\nprogress.js" />
<Content Include="Scripts\orchard-lists-admin.js" />
@@ -144,14 +143,16 @@
<Content Include="Scripts\orchard-lists-admin.min.js">
<DependentUpon>orchard-lists-admin.js</DependentUpon>
</Content>
<Content Include="Styles\images\menu.lists.png" />
<Content Include="Styles\images\menu.list-definition.png" />
<Content Include="Styles\images\move.gif" />
<Content Include="Styles\images\offline.gif" />
<Content Include="Styles\images\online.gif" />
<Content Include="Styles\list-admin.css" />
<Content Include="Styles\images\menu.list.png" />
<Content Include="Styles\list-admin.min.css">
<DependentUpon>list-admin.css</DependentUpon>
</Content>
<Content Include="Styles\menu.list-admin.css" />
</ItemGroup>
<ItemGroup>
<Content Include="Web.config">
@@ -271,4 +272,4 @@
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
</Project>
</Project>

View File

Before

Width:  |  Height:  |  Size: 998 B

After

Width:  |  Height:  |  Size: 998 B

View File

@@ -0,0 +1,22 @@
.navicon-list,
.navicon-lists,
.section-new .subnavicon-list,
.nav-list > h3 > a,
.nav-list-definition > h3 > a {
background-image:url(images/menu.list.png) !important;
}
.navicon-list:hover,
.navicon-lists:hover,
.nav-list > h3 > a:hover,
.nav-list-definition > h3 > a:hover {
background-position:0 -30px !important;
}
.navicon-lists {
background-image:url(images/menu.list.png) !important;
}
.navicon-lists:hover {
background-position:0 -30px !important;
}

View File

@@ -1,7 +0,0 @@
.navicon-lists {
background-image: url(images/menu.lists.png) !important;
}
.navicon-lists:hover {
background-position: 0 -30px !important;
}

View File

@@ -1,48 +0,0 @@
using System;
using System.Web.Mvc;
using Orchard.Autoroute.Models;
using Orchard.CulturePicker.Services;
using Orchard.Environment.Extensions;
using Orchard.Localization.Providers;
using Orchard.Localization.Services;
using Orchard.Mvc.Extensions;
namespace Orchard.Localization.Controllers {
[OrchardFeature("Orchard.Localization.CultureSelector")]
public class UserCultureSelectorController : Controller {
private readonly ILocalizationService _localizationService;
private readonly ICultureStorageProvider _cultureStorageProvider;
public IOrchardServices Services { get; set; }
public UserCultureSelectorController(
IOrchardServices services,
ILocalizationService localizationService,
ICultureStorageProvider cultureStorageProvider) {
Services = services;
_localizationService = localizationService;
_cultureStorageProvider = cultureStorageProvider;
}
public ActionResult ChangeCulture(string culture) {
if (string.IsNullOrEmpty(culture)) {
throw new ArgumentNullException(culture);
}
var returnUrl = Utils.GetReturnUrl(Services.WorkContext.HttpContext.Request);
if (string.IsNullOrEmpty(returnUrl))
returnUrl = "";
if (_localizationService.TryGetRouteForUrl(returnUrl, out AutoroutePart currentRoutePart)
&& _localizationService.TryFindLocalizedRoute(currentRoutePart.ContentItem, culture, out AutoroutePart localizedRoutePart)) {
returnUrl = localizedRoutePart.Path;
}
_cultureStorageProvider.SetCulture(culture);
if (!returnUrl.StartsWith("~/")) {
returnUrl = "~/" + returnUrl;
}
return this.RedirectLocal(returnUrl);
}
}
}

View File

@@ -9,7 +9,7 @@ Features:
Orchard.Localization:
Description: Enables localization of content items.
Category: Content
Dependencies: Settings, Orchard.Autoroute
Dependencies: Settings
Name: Content Localization
Orchard.Localization.DateTimeFormat:
Description: Enables PO-based translation of date/time formats and names of days and months.
@@ -30,4 +30,4 @@ Features:
Description: Enables transliteration of the autoroute slug when creating a piece of content.
Category: Content
Name: URL Transliteration
Dependencies: Orchard.Localization.Transliteration
Dependencies: Orchard.Localization.Transliteration, Orchard.Autoroute

View File

@@ -89,11 +89,10 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AdminMenu.cs" />
<Compile Include="Controllers\AdminCultureSelectorController.cs" />
<Compile Include="Extensions\Constants.cs" />
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Controllers\TransliterationAdminController.cs" />
<Compile Include="Controllers\UserCultureSelectorController.cs" />
<Compile Include="Controllers\AdminCultureSelectorController.cs" />
<Compile Include="Models\TransliterationSpecificationRecord.cs" />
<Compile Include="Providers\ContentLocalizationTokens.cs" />
<Compile Include="Selectors\ContentCultureSelector.cs" />
@@ -119,7 +118,6 @@
<Compile Include="Services\LocalizationService.cs" />
<Compile Include="Services\TransliterationService.cs" />
<Compile Include="Events\TransliterationSlugEventHandler.cs" />
<Compile Include="Services\Utils.cs" />
<Compile Include="ViewModels\ContentLocalizationsViewModel.cs" />
<Compile Include="ViewModels\EditLocalizationViewModel.cs" />
<Compile Include="ViewModels\CreateTransliterationViewModel.cs" />
@@ -198,9 +196,6 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\UserCultureSelector.cshtml" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
@@ -234,4 +229,4 @@
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
</Project>
</Project>

View File

@@ -1,3 +1,4 @@
using System;
using System.Web;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions;
@@ -18,8 +19,7 @@ namespace Orchard.Localization.Selectors {
private const string AdminCookieName = "OrchardCurrentCulture-Admin";
private const int DefaultExpireTimeYear = 1;
public CookieCultureSelector(
IHttpContextAccessor httpContextAccessor,
public CookieCultureSelector(IHttpContextAccessor httpContextAccessor,
IClock clock,
ShellSettings shellSettings) {
_httpContextAccessor = httpContextAccessor;
@@ -36,10 +36,11 @@ namespace Orchard.Localization.Selectors {
var cookie = new HttpCookie(cookieName, culture) {
Expires = _clock.UtcNow.AddYears(DefaultExpireTimeYear),
Domain = httpContext.Request.IsLocal ? null : httpContext.Request.Url.Host
};
if (!string.IsNullOrEmpty(_shellSettings.RequestUrlPrefix)) {
cookie.Domain = !httpContext.Request.IsLocal ? httpContext.Request.Url.Host : null;
if (!String.IsNullOrEmpty(_shellSettings.RequestUrlPrefix)) {
cookie.Path = GetCookiePath(httpContext);
}
@@ -72,4 +73,4 @@ namespace Orchard.Localization.Selectors {
return cookiePath;
}
}
}
}

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic;
using Orchard.Autoroute.Models;
using Orchard.ContentManagement;
using Orchard.Localization.Models;
@@ -11,7 +10,5 @@ namespace Orchard.Localization.Services {
void SetContentCulture(IContent content, string culture);
IEnumerable<LocalizationPart> GetLocalizations(IContent content);
IEnumerable<LocalizationPart> GetLocalizations(IContent content, VersionOptions versionOptions);
bool TryFindLocalizedRoute(ContentItem routableContent, string cultureName, out AutoroutePart localizedRoute);
bool TryGetRouteForUrl(string url, out AutoroutePart route);
}
}

View File

@@ -1,59 +1,53 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Orchard.Autoroute.Models;
using Orchard.Autoroute.Services;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Localization.Models;
namespace Orchard.Localization.Services {
public class LocalizationService : ILocalizationService {
private readonly IContentManager _contentManager;
private readonly ICultureManager _cultureManager;
private readonly IHomeAliasService _homeAliasService;
public LocalizationService(IContentManager contentManager, ICultureManager cultureManager, IHomeAliasService homeAliasService) {
public LocalizationService(IContentManager contentManager, ICultureManager cultureManager) {
_contentManager = contentManager;
_cultureManager = cultureManager;
_homeAliasService = homeAliasService;
}
/// <summary>
/// Warning: Returns only the first item of same culture localizations.
/// </summary>
public LocalizationPart GetLocalizedContentItem(IContent content, string culture) =>
GetLocalizedContentItem(content, culture, null);
/// <summary>
/// Warning: Returns only the first item of same culture localizations.
/// </summary>
public LocalizationPart GetLocalizedContentItem(IContent content, string culture) {
// Warning: Returns only the first of same culture localizations.
return GetLocalizedContentItem(content, culture, null);
}
public LocalizationPart GetLocalizedContentItem(IContent content, string culture, VersionOptions versionOptions) {
var cultureRecord = _cultureManager.GetCultureByName(culture);
if (cultureRecord == null) {
return null;
}
if (cultureRecord == null) return null;
var localized = content.As<LocalizationPart>();
if (localized == null) {
return null;
}
if (localized == null) return null;
var masterContentItemId = localized.HasTranslationGroup ? localized.Record.MasterContentItemId : localized.Id;
// Warning: Returns only the first of same culture localizations.
return _contentManager
.Query<LocalizationPart>(versionOptions, content.ContentItem.ContentType)
.Where<LocalizationPartRecord>(localization =>
(localization.Id == masterContentItemId || localization.MasterContentItemId == masterContentItemId)
&& localization.CultureId == cultureRecord.Id)
.Where<LocalizationPartRecord>(l =>
(l.Id == masterContentItemId || l.MasterContentItemId == masterContentItemId) &&
l.CultureId == cultureRecord.Id)
.Slice(1)
.FirstOrDefault();
}
public string GetContentCulture(IContent content) =>
content.As<LocalizationPart>()?.Culture?.Culture ?? _cultureManager.GetSiteCulture();
public string GetContentCulture(IContent content) {
var localized = content.As<LocalizationPart>();
return localized?.Culture == null ?
_cultureManager.GetSiteCulture() :
localized.Culture.Culture;
}
public void SetContentCulture(IContent content, string culture) {
var localized = content.As<LocalizationPart>();
@@ -63,14 +57,11 @@ namespace Orchard.Localization.Services {
localized.Culture = _cultureManager.GetCultureByName(culture);
}
/// <summary>
/// Warning: May contain more than one localization of the same culture.
/// </summary>
public IEnumerable<LocalizationPart> GetLocalizations(IContent content) => GetLocalizations(content, null);
public IEnumerable<LocalizationPart> GetLocalizations(IContent content) {
// Warning: May contain more than one localization of the same culture.
return GetLocalizations(content, null);
}
/// <summary>
/// Warning: May contain more than one localization of the same culture.
/// </summary>
public IEnumerable<LocalizationPart> GetLocalizations(IContent content, VersionOptions versionOptions) {
if (content.ContentItem.Id == 0) return Enumerable.Empty<LocalizationPart>();
@@ -85,58 +76,16 @@ namespace Orchard.Localization.Services {
if (localized.HasTranslationGroup) {
int masterContentItemId = localized.MasterContentItem.ContentItem.Id;
query = query.Where<LocalizationPartRecord>(localization =>
localization.Id != contentItemId && // Exclude the content
(localization.Id == masterContentItemId || localization.MasterContentItemId == masterContentItemId));
query = query.Where<LocalizationPartRecord>(l =>
l.Id != contentItemId && // Exclude the content
(l.Id == masterContentItemId || l.MasterContentItemId == masterContentItemId));
}
else {
query = query.Where<LocalizationPartRecord>(localization => localization.MasterContentItemId == contentItemId);
query = query.Where<LocalizationPartRecord>(l => l.MasterContentItemId == contentItemId);
}
// Warning: May contain more than one localization of the same culture.
return query.List().ToList();
}
public bool TryGetRouteForUrl(string url, out AutoroutePart route) {
route = _contentManager.Query<AutoroutePart, AutoroutePartRecord>()
.ForVersion(VersionOptions.Published)
.Where(r => r.DisplayAlias == url)
.List()
.FirstOrDefault();
route = route ?? _homeAliasService.GetHomePage(VersionOptions.Latest).As<AutoroutePart>();
return route != null;
}
public bool TryFindLocalizedRoute(ContentItem routableContent, string cultureName, out AutoroutePart localizedRoute) {
if (!routableContent.Parts.Any(p => p.Is<ILocalizableAspect>())) {
localizedRoute = null;
return false;
}
IEnumerable<LocalizationPart> localizations = GetLocalizations(routableContent, VersionOptions.Published);
ILocalizableAspect localizationPart = null, siteCultureLocalizationPart = null;
foreach (var localization in localizations) {
if (localization.Culture.Culture.Equals(cultureName, StringComparison.InvariantCultureIgnoreCase)) {
localizationPart = localization;
break;
}
if (localization.Culture == null && siteCultureLocalizationPart == null) {
siteCultureLocalizationPart = localization;
}
}
if (localizationPart == null) {
localizationPart = siteCultureLocalizationPart;
}
localizedRoute = localizationPart?.As<AutoroutePart>();
return localizedRoute != null;
}
}
}
}

View File

@@ -1,31 +0,0 @@
using System.Web;
namespace Orchard.CulturePicker.Services {
public static class Utils {
public static string GetReturnUrl(HttpRequestBase request) {
if (request.UrlReferrer == null) {
return "";
}
string localUrl = GetAppRelativePath(request.UrlReferrer.AbsolutePath, request);
return HttpUtility.UrlDecode(localUrl);
}
public static string GetAppRelativePath(string logicalPath, HttpRequestBase request) {
if (request.ApplicationPath == null) {
return "";
}
logicalPath = logicalPath.ToLower();
string appPath = request.ApplicationPath.ToLower();
if (appPath != "/") {
appPath += "/";
}
else {
return logicalPath.Substring(1);
}
return logicalPath.Replace(appPath, "");
}
}
}

View File

@@ -1,34 +0,0 @@
@using Orchard.Localization.Services
@{
var currentCulture = WorkContext.CurrentCulture;
var supportedCultures = WorkContext.Resolve<ICultureManager>().ListCultures().ToList();
}
<div id="culture-selection">
<ul>
@foreach (var supportedCulture in supportedCultures)
{
var url = Url.Action(
"ChangeCulture",
"UserCultureSelector",
new
{
area = "Orchard.Localization",
culture = supportedCulture,
returnUrl = Html.ViewContext.HttpContext.Request.RawUrl
});
<li>
@if (supportedCulture.Equals(currentCulture))
{
<a href="@url">@T("{0} (current)", supportedCulture)</a>
}
else
{
<a href="@url">@supportedCulture</a>
}
</li>
}
</ul>
</div>

View File

@@ -685,7 +685,6 @@
<Compile Include="Messaging\Services\IMessageManager.cs" />
<Compile Include="Messaging\Services\IMessagingChannel.cs" />
<Compile Include="IWorkContextAccessor.cs" />
<Compile Include="Utility\Extensions\NameValueCollectionExtensions.cs" />
<Compile Include="Utility\Extensions\VirtualPathProviderExtensions.cs" />
<Compile Include="Utility\NamedReaderWriterLock.cs" />
<Compile Include="Utility\ReflectionHelper.cs" />

View File

@@ -1,12 +0,0 @@
using System.Collections.Specialized;
using System.Linq;
using System.Web;
namespace Orchard.Utility.Extensions {
public static class NameValueCollectionExtensions {
public static string ToQueryString(this NameValueCollection nameValues) =>
string.Join(
"&",
(from string name in nameValues select string.Concat(name, "=", HttpUtility.UrlEncode(nameValues[name]))).ToArray());
}
}