mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Merge
--HG-- branch : dev
This commit is contained in:
@@ -2,7 +2,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using Autofac;
|
using Autofac;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -11,6 +10,7 @@ using Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy;
|
|||||||
using Orchard.Environment.Descriptor.Models;
|
using Orchard.Environment.Descriptor.Models;
|
||||||
using Orchard.Environment.Extensions;
|
using Orchard.Environment.Extensions;
|
||||||
using Orchard.Environment.Extensions.Models;
|
using Orchard.Environment.Extensions.Models;
|
||||||
|
using Orchard.FileSystems.VirtualPath;
|
||||||
|
|
||||||
namespace Orchard.Tests.DisplayManagement.Descriptors {
|
namespace Orchard.Tests.DisplayManagement.Descriptors {
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
@@ -18,16 +18,19 @@ namespace Orchard.Tests.DisplayManagement.Descriptors {
|
|||||||
private ShellDescriptor _descriptor;
|
private ShellDescriptor _descriptor;
|
||||||
private IList<FeatureDescriptor> _features;
|
private IList<FeatureDescriptor> _features;
|
||||||
private TestViewEngine _testViewEngine;
|
private TestViewEngine _testViewEngine;
|
||||||
|
private TestVirtualPathProvider _testVirtualPathProvider;
|
||||||
|
|
||||||
|
|
||||||
protected override void Register(Autofac.ContainerBuilder builder) {
|
protected override void Register(Autofac.ContainerBuilder builder) {
|
||||||
_descriptor = new ShellDescriptor { };
|
_descriptor = new ShellDescriptor { };
|
||||||
_testViewEngine = new TestViewEngine();
|
_testViewEngine = new TestViewEngine();
|
||||||
|
_testVirtualPathProvider = new TestVirtualPathProvider();
|
||||||
|
|
||||||
builder.Register(ctx => _descriptor);
|
builder.Register(ctx => _descriptor);
|
||||||
builder.RegisterType<ShapeTemplateBindingStrategy>().As<IShapeTableProvider>();
|
builder.RegisterType<ShapeTemplateBindingStrategy>().As<IShapeTableProvider>();
|
||||||
builder.RegisterType<BasicShapeTemplateHarvester>().As<IShapeTemplateHarvester>();
|
builder.RegisterType<BasicShapeTemplateHarvester>().As<IShapeTemplateHarvester>();
|
||||||
builder.RegisterInstance(_testViewEngine).As<IShapeTemplateViewEngine>();
|
builder.RegisterInstance(_testViewEngine).As<IShapeTemplateViewEngine>();
|
||||||
|
builder.RegisterInstance(_testVirtualPathProvider).As<IVirtualPathProvider>();
|
||||||
|
|
||||||
var extensionManager = new Mock<IExtensionManager>();
|
var extensionManager = new Mock<IExtensionManager>();
|
||||||
builder.Register(ctx => extensionManager);
|
builder.Register(ctx => extensionManager);
|
||||||
@@ -35,20 +38,62 @@ namespace Orchard.Tests.DisplayManagement.Descriptors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class TestViewEngine : Dictionary<string, object>, IShapeTemplateViewEngine {
|
public class TestViewEngine : Dictionary<string, object>, IShapeTemplateViewEngine {
|
||||||
public IEnumerable<string> DetectTemplateFileNames(string virtualPath) {
|
public IEnumerable<string> DetectTemplateFileNames(IEnumerable<string> fileNames) {
|
||||||
var virtualPathNorm = virtualPath.Replace("\\", "/");
|
return fileNames;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var key in Keys) {
|
public class TestVirtualPathProvider : IVirtualPathProvider {
|
||||||
var keyNorm = key.Replace("\\", "/");
|
public string Combine(params string[] paths) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
if (keyNorm.StartsWith(virtualPathNorm, StringComparison.OrdinalIgnoreCase)) {
|
public string ToAppRelative(string virtualPath) {
|
||||||
var rest = keyNorm.Substring(virtualPathNorm.Length).TrimStart('/', '\\');
|
throw new NotImplementedException();
|
||||||
if (rest.IndexOfAny(new[] { '/', '\\' }) != -1) {
|
}
|
||||||
continue;
|
|
||||||
}
|
public string MapPath(string virtualPath) {
|
||||||
yield return Path.GetFileNameWithoutExtension(rest);
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public bool FileExists(string virtualPath) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream OpenFile(string virtualPath) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StreamWriter CreateText(string virtualPath) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream CreateFile(string virtualPath) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime GetFileLastWriteTimeUtc(string virtualPath) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DirectoryExists(string virtualPath) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateDirectory(string virtualPath) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetDirectoryName(string virtualPath) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> ListFiles(string path) {
|
||||||
|
return new List<string> {"~/Modules/Alpha/Views/AlphaShape.blah"};
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> ListDirectories(string path) {
|
||||||
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,13 +2,11 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Orchard.ContentManagement.MetaData;
|
using Orchard.ContentManagement.MetaData;
|
||||||
using Orchard.Data.Migration.Interpreters;
|
using Orchard.Data.Migration.Interpreters;
|
||||||
using Orchard.Data.Migration.Records;
|
using Orchard.Data.Migration.Records;
|
||||||
using Orchard.Data.Migration.Schema;
|
using Orchard.Data.Migration.Schema;
|
||||||
using Orchard.Environment.Extensions;
|
using Orchard.Environment.Extensions;
|
||||||
using Orchard.Environment.State;
|
|
||||||
using Orchard.Localization;
|
using Orchard.Localization;
|
||||||
using Orchard.Logging;
|
using Orchard.Logging;
|
||||||
|
|
||||||
@@ -41,38 +39,17 @@ namespace Orchard.Data.Migration {
|
|||||||
public ILogger Logger { get; set; }
|
public ILogger Logger { get; set; }
|
||||||
|
|
||||||
public IEnumerable<string> GetFeaturesThatNeedUpdate() {
|
public IEnumerable<string> GetFeaturesThatNeedUpdate() {
|
||||||
|
var currentVersions = _dataMigrationRepository.Table.ToDictionary(r => r.DataMigrationClass);
|
||||||
|
|
||||||
var features = new List<string>();
|
var outOfDateMigrations = _dataMigrations.Where(dataMigration => {
|
||||||
|
DataMigrationRecord record;
|
||||||
|
if (currentVersions.TryGetValue(dataMigration.GetType().FullName, out record))
|
||||||
|
return CreateUpgradeLookupTable(dataMigration).ContainsKey(record.Version);
|
||||||
|
|
||||||
// compare current version and available migration methods for each migration class
|
return (GetCreateMethod(dataMigration) != null);
|
||||||
foreach ( var dataMigration in _dataMigrations ) {
|
});
|
||||||
|
|
||||||
// get current version for this migration
|
|
||||||
var dataMigrationRecord = GetDataMigrationRecord(dataMigration);
|
|
||||||
|
|
||||||
var current = 0;
|
return outOfDateMigrations.Select(m => m.Feature.Descriptor.Id).ToList();
|
||||||
if (dataMigrationRecord != null) {
|
|
||||||
current = dataMigrationRecord.Version;
|
|
||||||
}
|
|
||||||
|
|
||||||
// do we need to call Create() ?
|
|
||||||
if (current == 0) {
|
|
||||||
|
|
||||||
// try to resolve a Create method
|
|
||||||
if ( GetCreateMethod(dataMigration) != null ) {
|
|
||||||
features.Add(dataMigration.Feature.Descriptor.Id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var lookupTable = CreateUpgradeLookupTable(dataMigration);
|
|
||||||
|
|
||||||
if(lookupTable.ContainsKey(current)) {
|
|
||||||
features.Add(dataMigration.Feature.Descriptor.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return features;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(IEnumerable<string> features) {
|
public void Update(IEnumerable<string> features) {
|
||||||
@@ -206,20 +183,26 @@ namespace Orchard.Data.Migration {
|
|||||||
/// Create a list of all available Update methods from a data migration class, indexed by the version number
|
/// Create a list of all available Update methods from a data migration class, indexed by the version number
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static Dictionary<int, MethodInfo> CreateUpgradeLookupTable(IDataMigration dataMigration) {
|
private static Dictionary<int, MethodInfo> CreateUpgradeLookupTable(IDataMigration dataMigration) {
|
||||||
var updateMethodNameRegex = new Regex(@"^UpdateFrom(?<version>\d+)$", RegexOptions.Compiled);
|
return dataMigration
|
||||||
|
.GetType()
|
||||||
|
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||||
|
.Select(mi => GetUpdateMethod(mi))
|
||||||
|
.Where(tuple => tuple != null)
|
||||||
|
.ToDictionary(tuple => tuple.Item1, tuple => tuple.Item2);
|
||||||
|
}
|
||||||
|
|
||||||
// update methods might also be called after Create()
|
private static Tuple<int, MethodInfo> GetUpdateMethod(MethodInfo mi) {
|
||||||
var lookupTable = new Dictionary<int, MethodInfo>();
|
const string updatefromPrefix = "UpdateFrom";
|
||||||
|
|
||||||
// construct a lookup table with all managed initial versions
|
if (mi.Name.StartsWith(updatefromPrefix)) {
|
||||||
foreach ( var methodInfo in dataMigration.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance) ) {
|
var version = mi.Name.Substring(updatefromPrefix.Length);
|
||||||
var match = updateMethodNameRegex.Match(methodInfo.Name);
|
int versionValue;
|
||||||
if ( match.Success ) {
|
if (int.TryParse(version, out versionValue)) {
|
||||||
lookupTable.Add(int.Parse(match.Groups["version"].Value), methodInfo);
|
return new Tuple<int, MethodInfo>(versionValue, mi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lookupTable;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Orchard.Environment.Extensions.Models;
|
|
||||||
using Orchard.Events;
|
|
||||||
|
|
||||||
namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy {
|
namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -70,7 +66,7 @@ namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public interface IShapeTemplateViewEngine : IDependency {
|
public interface IShapeTemplateViewEngine : IDependency {
|
||||||
IEnumerable<string> DetectTemplateFileNames(string virtualPath);
|
IEnumerable<string> DetectTemplateFileNames(IEnumerable<string> fileNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -10,22 +9,27 @@ using Orchard.DisplayManagement.Implementation;
|
|||||||
using Orchard.Environment.Descriptor.Models;
|
using Orchard.Environment.Descriptor.Models;
|
||||||
using Orchard.Environment.Extensions;
|
using Orchard.Environment.Extensions;
|
||||||
using Orchard.Environment.Extensions.Models;
|
using Orchard.Environment.Extensions.Models;
|
||||||
|
using Orchard.FileSystems.VirtualPath;
|
||||||
|
|
||||||
namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy {
|
namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy {
|
||||||
public class ShapeTemplateBindingStrategy : IShapeTableProvider {
|
public class ShapeTemplateBindingStrategy : IShapeTableProvider {
|
||||||
private readonly ShellDescriptor _shellDescriptor;
|
private readonly ShellDescriptor _shellDescriptor;
|
||||||
private readonly IExtensionManager _extensionManager;
|
private readonly IExtensionManager _extensionManager;
|
||||||
|
private readonly IVirtualPathProvider _virtualPathProvider;
|
||||||
private readonly IEnumerable<IShapeTemplateHarvester> _harvesters;
|
private readonly IEnumerable<IShapeTemplateHarvester> _harvesters;
|
||||||
private readonly IEnumerable<IShapeTemplateViewEngine> _shapeTemplateViewEngines;
|
private readonly IEnumerable<IShapeTemplateViewEngine> _shapeTemplateViewEngines;
|
||||||
|
|
||||||
|
|
||||||
public ShapeTemplateBindingStrategy(
|
public ShapeTemplateBindingStrategy(
|
||||||
IEnumerable<IShapeTemplateHarvester> harvesters,
|
IEnumerable<IShapeTemplateHarvester> harvesters,
|
||||||
ShellDescriptor shellDescriptor,
|
ShellDescriptor shellDescriptor,
|
||||||
IExtensionManager extensionManager,
|
IExtensionManager extensionManager,
|
||||||
|
IVirtualPathProvider virtualPathProvider,
|
||||||
IEnumerable<IShapeTemplateViewEngine> shapeTemplateViewEngines) {
|
IEnumerable<IShapeTemplateViewEngine> shapeTemplateViewEngines) {
|
||||||
_harvesters = harvesters;
|
_harvesters = harvesters;
|
||||||
_shellDescriptor = shellDescriptor;
|
_shellDescriptor = shellDescriptor;
|
||||||
_extensionManager = extensionManager;
|
_extensionManager = extensionManager;
|
||||||
|
_virtualPathProvider = virtualPathProvider;
|
||||||
_shapeTemplateViewEngines = shapeTemplateViewEngines;
|
_shapeTemplateViewEngines = shapeTemplateViewEngines;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,14 +47,15 @@ namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy {
|
|||||||
|
|
||||||
var hits = activeExtensions.SelectMany(extensionDescriptor => {
|
var hits = activeExtensions.SelectMany(extensionDescriptor => {
|
||||||
var pathContexts = harvesterInfos.SelectMany(harvesterInfo => harvesterInfo.subPaths.Select(subPath => {
|
var pathContexts = harvesterInfos.SelectMany(harvesterInfo => harvesterInfo.subPaths.Select(subPath => {
|
||||||
var basePath = Path.Combine(extensionDescriptor.Location, extensionDescriptor.Id);
|
var basePath = Path.Combine(extensionDescriptor.Location, extensionDescriptor.Id).Replace(Path.DirectorySeparatorChar, '/');
|
||||||
var virtualPath = Path.Combine(basePath, subPath);
|
var virtualPath = Path.Combine(basePath, subPath).Replace(Path.DirectorySeparatorChar, '/');
|
||||||
return new { harvesterInfo.harvester, basePath, subPath, virtualPath };
|
var fileNames = _virtualPathProvider.ListFiles(virtualPath).Select(Path.GetFileName);
|
||||||
|
return new { harvesterInfo.harvester, basePath, subPath, virtualPath, fileNames };
|
||||||
}));
|
}));
|
||||||
|
|
||||||
var fileContexts = pathContexts.SelectMany(pathContext => _shapeTemplateViewEngines.SelectMany(ve => {
|
var fileContexts = pathContexts.SelectMany(pathContext => _shapeTemplateViewEngines.SelectMany(ve => {
|
||||||
var fileNames = ve.DetectTemplateFileNames(pathContext.virtualPath);
|
var fileNames = ve.DetectTemplateFileNames(pathContext.fileNames);
|
||||||
return fileNames.Select(fileName => new { fileName = Path.GetFileNameWithoutExtension(fileName), fileVirtualPath = Path.Combine(pathContext.virtualPath, fileName).Replace('\\', '/'), pathContext });
|
return fileNames.Select(fileName => new { fileName = Path.GetFileNameWithoutExtension(fileName), fileVirtualPath = Path.Combine(pathContext.virtualPath, fileName).Replace(Path.DirectorySeparatorChar, '/'), pathContext });
|
||||||
}));
|
}));
|
||||||
|
|
||||||
var shapeContexts = fileContexts.SelectMany(fileContext => {
|
var shapeContexts = fileContexts.SelectMany(fileContext => {
|
||||||
|
@@ -1,19 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Web.Mvc;
|
using System.Web.Mvc;
|
||||||
using Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy;
|
using Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy;
|
||||||
using Orchard.FileSystems.VirtualPath;
|
|
||||||
using Orchard.Logging;
|
using Orchard.Logging;
|
||||||
using Orchard.Mvc.ViewEngines.WebForms;
|
using Orchard.Mvc.ViewEngines.WebForms;
|
||||||
|
|
||||||
namespace Orchard.Mvc.ViewEngines.Razor {
|
namespace Orchard.Mvc.ViewEngines.Razor {
|
||||||
public class RazorViewEngineProvider : IViewEngineProvider, IShapeTemplateViewEngine {
|
public class RazorViewEngineProvider : IViewEngineProvider, IShapeTemplateViewEngine {
|
||||||
private readonly IVirtualPathProvider _virtualPathProvider;
|
public RazorViewEngineProvider() {
|
||||||
|
|
||||||
public RazorViewEngineProvider(IVirtualPathProvider virtualPathProvider) {
|
|
||||||
_virtualPathProvider = virtualPathProvider;
|
|
||||||
Logger = NullLogger.Instance;
|
Logger = NullLogger.Instance;
|
||||||
RazorCompilationEventsShim.EnsureInitialized();
|
RazorCompilationEventsShim.EnsureInitialized();
|
||||||
}
|
}
|
||||||
@@ -103,13 +98,8 @@ namespace Orchard.Mvc.ViewEngines.Razor {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> DetectTemplateFileNames(string virtualPath) {
|
public IEnumerable<string> DetectTemplateFileNames(IEnumerable<string> fileNames) {
|
||||||
var fileNames = _virtualPathProvider.ListFiles(virtualPath).Select(Path.GetFileName);
|
return fileNames.Where(fileName => fileName.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase));
|
||||||
foreach (var fileName in fileNames) {
|
|
||||||
if (fileName.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase)) {
|
|
||||||
yield return fileName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Web.Mvc;
|
using System.Web.Mvc;
|
||||||
using Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy;
|
using Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy;
|
||||||
using Orchard.FileSystems.VirtualPath;
|
|
||||||
using Orchard.Logging;
|
using Orchard.Logging;
|
||||||
|
|
||||||
namespace Orchard.Mvc.ViewEngines.WebForms {
|
namespace Orchard.Mvc.ViewEngines.WebForms {
|
||||||
public class WebFormViewEngineProvider : IViewEngineProvider, IShapeTemplateViewEngine {
|
public class WebFormViewEngineProvider : IViewEngineProvider, IShapeTemplateViewEngine {
|
||||||
private readonly IVirtualPathProvider _virtualPathProvider;
|
public WebFormViewEngineProvider() {
|
||||||
|
|
||||||
public WebFormViewEngineProvider(IVirtualPathProvider virtualPathProvider) {
|
|
||||||
_virtualPathProvider = virtualPathProvider;
|
|
||||||
Logger = NullLogger.Instance;
|
Logger = NullLogger.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,14 +104,9 @@ namespace Orchard.Mvc.ViewEngines.WebForms {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> DetectTemplateFileNames(string virtualPath) {
|
public IEnumerable<string> DetectTemplateFileNames(IEnumerable<string> fileNames) {
|
||||||
var fileNames = _virtualPathProvider.ListFiles(virtualPath).Select(Path.GetFileName);
|
return fileNames.Where(fileName => fileName.EndsWith(".aspx", StringComparison.OrdinalIgnoreCase) ||
|
||||||
foreach (var fileName in fileNames) {
|
fileName.EndsWith(".ascx", StringComparison.OrdinalIgnoreCase));
|
||||||
if (fileName.EndsWith(".aspx", StringComparison.OrdinalIgnoreCase) ||
|
}
|
||||||
fileName.EndsWith(".ascx", StringComparison.OrdinalIgnoreCase)) {
|
|
||||||
yield return fileName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user