diff --git a/.hgignore b/.hgignore index 91673c801..8ec3e6972 100644 --- a/.hgignore +++ b/.hgignore @@ -15,3 +15,4 @@ glob:src/Orchard.Azure.suo glob:src/Orchard.5.0.ReSharper glob:log.xml glob:profiling +glob:*.csproj.orig diff --git a/CREDITS.txt b/CREDITS.txt index d442a8f10..9940c0d8b 100644 --- a/CREDITS.txt +++ b/CREDITS.txt @@ -63,6 +63,18 @@ Website: http://fluentnhibernate.org/ Copyright: Copyright (c) 2008-2009 James Gregory and contributors License: New BSD +FluentPath +----- +Website: http://fluentpath.codeplex.com/ +Copyright: Copyright (c) 2010 Bertrand Le Roy +License: MS-PL + +Html Agility Pack +----- +Website: http://htmlagilitypack.codeplex.com/ +Copyright: Copyright (c) 2003-20010 Simon Mourier +License: MS-PL + IESI Collections ----- Website: http://www.codeproject.com/KB/recipes/sets.aspx @@ -93,6 +105,12 @@ Log4Net Website: http://logging.apache.org/log4net/index.html Copyright: Copyright (c) 2007 Apache Software Foundation License: Apache Software Foundation License 2.0 + +Lucene.net +----- +Website: http://incubator.apache.org/projects/lucene.net.html +Copyright: Copyright (c) 2009 Apache Software Foundation +License: Apache Software Foundation License 2.0 Moq ----- @@ -125,6 +143,12 @@ Website: http://www.icsharpcode.net/OpenSource/SharpZipLib/Default.aspx Copyright: Copyright (c) 2000-2009 IC#Code License: Modified GPL: http://www.icsharpcode.net/OpenSource/SharpZipLib/Default.aspx +SpecFlow +----- +Website: http://www.specflow.org/ +Copyright: Copyright (c) 2009 TechTalk +License: New BSD + SQLite ----- Website: http://www.sqlite.org @@ -137,6 +161,12 @@ WebSite: http://tinymce.moxiecode.com/ Copyright: Copyright (c) 2003-2009 Moxiecode Systems AB License: LGPL 2.1 +WCat +----- +WebSite: http://www.iis.net/community/default.aspx?tabid=34&i=1466&g=6 +Copyright: Copyright (c) 2007 Microsoft +License: "Free" + Yamlnet ----- Website: http://code.google.com/p/yamlnet/ diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Orchard.Azure.Web.csproj b/src/Orchard.Azure/Orchard.Azure.Web/Orchard.Azure.Web.csproj index 7db7fdf13..2d5045c4d 100644 --- a/src/Orchard.Azure/Orchard.Azure.Web/Orchard.Azure.Web.csproj +++ b/src/Orchard.Azure/Orchard.Azure.Web/Orchard.Azure.Web.csproj @@ -12,7 +12,7 @@ Orchard.Azure.Web Orchard.Azure.Web v3.5 - true + false true diff --git a/src/Orchard.Core.Tests/Common/Providers/CommonAspectProviderTests.cs b/src/Orchard.Core.Tests/Common/Providers/CommonAspectProviderTests.cs index d62bae234..210d12203 100644 --- a/src/Orchard.Core.Tests/Common/Providers/CommonAspectProviderTests.cs +++ b/src/Orchard.Core.Tests/Common/Providers/CommonAspectProviderTests.cs @@ -6,7 +6,7 @@ using JetBrains.Annotations; using Moq; using NUnit.Framework; using Orchard.ContentManagement.Aspects; -using Orchard.ContentManagement.MetaData.Records; +using Orchard.ContentManagement.MetaData; using Orchard.Core.Common; using Orchard.Core.Common.Handlers; using Orchard.Core.Common.Models; @@ -26,6 +26,7 @@ namespace Orchard.Core.Tests.Common.Providers { private Mock _authn; private Mock _authz; private Mock _membership; + private Mock _contentDefinitionManager; public override void Register(ContainerBuilder builder) { builder.RegisterType().As(); @@ -36,19 +37,18 @@ namespace Orchard.Core.Tests.Common.Providers { _authn = new Mock(); _authz = new Mock(); _membership = new Mock(); + _contentDefinitionManager = new Mock(); builder.RegisterInstance(_authn.Object); builder.RegisterInstance(_authz.Object); builder.RegisterInstance(_membership.Object); - + builder.RegisterInstance(_contentDefinitionManager.Object); } protected override IEnumerable DatabaseTypes { get { return new[] { typeof(ContentTypeRecord), - typeof(ContentTypePartRecord), - typeof(ContentTypePartNameRecord), typeof(ContentItemRecord), typeof(ContentItemVersionRecord), typeof(CommonRecord), diff --git a/src/Orchard.Core.Tests/Common/Services/RoutableServiceTests.cs b/src/Orchard.Core.Tests/Common/Services/RoutableServiceTests.cs index 69bb981db..51d5af0d7 100644 --- a/src/Orchard.Core.Tests/Common/Services/RoutableServiceTests.cs +++ b/src/Orchard.Core.Tests/Common/Services/RoutableServiceTests.cs @@ -2,11 +2,12 @@ using System.Collections.Generic; using Autofac; using JetBrains.Annotations; +using Moq; using NUnit.Framework; using Orchard.ContentManagement; using Orchard.ContentManagement.Drivers; using Orchard.ContentManagement.Handlers; -using Orchard.ContentManagement.MetaData.Records; +using Orchard.ContentManagement.MetaData; using Orchard.ContentManagement.Records; using Orchard.Core.Common.Models; using Orchard.Core.Common.Services; @@ -28,6 +29,7 @@ namespace Orchard.Core.Tests.Common.Services { public override void Register(ContainerBuilder builder) { builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterInstance(new Mock().Object); builder.RegisterType().As(); builder.RegisterType().As(); @@ -182,8 +184,6 @@ namespace Orchard.Core.Tests.Common.Services { return new[] { typeof(RoutableRecord), typeof(ContentTypeRecord), - typeof(ContentTypePartRecord), - typeof(ContentTypePartNameRecord), typeof(ContentItemRecord), typeof(ContentItemVersionRecord), typeof(CommonRecord), diff --git a/src/Orchard.Core.Tests/Feeds/Controllers/FeedControllerTests.cs b/src/Orchard.Core.Tests/Feeds/Controllers/FeedControllerTests.cs index e8cdd485a..3970d40f7 100644 --- a/src/Orchard.Core.Tests/Feeds/Controllers/FeedControllerTests.cs +++ b/src/Orchard.Core.Tests/Feeds/Controllers/FeedControllerTests.cs @@ -9,6 +9,8 @@ using Moq; using NUnit.Framework; using Orchard.ContentManagement; using Orchard.ContentManagement.Handlers; +using Orchard.ContentManagement.MetaData.Builders; +using Orchard.ContentManagement.MetaData.Models; using Orchard.Core.Common.Models; using Orchard.Core.Feeds; using Orchard.Core.Feeds.Controllers; @@ -145,7 +147,7 @@ namespace Orchard.Core.Tests.Feeds.Controllers { [Test] public void CorePartValuesAreExtracted() { var clock = new StubClock(); - var hello = new ContentItemBuilder("hello") + var hello = new ContentItemBuilder(new ContentTypeDefinitionBuilder().Named("hello").Build()) .Weld() .Weld() .Weld() diff --git a/src/Orchard.Core.Tests/Indexing/DefaultIndexProviderTests.cs b/src/Orchard.Core.Tests/Indexing/DefaultIndexProviderTests.cs index 953853e88..9152c2b98 100644 --- a/src/Orchard.Core.Tests/Indexing/DefaultIndexProviderTests.cs +++ b/src/Orchard.Core.Tests/Indexing/DefaultIndexProviderTests.cs @@ -58,7 +58,11 @@ namespace Orchard.Tests.Indexing { [Test] public void IndexProviderShouldOverwriteAlreadyExistingIndex() { _provider.CreateIndex("default"); - _provider.CreateIndex("default"); + _provider.Store("default", _provider.New(1).Add("body", null)); + Assert.That(_provider.IsEmpty("default"), Is.False); + + _provider.CreateIndex("default"); + Assert.That(_provider.IsEmpty("default"), Is.True); } [Test] @@ -168,5 +172,82 @@ namespace Orchard.Tests.Indexing { Assert.That(searchBuilder.WithField("body", "hr").Search().Count(), Is.EqualTo(1)); Assert.That(searchBuilder.WithField("body", "hr").Search().First().Id, Is.EqualTo(1)); } + + [Test] public void ShouldAllowNullOrEmptyStrings() { + _provider.CreateIndex("default"); + _provider.Store("default", _provider.New(1).Add("body", null)); + _provider.Store("default", _provider.New(2).Add("body", "")); + _provider.Store("default", _provider.New(3).Add("body", "
", true)); + + var searchBuilder = _provider.CreateSearchBuilder("default"); + + Assert.That(searchBuilder.Get(1).Id, Is.EqualTo(1)); + Assert.That(searchBuilder.Get(2).Id, Is.EqualTo(2)); + Assert.That(searchBuilder.Get(3).Id, Is.EqualTo(3)); + } + + [Test] + public void ProviderShouldStoreSettings() { + _provider.CreateIndex("default"); + Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(DefaultIndexProvider.DefaultMinDateTime)); + + _provider.SetLastIndexUtc("default", new DateTime(2010, 1, 1, 1, 1, 1, 1)); + Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(new DateTime(2010, 1, 1, 1, 1, 1, 0))); + + _provider.SetLastIndexUtc("default", new DateTime(1901, 1, 1, 1, 1, 1, 1)); + Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(DefaultIndexProvider.DefaultMinDateTime)); + } + + [Test] + public void IsEmptyShouldBeTrueForNoneExistingIndexes() { + _provider.IsEmpty("dummy"); + Assert.That(_provider.IsEmpty("default"), Is.True); + } + + [Test] + public void IsEmptyShouldBeTrueForJustNewIndexes() { + _provider.CreateIndex("default"); + Assert.That(_provider.IsEmpty("default"), Is.True); + } + + [Test] + public void IsEmptyShouldBeFalseWhenThereIsADocument() { + _provider.CreateIndex("default"); + _provider.Store("default", _provider.New(1).Add("body", null)); + Assert.That(_provider.IsEmpty("default"), Is.False); + } + + [Test] + public void IsDirtyShouldBeFalseForNewDocuments() { + IIndexDocument doc = _provider.New(1); + Assert.That(doc.IsDirty, Is.False); + } + + + [Test] + public void IsDirtyShouldBeTrueWhenIndexIsModified() { + IIndexDocument doc; + + doc = _provider.New(1); + doc.Add("foo", "value"); + Assert.That(doc.IsDirty, Is.True); + + doc = _provider.New(1); + doc.Add("foo", false); + Assert.That(doc.IsDirty, Is.True); + + doc = _provider.New(1); + doc.Add("foo", (float)1.0); + Assert.That(doc.IsDirty, Is.True); + + doc = _provider.New(1); + doc.Add("foo", 1); + Assert.That(doc.IsDirty, Is.True); + + doc = _provider.New(1); + doc.Add("foo", DateTime.Now); + Assert.That(doc.IsDirty, Is.True); + + } } } diff --git a/src/Orchard.Core.Tests/Indexing/DefaultSearchBuilderTests.cs b/src/Orchard.Core.Tests/Indexing/DefaultSearchBuilderTests.cs index 9ffa62d6c..b28036c57 100644 --- a/src/Orchard.Core.Tests/Indexing/DefaultSearchBuilderTests.cs +++ b/src/Orchard.Core.Tests/Indexing/DefaultSearchBuilderTests.cs @@ -177,5 +177,19 @@ namespace Orchard.Tests.Indexing { Assert.That(date[0].GetDateTime("date") < date[1].GetDateTime("date"), Is.True); Assert.That(date[1].GetDateTime("date") < date[2].GetDateTime("date"), Is.True); } + + [Test] + public void ShouldEscapeSpecialChars() { + _provider.CreateIndex("default"); + _provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#")); + _provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++")); + + var cs = _searchBuilder.WithField("body", "C#").Search().ToList(); + Assert.That(cs.Count(), Is.EqualTo(2)); + + var cpp = _searchBuilder.WithField("body", "C++").Search().ToList(); + Assert.That(cpp.Count(), Is.EqualTo(2)); + + } } } diff --git a/src/Orchard.Core.Tests/Orchard.Core.Tests.csproj b/src/Orchard.Core.Tests/Orchard.Core.Tests.csproj index 307f9ed26..1d07df81b 100644 --- a/src/Orchard.Core.Tests/Orchard.Core.Tests.csproj +++ b/src/Orchard.Core.Tests/Orchard.Core.Tests.csproj @@ -109,6 +109,7 @@ + diff --git a/src/Orchard.Core.Tests/Scheduling/ScheduledTaskExecutorTests.cs b/src/Orchard.Core.Tests/Scheduling/ScheduledTaskExecutorTests.cs index e7a7955dd..485167ce4 100644 --- a/src/Orchard.Core.Tests/Scheduling/ScheduledTaskExecutorTests.cs +++ b/src/Orchard.Core.Tests/Scheduling/ScheduledTaskExecutorTests.cs @@ -4,7 +4,7 @@ using Autofac; using Moq; using NUnit.Framework; using Orchard.ContentManagement; -using Orchard.ContentManagement.MetaData.Records; +using Orchard.ContentManagement.MetaData; using Orchard.ContentManagement.Records; using Orchard.Core.Scheduling.Models; using Orchard.Core.Scheduling.Services; @@ -30,6 +30,7 @@ namespace Orchard.Core.Tests.Scheduling { builder.RegisterInstance(new Mock().Object); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterInstance(new Mock().Object); builder.RegisterType().As().Named("ScheduledTaskExecutor", typeof(IBackgroundTask)); builder.RegisterInstance(_handler).As(); @@ -39,8 +40,6 @@ namespace Orchard.Core.Tests.Scheduling { get { return new[] { typeof(ContentTypeRecord), - typeof(ContentTypePartRecord), - typeof(ContentTypePartNameRecord), typeof(ContentItemRecord), typeof(ContentItemVersionRecord), typeof(ScheduledTaskRecord), diff --git a/src/Orchard.Core.Tests/Scheduling/ScheduledTaskManagerTests.cs b/src/Orchard.Core.Tests/Scheduling/ScheduledTaskManagerTests.cs index baf3063c3..e67d666f5 100644 --- a/src/Orchard.Core.Tests/Scheduling/ScheduledTaskManagerTests.cs +++ b/src/Orchard.Core.Tests/Scheduling/ScheduledTaskManagerTests.cs @@ -5,7 +5,7 @@ using Autofac; using Moq; using NUnit.Framework; using Orchard.ContentManagement; -using Orchard.ContentManagement.MetaData.Records; +using Orchard.ContentManagement.MetaData; using Orchard.ContentManagement.Records; using Orchard.Core.Scheduling.Models; using Orchard.Core.Scheduling.Services; @@ -34,6 +34,7 @@ namespace Orchard.Core.Tests.Scheduling { builder.RegisterInstance(_mockServices.Object); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterInstance(new Mock().Object); builder.RegisterType().As(); } @@ -42,8 +43,6 @@ namespace Orchard.Core.Tests.Scheduling { get { return new[] { typeof(ContentTypeRecord), - typeof(ContentTypePartRecord), - typeof(ContentTypePartNameRecord), typeof(ContentItemRecord), typeof(ContentItemVersionRecord), typeof(ScheduledTaskRecord), diff --git a/src/Orchard.Core.Tests/Settings/Metadata/ContentDefinitionManagerTests.cs b/src/Orchard.Core.Tests/Settings/Metadata/ContentDefinitionManagerTests.cs new file mode 100644 index 000000000..440c38864 --- /dev/null +++ b/src/Orchard.Core.Tests/Settings/Metadata/ContentDefinitionManagerTests.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Autofac; +using Moq; +using NHibernate; +using NUnit.Framework; +using Orchard.ContentManagement.MetaData; +using Orchard.ContentManagement.MetaData.Builders; +using Orchard.ContentManagement.MetaData.Models; +using Orchard.ContentManagement.MetaData.Services; +using Orchard.Core.Settings.Metadata; +using Orchard.Core.Settings.Metadata.Records; +using Orchard.Data; +using Orchard.Tests; +using Orchard.Tests.Utility; + +namespace Orchard.Core.Tests.Settings.Metadata { + [TestFixture] + public class ContentDefinitionManagerTests { + private string _databaseFileName; + private ISessionFactory _sessionFactory; + private ISession _session; + private IContainer _container; + + [TestFixtureSetUp] + public void InitFixture() { + _databaseFileName = Path.GetTempFileName(); + _sessionFactory = DataUtility.CreateSessionFactory( + _databaseFileName, + typeof(ContentTypeDefinitionRecord), + typeof(ContentTypePartDefinitionRecord), + typeof(ContentPartDefinitionRecord), + typeof(ContentPartFieldDefinitionRecord), + typeof(ContentFieldDefinitionRecord) + ); + } + + [SetUp] + public void Init() { + var builder = new ContainerBuilder(); + builder.RegisterAutoMocking(); + builder.RegisterType().As(); + builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)); + builder.RegisterType(typeof(SettingsFormatter)) + .As(typeof(IMapper>)) + .As(typeof(IMapper, XElement>)); + _container = builder.Build(); + + _container.Mock() + .Setup(x => x.For(It.IsAny())) + .Returns(() => _session); + + _session = _sessionFactory.OpenSession(); + foreach (var killType in new[] { typeof(ContentTypeDefinitionRecord), typeof(ContentPartDefinitionRecord), typeof(ContentFieldDefinitionRecord) }) { + foreach (var killRecord in _session.CreateCriteria(killType).List()) { + _session.Delete(killRecord); + } + } + _session.Flush(); + } + + void ResetSession() { + _session.Flush(); + _session.Dispose(); + _session = _sessionFactory.OpenSession(); + } + + [TearDown] + public void Term() { + _session.Dispose(); + } + + [TestFixtureTearDown] + public void TermFixture() { + File.Delete(_databaseFileName); + } + + [Test] + public void NoTypesAreAvailableByDefault() { + var types = _container.Resolve().ListTypeDefinitions(); + Assert.That(types.Count(), Is.EqualTo(0)); + } + + [Test] + public void TypeRecordsAreReturned() { + var repository = _container.Resolve>(); + repository.Create(new ContentTypeDefinitionRecord { Name = "alpha" }); + repository.Create(new ContentTypeDefinitionRecord { Name = "beta" }); + ResetSession(); + var types = _container.Resolve().ListTypeDefinitions(); + Assert.That(types.Count(), Is.EqualTo(2)); + } + + [Test] + public void TypeSettingsAreParsed() { + var repository = _container.Resolve>(); + repository.Create(new ContentTypeDefinitionRecord { Name = "alpha", Settings = "" }); + ResetSession(); + var alpha = _container.Resolve().ListTypeDefinitions().Single(); + Assert.That(alpha.Settings["a"], Is.EqualTo("1")); + Assert.That(alpha.Settings["b"], Is.EqualTo("2")); + } + + [Test] + public void ContentTypesWithSettingsCanBeCreatedAndModified() { + var manager = _container.Resolve(); + manager.StoreTypeDefinition(new ContentTypeDefinitionBuilder() + .Named("alpha") + .WithSetting("a", "1") + .WithSetting("b", "2") + .Build()); + + manager.StoreTypeDefinition(new ContentTypeDefinitionBuilder() + .Named("beta") + .WithSetting("c", "3") + .WithSetting("d", "4") + .Build()); + + ResetSession(); + + var types1 = manager.ListTypeDefinitions(); + Assert.That(types1.Count(), Is.EqualTo(2)); + var alpha1 = types1.Single(t => t.Name == "alpha"); + Assert.That(alpha1.Settings["a"], Is.EqualTo("1")); + manager.StoreTypeDefinition(new ContentTypeDefinitionBuilder(alpha1).WithSetting("a", "5").Build()); + ResetSession(); + + var types2 = manager.ListTypeDefinitions(); + Assert.That(types2.Count(), Is.EqualTo(2)); + var alpha2 = types2.Single(t => t.Name == "alpha"); + Assert.That(alpha2.Settings["a"], Is.EqualTo("5")); + Assert.That(alpha2.Settings["a"], Is.EqualTo("5")); + } + + [Test] + public void StubPartDefinitionsAreCreatedWhenContentTypesAreStored() { + var manager = _container.Resolve(); + manager.StoreTypeDefinition(new ContentTypeDefinitionBuilder() + .Named("alpha") + .WithPart("foo", pb => { }) + .Build()); + + ResetSession(); + + var fooRecord = _container.Resolve>().Fetch(r => r.Name == "foo").SingleOrDefault(); + Assert.That(fooRecord, Is.Not.Null); + Assert.That(fooRecord.Name, Is.EqualTo("foo")); + + var foo = manager.GetPartDefinition("foo"); + Assert.That(foo, Is.Not.Null); + Assert.That(foo.Name, Is.EqualTo("foo")); + + var alpha = manager.GetTypeDefinition("alpha"); + Assert.That(alpha, Is.Not.Null); + Assert.That(alpha.Parts.Count(), Is.EqualTo(1)); + Assert.That(alpha.Parts.Single().PartDefinition.Name, Is.EqualTo("foo")); + } + + [Test] + public void GettingDefinitionsByNameCanReturnNullAndWillAcceptNullEmptyOrInvalidNames() { + var manager = _container.Resolve(); + Assert.That(manager.GetTypeDefinition("no such name"), Is.Null); + Assert.That(manager.GetTypeDefinition(string.Empty), Is.Null); + Assert.That(manager.GetTypeDefinition(null), Is.Null); + Assert.That(manager.GetPartDefinition("no such name"), Is.Null); + Assert.That(manager.GetPartDefinition(string.Empty), Is.Null); + Assert.That(manager.GetPartDefinition(null), Is.Null); + } + + [Test] + public void PartsAreRemovedWhenNotReferencedButPartDefinitionRemains() { + var manager = _container.Resolve(); + manager.StoreTypeDefinition( + new ContentTypeDefinitionBuilder() + .Named("alpha") + .WithPart("foo", pb => { }) + .WithPart("bar", pb => { }) + .Build()); + + AssertThatTypeHasParts("alpha","foo","bar"); + Assert.That(manager.ListPartDefinitions().Count(), Is.EqualTo(2)); + ResetSession(); + AssertThatTypeHasParts("alpha","foo","bar"); + Assert.That(manager.ListPartDefinitions().Count(), Is.EqualTo(2)); + + manager.StoreTypeDefinition( + new ContentTypeDefinitionBuilder(manager.GetTypeDefinition("alpha")) + .WithPart("frap", pb => { }) + .RemovePart("bar") + .Build()); + + AssertThatTypeHasParts("alpha","foo","frap"); + Assert.That(manager.ListPartDefinitions().Count(), Is.EqualTo(3)); + ResetSession(); + AssertThatTypeHasParts("alpha","foo","frap"); + Assert.That(manager.ListPartDefinitions().Count(), Is.EqualTo(3)); + } + + private void AssertThatTypeHasParts(string typeName, params string[] partNames) { + var type = _container.Resolve().GetTypeDefinition(typeName); + Assert.That(type, Is.Not.Null); + Assert.That(type.Parts.Count(), Is.EqualTo(partNames.Count())); + foreach(var partName in partNames) { + Assert.That(type.Parts.Select(p=>p.PartDefinition.Name), Has.Some.EqualTo(partName)); + } + } + } +} diff --git a/src/Orchard.Specs/Modules.feature b/src/Orchard.Specs/Modules.feature index 31482a274..2509b5ddd 100644 --- a/src/Orchard.Specs/Modules.feature +++ b/src/Orchard.Specs/Modules.feature @@ -7,15 +7,9 @@ Scenario: Installed modules are listed Given I have installed Orchard When I go to "admin/modules" Then I should see "

Installed Modules

" - And I should see "

Themes

" + And I should see "

Themes" And the status should be 200 OK -Scenario: Edit module shows its features - Given I have installed Orchard - When I go to "admin/modules/Edit/Orchard.Themes" - Then I should see "

Edit Module: Themes

" - And the status should be 200 OK - Scenario: Features of installed modules are listed Given I have installed Orchard When I go to "admin/modules/features" diff --git a/src/Orchard.Specs/MultiTenancy.feature b/src/Orchard.Specs/MultiTenancy.feature index c8602abb4..b6cabfd97 100644 --- a/src/Orchard.Specs/MultiTenancy.feature +++ b/src/Orchard.Specs/MultiTenancy.feature @@ -7,7 +7,7 @@ Scenario: Default site is listed Given I have installed Orchard And I have installed "Orchard.MultiTenancy" When I go to "Admin/MultiTenancy" - Then I should see "List of Site's Tenants" + Then I should see "List of Site's Tenants" And I should see "Default" And the status should be 200 OK diff --git a/src/Orchard.Tests.Modules/Users/Controllers/AdminControllerTests.cs b/src/Orchard.Tests.Modules/Users/Controllers/AdminControllerTests.cs index 54dfd37f3..5b1f3a49f 100644 --- a/src/Orchard.Tests.Modules/Users/Controllers/AdminControllerTests.cs +++ b/src/Orchard.Tests.Modules/Users/Controllers/AdminControllerTests.cs @@ -6,7 +6,6 @@ using System.Web.Routing; using Autofac; using Moq; using NUnit.Framework; -using Orchard.ContentManagement.MetaData.Records; using Orchard.Data; using Orchard.Environment; using Orchard.ContentManagement; @@ -47,8 +46,6 @@ namespace Orchard.Tests.Modules.Users.Controllers { get { return new[] { typeof(UserRecord), typeof(ContentTypeRecord), - typeof(ContentTypePartRecord), - typeof(ContentTypePartNameRecord), typeof(ContentItemRecord), typeof(ContentItemVersionRecord), }; diff --git a/src/Orchard.Tests.Modules/Users/Services/MembershipServiceTests.cs b/src/Orchard.Tests.Modules/Users/Services/MembershipServiceTests.cs index 81551167c..ea7ee2f3d 100644 --- a/src/Orchard.Tests.Modules/Users/Services/MembershipServiceTests.cs +++ b/src/Orchard.Tests.Modules/Users/Services/MembershipServiceTests.cs @@ -3,7 +3,6 @@ using System.Web.Security; using Autofac; using NHibernate; using NUnit.Framework; -using Orchard.ContentManagement.MetaData.Records; using Orchard.Data; using Orchard.ContentManagement; using Orchard.ContentManagement.Handlers; @@ -42,9 +41,7 @@ namespace Orchard.Tests.Modules.Users.Services { typeof(UserRecord), typeof(ContentItemVersionRecord), typeof(ContentItemRecord), - typeof(ContentTypeRecord), - typeof(ContentTypePartRecord), - typeof(ContentTypePartNameRecord)); + typeof(ContentTypeRecord)); } [TestFixtureTearDown] diff --git a/src/Orchard.Tests/ContentManagement/ContentQueryTests.cs b/src/Orchard.Tests/ContentManagement/ContentQueryTests.cs index 55d9960e5..031108e64 100644 --- a/src/Orchard.Tests/ContentManagement/ContentQueryTests.cs +++ b/src/Orchard.Tests/ContentManagement/ContentQueryTests.cs @@ -1,8 +1,9 @@ using System.Linq; using Autofac; +using Moq; using NHibernate; using NUnit.Framework; -using Orchard.ContentManagement.MetaData.Records; +using Orchard.ContentManagement.MetaData; using Orchard.Data; using Orchard.ContentManagement; using Orchard.ContentManagement.Handlers; @@ -28,9 +29,7 @@ namespace Orchard.Tests.ContentManagement { typeof(EpsilonRecord), typeof(ContentItemVersionRecord), typeof(ContentItemRecord), - typeof(ContentTypeRecord), - typeof(ContentTypePartRecord), - typeof(ContentTypePartNameRecord)); + typeof(ContentTypeRecord)); } [TestFixtureTearDown] @@ -47,6 +46,7 @@ namespace Orchard.Tests.ContentManagement { builder.RegisterModule(new ContentModule()); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As(); + builder.RegisterInstance(new Mock().Object); builder.RegisterType().As(); builder.RegisterType().As(); diff --git a/src/Orchard.Tests/ContentManagement/ContentTypeMetaDataTests.cs b/src/Orchard.Tests/ContentManagement/ContentTypeMetaDataTests.cs deleted file mode 100644 index 94a31ebba..000000000 --- a/src/Orchard.Tests/ContentManagement/ContentTypeMetaDataTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Autofac; -using NHibernate; -using NUnit.Framework; -using Orchard.ContentManagement; -using Orchard.ContentManagement.MetaData.Records; -using Orchard.ContentManagement.MetaData.Services; -using Orchard.ContentManagement.Records; -using Orchard.Data; - - -namespace Orchard.Tests.ContentManagement{ - [TestFixture] - public class ContentTypeMetaDataTests - { - private IContainer _container; - private ISessionFactory _sessionFactory; - private ISession _session; - - [TestFixtureSetUp] - public void InitFixture() - { - var databaseFileName = System.IO.Path.GetTempFileName(); - _sessionFactory = DataUtility.CreateSessionFactory( - databaseFileName, - typeof(ContentTypeRecord), - typeof(ContentItemRecord), - typeof(ContentTypePartRecord), - typeof(ContentTypePartNameRecord), - typeof(ContentItemVersionRecord)); - } - - [TestFixtureTearDown] - public void TermFixture() - { - - } - - [SetUp] - public void Init() - { - var builder = new ContainerBuilder(); - builder.RegisterType().As(); - builder.RegisterType().As(); - builder.RegisterType().As(); - builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)); - _session = _sessionFactory.OpenSession(); - builder.RegisterInstance(new DefaultContentManagerTests.TestSessionLocator(_session)).As(); - - _container = builder.Build(); - } - - [Test] - public void MapandUnMapContentTypeToContentPart() - { - var contentTypeService = _container.Resolve(); - contentTypeService.MapContentTypeToContentPart("foo", "bar"); - Assert.IsTrue(contentTypeService.ValidateContentTypeToContentPartMapping("foo","bar"),"Content Type not successfully mapped"); - contentTypeService.UnMapContentTypeToContentPart("foo", "bar"); - Assert.IsFalse(contentTypeService.ValidateContentTypeToContentPartMapping("foo", "bar"), "Content Type mapping not successfully deleted"); - } - } -} diff --git a/src/Orchard.Tests/ContentManagement/DefaultContentManagerTests.cs b/src/Orchard.Tests/ContentManagement/DefaultContentManagerTests.cs index 6341d4f4c..5a0397f76 100644 --- a/src/Orchard.Tests/ContentManagement/DefaultContentManagerTests.cs +++ b/src/Orchard.Tests/ContentManagement/DefaultContentManagerTests.cs @@ -2,9 +2,11 @@ using System.Diagnostics; using System.Linq; using Autofac; +using Moq; using NHibernate; using NUnit.Framework; -using Orchard.ContentManagement.MetaData.Records; +using Orchard.ContentManagement.MetaData; +using Orchard.ContentManagement.MetaData.Builders; using Orchard.Data; using Orchard.ContentManagement; using Orchard.ContentManagement.Handlers; @@ -19,6 +21,7 @@ namespace Orchard.Tests.ContentManagement { private IContentManager _manager; private ISessionFactory _sessionFactory; private ISession _session; + private Mock _contentDefinitionManager; [TestFixtureSetUp] public void InitFixture() { @@ -26,8 +29,6 @@ namespace Orchard.Tests.ContentManagement { _sessionFactory = DataUtility.CreateSessionFactory( databaseFileName, typeof(ContentTypeRecord), - typeof(ContentTypePartRecord), - typeof(ContentTypePartNameRecord), typeof(ContentItemRecord), typeof(ContentItemVersionRecord), typeof(GammaRecord), @@ -42,10 +43,12 @@ namespace Orchard.Tests.ContentManagement { [SetUp] public void Init() { + _contentDefinitionManager = new Mock(); + var builder = new ContainerBuilder(); - //builder.RegisterModule(new ImplicitCollectionSupportModule()); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterInstance(_contentDefinitionManager.Object); builder.RegisterType().As(); builder.RegisterType().As(); @@ -462,6 +465,40 @@ namespace Orchard.Tests.ContentManagement { Assert.That(gammas[3].Version, Is.EqualTo(4)); } + [Test] + public void EmptyTypeDefinitionShouldBeCreatedIfNotAlreadyDefined() { + var contentItem = _manager.New("no-such-type"); + Assert.That(contentItem.ContentType, Is.EqualTo("no-such-type")); + Assert.That(contentItem.TypeDefinition, Is.Not.Null); + Assert.That(contentItem.TypeDefinition.Name, Is.EqualTo("no-such-type")); + Assert.That(contentItem.TypeDefinition.Settings.Count(), Is.EqualTo(0)); + Assert.That(contentItem.TypeDefinition.Parts.Count(), Is.EqualTo(0)); + } + + + [Test] + public void ExistingTypeAndPartDefinitionShouldBeUsed() { + var alphaType = new ContentTypeDefinitionBuilder() + .Named("alpha") + .WithSetting("x", "1") + .WithPart("foo") + .WithPart("Flavored", part => part.WithSetting("spin", "clockwise")) + .Build(); + + _contentDefinitionManager + .Setup(x => x.GetTypeDefinition("alpha")) + .Returns(alphaType); + + var contentItem = _manager.New("alpha"); + Assert.That(contentItem.ContentType, Is.EqualTo("alpha")); + Assert.That(contentItem.TypeDefinition, Is.Not.Null); + Assert.That(contentItem.TypeDefinition, Is.SameAs(alphaType)); + + var flavored = contentItem.As(); + Assert.That(flavored, Is.Not.Null); + Assert.That(flavored.TypePartDefinition, Is.Not.Null); + Assert.That(flavored.TypePartDefinition.Settings["spin"], Is.EqualTo("clockwise")); + } } } diff --git a/src/Orchard.Tests/ContentManagement/Handlers/ContentHandlerTests.cs b/src/Orchard.Tests/ContentManagement/Handlers/ContentHandlerTests.cs index c7d7ae087..3f7b302e0 100644 --- a/src/Orchard.Tests/ContentManagement/Handlers/ContentHandlerTests.cs +++ b/src/Orchard.Tests/ContentManagement/Handlers/ContentHandlerTests.cs @@ -1,6 +1,7 @@ using NUnit.Framework; using Orchard.ContentManagement; using Orchard.ContentManagement.Handlers; +using Orchard.ContentManagement.MetaData.Builders; namespace Orchard.Tests.ContentManagement.Handlers { @@ -22,7 +23,7 @@ namespace Orchard.Tests.ContentManagement.Handlers { public void PartShouldBeAddedBasedOnSimplePredicate() { var modelDriver = new TestModelHandler(); - var builder = new ContentItemBuilder("testing"); + var builder = new ContentItemBuilder(new ContentTypeDefinitionBuilder().Named("testing").Build()); ((IContentHandler)modelDriver).Activating(new ActivatingContentContext { Builder = builder, ContentType = "testing" }); var model = builder.Build(); Assert.That(model.Is(), Is.True); diff --git a/src/Orchard.Tests/ContentManagement/Handlers/ModelBuilderTests.cs b/src/Orchard.Tests/ContentManagement/Handlers/ModelBuilderTests.cs index ea19ecc5c..ab4aada7e 100644 --- a/src/Orchard.Tests/ContentManagement/Handlers/ModelBuilderTests.cs +++ b/src/Orchard.Tests/ContentManagement/Handlers/ModelBuilderTests.cs @@ -5,6 +5,7 @@ using System.Text; using NUnit.Framework; using Orchard.ContentManagement; using Orchard.ContentManagement.Handlers; +using Orchard.ContentManagement.MetaData.Builders; using Orchard.Tests.ContentManagement.Models; namespace Orchard.Tests.ContentManagement.Handlers { @@ -12,21 +13,21 @@ namespace Orchard.Tests.ContentManagement.Handlers { public class ModelBuilderTests { [Test] public void BuilderShouldReturnWorkingModelWithTypeAndId() { - var builder = new ContentItemBuilder("foo"); + var builder = new ContentItemBuilder(new ContentTypeDefinitionBuilder().Named("foo").Build()); var model = builder.Build(); Assert.That(model.ContentType, Is.EqualTo("foo")); } [Test] public void IdShouldDefaultToZero() { - var builder = new ContentItemBuilder("foo"); + var builder = new ContentItemBuilder(new ContentTypeDefinitionBuilder().Named("foo").Build()); var model = builder.Build(); Assert.That(model.Id, Is.EqualTo(0)); } [Test] public void WeldShouldAddPartToModel() { - var builder = new ContentItemBuilder("foo"); + var builder = new ContentItemBuilder(new ContentTypeDefinitionBuilder().Named("foo").Build()); builder.Weld(); var model = builder.Build(); diff --git a/src/Orchard.Tests/ContentManagement/MetaData/Builders/ContentTypeDefinitionBuilderTests.cs b/src/Orchard.Tests/ContentManagement/MetaData/Builders/ContentTypeDefinitionBuilderTests.cs new file mode 100644 index 000000000..be8549f18 --- /dev/null +++ b/src/Orchard.Tests/ContentManagement/MetaData/Builders/ContentTypeDefinitionBuilderTests.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Orchard.ContentManagement.MetaData.Builders; + +namespace Orchard.Tests.ContentManagement.MetaData.Builders { + [TestFixture] + public class ContentTypeDefinitionBuilderTests { + [Test] + public void ContentTypeNameAndSettingsFromScratch() { + var contentTypeDefinition = new ContentTypeDefinitionBuilder() + .Named("alpha") + .WithSetting("a", "1") + .WithSetting("b", "2") + .Build(); + Assert.That(contentTypeDefinition.Name, Is.EqualTo("alpha")); + Assert.That(contentTypeDefinition.Settings.Count(), Is.EqualTo(2)); + Assert.That(contentTypeDefinition.Settings["a"], Is.EqualTo("1")); + Assert.That(contentTypeDefinition.Settings["b"], Is.EqualTo("2")); + } + + [Test] + public void ContentRebuildWithoutModification() { + var contentTypeDefinition1 = new ContentTypeDefinitionBuilder() + .Named("alpha") + .WithSetting("a", "1") + .WithSetting("b", "2") + .Build(); + var contentTypeDefinition2 = new ContentTypeDefinitionBuilder(contentTypeDefinition1) + .Build(); + Assert.That(contentTypeDefinition1, Is.Not.SameAs(contentTypeDefinition2)); + Assert.That(contentTypeDefinition2.Name, Is.EqualTo("alpha")); + Assert.That(contentTypeDefinition2.Settings.Count(), Is.EqualTo(2)); + Assert.That(contentTypeDefinition2.Settings["a"], Is.EqualTo("1")); + Assert.That(contentTypeDefinition2.Settings["b"], Is.EqualTo("2")); + } + + [Test] + public void ContentRebuildWithModification() { + var contentTypeDefinition1 = new ContentTypeDefinitionBuilder() + .Named("alpha") + .WithSetting("a", "1") + .WithSetting("b", "2") + .Build(); + var contentTypeDefinition2 = new ContentTypeDefinitionBuilder(contentTypeDefinition1) + .Named("beta") + .WithSetting("b", "22") + .WithSetting("c", "3") + .Build(); + Assert.That(contentTypeDefinition1, Is.Not.SameAs(contentTypeDefinition2)); + Assert.That(contentTypeDefinition1.Name, Is.EqualTo("alpha")); + Assert.That(contentTypeDefinition1.Settings.Count(), Is.EqualTo(2)); + Assert.That(contentTypeDefinition1.Settings["a"], Is.EqualTo("1")); + Assert.That(contentTypeDefinition1.Settings["b"], Is.EqualTo("2")); + Assert.That(contentTypeDefinition2.Name, Is.EqualTo("beta")); + Assert.That(contentTypeDefinition2.Settings.Count(), Is.EqualTo(3)); + Assert.That(contentTypeDefinition2.Settings["a"], Is.EqualTo("1")); + Assert.That(contentTypeDefinition2.Settings["b"], Is.EqualTo("22")); + Assert.That(contentTypeDefinition2.Settings["c"], Is.EqualTo("3")); + } + + [Test] + public void AddingPartWithSettings() { + var contentTypeDefinition = new ContentTypeDefinitionBuilder() + .Named("alpha") + .WithSetting("a", "1") + .WithSetting("b", "2") + .WithPart("foo", pb => pb.WithSetting("x", "10").WithSetting("y", "11")) + .Build(); + + Assert.That(contentTypeDefinition.Name, Is.EqualTo("alpha")); + Assert.That(contentTypeDefinition.Parts.Count(), Is.EqualTo(1)); + Assert.That(contentTypeDefinition.Parts.Single().PartDefinition.Name, Is.EqualTo("foo")); + Assert.That(contentTypeDefinition.Parts.Single().Settings.Count(), Is.EqualTo(2)); + Assert.That(contentTypeDefinition.Parts.Single().Settings["x"], Is.EqualTo("10")); + Assert.That(contentTypeDefinition.Parts.Single().Settings["y"], Is.EqualTo("11")); + } + + [Test] + public void CanAlterPartSettingsByNameDuringBuild() { + var contentTypeDefinition = new ContentTypeDefinitionBuilder() + .Named("alpha") + .WithSetting("a", "1") + .WithSetting("b", "2") + .WithPart("foo", pb => pb.WithSetting("x", "10")) + .WithPart("foo", pb => pb.WithSetting("y", "11")) + .Build(); + + Assert.That(contentTypeDefinition.Name, Is.EqualTo("alpha")); + Assert.That(contentTypeDefinition.Parts.Count(), Is.EqualTo(1)); + Assert.That(contentTypeDefinition.Parts.Single().PartDefinition.Name, Is.EqualTo("foo")); + Assert.That(contentTypeDefinition.Parts.Single().Settings.Count(), Is.EqualTo(2)); + Assert.That(contentTypeDefinition.Parts.Single().Settings["x"], Is.EqualTo("10")); + Assert.That(contentTypeDefinition.Parts.Single().Settings["y"], Is.EqualTo("11")); + } + + [Test] + public void CanAlterPartSettingsByNameDuringRebuild() { + var contentTypeDefinition1 = new ContentTypeDefinitionBuilder() + .Named("alpha") + .WithPart("foo", pb => pb.WithSetting("x", "10").WithSetting("y", "11")) + .Build(); + + var contentTypeDefinition2 = new ContentTypeDefinitionBuilder(contentTypeDefinition1) + .WithPart("foo", pb => pb.WithSetting("x", "12").WithSetting("z", "13")) + .Build(); + + Assert.That(contentTypeDefinition1.Name, Is.EqualTo("alpha")); + Assert.That(contentTypeDefinition1.Parts.Count(), Is.EqualTo(1)); + Assert.That(contentTypeDefinition1.Parts.Single().PartDefinition.Name, Is.EqualTo("foo")); + Assert.That(contentTypeDefinition1.Parts.Single().Settings.Count(), Is.EqualTo(2)); + Assert.That(contentTypeDefinition1.Parts.Single().Settings["x"], Is.EqualTo("10")); + Assert.That(contentTypeDefinition1.Parts.Single().Settings["y"], Is.EqualTo("11")); + Assert.That(contentTypeDefinition2.Name, Is.EqualTo("alpha")); + Assert.That(contentTypeDefinition2.Parts.Count(), Is.EqualTo(1)); + Assert.That(contentTypeDefinition2.Parts.Single().PartDefinition.Name, Is.EqualTo("foo")); + Assert.That(contentTypeDefinition2.Parts.Single().Settings.Count(), Is.EqualTo(3)); + Assert.That(contentTypeDefinition2.Parts.Single().Settings["x"], Is.EqualTo("12")); + Assert.That(contentTypeDefinition2.Parts.Single().Settings["y"], Is.EqualTo("11")); + Assert.That(contentTypeDefinition2.Parts.Single().Settings["z"], Is.EqualTo("13")); + } + + [Test, IgnoreAttribute("Merging not yet implemented")] + public void ContentMergeOverlaysSettings() { + Assert.Fail(); + } + } +} diff --git a/src/Orchard.Tests/ContentManagement/MetaData/Services/ContentDefinitionReaderTests.cs b/src/Orchard.Tests/ContentManagement/MetaData/Services/ContentDefinitionReaderTests.cs new file mode 100644 index 000000000..07a363a4d --- /dev/null +++ b/src/Orchard.Tests/ContentManagement/MetaData/Services/ContentDefinitionReaderTests.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using NUnit.Framework; +using Orchard.ContentManagement.MetaData; +using Orchard.ContentManagement.MetaData.Builders; +using Orchard.ContentManagement.MetaData.Services; + +namespace Orchard.Tests.ContentManagement.MetaData.Services { + [TestFixture] + public class ContentDefinitionReaderTests { + private IContentDefinitionReader _reader; + + [SetUp] + public void Init() { + _reader = new ContentDefinitionReader(new SettingsFormatter()); + } + + [Test] + public void ReadingElementSetsName() { + var builder = new ContentTypeDefinitionBuilder(); + _reader.Merge(new XElement("foo"), builder); + var type = builder.Build(); + Assert.That(type.Name, Is.EqualTo("foo")); + } + + + [Test] + public void AttributesAreAppliedAsSettings() { + var builder = new ContentTypeDefinitionBuilder(); + _reader.Merge(new XElement("foo", new XAttribute("x", "1")), builder); + var type = builder.Build(); + Assert.That(type.Settings["x"], Is.EqualTo("1")); + } + + [Test] + public void ChildElementsAreAddedAsPartsWithSettings() { + var builder = new ContentTypeDefinitionBuilder(); + _reader.Merge(new XElement("foo", new XElement("bar", new XAttribute("y", "2"))), builder); + var type = builder.Build(); + Assert.That(type.Parts.Single().PartDefinition.Name, Is.EqualTo("bar")); + Assert.That(type.Parts.Single().Settings["y"], Is.EqualTo("2")); + } + + [Test, Ignore("Parts can be removed by name")] + public void PartsCanBeRemovedByNameWhenImporting() { + Assert.Fail(); + } + } +} diff --git a/src/Orchard.Tests/ContentManagement/MetaData/Services/ContentDefinitionWriterTests.cs b/src/Orchard.Tests/ContentManagement/MetaData/Services/ContentDefinitionWriterTests.cs new file mode 100644 index 000000000..6182f7b25 --- /dev/null +++ b/src/Orchard.Tests/ContentManagement/MetaData/Services/ContentDefinitionWriterTests.cs @@ -0,0 +1,55 @@ +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using NUnit.Framework; +using Orchard.ContentManagement.MetaData.Builders; +using Orchard.ContentManagement.MetaData.Services; + +namespace Orchard.Tests.ContentManagement.MetaData.Services { + [TestFixture] + public class ContentDefinitionWriterTests { + private ContentDefinitionWriter _writer; + + [SetUp] + public void Init() { + _writer = new ContentDefinitionWriter(new SettingsFormatter()); + } + + [Test] + public void CreatesElementWithEncodedContentTypeName() { + var alphaInfoset = _writer.Export(new ContentTypeDefinitionBuilder().Named("alpha").Build()); + var betaInfoset = _writer.Export(new ContentTypeDefinitionBuilder().Named(":beta").Build()); + var gammaInfoset = _writer.Export(new ContentTypeDefinitionBuilder().Named(" g a m m a ").Build()); + var deltaInfoset = _writer.Export(new ContentTypeDefinitionBuilder().Named("del\r\nta").Build()); + + Assert.That(XmlConvert.DecodeName(alphaInfoset.Name.LocalName), Is.EqualTo("alpha")); + Assert.That(XmlConvert.DecodeName(betaInfoset.Name.LocalName), Is.EqualTo(":beta")); + Assert.That(XmlConvert.DecodeName(gammaInfoset.Name.LocalName), Is.EqualTo(" g a m m a ")); + Assert.That(XmlConvert.DecodeName(deltaInfoset.Name.LocalName), Is.EqualTo("del\r\nta")); + } + + [Test] + public void ChildElementsArePartNames() { + var alphaInfoset = _writer.Export(new ContentTypeDefinitionBuilder().Named("alpha").WithPart(":beta").WithPart("del\r\nta").Build()); + + Assert.That(XmlConvert.DecodeName(alphaInfoset.Name.LocalName), Is.EqualTo("alpha")); + Assert.That(alphaInfoset.Elements().Count(), Is.EqualTo(2)); + Assert.That(alphaInfoset.Elements().Select(elt => elt.Name.LocalName), Has.Some.EqualTo(XmlConvert.EncodeLocalName(":beta"))); + Assert.That(alphaInfoset.Elements().Select(elt => elt.Name.LocalName), Has.Some.EqualTo(XmlConvert.EncodeLocalName("del\r\nta"))); + } + + [Test] + public void TypeAndTypePartSettingsAreAttributes() { + + var alpha = new ContentTypeDefinitionBuilder() + .Named("alpha") + .WithSetting("x", "1") + .WithPart("beta", part => part.WithSetting(" y ", "2")) + .Build(); + + var alphaInfoset = _writer.Export(alpha); + Assert.That(alphaInfoset.Attributes("x").Single().Value, Is.EqualTo("1")); + Assert.That(alphaInfoset.Elements("beta").Attributes(XmlConvert.EncodeLocalName(" y ")).Single().Value, Is.EqualTo("2")); + } + } +} diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index 258137642..4b9753276 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -148,12 +148,14 @@ Code - Code + + + diff --git a/src/Orchard.Web/Core/App_Data/Localization/en-US/orchard.core.po b/src/Orchard.Web/Core/App_Data/Localization/en-US/orchard.core.po index 7c354db9c..a459214e8 100644 --- a/src/Orchard.Web/Core/App_Data/Localization/en-US/orchard.core.po +++ b/src/Orchard.Web/Core/App_Data/Localization/en-US/orchard.core.po @@ -23,6 +23,21 @@ msgstr "[more]" msgid "Welcome to Orchard" msgstr "Welcome to Orchard" +#: ~/Core/Dashboard/Views/Admin/Index.ascx +#| msgid : "The Orchard Team" +msgid "The Orchard Team" +msgstr "The Orchard Team" + +#: ~/Core/Dashboard/Views/Admin/Index.ascx +#| msgid : "This is the place where you can manage your web site, its appearance and its contents. Please take a moment to explore the different menu items on the left of the screen to familiarize yourself with the features of the application. For example, try to change the theme through the “Manage Themes” menu entry. You can also create new pages and manage existing ones through the “Manage Pages” menu entry or create blogs through “Manage Blogs”." +msgid "This is the place where you can manage your web site, its appearance and its contents. Please take a moment to explore the different menu items on the left of the screen to familiarize yourself with the features of the application. For example, try to change the theme through the “Manage Themes” menu entry. You can also create new pages and manage existing ones through the “Manage Pages” menu entry or create blogs through “Manage Blogs”." +msgstr "This is the place where you can manage your web site, its appearance and its contents. Please take a moment to explore the different menu items on the left of the screen to familiarize yourself with the features of the application. For example, try to change the theme through the “Manage Themes” menu entry. You can also create new pages and manage existing ones through the “Manage Pages” menu entry or create blogs through “Manage Blogs”." + +#: ~/Core/Dashboard/Views/Admin/Index.ascx +#| msgid : "Have fun!" +msgid "Have fun!" +msgstr "Have fun!" + #: ~/Core/Navigation/Controllers/AdminController.cs #| msgid : "Not allowed to manage the main menu" msgid "Not allowed to manage the main menu" @@ -278,6 +293,16 @@ msgstr "Edit Post" msgid "Discard Draft" msgstr "Discard Draft" +#: ~/Modules/Orchard.Comments/AdminMenu.cs +#| msgid : "Comments" +msgid "Comments" +msgstr "Comments" + +#: ~/Modules/Orchard.Comments/AdminMenu.cs +#| msgid : "Manage Comments" +msgid "Manage Comments" +msgstr "Manage Comments" + #: ~/Modules/Orchard.Comments/Controllers/AdminController.cs #| msgid : "Listing comments failed: " msgid "Listing comments failed: " @@ -438,11 +463,36 @@ msgstr "Manage Comments" msgid "log on" msgstr "log on" +#: ~/Modules/Orchard.Comments/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Add a Comment" +msgid "Add a Comment" +msgstr "Add a Comment" + +#: ~/Themes/Contoso/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "You must {0} to comment." +msgid "You must {0} to comment." +msgstr "You must {0} to comment." + +#: ~/Themes/Corporate/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Comments have been disabled for this content." +msgid "Comments have been disabled for this content." +msgstr "Comments have been disabled for this content." + #: ~/Modules/Orchard.Comments/Views/DisplayTemplates/Parts/Comments.HasComments.ascx #| msgid : "Hi, {0}!" msgid "Hi, {0}!" msgstr "Hi, {0}!" +#: ~/Modules/Orchard.Comments/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Comment" +msgid "Comment" +msgstr "Comment" + +#: ~/Modules/Orchard.Comments/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Submit Comment" +msgid "Submit Comment" +msgstr "Submit Comment" + #: ~/Modules/Orchard.Comments/Views/EditorTemplates/Parts/Comments.HasComments.ascx #| msgid : "Comments are shown. Existing comments are displayed." msgid "Comments are shown. Existing comments are displayed." @@ -1748,11 +1798,41 @@ msgstr "User deleted" msgid "Access Denied" msgstr "Access Denied" +#: ~/Modules/Orchard.Users/Views/Account/AccessDenied.ascx +#| msgid : "You do not have permission to complete your request." +msgid "You do not have permission to complete your request." +msgstr "You do not have permission to complete your request." + #: ~/Modules/Orchard.Users/Views/Account/ChangePassword.ascx #| msgid : "Change Password" msgid "Change Password" msgstr "Change Password" +#: ~/Modules/Orchard.Users/Views/Account/ChangePassword.ascx +#| msgid : "Use the form below to change your password." +msgid "Use the form below to change your password." +msgstr "Use the form below to change your password." + +#: ~/Modules/Orchard.Users/Views/Account/ChangePassword.ascx +#| msgid : "New passwords are required to be a minimum of {0} characters in length." +msgid "New passwords are required to be a minimum of {0} characters in length." +msgstr "New passwords are required to be a minimum of {0} characters in length." + +#: ~/Modules/Orchard.Users/Views/Account/ChangePassword.ascx +#| msgid : "Current password:" +msgid "Current password:" +msgstr "Current password:" + +#: ~/Modules/Orchard.Users/Views/Account/ChangePassword.ascx +#| msgid : "New password:" +msgid "New password:" +msgstr "New password:" + +#: ~/Modules/Orchard.Users/Views/Account/ChangePassword.ascx +#| msgid : "Confirm new password:" +msgid "Confirm new password:" +msgstr "Confirm new password:" + #: ~/Modules/Orchard.Users/Views/Account/ChangePassword.ascx #| msgid : "Password change was unsuccessful. Please correct the errors and try again." msgid "Password change was unsuccessful. Please correct the errors and try again." @@ -1763,16 +1843,81 @@ msgstr "Password change was unsuccessful. Please correct the errors and try agai msgid "Change Password" msgstr "Change Password" +#: ~/Modules/Orchard.Users/Views/Account/ChangePasswordSuccess.ascx +#| msgid : "Your password has been changed successfully." +msgid "Your password has been changed successfully." +msgstr "Your password has been changed successfully." + #: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx #| msgid : "Login was unsuccessful. Please correct the errors and try again." msgid "Login was unsuccessful. Please correct the errors and try again." msgstr "Login was unsuccessful. Please correct the errors and try again." +#: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx +#| msgid : "Please enter your username and password." +msgid "Please enter your username and password." +msgstr "Please enter your username and password." + +#: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx +#| msgid : "Register" +msgid "Register" +msgstr "Register" + +#: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx +#| msgid : " if you don't have an account." +msgid " if you don't have an account." +msgstr " if you don't have an account." + +#: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx +#| msgid : "Account Information" +msgid "Account Information" +msgstr "Account Information" + +#: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx +#| msgid : "Username or Email:" +msgid "Username or Email:" +msgstr "Username or Email:" + +#: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx +#| msgid : "Password:" +msgid "Password:" +msgstr "Password:" + +#: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx +#| msgid : "Remember me?" +msgid "Remember me?" +msgstr "Remember me?" + #: ~/Modules/Orchard.Users/Views/Account/Register.ascx #| msgid : "Create a New Account" msgid "Create a New Account" msgstr "Create a New Account" +#: ~/Modules/Orchard.Users/Views/Account/Register.ascx +#| msgid : "Use the form below to create a new account." +msgid "Use the form below to create a new account." +msgstr "Use the form below to create a new account." + +#: ~/Modules/Orchard.Users/Views/Account/Register.ascx +#| msgid : "Passwords are required to be a minimum of {0} characters in length." +msgid "Passwords are required to be a minimum of {0} characters in length." +msgstr "Passwords are required to be a minimum of {0} characters in length." + +#: ~/Modules/Orchard.Users/Views/Account/Register.ascx +#| msgid : "Username:" +msgid "Username:" +msgstr "Username:" + +#: ~/Modules/Orchard.Users/Views/Account/Register.ascx +#| msgid : "Email:" +msgid "Email:" +msgstr "Email: + +#: ~/Modules/Orchard.Users/Views/Account/Register.ascx +#| msgid : "Confirm password:" +msgid "Confirm password:" +msgstr "Confirm password:" + #: ~/Modules/Orchard.Users/Views/Account/Register.ascx #| msgid : "Account creation was unsuccessful. Please correct the errors and try again." msgid "Account creation was unsuccessful. Please correct the errors and try again." @@ -1823,6 +1968,41 @@ msgstr "nobody(?)" msgid "Login was unsuccessful. Please correct the errors and try again." msgstr "Login was unsuccessful. Please correct the errors and try again." +#: ~/Themes/Contoso/Views/LogOn.ascx +#| msgid : "Please enter your username and password." +msgid "Please enter your username and password." +msgstr "Please enter your username and password." + +#: ~/Themes/Contoso/Views/LogOn.ascx +#| msgid : "Register" +msgid "Register" +msgstr "Register" + +#: ~/Themes/Contoso/Views/LogOn.ascx +#| msgid : " if you don't have an account." +msgid " if you don't have an account." +msgstr " if you don't have an account." + +#: ~/Themes/Contoso/Views/LogOn.ascx +#| msgid : "Account Information" +msgid "Account Information" +msgstr "Account Information" + +#: ~/Themes/Contoso/Views/LogOn.ascx +#| msgid : "Username or Email:" +msgid "Username or Email:" +msgstr "Username or Email:" + +#: ~/Themes/Contoso/Views/LogOn.ascx +#| msgid : "Password:" +msgid "Password:" +msgstr "Password:" + +#: ~/Themes/Contoso/Views/LogOn.ascx +#| msgid : "Remember me?" +msgid "Remember me?" +msgstr "Remember me?" + #: ~/Themes/Contoso/Views/User.ascx #| msgid : "Log Off" msgid "Log Off" @@ -1848,6 +2028,21 @@ msgstr "Archives" msgid "log on" msgstr "log on" +#: ~/Themes/Contoso/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Add a Comment" +msgid "Add a Comment" +msgstr "Add a Comment" + +#: ~/Themes/Contoso/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "You must {0} to comment." +msgid "You must {0} to comment." +msgstr "You must {0} to comment." + +#: ~/Themes/Corporate/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Comments have been disabled for this content." +msgid "Comments have been disabled for this content." +msgstr "Comments have been disabled for this content." + #: ~/Themes/Contoso/Views/DisplayTemplates/Parts/Comments.HasComments.ascx #| msgid : "Hi, {0}!" msgid "Hi, {0}!" @@ -1863,6 +2058,41 @@ msgstr "nobody(?)" msgid "Login was unsuccessful. Please correct the errors and try again." msgstr "Login was unsuccessful. Please correct the errors and try again." +#: ~/Themes/Corporate/Views/LogOn.ascx +#| msgid : "Please enter your username and password." +msgid "Please enter your username and password." +msgstr "Please enter your username and password." + +#: ~/Themes/Corporate/Views/LogOn.ascx +#| msgid : "Register" +msgid "Register" +msgstr "Register" + +#: ~/Themes/Corporate/Views/LogOn.ascx +#| msgid : " if you don't have an account." +msgid " if you don't have an account." +msgstr " if you don't have an account." + +#: ~/Themes/Corporate/Views/LogOn.ascx +#| msgid : "Account Information" +msgid "Account Information" +msgstr "Account Information" + +#: ~/Themes/Corporate/Views/LogOn.ascx +#| msgid : "Username or Email:" +msgid "Username or Email:" +msgstr "Username or Email:" + +#: ~/Themes/Corporate/Views/LogOn.ascx +#| msgid : "Password:" +msgid "Password:" +msgstr "Password:" + +#: ~/Themes/Corporate/Views/LogOn.ascx +#| msgid : "Remember me?" +msgid "Remember me?" +msgstr "Remember me?" + #: ~/Themes/Corporate/Views/User.ascx #| msgid : "Log Off" msgid "Log Off" @@ -1893,6 +2123,21 @@ msgstr "log on" msgid "Hi, {0}!" msgstr "Hi, {0}!" +#: ~/Themes/Corporate/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Add a Comment" +msgid "Add a Comment" +msgstr "Add a Comment" + +#: ~/Themes/Corporate/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "You must {0} to comment." +msgid "You must {0} to comment." +msgstr "You must {0} to comment." + +#: ~/Themes/Corporate/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Comments have been disabled for this content." +msgid "Comments have been disabled for this content." +msgstr "Comments have been disabled for this content." + #: ~/Themes/Corporate/Views/DisplayTemplates/Parts/Pages.Page.Metadata.ascx #| msgid : "nobody(?)" msgid "nobody(?)" @@ -1918,6 +2163,11 @@ msgstr "Your Site" msgid "Logout" msgstr "Logout" +#: ~/Themes/TheAdmin/Views/User.ascx +#| msgid : "User:" +msgid "User:" +msgstr "User:" + #: ~/Commands/DefaultOrchardCommandHandler.cs #| msgid : "Switch was not found: " msgid "Switch was not found: " diff --git a/src/Orchard.Web/Core/App_Data/Localization/fr-FR/orchard.core.po b/src/Orchard.Web/Core/App_Data/Localization/fr-FR/orchard.core.po index a469a4511..df5797be4 100644 --- a/src/Orchard.Web/Core/App_Data/Localization/fr-FR/orchard.core.po +++ b/src/Orchard.Web/Core/App_Data/Localization/fr-FR/orchard.core.po @@ -23,6 +23,21 @@ msgstr "[En voir plus]" msgid "Welcome to Orchard" msgstr "Bienvenue dans Orchard" +#: ~/Core/Dashboard/Views/Admin/Index.ascx +#| msgid : "The Orchard Team" +msgid "The Orchard Team" +msgstr "L'équipe Orchard" + +#: ~/Core/Dashboard/Views/Admin/Index.ascx +#| msgid : "This is the place where you can manage your web site, its appearance and its contents. Please take a moment to explore the different menu items on the left of the screen to familiarize yourself with the features of the application. For example, try to change the theme through the “Manage Themes” menu entry. You can also create new pages and manage existing ones through the “Manage Pages” menu entry or create blogs through “Manage Blogs”." +msgid "This is the place where you can manage your web site, its appearance and its contents. Please take a moment to explore the different menu items on the left of the screen to familiarize yourself with the features of the application. For example, try to change the theme through the “Manage Themes” menu entry. You can also create new pages and manage existing ones through the “Manage Pages” menu entry or create blogs through “Manage Blogs”." +msgstr "Ceci est l'endroit où vous pouvez gérer votre site, son apparence et son contenu. Veuillez passer un instant à explorer le menu à gauche de l'écran pour vous familiariser avec les différents aspects de l'application. Par exemple, vous pouvez changer le thème en utilisant “Gérer les thèmes”. Vous pouvez également créer de nouvelles pages et gérer les pages existantes via “Gérer les pages” ou bien créer de nouveaux blogs via “Gérer les blogs”." + +#: ~/Core/Dashboard/Views/Admin/Index.ascx +#| msgid : "Have fun!" +msgid "Have fun!" +msgstr "Amusez-vous bien!" + #: ~/Core/Navigation/Controllers/AdminController.cs #| msgid : "Not allowed to manage the main menu" msgid "Not allowed to manage the main menu" @@ -278,6 +293,16 @@ msgstr "Modifier un billet" msgid "Discard Draft" msgstr "Effacer le brouillon" +#: ~/Modules/Orchard.Comments/AdminMenu.cs +#| msgid : "Comments" +msgid "Comments" +msgstr "Commentaires" + +#: ~/Modules/Orchard.Comments/AdminMenu.cs +#| msgid : "Manage Comments" +msgid "Manage Comments" +msgstr "Gérer les Commentaires" + #: ~/Modules/Orchard.Comments/Controllers/AdminController.cs #| msgid : "Listing comments failed: " msgid "Listing comments failed: " @@ -436,13 +461,38 @@ msgstr "Gérer les commentaires" #: ~/Modules/Orchard.Comments/Views/DisplayTemplates/Parts/Comments.HasComments.ascx #| msgid : "log on" msgid "log on" -msgstr "S'authentifier" +msgstr "s'authentifier" + +#: ~/Modules/Orchard.Comments/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Add a Comment" +msgid "Add a Comment" +msgstr "Ajouter un commentaire" + +#: ~/Modules/Orchard.Comments/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "You must {0} to comment." +msgid "You must {0} to comment." +msgstr "Vous devez {0} pour ajouter un commentaire." + +#: ~/Modules/Orchard.Comments/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Comments have been disabled for this content." +msgid "Comments have been disabled for this content." +msgstr "Les commentaires ont été désactivés pour ce contenu." #: ~/Modules/Orchard.Comments/Views/DisplayTemplates/Parts/Comments.HasComments.ascx #| msgid : "Hi, {0}!" msgid "Hi, {0}!" msgstr "Bonjour {0}!" +#: ~/Modules/Orchard.Comments/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Comment" +msgid "Comment" +msgstr "Commentaire" + +#: ~/Modules/Orchard.Comments/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Submit Comment" +msgid "Submit Comment" +msgstr "Envoyer le commentaire" + #: ~/Modules/Orchard.Comments/Views/EditorTemplates/Parts/Comments.HasComments.ascx #| msgid : "Comments are shown. Existing comments are displayed." msgid "Comments are shown. Existing comments are displayed." @@ -1748,11 +1798,41 @@ msgstr "Utilisateur effacé" msgid "Access Denied" msgstr "Accès refusé" +#: ~/Modules/Orchard.Users/Views/Account/AccessDenied.ascx +#| msgid : "You do not have permission to complete your request." +msgid "You do not have permission to complete your request." +msgstr "Vous n'avez pas la permission de compléter cette requête." + #: ~/Modules/Orchard.Users/Views/Account/ChangePassword.ascx #| msgid : "Change Password" msgid "Change Password" msgstr "Changer le mot de passe" +#: ~/Modules/Orchard.Users/Views/Account/ChangePassword.ascx +#| msgid : "Use the form below to change your password." +msgid "Use the form below to change your password." +msgstr "Veuillez utiliser le formulaire ci-dessous pour changer votre mot de passe." + +#: ~/Modules/Orchard.Users/Views/Account/ChangePassword.ascx +#| msgid : "New passwords are required to be a minimum of {0} characters in length." +msgid "New passwords are required to be a minimum of {0} characters in length." +msgstr "Les mots de passe doivent contenir au moins {0} caractères." + +#: ~/Modules/Orchard.Users/Views/Account/ChangePassword.ascx +#| msgid : "Current password:" +msgid "Current password:" +msgstr "Mot de passe courant:" + +#: ~/Modules/Orchard.Users/Views/Account/ChangePassword.ascx +#| msgid : "New password:" +msgid "New password:" +msgstr "Nouveau mot de passe:" + +#: ~/Modules/Orchard.Users/Views/Account/ChangePassword.ascx +#| msgid : "Confirm new password:" +msgid "Confirm new password:" +msgstr "Confirmez le nouveau mot de passe:" + #: ~/Modules/Orchard.Users/Views/Account/ChangePassword.ascx #| msgid : "Password change was unsuccessful. Please correct the errors and try again." msgid "Password change was unsuccessful. Please correct the errors and try again." @@ -1763,16 +1843,81 @@ msgstr "Le changement de mot de passe a échoué. Veuillez corriger les erreurs msgid "Change Password" msgstr "Changer le mot de passe" +#: ~/Modules/Orchard.Users/Views/Account/ChangePasswordSuccess.ascx +#| msgid : "Your password has been changed successfully." +msgid "Your password has been changed successfully." +msgstr "Votre mot de passe a été changé." + #: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx #| msgid : "Login was unsuccessful. Please correct the errors and try again." msgid "Login was unsuccessful. Please correct the errors and try again." msgstr "La connexion a échoué. Veuillez corriger les erreurs et réessayer." +#: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx +#| msgid : "Please enter your username and password." +msgid "Please enter your username and password." +msgstr "Veuillez saisir votre nom d'utilisateur et votre mot de passe." + +#: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx +#| msgid : "Register" +msgid "Register" +msgstr "Enregistrer" + +#: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx +#| msgid : " if you don't have an account." +msgid " if you don't have an account." +msgstr " si vous n'avez pas de compte." + +#: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx +#| msgid : "Account Information" +msgid "Account Information" +msgstr "Données du compte" + +#: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx +#| msgid : "Username or Email:" +msgid "Username or Email:" +msgstr "Nom d'utilisateur ou e-mail:" + +#: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx +#| msgid : "Password:" +msgid "Password:" +msgstr "Mot de passe:" + +#: ~/Modules/Orchard.Users/Views/Account/LogOn.ascx +#| msgid : "Remember me?" +msgid "Remember me?" +msgstr "Se souvenir de moi?" + #: ~/Modules/Orchard.Users/Views/Account/Register.ascx #| msgid : "Create a New Account" msgid "Create a New Account" msgstr "Créer un nouveau compte utilisateur" +#: ~/Modules/Orchard.Users/Views/Account/Register.ascx +#| msgid : "Use the form below to create a new account." +msgid "Use the form below to create a new account." +msgstr "Veuillez utiliser le formulaire ci-dessous pour créer un nouveau compte." + +#: ~/Modules/Orchard.Users/Views/Account/Register.ascx +#| msgid : "Passwords are required to be a minimum of {0} characters in length." +msgid "Passwords are required to be a minimum of {0} characters in length." +msgstr "Les mots de passe doivent contenir au moins {0} caractères." + +#: ~/Modules/Orchard.Users/Views/Account/Register.ascx +#| msgid : "Username:" +msgid "Username:" +msgstr "Nom d'utilisateur:" + +#: ~/Modules/Orchard.Users/Views/Account/Register.ascx +#| msgid : "Email:" +msgid "Email:" +msgstr "E-mail: + +#: ~/Modules/Orchard.Users/Views/Account/Register.ascx +#| msgid : "Confirm password:" +msgid "Confirm password:" +msgstr "Confirmez le mot de passe:" + #: ~/Modules/Orchard.Users/Views/Account/Register.ascx #| msgid : "Account creation was unsuccessful. Please correct the errors and try again." msgid "Account creation was unsuccessful. Please correct the errors and try again." @@ -1823,6 +1968,41 @@ msgstr "personne(?)" msgid "Login was unsuccessful. Please correct the errors and try again." msgstr "La connexion a échoué. Veuillez corriger les erreurs et réessayer." +#: ~/Themes/Contoso/Views/LogOn.ascx +#| msgid : "Please enter your username and password." +msgid "Please enter your username and password." +msgstr "Veuillez saisir votre nom d'utilisateur et votre mot de passe." + +#: ~/Themes/Contoso/Views/LogOn.ascx +#| msgid : "Register" +msgid "Register" +msgstr "S'enregistrer" + +#: ~/Themes/Contoso/Views/LogOn.ascx +#| msgid : " if you don't have an account." +msgid " if you don't have an account." +msgstr " si vous n'avez pas de compte." + +#: ~/Themes/Contoso/Views/LogOn.ascx +#| msgid : "Account Information" +msgid "Account Information" +msgstr "Données du Compte" + +#: ~/Themes/Contoso/Views/LogOn.ascx +#| msgid : "Username or Email:" +msgid "Username or Email:" +msgstr "Nom d'utilisateur ou e-mail:" + +#: ~/Themes/Contoso/Views/LogOn.ascx +#| msgid : "Password:" +msgid "Password:" +msgstr "Mot de passe:" + +#: ~/Themes/Contoso/Views/LogOn.ascx +#| msgid : "Remember me?" +msgid "Remember me?" +msgstr "Se souvenir de moi?" + #: ~/Themes/Contoso/Views/User.ascx #| msgid : "Log Off" msgid "Log Off" @@ -1848,6 +2028,21 @@ msgstr "Archives" msgid "log on" msgstr "se connecter" +#: ~/Themes/Contoso/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Add a Comment" +msgid "Add a Comment" +msgstr "Ajouter un commentaire" + +#: ~/Themes/Contoso/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "You must {0} to comment." +msgid "You must {0} to comment." +msgstr "Vous devez {0} pour ajouter un commentaire." + +#: ~/Themes/Contoso/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Comments have been disabled for this content." +msgid "Comments have been disabled for this content." +msgstr "Les commentaires ont été désactivés pour ce contenu." + #: ~/Themes/Contoso/Views/DisplayTemplates/Parts/Comments.HasComments.ascx #| msgid : "Hi, {0}!" msgid "Hi, {0}!" @@ -1863,6 +2058,41 @@ msgstr "personne(?)" msgid "Login was unsuccessful. Please correct the errors and try again." msgstr "La connexion a échoué. Veuillez corriger les erreurs et réessayer." +#: ~/Themes/Corporate/Views/LogOn.ascx +#| msgid : "Please enter your username and password." +msgid "Please enter your username and password." +msgstr "Veuillez saisir votre nom d'utilisateur et votre mot de passe." + +#: ~/Themes/Corporate/Views/LogOn.ascx +#| msgid : "Register" +msgid "Register" +msgstr "S'enregistrer" + +#: ~/Themes/Corporate/Views/LogOn.ascx +#| msgid : " if you don't have an account." +msgid " if you don't have an account." +msgstr " si vous n'avez pas de compte." + +#: ~/Themes/Corporate/Views/LogOn.ascx +#| msgid : "Account Information" +msgid "Account Information" +msgstr "Données du Compte" + +#: ~/Themes/Corporate/Views/LogOn.ascx +#| msgid : "Username or Email:" +msgid "Username or Email:" +msgstr "Nom d'utilisateur ou e-mail:" + +#: ~/Themes/Corporate/Views/LogOn.ascx +#| msgid : "Password:" +msgid "Password:" +msgstr "Mot de passe:" + +#: ~/Themes/Corporate/Views/LogOn.ascx +#| msgid : "Remember me?" +msgid "Remember me?" +msgstr "Se souvenir de moi?" + #: ~/Themes/Corporate/Views/User.ascx #| msgid : "Log Off" msgid "Log Off" @@ -1888,6 +2118,21 @@ msgstr "Archives" msgid "log on" msgstr "se connecter" +#: ~/Themes/Corporate/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Add a Comment" +msgid "Add a Comment" +msgstr "Ajouter un commentaire" + +#: ~/Themes/Corporate/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "You must {0} to comment." +msgid "You must {0} to comment." +msgstr "Vous devez {0} pour ajouter un commentaire." + +#: ~/Themes/Corporate/Views/DisplayTemplates/Parts/Comments.HasComments.ascx +#| msgid : "Comments have been disabled for this content." +msgid "Comments have been disabled for this content." +msgstr "Les commentaires ont été désactivés pour ce contenu." + #: ~/Themes/Corporate/Views/DisplayTemplates/Parts/Comments.HasComments.ascx #| msgid : "Hi, {0}!" msgid "Hi, {0}!" @@ -1918,6 +2163,11 @@ msgstr "Votre site" msgid "Logout" msgstr "Déconnexion" +#: ~/Themes/TheAdmin/Views/User.ascx +#| msgid : "User:" +msgid "User:" +msgstr "Utilisateur:" + #: ~/Commands/DefaultOrchardCommandHandler.cs #| msgid : "Switch was not found: " msgid "Switch was not found: " diff --git a/src/Orchard.Web/Core/Common/Models/RoutableAspect.cs b/src/Orchard.Web/Core/Common/Models/RoutableAspect.cs index 1253f05d2..45fe93890 100644 --- a/src/Orchard.Web/Core/Common/Models/RoutableAspect.cs +++ b/src/Orchard.Web/Core/Common/Models/RoutableAspect.cs @@ -1,7 +1,8 @@ using Orchard.ContentManagement; +using Orchard.ContentManagement.Aspects; namespace Orchard.Core.Common.Models { - public class RoutableAspect : ContentPart { + public class RoutableAspect : ContentPart, IRoutableAspect { public string ContentItemBasePath { get; set; } public string Title { @@ -14,4 +15,4 @@ namespace Orchard.Core.Common.Models { set { Record.Slug = value; } } } -} \ No newline at end of file +} diff --git a/src/Orchard.Web/Core/Common/Models/RoutableRecord.cs b/src/Orchard.Web/Core/Common/Models/RoutableRecord.cs index 9951a8b24..ea142a302 100644 --- a/src/Orchard.Web/Core/Common/Models/RoutableRecord.cs +++ b/src/Orchard.Web/Core/Common/Models/RoutableRecord.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using Orchard.ContentManagement.Records; namespace Orchard.Core.Common.Models { @@ -7,5 +8,8 @@ namespace Orchard.Core.Common.Models { public virtual string Title { get; set; } public virtual string Slug { get; set; } + + [StringLength(2048)] + public virtual string Path { get; set; } } -} \ No newline at end of file +} diff --git a/src/Orchard.Web/Core/Common/Views/DisplayTemplates/Parts/Common.Body.ManageWrapperPost.ascx b/src/Orchard.Web/Core/Common/Views/DisplayTemplates/Parts/Common.Body.ManageWrapperPost.ascx index d39d7cc8d..86cbb66fa 100644 --- a/src/Orchard.Web/Core/Common/Views/DisplayTemplates/Parts/Common.Body.ManageWrapperPost.ascx +++ b/src/Orchard.Web/Core/Common/Views/DisplayTemplates/Parts/Common.Body.ManageWrapperPost.ascx @@ -1,3 +1,7 @@ <%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> <%@ Import Namespace="Orchard.Core.Common.ViewModels"%> - \ No newline at end of file +<%-- begin: knowingly broken HTML (hence the ManageWrapperPre and ManageWrapperPost templates) +we need "wrapper templates" (among other functionality) in the future of UI composition +please do not delete or the front end will be broken when the user is authenticated. --%> + +<%-- begin: knowingly broken HTML --%> \ No newline at end of file diff --git a/src/Orchard.Web/Core/Common/Views/EditorTemplates/Parts/Common.Routable.ascx b/src/Orchard.Web/Core/Common/Views/EditorTemplates/Parts/Common.Routable.ascx index a71479bc4..e018b7dac 100644 --- a/src/Orchard.Web/Core/Common/Views/EditorTemplates/Parts/Common.Routable.ascx +++ b/src/Orchard.Web/Core/Common/Views/EditorTemplates/Parts/Common.Routable.ascx @@ -24,7 +24,8 @@ url:"<%=Url.Slugify() %>", contentType:"<%=Model.RoutableAspect.ContentItem.ContentType %>", id:"<%=Model.RoutableAspect.ContentItem.Id %>"<% - var container = Model.RoutableAspect.ContentItem.As().Container; + var commonAspect = Model.RoutableAspect.ContentItem.As(); + var container = commonAspect != null ? commonAspect.Container : null; if (container != null) { %>, containerId:<%=container.ContentItem.Id %><% } %> diff --git a/src/Orchard.Web/Core/Contents/AdminMenu.cs b/src/Orchard.Web/Core/Contents/AdminMenu.cs new file mode 100644 index 000000000..9683a9478 --- /dev/null +++ b/src/Orchard.Web/Core/Contents/AdminMenu.cs @@ -0,0 +1,19 @@ +using Orchard.Localization; +using Orchard.Security; +using Orchard.UI.Navigation; + +namespace Orchard.Core.Contents { + public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } + public string MenuName { get { return "admin"; } } + + public void GetNavigation(NavigationBuilder builder) { + builder.Add(T("Content"), "1", + menu => { + menu.Add(T("Create"), "1.1", item => item.Action("Create", "Admin", new { area = "Contents" })); + menu.Add(T("List"), "1.2", item => item.Action("List", "Admin", new { area = "Contents" })); + menu.Add(T("Types"), "1.3", item => item.Action("Types", "Admin", new { area = "Contents" })); + }); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Contents/Controllers/AdminController.cs b/src/Orchard.Web/Core/Contents/Controllers/AdminController.cs new file mode 100644 index 000000000..7667980f8 --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Controllers/AdminController.cs @@ -0,0 +1,166 @@ +using System; +using System.Linq; +using System.Web.Mvc; +using System.Web.Routing; +using Orchard.ContentManagement; +using Orchard.ContentManagement.MetaData; +using Orchard.ContentManagement.Records; +using Orchard.Core.Contents.ViewModels; +using Orchard.Data; +using Orchard.Localization; +using Orchard.Logging; +using Orchard.Mvc.ViewModels; +using Orchard.UI.Notify; + +namespace Orchard.Core.Contents.Controllers { + [ValidateInput(false)] + public class AdminController : Controller, IUpdateModel { + private readonly INotifier _notifier; + private readonly IContentDefinitionManager _contentDefinitionManager; + private readonly IContentManager _contentManager; + private readonly ITransactionManager _transactionManager; + + public AdminController( + INotifier notifier, + IContentDefinitionManager contentDefinitionManager, + IContentManager contentManager, + ITransactionManager transactionManager) { + _notifier = notifier; + _contentDefinitionManager = contentDefinitionManager; + _contentManager = contentManager; + _transactionManager = transactionManager; + T = NullLocalizer.Instance; + Logger = NullLogger.Instance; + } + + public Localizer T { get; set; } + public ILogger Logger { get; set; } + + public ActionResult Index() { + return Types(); + } + + public ActionResult Types() { + return View("Types", new ContentTypeListViewModel { + Types = _contentDefinitionManager.ListTypeDefinitions() + }); + } + + public ActionResult List(ListContentViewModel model) { + const int pageSize = 20; + var skip = (Math.Max(model.Page ?? 0, 1) - 1) * pageSize; + + var query = _contentManager.Query(VersionOptions.Latest); + + if (!string.IsNullOrEmpty(model.Id)) { + query = query.ForType(model.Id); + } + + var contentItems = query.Slice(skip, pageSize); + + model.Entries = contentItems.Select(BuildEntry).ToList(); + + return View("List", model); + } + + private ListContentViewModel.Entry BuildEntry(ContentItem contentItem) { + var entry = new ListContentViewModel.Entry { + ContentItem = contentItem, + ContentItemMetadata = _contentManager.GetItemMetadata(contentItem), + ViewModel = _contentManager.BuildDisplayModel(contentItem, "List"), + }; + if (string.IsNullOrEmpty(entry.ContentItemMetadata.DisplayText)) { + entry.ContentItemMetadata.DisplayText = string.Format("[{0}#{1}]", contentItem.ContentType, contentItem.Id); + } + if (entry.ContentItemMetadata.EditorRouteValues == null) { + entry.ContentItemMetadata.EditorRouteValues = new RouteValueDictionary { + {"Area", "Contents"}, + {"Controller", "Admin"}, + {"Action", "Edit"}, + {"Id", contentItem.Id} + }; + } + return entry; + } + + ActionResult CreatableTypeList() { + var model = new ContentTypeListViewModel { + Types = _contentDefinitionManager.ListTypeDefinitions() + }; + + return View("CreatableTypeList", model); + } + + public ActionResult Create(string id) { + if (string.IsNullOrEmpty(id)) + return CreatableTypeList(); + + var contentItem = _contentManager.New(id); + var model = new CreateItemViewModel { + Id = id, + Content = _contentManager.BuildEditorModel(contentItem) + }; + PrepareEditorViewModel(model.Content); + return View("Create", model); + } + + + [HttpPost] + public ActionResult Create(CreateItemViewModel model) { + var contentItem = _contentManager.New(model.Id); + model.Content = _contentManager.UpdateEditorModel(contentItem, this); + if (ModelState.IsValid) { + _contentManager.Create(contentItem, VersionOptions.Draft); + model.Content = _contentManager.UpdateEditorModel(contentItem, this); + } + if (ModelState.IsValid) { + _contentManager.Publish(contentItem); + } + if (!ModelState.IsValid) { + _transactionManager.Cancel(); + PrepareEditorViewModel(model.Content); + return View("Create", model); + } + + _notifier.Information(T("Created content item")); + return RedirectToAction("Edit", new RouteValueDictionary { { "Id", contentItem.Id } }); + } + + public ActionResult Edit(int id) { + var contentItem = _contentManager.Get(id, VersionOptions.Latest); + var model = new EditItemViewModel { + Id = id, + Content = _contentManager.BuildEditorModel(contentItem) + }; + PrepareEditorViewModel(model.Content); + return View("Edit", model); + } + + [HttpPost] + public ActionResult Edit(EditItemViewModel model) { + var contentItem = _contentManager.Get(model.Id, VersionOptions.DraftRequired); + model.Content = _contentManager.UpdateEditorModel(contentItem, this); + if (!ModelState.IsValid) { + _transactionManager.Cancel(); + PrepareEditorViewModel(model.Content); + return View("Edit", model); + } + _contentManager.Publish(contentItem); + return RedirectToAction("Edit", new RouteValueDictionary { { "Id", contentItem.Id } }); + } + + private void PrepareEditorViewModel(ContentItemViewModel itemViewModel) { + if (string.IsNullOrEmpty(itemViewModel.TemplateName)) { + itemViewModel.TemplateName = "Items/Contents.Item"; + } + } + + bool IUpdateModel.TryUpdateModel(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) { + return TryUpdateModel(model, prefix, includeProperties, excludeProperties); + } + + void IUpdateModel.AddModelError(string key, LocalizedString errorMessage) { + ModelState.AddModelError(key, errorMessage.ToString()); + } + } +} diff --git a/src/Orchard.Web/Core/Contents/Controllers/ItemController.cs b/src/Orchard.Web/Core/Contents/Controllers/ItemController.cs new file mode 100644 index 000000000..2a87f89f9 --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Controllers/ItemController.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using Orchard.ContentManagement; +using Orchard.Core.Contents.ViewModels; +using Orchard.Mvc.ViewModels; + +namespace Orchard.Core.Contents.Controllers { + public class ItemController : Controller { + private readonly IContentManager _contentManager; + + public ItemController(IContentManager contentManager) { + _contentManager = contentManager; + } + + public ActionResult Display(int id) { + var contentItem = _contentManager.Get(id, VersionOptions.Published); + + var model = new DisplayItemViewModel { + Content = _contentManager.BuildDisplayModel(contentItem, "Detail") + }; + PrepareDisplayViewModel(model.Content); + return View("Display", model); + } + + public ActionResult Preview(int id, int? version) { + var versionOptions = VersionOptions.Latest; + if (version != null) { + versionOptions = VersionOptions.Number((int)version); + } + + var contentItem = _contentManager.Get(id, versionOptions); + + var model = new DisplayItemViewModel { + Content = _contentManager.BuildDisplayModel(contentItem, "Detail") + }; + PrepareDisplayViewModel(model.Content); + return View("Preview", model); + } + + private static void PrepareDisplayViewModel(ContentItemViewModel itemViewModel) { + if (string.IsNullOrEmpty(itemViewModel.TemplateName)) { + itemViewModel.TemplateName = "Items/Contents.Item"; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Contents/Handlers/ContentsModuleHandler.cs b/src/Orchard.Web/Core/Contents/Handlers/ContentsModuleHandler.cs new file mode 100644 index 000000000..9abef1511 --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Handlers/ContentsModuleHandler.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Routing; +using Orchard.ContentManagement.Handlers; + +namespace Orchard.Core.Contents.Handlers { + public class ContentsModuleHandler : ContentHandlerBase { + public override void GetContentItemMetadata(GetContentItemMetadataContext context) { + if (context.Metadata.EditorRouteValues == null) { + context.Metadata.EditorRouteValues = new RouteValueDictionary { + {"Area", "Contents"}, + {"Controller", "Admin"}, + {"Action", "Edit"}, + {"Id", context.ContentItem.Id} + }; + } + if (context.Metadata.DisplayRouteValues == null) { + context.Metadata.DisplayRouteValues = new RouteValueDictionary { + {"Area", "Contents"}, + {"Controller", "Item"}, + {"Action", "Display"}, + {"Id", context.ContentItem.Id} + }; + } + } + } +} diff --git a/src/Orchard.Web/Core/Contents/Module.txt b/src/Orchard.Web/Core/Contents/Module.txt new file mode 100644 index 000000000..4933bb64d --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Module.txt @@ -0,0 +1,11 @@ +Name: Contents +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas consectetur consequat risus, vel blandit arcu tincidunt eget. Nam rutrum nulla vestibulum dolor dapibus sagittis. Vivamus convallis faucibus accumsan. Suspendisse sapien enim, cursus at dignissim a, sollicitudin sit amet est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec sed urna magna, in luctus nulla. Pellentesque erat ipsum, convallis sed molestie tempus, mattis vel leo metus. +features: + Contents: + Description: Default controllers for some content types. + Category: Core \ No newline at end of file diff --git a/src/Orchard.Web/Core/Contents/ViewModels/ContentTypeListViewModel.cs b/src/Orchard.Web/Core/Contents/ViewModels/ContentTypeListViewModel.cs new file mode 100644 index 000000000..321e98365 --- /dev/null +++ b/src/Orchard.Web/Core/Contents/ViewModels/ContentTypeListViewModel.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using Orchard.ContentManagement.MetaData.Models; +using Orchard.Mvc.ViewModels; + +namespace Orchard.Core.Contents.ViewModels { + public class ContentTypeListViewModel : BaseViewModel { + public IEnumerable Types { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Contents/ViewModels/CreateItemViewModel.cs b/src/Orchard.Web/Core/Contents/ViewModels/CreateItemViewModel.cs new file mode 100644 index 000000000..b3636d9cb --- /dev/null +++ b/src/Orchard.Web/Core/Contents/ViewModels/CreateItemViewModel.cs @@ -0,0 +1,8 @@ +using Orchard.Mvc.ViewModels; + +namespace Orchard.Core.Contents.ViewModels { + public class CreateItemViewModel : BaseViewModel { + public string Id { get; set; } + public ContentItemViewModel Content { get; set; } + } +} diff --git a/src/Orchard.Web/Core/Contents/ViewModels/EditItemViewModel.cs b/src/Orchard.Web/Core/Contents/ViewModels/EditItemViewModel.cs new file mode 100644 index 000000000..908b201e0 --- /dev/null +++ b/src/Orchard.Web/Core/Contents/ViewModels/EditItemViewModel.cs @@ -0,0 +1,11 @@ +using Orchard.Mvc.ViewModels; + +namespace Orchard.Core.Contents.ViewModels { + public class EditItemViewModel : BaseViewModel { + public int Id { get; set; } + public ContentItemViewModel Content { get; set; } + } + public class DisplayItemViewModel : BaseViewModel { + public ContentItemViewModel Content { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Contents/ViewModels/ListContentViewModel.cs b/src/Orchard.Web/Core/Contents/ViewModels/ListContentViewModel.cs new file mode 100644 index 000000000..36b8cb39a --- /dev/null +++ b/src/Orchard.Web/Core/Contents/ViewModels/ListContentViewModel.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using Orchard.ContentManagement; +using Orchard.Mvc.ViewModels; + +namespace Orchard.Core.Contents.ViewModels { + public class ListContentViewModel : BaseViewModel { + public string Id { get; set; } + public int? Page { get; set; } + public IList Entries { get; set; } + + public class Entry { + public ContentItem ContentItem { get; set; } + public ContentItemMetadata ContentItemMetadata { get; set; } + public ContentItemViewModel ViewModel { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Contents/Views/Admin/CreatableTypeList.aspx b/src/Orchard.Web/Core/Contents/Views/Admin/CreatableTypeList.aspx new file mode 100644 index 000000000..f0859f699 --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Views/Admin/CreatableTypeList.aspx @@ -0,0 +1,12 @@ +<%@ Page Language="C#" Inherits="Orchard.Mvc.ViewPage" %> + +<%@ Import Namespace="Orchard.Core.Contents.ViewModels" %> +<% Html.AddTitleParts(T("Create Content").ToString()); %> +

+ Create content

+
    + <% foreach (var t in Model.Types) {%> +
  • + <%:Html.ActionLink(t.Name, "Create", new RouteValueDictionary{{"Area","Contents"},{"Id",t.Name}}) %>
  • + <%} %> +
diff --git a/src/Orchard.Web/Core/Contents/Views/Admin/Create.aspx b/src/Orchard.Web/Core/Contents/Views/Admin/Create.aspx new file mode 100644 index 000000000..75a52c8c8 --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Views/Admin/Create.aspx @@ -0,0 +1,8 @@ +<%@ Page Language="C#" Inherits="Orchard.Mvc.ViewPage" %> + +<%@ Import Namespace="Orchard.Core.Contents.ViewModels" %> +<% Html.AddTitleParts(T("Create Content").ToString()); %> +<% using (Html.BeginFormAntiForgeryPost()) { %> +<%:Html.ValidationSummary() %> +<%:Html.EditorForItem(m=>m.Content) %> +<%} %> diff --git a/src/Orchard.Web/Core/Contents/Views/Admin/Edit.aspx b/src/Orchard.Web/Core/Contents/Views/Admin/Edit.aspx new file mode 100644 index 000000000..e6edeec39 --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Views/Admin/Edit.aspx @@ -0,0 +1,8 @@ +<%@ Page Language="C#" Inherits="Orchard.Mvc.ViewPage" %> + +<%@ Import Namespace="Orchard.Core.Contents.ViewModels" %> +<% Html.AddTitleParts(T("Edit Content").ToString()); %> +<% using (Html.BeginFormAntiForgeryPost()) { %> +<%:Html.ValidationSummary() %> +<%:Html.EditorForItem(m=>m.Content) %> +<%} %> diff --git a/src/Orchard.Web/Core/Contents/Views/Admin/List.aspx b/src/Orchard.Web/Core/Contents/Views/Admin/List.aspx new file mode 100644 index 000000000..6921ed2f8 --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Views/Admin/List.aspx @@ -0,0 +1,33 @@ +<%@ Page Language="C#" Inherits="Orchard.Mvc.ViewPage" %> + +<%@ Import Namespace="Orchard.Core.Contents.ViewModels" %> +<% Html.AddTitleParts(T("Browse Contents").ToString()); %> +

+ Browse Contents

+ + <% foreach (var t in Model.Entries) {%> + + + + + + + + <%} %> +
+ <%:t.ContentItem.Id %>. + + <%:t.ContentItem.ContentType %> + + ver #<%:t.ContentItem.Version %> + + <%if (t.ContentItemMetadata.DisplayRouteValues != null) {%> + <%:Html.ActionLink(t.ContentItemMetadata.DisplayText, t.ContentItemMetadata.DisplayRouteValues["Action"].ToString(), t.ContentItemMetadata.DisplayRouteValues)%> + <%}%> + + <%if (t.ContentItemMetadata.EditorRouteValues != null) {%> + <%:Html.ActionLink("edit", t.ContentItemMetadata.EditorRouteValues["Action"].ToString(), t.ContentItemMetadata.EditorRouteValues)%> + <%}%> +
+

+ <%:Html.ActionLink("Create new item", "Create", "Admin", new RouteValueDictionary{{"Area","Contents"},{"Id",Model.Id}}, new Dictionary()) %>

diff --git a/src/Orchard.Web/Core/Contents/Views/Admin/Types.aspx b/src/Orchard.Web/Core/Contents/Views/Admin/Types.aspx new file mode 100644 index 000000000..51b90a383 --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Views/Admin/Types.aspx @@ -0,0 +1,24 @@ +<%@ Page Language="C#" Inherits="Orchard.Mvc.ViewPage" %> + +<%@ Import Namespace="Orchard.Core.Contents.ViewModels" %> +<% Html.AddTitleParts(T("Create Content").ToString()); %> +

+ Create content

+ + <% foreach (var t in Model.Types) {%> + + + + + + + <%} %> +
+ <%:t.Name %> + + <%:Html.ActionLink(T("List Items").ToString(), "List", "Admin", new RouteValueDictionary{{"Area","Contents"},{"Id",t.Name}}, new Dictionary()) %> + + <%:Html.ActionLink(T("Create Item").ToString(), "Create", "Admin", new RouteValueDictionary{{"Area","Contents"},{"Id",t.Name}}, new Dictionary()) %> + + <%:Html.ActionLink(T("Edit Type").ToString(), "ContentTypeList", "Admin", new RouteValueDictionary{{"Area","Orchard.MetaData"},{"Id",t.Name}}, new Dictionary()) %> +
diff --git a/src/Orchard.Web/Core/Contents/Views/DisplayTemplates/Items/Contents.Item.ascx b/src/Orchard.Web/Core/Contents/Views/DisplayTemplates/Items/Contents.Item.ascx new file mode 100644 index 000000000..072cecebe --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Views/DisplayTemplates/Items/Contents.Item.ascx @@ -0,0 +1,11 @@ +<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> +<%@ Import Namespace="Orchard.Mvc.ViewModels" %> +<%@ Import Namespace="Orchard.ContentManagement.Aspects" %> +<%@ Import Namespace="Orchard.ContentManagement" %> +<%var routable = Model.Item.As(); + if (routable != null && !string.IsNullOrEmpty(routable.Title)) {%> +

+ <%:routable.Title%>

+<%} %> +<% Html.Zone("primary", ":manage :metadata"); + Html.ZonesAny(); %> diff --git a/src/Orchard.Web/Core/Contents/Views/EditorTemplates/Items/Contents.Item.ascx b/src/Orchard.Web/Core/Contents/Views/EditorTemplates/Items/Contents.Item.ascx new file mode 100644 index 000000000..5e0cc4986 --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Views/EditorTemplates/Items/Contents.Item.ascx @@ -0,0 +1,14 @@ +<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> +<%@ Import Namespace="Orchard.Mvc.ViewModels"%> +
+
<% + Html.Zone("primary"); + Html.ZonesExcept("secondary"); %> +
+
+ <% Html.Zone("secondary");%> +
+ "/> +
+
+
\ No newline at end of file diff --git a/src/Orchard.Web/Core/Contents/Views/Item/Display.aspx b/src/Orchard.Web/Core/Contents/Views/Item/Display.aspx new file mode 100644 index 000000000..2eb28576d --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Views/Item/Display.aspx @@ -0,0 +1,5 @@ +<%@ Page Language="C#" Inherits="Orchard.Mvc.ViewPage" %> + +
+ <%=Html.DisplayForItem(m=>m.Content) %> +
diff --git a/src/Orchard.Web/Core/Contents/Views/Item/Preview.aspx b/src/Orchard.Web/Core/Contents/Views/Item/Preview.aspx new file mode 100644 index 000000000..9bb5300af --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Views/Item/Preview.aspx @@ -0,0 +1,3 @@ +<%@ Page Language="C#" Inherits="Orchard.Mvc.ViewPage" %> + +<%=Html.DisplayForItem(m=>m.Content) %> diff --git a/src/Orchard.Web/Core/Contents/Views/Web.config b/src/Orchard.Web/Core/Contents/Views/Web.config new file mode 100644 index 000000000..e065d8735 --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Views/Web.config @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Core/Dashboard/AdminMenu.cs b/src/Orchard.Web/Core/Dashboard/AdminMenu.cs index 7b5dc0123..d960f6400 100644 --- a/src/Orchard.Web/Core/Dashboard/AdminMenu.cs +++ b/src/Orchard.Web/Core/Dashboard/AdminMenu.cs @@ -1,13 +1,15 @@ -using Orchard.Security; +using Orchard.Localization; +using Orchard.Security; using Orchard.UI.Navigation; namespace Orchard.Core.Dashboard { public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { - builder.Add("Orchard", "0", - menu => menu.Add("Dashboard", "0", item => item.Action("Index", "Admin", new { area = "Dashboard" }).Permission(StandardPermissions.AccessAdminPanel))); + builder.Add(T("Orchard"), "0", + menu => menu.Add(T("Dashboard"), "0", item => item.Action("Index", "Admin", new { area = "Dashboard" }).Permission(StandardPermissions.AccessAdminPanel))); } } } \ No newline at end of file diff --git a/src/Orchard.Web/Core/Indexing/Commands/IndexingCommands.cs b/src/Orchard.Web/Core/Indexing/Commands/IndexingCommands.cs new file mode 100644 index 000000000..02c33923d --- /dev/null +++ b/src/Orchard.Web/Core/Indexing/Commands/IndexingCommands.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Orchard.Commands; +using Orchard.ContentManagement; +using Orchard.Indexing; +using Orchard.Security; +using Orchard.Tasks.Indexing; + +namespace Orchard.Core.Indexing.Commands { + public class IndexingCommands : DefaultOrchardCommandHandler { + private readonly IEnumerable _indexNotifierHandlers; + private readonly IIndexManager _indexManager; + private readonly IIndexingTaskManager _indexingTaskManager; + private readonly IContentManager _contentManager; + private const string SearchIndexName = "Search"; + + public IndexingCommands( + IEnumerable indexNotifierHandlers, + IIndexManager indexManager, + IIndexingTaskManager indexingTaskManager, + IContentManager contentManager) { + _indexNotifierHandlers = indexNotifierHandlers; + _indexingTaskManager = indexingTaskManager; + _contentManager = contentManager; + _indexManager = indexManager; + } + + [OrchardSwitch] + public string IndexName { get; set; } + + [OrchardSwitch] + public string Query { get; set; } + + [OrchardSwitch] + public string ContentItemId { get; set; } + + [CommandName("index update")] + [CommandHelp("index update [/IndexName:]\r\n\t" + "Updates the index with the specified , or the search index if not specified")] + [OrchardSwitches("IndexName")] + public string Update() { + if ( !_indexManager.HasIndexProvider() ) { + return "No index available"; + } + + var indexName = String.IsNullOrWhiteSpace(IndexName) ? SearchIndexName : IndexName; + foreach ( var handler in _indexNotifierHandlers ) { + handler.UpdateIndex(indexName); + } + + return "Index is now being updated..."; + } + + [CommandName("index rebuild")] + [CommandHelp("index rebuild [/IndexName:]\r\n\t" + "Rebuilds the index with the specified , or the search index if not specified")] + [OrchardSwitches("IndexName")] + public string Rebuild() { + if ( !_indexManager.HasIndexProvider() ) { + return "No index available"; + } + + var indexName = String.IsNullOrWhiteSpace(IndexName) ? SearchIndexName : IndexName; + var searchProvider = _indexManager.GetSearchIndexProvider(); + if ( searchProvider.Exists(indexName) ) + searchProvider.DeleteIndex(indexName); + + searchProvider.CreateIndex(indexName); + return "Index is now being rebuilt..."; + } + + [CommandName("index search")] + [CommandHelp("index search /Query: [/IndexName:]\r\n\t" + "Searches the specified terms in the index with the specified , or in the search index if not specified")] + [OrchardSwitches("Query,IndexName")] + public string Search() { + if ( !_indexManager.HasIndexProvider() ) { + return "No index available"; + } + var indexName = String.IsNullOrWhiteSpace(IndexName) ? SearchIndexName : IndexName; + var searchBuilder = _indexManager.GetSearchIndexProvider().CreateSearchBuilder(indexName); + var results = searchBuilder.WithField("body", Query).WithField("title", Query).Search(); + + Context.Output.WriteLine("{0} result{1}\r\n-----------------\r\n", results.Count(), results.Count() > 0 ? "s" : ""); + + Context.Output.WriteLine("┌──────────────────────────────────────────────────────────────┬────────┐"); + Context.Output.WriteLine("│ {0} │ {1,6} │", "Title" + new string(' ', 60 - "Title".Length), "Score"); + Context.Output.WriteLine("├──────────────────────────────────────────────────────────────┼────────┤"); + foreach ( var searchHit in results ) { + var title = searchHit.GetString("title"); + title = title.Substring(0, Math.Min(60, title.Length)) ?? "- no title -"; + var score = searchHit.Score; + Context.Output.WriteLine("│ {0} │ {1,6} │", title + new string(' ', 60 - title.Length), score); + } + Context.Output.WriteLine("└──────────────────────────────────────────────────────────────┴────────┘"); + + Context.Output.WriteLine(); + return "End of search results"; + } + + [CommandName("index stats")] + [CommandHelp("index stats [/IndexName:]\r\n\t" + "Displays some statistics about the index with the specified , or in the search index if not specified")] + [OrchardSwitches("IndexName")] + public string Stats() { + if ( !_indexManager.HasIndexProvider() ) { + return "No index available"; + } + var indexName = String.IsNullOrWhiteSpace(IndexName) ? SearchIndexName : IndexName; + Context.Output.WriteLine("Number of indexed documents: {0}", _indexManager.GetSearchIndexProvider().NumDocs(indexName)); + return ""; + } + + [CommandName("index refresh")] + [CommandHelp("index refresh /ContenItem: \r\n\t" + "Refreshes the index for the specifed ")] + [OrchardSwitches("ContentItem")] + public string Refresh() { + int contenItemId; + if ( !int.TryParse(ContentItemId, out contenItemId) ) { + return "Invalid content item id. Not an integer."; + } + + var contentItem = _contentManager.Get(contenItemId); + _indexingTaskManager.CreateUpdateIndexTask(contentItem); + + return "Content Item marked for reindexing"; + } + + [CommandName("index delete")] + [CommandHelp("index delete /ContenItem:\r\n\t" + "Deletes the specifed from the index")] + [OrchardSwitches("ContentItem")] + public string Delete() { + int contenItemId; + if(!int.TryParse(ContentItemId, out contenItemId)) { + return "Invalid content item id. Not an integer."; + } + + var contentItem = _contentManager.Get(contenItemId); + _indexingTaskManager.CreateDeleteIndexTask(contentItem); + + return "Content Item marked for deletion"; + } + + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexDocument.cs b/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexDocument.cs index 8c10ff43b..571a06cc0 100644 --- a/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexDocument.cs +++ b/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexDocument.cs @@ -12,54 +12,69 @@ namespace Orchard.Core.Indexing.Lucene { public List Fields { get; private set; } private AbstractField _previousField; + public int Id { get; private set; } public DefaultIndexDocument(int documentId) { Fields = new List(); SetContentItemId(documentId); + IsDirty = false; } + public bool IsDirty { get; private set; } + public IIndexDocument Add(string name, string value) { return Add(name, value, false); } public IIndexDocument Add(string name, string value, bool removeTags) { AppendPreviousField(); + + if(value == null) { + value = String.Empty; + } + if(removeTags) { value = value.RemoveTags(); } _previousField = new Field(name, value, Field.Store.YES, Field.Index.ANALYZED); + IsDirty = true; return this; } public IIndexDocument Add(string name, DateTime value) { AppendPreviousField(); _previousField = new Field(name, DateTools.DateToString(value, DateTools.Resolution.SECOND), Field.Store.YES, Field.Index.NOT_ANALYZED); + IsDirty = true; return this; } public IIndexDocument Add(string name, int value) { AppendPreviousField(); _previousField = new NumericField(name, Field.Store.YES, true).SetIntValue(value); + IsDirty = true; return this; } public IIndexDocument Add(string name, bool value) { AppendPreviousField(); _previousField = new Field(name, value.ToString().ToLower(), Field.Store.YES, Field.Index.NOT_ANALYZED); + IsDirty = true; return this; } public IIndexDocument Add(string name, float value) { AppendPreviousField(); _previousField = new NumericField(name, Field.Store.YES, true).SetFloatValue(value); + IsDirty = true; return this; } public IIndexDocument Add(string name, object value) { AppendPreviousField(); _previousField = new Field(name, value.ToString(), Field.Store.NO, Field.Index.NOT_ANALYZED); + IsDirty = true; return this; } diff --git a/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexProvider.cs b/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexProvider.cs index d7df669a5..8132e1a54 100644 --- a/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexProvider.cs +++ b/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexProvider.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using Lucene.Net.Analysis; using Lucene.Net.Analysis.Standard; using Lucene.Net.Documents; @@ -11,23 +13,28 @@ using Orchard.Indexing; using Directory = Lucene.Net.Store.Directory; using Version = Lucene.Net.Util.Version; using Orchard.Logging; +using System.Xml.Linq; namespace Orchard.Core.Indexing.Lucene { /// - /// Represents the default implementation of an IIndexProvider based on Lucene + /// Represents the default implementation of an IIndexProvider, based on Lucene /// public class DefaultIndexProvider : IIndexProvider { private readonly IAppDataFolder _appDataFolder; private readonly ShellSettings _shellSettings; public static readonly Version LuceneVersion = Version.LUCENE_29; - private readonly Analyzer _analyzer = new StandardAnalyzer(LuceneVersion); + private readonly Analyzer _analyzer ; private readonly string _basePath; + public static readonly DateTime DefaultMinDateTime = new DateTime(1980, 1, 1); + public static readonly string Settings = "Settings"; + public static readonly string LastIndexUtc = "LastIndexedUtc"; public ILogger Logger { get; set; } public DefaultIndexProvider(IAppDataFolder appDataFolder, ShellSettings shellSettings) { _appDataFolder = appDataFolder; _shellSettings = shellSettings; + _analyzer = CreateAnalyzer(); // TODO: (sebros) Find a common way to get where tenant's specific files should go. "Sites/Tenant" is hard coded in multiple places _basePath = Path.Combine("Sites", _shellSettings.Name, "Indexes"); @@ -35,6 +42,15 @@ namespace Orchard.Core.Indexing.Lucene { Logger = NullLogger.Instance; // Ensures the directory exists + EnsureDirectoryExists(); + } + + public static Analyzer CreateAnalyzer() { + // StandardAnalyzer does lower-case and stop-word filtering. It also removes punctuation + return new StandardAnalyzer(LuceneVersion); + } + + private void EnsureDirectoryExists() { var directory = new DirectoryInfo(_appDataFolder.MapPath(_basePath)); if(!directory.Exists) { directory.Create(); @@ -60,6 +76,36 @@ namespace Orchard.Core.Indexing.Lucene { return new DirectoryInfo(_appDataFolder.MapPath(Path.Combine(_basePath, indexName))).Exists; } + public bool IsEmpty(string indexName) { + if ( !Exists(indexName) ) { + return true; + } + + var reader = IndexReader.Open(GetDirectory(indexName), true); + + try { + return reader.NumDocs() == 0; + } + finally { + reader.Close(); + } + } + + public int NumDocs(string indexName) { + if ( !Exists(indexName) ) { + return 0; + } + + var reader = IndexReader.Open(GetDirectory(indexName), true); + + try { + return reader.NumDocs(); + } + finally { + reader.Close(); + } + } + public void CreateIndex(string indexName) { var writer = new IndexWriter(GetDirectory(indexName), _analyzer, true, IndexWriter.MaxFieldLength.UNLIMITED); writer.Close(); @@ -70,43 +116,72 @@ namespace Orchard.Core.Indexing.Lucene { public void DeleteIndex(string indexName) { new DirectoryInfo(Path.Combine(_appDataFolder.MapPath(Path.Combine(_basePath, indexName)))) .Delete(true); + + var settingsFileName = GetSettingsFileName(indexName); + if(File.Exists(settingsFileName)) { + File.Delete(settingsFileName); + } } public void Store(string indexName, IIndexDocument indexDocument) { - Store(indexName, (DefaultIndexDocument)indexDocument); + Store(indexName, new [] { (DefaultIndexDocument)indexDocument }); } - public void Store(string indexName, DefaultIndexDocument indexDocument) { + public void Store(string indexName, IEnumerable indexDocuments) { + Store(indexName, indexDocuments.Cast()); + } + + public void Store(string indexName, IEnumerable indexDocuments) { + if(indexDocuments.AsQueryable().Count() == 0) { + return; + } + var writer = new IndexWriter(GetDirectory(indexName), _analyzer, false, IndexWriter.MaxFieldLength.UNLIMITED); + DefaultIndexDocument current = null; try { - var doc = CreateDocument(indexDocument); - writer.AddDocument(doc); - Logger.Debug("Document [{0}] indexed", indexDocument.Id); + foreach ( var indexDocument in indexDocuments ) { + current = indexDocument; + var doc = CreateDocument(indexDocument); + writer.AddDocument(doc); + Logger.Debug("Document [{0}] indexed", indexDocument.Id); + } } catch ( Exception ex ) { - Logger.Error(ex, "An unexpected error occured while removing the document [{0}] from the index [{1}].", indexDocument.Id, indexName); + Logger.Error(ex, "An unexpected error occured while add the document [{0}] from the index [{1}].", current.Id, indexName); } finally { + writer.Optimize(); writer.Close(); } - } - public void Delete(string indexName, int id) { - var reader = IndexReader.Open(GetDirectory(indexName), false); + public void Delete(string indexName, int documentId) { + Delete(indexName, new[] { documentId }); + } + + public void Delete(string indexName, IEnumerable documentIds) { + if ( documentIds.AsQueryable().Count() == 0 ) { + return; + } + + var reader = IndexReader.Open(GetDirectory(indexName), false); try { - var term = new Term("id", id.ToString()); - if ( reader.DeleteDocuments(term) != 0 ) { - Logger.Error("The document [{0}] could not be removed from the index [{1}]", id, indexName); + foreach (var id in documentIds) { + try { + var term = new Term("id", id.ToString()); + if (reader.DeleteDocuments(term) != 0) { + Logger.Error("The document [{0}] could not be removed from the index [{1}]", id, indexName); + } + else { + Logger.Debug("Document [{0}] removed from index", id); + } + } + catch (Exception ex) { + Logger.Error(ex, "An unexpected error occured while removing the document [{0}] from the index [{1}].", id, indexName); + } } - else { - Logger.Debug("Document [{0}] removed from index", id); - } - } - catch ( Exception ex ) { - Logger.Error(ex, "An unexpected error occured while removing the document [{0}] from the index [{1}].", id, indexName); } finally { reader.Close(); @@ -121,8 +196,38 @@ namespace Orchard.Core.Indexing.Lucene { return new DefaultSearchBuilder(GetDirectory(indexName)); } - public IIndexDocument Get(string indexName, int id) { - throw new NotImplementedException(); + private string GetSettingsFileName(string indexName) { + return Path.Combine(_appDataFolder.MapPath(_basePath), indexName + ".settings.xml"); } + + public DateTime GetLastIndexUtc(string indexName) { + var settingsFileName = GetSettingsFileName(indexName); + + return File.Exists(settingsFileName) + ? DateTime.Parse(XDocument.Load(settingsFileName).Descendants(LastIndexUtc).First().Value) + : DefaultMinDateTime; + } + + public void SetLastIndexUtc(string indexName, DateTime lastIndexUtc) { + if ( lastIndexUtc < DefaultMinDateTime ) { + lastIndexUtc = DefaultMinDateTime; + } + + XDocument doc; + var settingsFileName = GetSettingsFileName(indexName); + if ( !File.Exists(settingsFileName) ) { + EnsureDirectoryExists(); + doc = new XDocument( + new XElement(Settings, + new XElement(LastIndexUtc, lastIndexUtc.ToString("s")))); + } + else { + doc = XDocument.Load(settingsFileName); + doc.Element(Settings).Element(LastIndexUtc).Value = lastIndexUtc.ToString("s"); + } + + doc.Save(settingsFileName); + } + } } diff --git a/src/Orchard.Web/Core/Indexing/Lucene/DefaultSearchBuilder.cs b/src/Orchard.Web/Core/Indexing/Lucene/DefaultSearchBuilder.cs index c73192c52..a68965594 100644 --- a/src/Orchard.Web/Core/Indexing/Lucene/DefaultSearchBuilder.cs +++ b/src/Orchard.Web/Core/Indexing/Lucene/DefaultSearchBuilder.cs @@ -2,12 +2,15 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using Lucene.Net.Analysis; +using Lucene.Net.Analysis.Tokenattributes; using Lucene.Net.Index; using Lucene.Net.Search; using Lucene.Net.Store; using Orchard.Logging; using Lucene.Net.Documents; using Orchard.Indexing; +using Lucene.Net.QueryParsers; namespace Orchard.Core.Indexing.Lucene { public class DefaultSearchBuilder : ISearchBuilder { @@ -23,6 +26,9 @@ namespace Orchard.Core.Indexing.Lucene { private readonly Dictionary _after; private string _sort; private bool _sortDescending; + private string _parse; + private readonly Analyzer _analyzer; + private string _defaultField; public ILogger Logger { get; set; } @@ -37,9 +43,21 @@ namespace Orchard.Core.Indexing.Lucene { _fields = new Dictionary(); _sort = String.Empty; _sortDescending = true; + _parse = String.Empty; + _analyzer = DefaultIndexProvider.CreateAnalyzer(); } - public ISearchBuilder Parse(string query) { + public ISearchBuilder Parse(string defaultField, string query) { + if ( String.IsNullOrWhiteSpace(defaultField) ) { + throw new ArgumentException("Default field can't be empty"); + } + + if ( String.IsNullOrWhiteSpace(query) ) { + throw new ArgumentException("Query can't be empty"); + } + + _defaultField = defaultField; + _parse = query; return this; } @@ -49,8 +67,17 @@ namespace Orchard.Core.Indexing.Lucene { public ISearchBuilder WithField(string field, string value, bool wildcardSearch) { - _fields[field] = value.Split(' ') + var tokens = new List(); + using(var sr = new System.IO.StringReader(value)) { + var stream = _analyzer.TokenStream(field, sr); + while(stream.IncrementToken()) { + tokens.Add(((TermAttribute)stream.GetAttribute(typeof(TermAttribute))).Term()); + } + } + + _fields[field] = tokens .Where(k => !String.IsNullOrWhiteSpace(k)) + .Select(QueryParser.Escape) .Select(k => wildcardSearch ? (Query)new PrefixQuery(new Term(field, k)) : new TermQuery(new Term(k))) .ToArray(); @@ -93,6 +120,10 @@ namespace Orchard.Core.Indexing.Lucene { } private Query CreateQuery() { + if(!String.IsNullOrWhiteSpace(_parse)) { + return new QueryParser(DefaultIndexProvider.LuceneVersion, _defaultField, DefaultIndexProvider.CreateAnalyzer()).Parse(_parse); + } + var query = new BooleanQuery(); if ( _fields.Keys.Count > 0 ) { // apply specific filters if defined @@ -124,7 +155,16 @@ namespace Orchard.Core.Indexing.Lucene { public IEnumerable Search() { var query = CreateQuery(); - var searcher = new IndexSearcher(_directory, true); + IndexSearcher searcher; + + try { + searcher = new IndexSearcher(_directory, true); + } + catch { + // index might not exist if it has been rebuilt + Logger.Information("Attempt to read a none existing index"); + return Enumerable.Empty(); + } try { var sort = String.IsNullOrEmpty(_sort) @@ -157,8 +197,17 @@ namespace Orchard.Core.Indexing.Lucene { public int Count() { var query = CreateQuery(); + IndexSearcher searcher; + + try { + searcher = new IndexSearcher(_directory, true); + } + catch { + // index might not exist if it has been rebuilt + Logger.Information("Attempt to read a none existing index"); + return 0; + } - var searcher = new IndexSearcher(_directory, true); try { var hits = searcher.Search(query, Int16.MaxValue); Logger.Information("Search results: {0}", hits.scoreDocs.Length); diff --git a/src/Orchard.Web/Core/Indexing/Models/IndexingSettingsRecord.cs b/src/Orchard.Web/Core/Indexing/Models/IndexingSettingsRecord.cs deleted file mode 100644 index 28cd06b9d..000000000 --- a/src/Orchard.Web/Core/Indexing/Models/IndexingSettingsRecord.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Orchard.Core.Indexing.Models { - public class IndexingSettingsRecord { - public virtual int Id { get; set; } - public virtual DateTime? LatestIndexingUtc { get; set; } - } -} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Indexing/Models/IndexingTaskRecord.cs b/src/Orchard.Web/Core/Indexing/Models/IndexingTaskRecord.cs index e0bb9f54b..3cab96601 100644 --- a/src/Orchard.Web/Core/Indexing/Models/IndexingTaskRecord.cs +++ b/src/Orchard.Web/Core/Indexing/Models/IndexingTaskRecord.cs @@ -3,7 +3,12 @@ using Orchard.ContentManagement.Records; namespace Orchard.Core.Indexing.Models { public class IndexingTaskRecord { + + public const int Update = 0; + public const int Delete = 1; + public virtual int Id { get; set; } + public virtual int Action { get; set; } public virtual DateTime? CreatedUtc { get; set; } public virtual ContentItemRecord ContentItemRecord { get; set; } } diff --git a/src/Orchard.Web/Core/Indexing/Services/CreateIndexingTaskHandler.cs b/src/Orchard.Web/Core/Indexing/Services/CreateIndexingTaskHandler.cs index 7924afb75..4da37eddc 100644 --- a/src/Orchard.Web/Core/Indexing/Services/CreateIndexingTaskHandler.cs +++ b/src/Orchard.Web/Core/Indexing/Services/CreateIndexingTaskHandler.cs @@ -19,11 +19,11 @@ namespace Orchard.Core.Indexing.Services { } void CreateIndexingTask(PublishContentContext context, ContentPart part) { - _indexingTaskManager.CreateTask(context.ContentItem); + _indexingTaskManager.CreateUpdateIndexTask(context.ContentItem); } void RemoveIndexingTask(RemoveContentContext context, ContentPart part) { - _indexingTaskManager.DeleteTasks(context.ContentItem); + _indexingTaskManager.CreateDeleteIndexTask(context.ContentItem); } } diff --git a/src/Orchard.Web/Core/Indexing/Services/IndexingTaskExecutor.cs b/src/Orchard.Web/Core/Indexing/Services/IndexingTaskExecutor.cs index 7fa3d8a6c..2cf986227 100644 --- a/src/Orchard.Web/Core/Indexing/Services/IndexingTaskExecutor.cs +++ b/src/Orchard.Web/Core/Indexing/Services/IndexingTaskExecutor.cs @@ -10,99 +10,171 @@ using Orchard.Logging; using Orchard.Services; using Orchard.Tasks; using Orchard.Core.Indexing.Models; +using Orchard.Tasks.Indexing; +using Orchard.Indexing; namespace Orchard.Core.Indexing.Services { /// /// Contains the logic which is regularly executed to retrieve index information from multiple content handlers. /// [UsedImplicitly] - public class IndexingTaskExecutor : IBackgroundTask { + public class IndexingTaskExecutor : IBackgroundTask, IIndexNotifierHandler { private readonly IClock _clock; private readonly IRepository _repository; - private readonly IRepository _settings; private readonly IEnumerable _handlers; private IIndexProvider _indexProvider; - private IIndexManager _indexManager; + private readonly IIndexManager _indexManager; + private readonly IIndexingTaskManager _indexingTaskManager; private readonly IContentManager _contentManager; - private const string SearchIndexName = "search"; + private const string SearchIndexName = "Search"; + + private readonly object _synLock = new object(); public IndexingTaskExecutor( IClock clock, IRepository repository, - IRepository settings, IEnumerable handlers, IIndexManager indexManager, + IIndexingTaskManager indexingTaskManager, IContentManager contentManager) { _clock = clock; _repository = repository; - _settings = settings; _indexManager = indexManager; _handlers = handlers; + _indexingTaskManager = indexingTaskManager; _contentManager = contentManager; Logger = NullLogger.Instance; } public ILogger Logger { get; set; } + public void UpdateIndex(string indexName) { + if (indexName == SearchIndexName) { + Sweep(); + } + } + public void Sweep() { - if(!_indexManager.HasIndexProvider()) { + if ( !System.Threading.Monitor.TryEnter(_synLock) ) { + Logger.Information("Index was requested but was already running"); return; } - _indexProvider = _indexManager.GetSearchIndexProvider(); + try { - // retrieve last processed index time - var settingsRecord = _settings.Table.FirstOrDefault(); + if (!_indexManager.HasIndexProvider()) { + return; + } - if (settingsRecord == null) { - _settings.Create(settingsRecord = new IndexingSettingsRecord { LatestIndexingUtc = new DateTime(1980, 1, 1)}); - } + _indexProvider = _indexManager.GetSearchIndexProvider(); + var updateIndexDocuments = new List(); + var lastIndexing = DateTime.UtcNow; - var lastIndexing = settingsRecord.LatestIndexingUtc; - settingsRecord.LatestIndexingUtc = _clock.UtcNow; + // Do we need to rebuild the full index (first time module is used, or rebuild index requested) ? + if (_indexProvider.IsEmpty(SearchIndexName)) { + Logger.Information("Rebuild index started"); - // retrieved not yet processed tasks - var taskRecords = _repository.Fetch(x => x.CreatedUtc >= lastIndexing) - .ToArray(); - - if (taskRecords.Length == 0) - return; + // mark current last task, as we should process older ones (in case of rebuild index only) + lastIndexing = _indexingTaskManager.GetLastTaskDateTime(); - Logger.Information("Processing {0} indexing tasks", taskRecords.Length); + // get every existing content item to index it + foreach (var contentItem in _contentManager.Query(VersionOptions.Published).List()) { + try { + var context = new IndexContentContext { + ContentItem = contentItem, + IndexDocument = _indexProvider.New(contentItem.Id) + }; - - if(!_indexProvider.Exists(SearchIndexName)) { - _indexProvider.CreateIndex(SearchIndexName); - } + // dispatch to handlers to retrieve index information + foreach (var handler in _handlers) { + handler.Indexing(context); + } - foreach (var taskRecord in taskRecords) { + if ( context.IndexDocument.IsDirty ) { + updateIndexDocuments.Add(context.IndexDocument); + foreach ( var handler in _handlers ) { + handler.Indexed(context); + } + } + } + catch (Exception ex) { + Logger.Warning(ex, "Unable to index content item #{0} during rebuild", contentItem.Id); + } + } + + } + else { + // retrieve last processed index time + lastIndexing = _indexProvider.GetLastIndexUtc(SearchIndexName); + } + + _indexProvider.SetLastIndexUtc(SearchIndexName, _clock.UtcNow); + + // retrieve not yet processed tasks + var taskRecords = _repository.Fetch(x => x.CreatedUtc > lastIndexing) + .ToArray(); + + // nothing to do ? + if (taskRecords.Length + updateIndexDocuments.Count == 0) + return; + + Logger.Information("Processing {0} indexing tasks", taskRecords.Length); + + if (!_indexProvider.Exists(SearchIndexName)) { + _indexProvider.CreateIndex(SearchIndexName); + } + + // process Delete tasks try { - var task = new IndexingTask(_contentManager, taskRecord); - var context = new IndexContentContext { - ContentItem = task.ContentItem, - IndexDocument = _indexProvider.New(task.ContentItem.Id) - }; - - // dispatch to handlers to retrieve index information - foreach (var handler in _handlers) { - handler.Indexing(context); - } - - _indexProvider.Store(SearchIndexName, context.IndexDocument); - - foreach ( var handler in _handlers ) { - handler.Indexed(context); - } + _indexProvider.Delete(SearchIndexName, taskRecords.Where(t => t.Action == IndexingTaskRecord.Delete).Select(t => t.Id)); } catch (Exception ex) { - Logger.Warning(ex, "Unable to process indexing task #{0}", taskRecord.Id); + Logger.Warning(ex, "An error occured while removing a document from the index"); } - } + // process Update tasks + foreach (var taskRecord in taskRecords.Where(t => t.Action == IndexingTaskRecord.Update)) { + var task = new IndexingTask(_contentManager, taskRecord); - _settings.Update(settingsRecord); + try { + var context = new IndexContentContext { + ContentItem = task.ContentItem, + IndexDocument = _indexProvider.New(task.ContentItem.Id) + }; + + // dispatch to handlers to retrieve index information + foreach (var handler in _handlers) { + handler.Indexing(context); + } + + if ( context.IndexDocument.IsDirty ) { + updateIndexDocuments.Add(context.IndexDocument); + + foreach (var handler in _handlers) { + handler.Indexed(context); + } + } + + } + catch (Exception ex) { + Logger.Warning(ex, "Unable to process indexing task #{0}", taskRecord.Id); + } + } + + if (updateIndexDocuments.Count > 0) { + try { + _indexProvider.Store(SearchIndexName, updateIndexDocuments); + } + catch (Exception ex) { + Logger.Warning(ex, "An error occured while adding a document to the index"); + } + } + } + finally { + System.Threading.Monitor.Exit(_synLock); + } } } } diff --git a/src/Orchard.Web/Core/Indexing/Services/IndexingTaskManager.cs b/src/Orchard.Web/Core/Indexing/Services/IndexingTaskManager.cs index aa37a69e7..9969920c3 100644 --- a/src/Orchard.Web/Core/Indexing/Services/IndexingTaskManager.cs +++ b/src/Orchard.Web/Core/Indexing/Services/IndexingTaskManager.cs @@ -16,13 +16,11 @@ namespace Orchard.Core.Indexing.Services { public class IndexingTaskManager : IIndexingTaskManager { private readonly IContentManager _contentManager; private readonly IRepository _repository; - private readonly IRepository _settings; private readonly IClock _clock; public IndexingTaskManager( IContentManager contentManager, IRepository repository, - IRepository settings, IClock clock) { _clock = clock; _repository = repository; @@ -32,70 +30,49 @@ namespace Orchard.Core.Indexing.Services { public ILogger Logger { get; set; } - public void CreateTask(ContentItem contentItem) { - if (contentItem == null) { + private void CreateTask(ContentItem contentItem, int action) { + if ( contentItem == null ) { throw new ArgumentNullException("contentItem"); } - // remove previous tasks for the same content item - var tasks = _repository - .Fetch(x => x.Id == contentItem.Id ) - .ToArray(); - - foreach (var task in tasks) { - _repository.Delete(task); - } + DeleteTasks(contentItem); var taskRecord = new IndexingTaskRecord { - CreatedUtc = _clock.UtcNow, - ContentItemRecord = contentItem.Record - }; + CreatedUtc = _clock.UtcNow, + ContentItemRecord = contentItem.Record, + Action = action + }; _repository.Create(taskRecord); - - Logger.Information("Indexing task created for [{0}:{1}]", contentItem.ContentType, contentItem.Id); - - } - - public IEnumerable GetTasks(DateTime? createdAfter) { - return _repository - .Fetch(x => x.CreatedUtc > createdAfter) - .Select(x => new IndexingTask(_contentManager, x)) - .Cast() - .ToReadOnlyCollection(); - } - - public void DeleteTasks(DateTime? createdBefore) { - Logger.Debug("Deleting Indexing tasks created before {0}", createdBefore); - - var tasks = _repository - .Fetch(x => x.CreatedUtc <= createdBefore); - - foreach (var task in tasks) { - _repository.Delete(task); - } - } - - public void DeleteTasks(ContentItem contentItem) { - Logger.Debug("Deleting Indexing tasks for ContentItem [{0}:{1}]", contentItem.ContentType, contentItem.Id); - - var tasks = _repository - .Fetch(x => x.Id == contentItem.Id); - - foreach (var task in tasks) { - _repository.Delete(task); - } - } - - public void RebuildIndex() { - var settingsRecord = _settings.Table.FirstOrDefault(); - if (settingsRecord == null) { - _settings.Create(settingsRecord = new IndexingSettingsRecord() ); - } - settingsRecord.LatestIndexingUtc = new DateTime(1980, 1, 1); - _settings.Update(settingsRecord); } + public void CreateUpdateIndexTask(ContentItem contentItem) { + + CreateTask(contentItem, IndexingTaskRecord.Update); + Logger.Information("Indexing task created for [{0}:{1}]", contentItem.ContentType, contentItem.Id); + } + + public void CreateDeleteIndexTask(ContentItem contentItem) { + + CreateTask(contentItem, IndexingTaskRecord.Delete); + Logger.Information("Deleting index task created for [{0}:{1}]", contentItem.ContentType, contentItem.Id); + } + + public DateTime GetLastTaskDateTime() { + return _repository.Table.Max(t => t.CreatedUtc) ?? DateTime.MinValue; + } + + /// + /// Removes existing tasks for the specified content item + /// + public void DeleteTasks(ContentItem contentItem) { + var tasks = _repository + .Fetch(x => x.ContentItemRecord.Id == contentItem.Id) + .ToArray(); + foreach (var task in tasks) { + _repository.Delete(task); + } + } } } diff --git a/src/Orchard.Web/Core/Localization/Drivers/LocalizedDriver.cs b/src/Orchard.Web/Core/Localization/Drivers/LocalizedDriver.cs new file mode 100644 index 000000000..8ef5c579f --- /dev/null +++ b/src/Orchard.Web/Core/Localization/Drivers/LocalizedDriver.cs @@ -0,0 +1,9 @@ +using JetBrains.Annotations; +using Orchard.ContentManagement.Drivers; +using Orchard.Core.Localization.Models; + +namespace Orchard.Core.Localization.Drivers { + [UsedImplicitly] + public class LocalizedDriver : ContentPartDriver { + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Localization/Handlers/LocalizedHandler.cs b/src/Orchard.Web/Core/Localization/Handlers/LocalizedHandler.cs new file mode 100644 index 000000000..9a1ac701e --- /dev/null +++ b/src/Orchard.Web/Core/Localization/Handlers/LocalizedHandler.cs @@ -0,0 +1,48 @@ +using JetBrains.Annotations; +using Orchard.Core.Localization.Models; +using Orchard.Data; +using Orchard.Localization; +using Orchard.ContentManagement; +using Orchard.ContentManagement.Handlers; +using Orchard.Localization.Services; +using Orchard.Settings; + +namespace Orchard.Core.Localization.Handlers { + [UsedImplicitly] + public class LocalizedHandler : ContentHandler { + private readonly ICultureManager _cultureManager; + private readonly IContentManager _contentManager; + + public LocalizedHandler(IRepository localizedRepository, ICultureManager cultureManager, IContentManager contentManager) { + _cultureManager = cultureManager; + _contentManager = contentManager; + T = NullLocalizer.Instance; + + Filters.Add(StorageFilter.For(localizedRepository)); + + OnActivated(InitializePart); + + OnLoaded(LazyLoadHandlers); + + OnIndexed((context, localized) => context.IndexDocument.Add("culture", localized.Culture != null ? localized.Culture.Culture : _cultureManager.GetSiteCulture()).Store(false).Analyze(false)); + } + + public Localizer T { get; set; } + + void LazyLoadHandlers(LoadContentContext context, Localized localized) { + localized.CultureField.Loader(ctx => _cultureManager.GetCultureById(localized.Record.CultureId)); + localized.MasterContentItemField.Loader(ctx => _contentManager.Get(localized.Record.MasterContentItemId)); + } + + void InitializePart(ActivatedContentContext context, Localized localized) { + localized.CultureField.Setter(cultureRecord => { + localized.Record.CultureId = cultureRecord.Id; + return cultureRecord; + }); + localized.MasterContentItemField.Setter(masterContentItem => { + localized.Record.MasterContentItemId = masterContentItem.ContentItem.Id; + return masterContentItem; + }); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Localization/Models/Localized.cs b/src/Orchard.Web/Core/Localization/Models/Localized.cs new file mode 100644 index 000000000..cc703cdae --- /dev/null +++ b/src/Orchard.Web/Core/Localization/Models/Localized.cs @@ -0,0 +1,33 @@ +using System.Web.Mvc; +using Orchard.ContentManagement; +using Orchard.ContentManagement.Utilities; +using Orchard.Localization.Records; + +namespace Orchard.Core.Localization.Models { + public sealed class Localized : ContentPart { + private readonly LazyField _culture = new LazyField(); + private readonly LazyField _masterContentItem = new LazyField(); + + public LazyField CultureField { get { return _culture; } } + public LazyField MasterContentItemField { get { return _masterContentItem; } } + + [HiddenInput(DisplayValue = false)] + public int Id { get { return ContentItem.Id; } } + + public CultureRecord Culture { + get { return _culture.Value; } + set { _culture.Value = value; } + } + + public IContent MasterContentItem { + get { return _masterContentItem.Value; } + set { _masterContentItem.Value = value; } + } + + public bool HasTranslationGroup { + get { + return Record.MasterContentItemId != 0; + } + } + } +} diff --git a/src/Orchard.Web/Core/Localization/Models/LocalizedRecord.cs b/src/Orchard.Web/Core/Localization/Models/LocalizedRecord.cs new file mode 100644 index 000000000..cab717d5f --- /dev/null +++ b/src/Orchard.Web/Core/Localization/Models/LocalizedRecord.cs @@ -0,0 +1,8 @@ +using Orchard.ContentManagement.Records; + +namespace Orchard.Core.Localization.Models { + public class LocalizedRecord : ContentPartRecord { + public virtual int CultureId { get; set; } + public virtual int MasterContentItemId { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Localization/Module.txt b/src/Orchard.Web/Core/Localization/Module.txt new file mode 100644 index 000000000..6cb082dda --- /dev/null +++ b/src/Orchard.Web/Core/Localization/Module.txt @@ -0,0 +1,11 @@ +name: Localization +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +description: Support for localizing content items for cultures. +features: + Localization: + Description: Localize content items. + Category: Core \ No newline at end of file diff --git a/src/Orchard.Web/Core/Navigation/AdminMenu.cs b/src/Orchard.Web/Core/Navigation/AdminMenu.cs index 0b46da08e..e95ddf1d2 100644 --- a/src/Orchard.Web/Core/Navigation/AdminMenu.cs +++ b/src/Orchard.Web/Core/Navigation/AdminMenu.cs @@ -1,13 +1,15 @@ -using Orchard.UI.Navigation; +using Orchard.Localization; +using Orchard.UI.Navigation; namespace Orchard.Core.Navigation { public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { - builder.Add("Site", "12", + builder.Add(T("Site"), "12", menu => menu - .Add("Manage Menu", "6.0", item => item.Action("Index", "Admin", new { area = "Navigation" }).Permission(Permissions.ManageMainMenu))); + .Add(T("Manage Menu"), "6.0", item => item.Action("Index", "Admin", new { area = "Navigation" }).Permission(Permissions.ManageMainMenu))); } } } diff --git a/src/Orchard.Web/Core/Navigation/Views/Admin/Index.ascx b/src/Orchard.Web/Core/Navigation/Views/Admin/Index.ascx index acfeeb1e3..9725443cd 100644 --- a/src/Orchard.Web/Core/Navigation/Views/Admin/Index.ascx +++ b/src/Orchard.Web/Core/Navigation/Views/Admin/Index.ascx @@ -38,7 +38,7 @@ using (Html.BeginFormAntiForgeryPost()) { %> %>

<%: T("Add New Item") %>

<% -using (Html.BeginFormAntiForgeryPost("/admin/navigation/create", FormMethod.Post)) { %> +using (Html.BeginFormAntiForgeryPost(Url.Action("create"), FormMethod.Post)) { %> diff --git a/src/Orchard.Web/Core/Orchard.Core.csproj b/src/Orchard.Web/Core/Orchard.Core.csproj index b7d14d395..2dd46e48f 100644 --- a/src/Orchard.Web/Core/Orchard.Core.csproj +++ b/src/Orchard.Web/Core/Orchard.Core.csproj @@ -47,6 +47,7 @@ 3.5 + @@ -68,6 +69,14 @@ + + + + + + + + @@ -86,6 +95,12 @@ + + + + + + @@ -109,16 +124,19 @@ + - + + + @@ -136,13 +154,24 @@ + + + + + + + + + + + @@ -173,9 +202,26 @@ + + + + + + + + + + + + + + + + + @@ -225,6 +271,8 @@ + + diff --git a/src/Orchard.Web/Core/Routable/Controllers/ItemController.cs b/src/Orchard.Web/Core/Routable/Controllers/ItemController.cs new file mode 100644 index 000000000..856f53052 --- /dev/null +++ b/src/Orchard.Web/Core/Routable/Controllers/ItemController.cs @@ -0,0 +1,51 @@ +using System; +using System.Linq; +using System.Web.Mvc; +using Orchard.ContentManagement; +using Orchard.ContentManagement.Aspects; +using Orchard.Core.Common.Models; +using Orchard.Core.Routable.Models; +using Orchard.Core.Routable.ViewModels; +using Orchard.Mvc.ViewModels; + +namespace Orchard.Core.Routable.Controllers { + [ValidateInput(false)] + public class ItemController : Controller { + private readonly IContentManager _contentManager; + private readonly IRoutablePathConstraint _routablePathConstraint; + + public ItemController(IContentManager contentManager, IRoutablePathConstraint routablePathConstraint) { + _contentManager = contentManager; + _routablePathConstraint = routablePathConstraint; + } + + public ActionResult Display(string path) { + var matchedPath = _routablePathConstraint.FindPath(path); + if (string.IsNullOrEmpty(matchedPath)) { + throw new ApplicationException("404 - should not have passed path constraint"); + } + + var hits = _contentManager + .Query(VersionOptions.Published) + .Where(r => r.Path == matchedPath) + .Slice(0, 2); + if (hits.Count() == 0) { + throw new ApplicationException("404 - should not have passed path constraint"); + } + if (hits.Count() != 1) { + throw new ApplicationException("Ambiguous content"); + } + var model = new RoutableDisplayViewModel { + Routable = _contentManager.BuildDisplayModel(hits.Single(), "Detail") + }; + PrepareDisplayViewModel(model.Routable); + return View("Display", model); + } + + private void PrepareDisplayViewModel(ContentItemViewModel itemViewModel) { + if (string.IsNullOrEmpty(itemViewModel.TemplateName)) { + itemViewModel.TemplateName = "Items/Contents.Item"; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Routable/Drivers/RoutableDriver.cs b/src/Orchard.Web/Core/Routable/Drivers/RoutableDriver.cs new file mode 100644 index 000000000..b3a2f6e02 --- /dev/null +++ b/src/Orchard.Web/Core/Routable/Drivers/RoutableDriver.cs @@ -0,0 +1,61 @@ +using JetBrains.Annotations; +using Orchard.ContentManagement; +using Orchard.ContentManagement.Drivers; +using Orchard.Core.Common.Models; +using Orchard.Core.Common.ViewModels; +using Orchard.Core.Common.Services; +using Orchard.Core.Routable.Models; +using Orchard.Localization; +using Orchard.UI.Notify; + +namespace Orchard.Core.Routable.Drivers { + public class RoutableDriver : ContentPartDriver { + protected override DriverResult Editor(IsRoutable part, IUpdateModel updater) { + part.Record.Title = "Routable #" + part.ContentItem.Id; + part.Record.Slug = "routable" + part.ContentItem.Id; + part.Record.Path = "routable" + part.ContentItem.Id; + return base.Editor(part, updater); + } + + //private const string TemplateName = "Parts/Common.Routable"; + + //private readonly IOrchardServices _services; + //private readonly IRoutableService _routableService; + //public Localizer T { get; set; } + + //protected override string Prefix { + // get { return "Routable"; } + //} + + //public Routable(IOrchardServices services, IRoutableService routableService) + //{ + // _services = services; + // _routableService = routableService; + + // T = NullLocalizer.Instance; + //} + + //protected override DriverResult Editor(RoutableAspect part) { + // var model = new RoutableEditorViewModel { Prefix = Prefix, RoutableAspect = part }; + // return ContentPartTemplate(model, TemplateName, Prefix).Location("primary", "before.5"); + //} + + //protected override DriverResult Editor(RoutableAspect part, IUpdateModel updater) { + // var model = new RoutableEditorViewModel { Prefix = Prefix, RoutableAspect = part }; + // updater.TryUpdateModel(model, Prefix, null, null); + + // if (!_routableService.IsSlugValid(part.Slug)){ + // updater.AddModelError("Routable.Slug", T("Please do not use any of the following characters in your slugs: \"/\", \":\", \"?\", \"#\", \"[\", \"]\", \"@\", \"!\", \"$\", \"&\", \"'\", \"(\", \")\", \"*\", \"+\", \",\", \";\", \"=\". No spaces are allowed (please use dashes or underscores instead).").ToString()); + // } + + // string originalSlug = part.Slug; + // if(!_routableService.ProcessSlug(part)) { + // _services.Notifier.Warning(T("Slugs in conflict. \"{0}\" is already set for a previously created {2} so now it has the slug \"{1}\"", + // originalSlug, part.Slug, part.ContentItem.ContentType)); + // } + + // return ContentPartTemplate(model, TemplateName, Prefix).Location("primary", "before.5"); + //} + + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Routable/Handlers/RoutableHandler.cs b/src/Orchard.Web/Core/Routable/Handlers/RoutableHandler.cs new file mode 100644 index 000000000..f83864661 --- /dev/null +++ b/src/Orchard.Web/Core/Routable/Handlers/RoutableHandler.cs @@ -0,0 +1,20 @@ +using System.Web.Routing; +using Orchard.ContentManagement; +using Orchard.ContentManagement.Handlers; +using Orchard.Core.Routable.Models; + +namespace Orchard.Core.Routable.Handlers { + public class RoutableHandler : ContentHandlerBase { + public override void GetContentItemMetadata(GetContentItemMetadataContext context) { + var routable = context.ContentItem.As(); + if (routable != null) { + context.Metadata.DisplayRouteValues = new RouteValueDictionary { + {"Area", "Routable"}, + {"Controller", "Item"}, + {"Action", "Display"}, + {"Path", context.ContentItem.As().Record.Path} + }; + } + } + } +} diff --git a/src/Orchard.Web/Core/Routable/IRoutablePathConstraint.cs b/src/Orchard.Web/Core/Routable/IRoutablePathConstraint.cs new file mode 100644 index 000000000..fd20e37e5 --- /dev/null +++ b/src/Orchard.Web/Core/Routable/IRoutablePathConstraint.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Web.Routing; + +namespace Orchard.Core.Routable { + public interface IRoutablePathConstraint : IRouteConstraint, ISingletonDependency { + void SetPaths(IEnumerable paths); + string FindPath(string path); + void AddPath(string path); + void RemovePath(string path); + } +} diff --git a/src/Orchard.Web/Core/Routable/Models/IsRoutable.cs b/src/Orchard.Web/Core/Routable/Models/IsRoutable.cs new file mode 100644 index 000000000..bf877799a --- /dev/null +++ b/src/Orchard.Web/Core/Routable/Models/IsRoutable.cs @@ -0,0 +1,18 @@ +using Orchard.ContentManagement; +using Orchard.ContentManagement.Aspects; +using Orchard.Core.Common.Models; + +namespace Orchard.Core.Routable.Models { + public class IsRoutable : ContentPart, IRoutableAspect { + public string Title { + get { return Record.Title; } + set { Record.Title = value; } + } + + public string Slug { + get { return Record.Slug; } + set { Record.Slug = value; } + } + + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Routable/Module.txt b/src/Orchard.Web/Core/Routable/Module.txt new file mode 100644 index 000000000..4cd6d7542 --- /dev/null +++ b/src/Orchard.Web/Core/Routable/Module.txt @@ -0,0 +1,11 @@ +Name: Routable +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas consectetur consequat risus, vel blandit arcu tincidunt eget. Nam rutrum nulla vestibulum dolor dapibus sagittis. Vivamus convallis faucibus accumsan. Suspendisse sapien enim, cursus at dignissim a, sollicitudin sit amet est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec sed urna magna, in luctus nulla. Pellentesque erat ipsum, convallis sed molestie tempus, mattis vel leo metus. +features: + Routable: + Description: Routable content part. + Category: Core2 diff --git a/src/Orchard.Web/Core/Routable/Routes.cs b/src/Orchard.Web/Core/Routable/Routes.cs new file mode 100644 index 000000000..ba858b27c --- /dev/null +++ b/src/Orchard.Web/Core/Routable/Routes.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using System.Web.Routing; +using Orchard.Mvc.Routes; + +namespace Orchard.Core.Routable { + public class Routes : IRouteProvider { + private readonly IRoutablePathConstraint _routablePathConstraint; + + public Routes(IRoutablePathConstraint routablePathConstraint) { + _routablePathConstraint = routablePathConstraint; + } + + public void GetRoutes(ICollection routes) { + foreach (var routeDescriptor in GetRoutes()) + routes.Add(routeDescriptor); + } + + public IEnumerable GetRoutes() { + return new[] { + new RouteDescriptor { + Priority = 10, + Route = new Route( + "{*path}", + new RouteValueDictionary { + {"area", "Routable"}, + {"controller", "Item"}, + {"action", "Display"} + }, + new RouteValueDictionary { + {"path", _routablePathConstraint} + }, + new RouteValueDictionary { + {"area", "Routable"} + }, + new MvcRouteHandler()) + } + }; + } + + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Routable/Services/RoutablePathConstraint.cs b/src/Orchard.Web/Core/Routable/Services/RoutablePathConstraint.cs new file mode 100644 index 000000000..ddde25951 --- /dev/null +++ b/src/Orchard.Web/Core/Routable/Services/RoutablePathConstraint.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Routing; +using JetBrains.Annotations; +using Orchard.Logging; + +namespace Orchard.Core.Routable.Services { + [UsedImplicitly] + public class RoutablePathConstraint : IRoutablePathConstraint { + /// + /// Singleton object, per Orchard Shell instance. We need to protect concurrent access to the dictionary. + /// + private readonly object _syncLock = new object(); + private IDictionary _paths = new Dictionary(); + + public RoutablePathConstraint() { + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + + public void SetPaths(IEnumerable paths) { + // Make a copy to avoid performing potential lazy computation inside the lock + var slugsArray = paths.ToArray(); + + lock (_syncLock) { + _paths = slugsArray.Distinct(StringComparer.OrdinalIgnoreCase).ToDictionary(value => value, StringComparer.OrdinalIgnoreCase); + } + } + + public string FindPath(string path) { + lock (_syncLock) { + string actual; + return _paths.TryGetValue(path, out actual) ? actual : path; + } + } + + public void AddPath(string path) { + lock (_syncLock) { + _paths[path] = path; + } + } + + public void RemovePath(string path) { + lock (_syncLock) { + _paths.Remove(path); + } + } + + public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { + if (routeDirection == RouteDirection.UrlGeneration) + return true; + + object value; + if (values.TryGetValue(parameterName, out value)) { + var parameterValue = Convert.ToString(value); + + lock (_syncLock) { + return _paths.ContainsKey(parameterValue); + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Routable/Services/RoutablePathConstraintUpdator.cs b/src/Orchard.Web/Core/Routable/Services/RoutablePathConstraintUpdator.cs new file mode 100644 index 000000000..5f5a8eb3d --- /dev/null +++ b/src/Orchard.Web/Core/Routable/Services/RoutablePathConstraintUpdator.cs @@ -0,0 +1,36 @@ +using System.Linq; +using JetBrains.Annotations; +using Orchard.Core.Common.Models; +using Orchard.Data; +using Orchard.Environment; +using Orchard.Tasks; + +namespace Orchard.Core.Routable.Services { + [UsedImplicitly] + public class RoutablePathConstraintUpdator : IOrchardShellEvents, IBackgroundTask { + private readonly IRoutablePathConstraint _pageSlugConstraint; + private readonly IRepository _repository; + + public RoutablePathConstraintUpdator(IRoutablePathConstraint pageSlugConstraint, IRepository repository) { + _pageSlugConstraint = pageSlugConstraint; + _repository = repository; + } + + void IOrchardShellEvents.Activated() { + Refresh(); + } + + void IOrchardShellEvents.Terminating() { + } + + void IBackgroundTask.Sweep() { + Refresh(); + } + + private void Refresh() { + var slugs = _repository.Fetch(r => r.ContentItemVersionRecord.Published && r.Path != "" && r.Path != null).Select(r => r.Path); + + _pageSlugConstraint.SetPaths(slugs); + } + } +} diff --git a/src/Orchard.Web/Core/Routable/ViewModels/RoutableDisplayViewModel.cs b/src/Orchard.Web/Core/Routable/ViewModels/RoutableDisplayViewModel.cs new file mode 100644 index 000000000..e07d266f5 --- /dev/null +++ b/src/Orchard.Web/Core/Routable/ViewModels/RoutableDisplayViewModel.cs @@ -0,0 +1,8 @@ +using Orchard.ContentManagement.Aspects; +using Orchard.Mvc.ViewModels; + +namespace Orchard.Core.Routable.ViewModels { + public class RoutableDisplayViewModel : BaseViewModel { + public ContentItemViewModel Routable {get;set;} + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Routable/Views/Item/Display.aspx b/src/Orchard.Web/Core/Routable/Views/Item/Display.aspx new file mode 100644 index 000000000..c4878ad6d --- /dev/null +++ b/src/Orchard.Web/Core/Routable/Views/Item/Display.aspx @@ -0,0 +1,3 @@ +<%@ Page Language="C#" Inherits="Orchard.Mvc.ViewPage" %> +<% Html.AddTitleParts(Model.Routable.Item.Title); %> +<%=Html.DisplayForItem(m=>m.Routable) %> diff --git a/src/Orchard.Web/Core/Routable/Views/Web.config b/src/Orchard.Web/Core/Routable/Views/Web.config new file mode 100644 index 000000000..e065d8735 --- /dev/null +++ b/src/Orchard.Web/Core/Routable/Views/Web.config @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Core/Settings/AdminMenu.cs b/src/Orchard.Web/Core/Settings/AdminMenu.cs index 498f3266e..01f32cd77 100644 --- a/src/Orchard.Web/Core/Settings/AdminMenu.cs +++ b/src/Orchard.Web/Core/Settings/AdminMenu.cs @@ -1,13 +1,15 @@ -using Orchard.UI.Navigation; +using Orchard.Localization; +using Orchard.UI.Navigation; namespace Orchard.Core.Settings { public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { - builder.Add("Site", "11", + builder.Add(T("Site"), "11", menu => menu - .Add("Manage Settings", "2.0", item => item.Action("Index", "Admin", new { area = "Settings" }).Permission(Permissions.ManageSettings))); + .Add(T("Manage Settings"), "2.0", item => item.Action("Index", "Admin", new { area = "Settings" }).Permission(Permissions.ManageSettings))); } } } diff --git a/src/Orchard.Web/Core/Settings/Controllers/AdminController.cs b/src/Orchard.Web/Core/Settings/Controllers/AdminController.cs index 747efb49a..71d5b36dc 100644 --- a/src/Orchard.Web/Core/Settings/Controllers/AdminController.cs +++ b/src/Orchard.Web/Core/Settings/Controllers/AdminController.cs @@ -1,8 +1,11 @@ -using System.Web.Mvc; +using System.Globalization; +using System.Linq; +using System.Web.Mvc; using Orchard.Core.Settings.Models; using Orchard.Core.Settings.ViewModels; using Orchard.Localization; using Orchard.ContentManagement; +using Orchard.Localization.Services; using Orchard.Settings; using Orchard.UI.Notify; @@ -10,10 +13,12 @@ namespace Orchard.Core.Settings.Controllers { [ValidateInput(false)] public class AdminController : Controller, IUpdateModel { private readonly ISiteService _siteService; + private readonly ICultureManager _cultureManager; public IOrchardServices Services { get; private set; } - public AdminController(ISiteService siteService, IOrchardServices services) { + public AdminController(ISiteService siteService, IOrchardServices services, ICultureManager cultureManager) { _siteService = siteService; + _cultureManager = cultureManager; Services = services; T = NullLocalizer.Instance; } @@ -25,7 +30,8 @@ namespace Orchard.Core.Settings.Controllers { return new HttpUnauthorizedResult(); var model = new SettingsIndexViewModel { - Site = _siteService.GetSiteSettings().As() + Site = _siteService.GetSiteSettings().As(), + SiteCultures = _cultureManager.ListCultures() }; model.ViewModel = Services.ContentManager.BuildEditorModel(model.Site); return View(model); @@ -47,6 +53,39 @@ namespace Orchard.Core.Settings.Controllers { return RedirectToAction("Index"); } + public ActionResult Culture() { + //todo: class and/or method attributes for our auth? + if (!Services.Authorizer.Authorize(Permissions.ManageSettings, T("Not authorized to manage settings"))) + return new HttpUnauthorizedResult(); + + var viewModel = new SiteCulturesViewModel { + CurrentCulture = CultureInfo.CurrentCulture.Name, + SiteCultures = _cultureManager.ListCultures(), + }; + viewModel.AvailableSystemCultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures) + .Select(ci => ci.Name) + .Where(s => !viewModel.SiteCultures.Contains(s)); + + return View(viewModel); + } + + [HttpPost] + public ActionResult AddCulture(string cultureName) { + if (!Services.Authorizer.Authorize(Permissions.ManageSettings, T("Not authorized to manage settings"))) + return new HttpUnauthorizedResult(); + + _cultureManager.AddCulture(cultureName); + return RedirectToAction("Culture"); + } + + [HttpPost] + public ActionResult DeleteCulture(string cultureName) { + if (!Services.Authorizer.Authorize(Permissions.ManageSettings, T("Not authorized to manage settings"))) + return new HttpUnauthorizedResult(); + + _cultureManager.DeleteCulture(cultureName); + return RedirectToAction("Culture"); + } bool IUpdateModel.TryUpdateModel(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) { return TryUpdateModel(model, prefix, includeProperties, excludeProperties); diff --git a/src/Orchard.Web/Core/Settings/Metadata/ContentDefinitionManager.cs b/src/Orchard.Web/Core/Settings/Metadata/ContentDefinitionManager.cs new file mode 100644 index 000000000..c3be204ac --- /dev/null +++ b/src/Orchard.Web/Core/Settings/Metadata/ContentDefinitionManager.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using Orchard.ContentManagement.MetaData; +using Orchard.ContentManagement.MetaData.Models; +using Orchard.Core.Settings.Metadata.Records; +using Orchard.Data; +using Orchard.Logging; +using Orchard.Utility.Extensions; + +namespace Orchard.Core.Settings.Metadata { + public class ContentDefinitionManager : Component, IContentDefinitionManager { + private readonly IRepository _typeDefinitionRepository; + private readonly IRepository _partDefinitionRepository; + private readonly IMapper> _settingsReader; + private readonly IMapper, XElement> _settingsWriter; + + public ContentDefinitionManager( + IRepository typeDefinitionRepository, + IRepository partDefinitionRepository, + IMapper> settingsReader, + IMapper, XElement> settingsWriter) { + _typeDefinitionRepository = typeDefinitionRepository; + _partDefinitionRepository = partDefinitionRepository; + _settingsReader = settingsReader; + _settingsWriter = settingsWriter; + } + + public ContentTypeDefinition GetTypeDefinition(string name) { + return _typeDefinitionRepository.Fetch(x => x.Name == name).Select(Build).SingleOrDefault(); + } + + public ContentPartDefinition GetPartDefinition(string name) { + return _partDefinitionRepository.Fetch(x => x.Name == name).Select(Build).SingleOrDefault(); + } + + public IEnumerable ListTypeDefinitions() { + return _typeDefinitionRepository.Fetch(x => !x.Hidden).Select(Build).ToReadOnlyCollection(); + } + + public IEnumerable ListPartDefinitions() { + return _partDefinitionRepository.Fetch(x => !x.Hidden).Select(Build).ToReadOnlyCollection(); + } + + public void StoreTypeDefinition(ContentTypeDefinition contentTypeDefinition) { + Apply(contentTypeDefinition, Acquire(contentTypeDefinition)); + } + + public void StorePartDefinition(ContentPartDefinition contentPartDefinition) { + throw new NotImplementedException(); + } + + private ContentTypeDefinitionRecord Acquire(ContentTypeDefinition contentTypeDefinition) { + var result = _typeDefinitionRepository.Fetch(x => x.Name == contentTypeDefinition.Name).SingleOrDefault(); + if (result == null) { + result = new ContentTypeDefinitionRecord { Name = contentTypeDefinition.Name }; + _typeDefinitionRepository.Create(result); + } + return result; + } + + private ContentPartDefinitionRecord Acquire(ContentPartDefinition contentPartDefinition) { + var result = _partDefinitionRepository.Fetch(x => x.Name == contentPartDefinition.Name).SingleOrDefault(); + if (result == null) { + result = new ContentPartDefinitionRecord { Name = contentPartDefinition.Name }; + _partDefinitionRepository.Create(result); + } + return result; + } + + private void Apply(ContentTypeDefinition model, ContentTypeDefinitionRecord record) { + record.Settings = _settingsWriter.Map(model.Settings).ToString(); + + var toRemove = record.ContentTypePartDefinitionRecords + .Where(partDefinitionRecord => !model.Parts.Any(part => partDefinitionRecord.ContentPartDefinitionRecord.Name == part.PartDefinition.Name)) + .ToList(); + + foreach (var remove in toRemove) { + record.ContentTypePartDefinitionRecords.Remove(remove); + } + + foreach (var part in model.Parts) { + var partName = part.PartDefinition.Name; + var typePartRecord = record.ContentTypePartDefinitionRecords.SingleOrDefault(r => r.ContentPartDefinitionRecord.Name == partName); + if (typePartRecord == null) { + typePartRecord = new ContentTypePartDefinitionRecord { ContentPartDefinitionRecord = Acquire(part.PartDefinition) }; + record.ContentTypePartDefinitionRecords.Add(typePartRecord); + } + Apply(part, typePartRecord); + } + } + + private void Apply(ContentTypeDefinition.Part model, ContentTypePartDefinitionRecord record) { + record.Settings = Compose(_settingsWriter.Map(model.Settings)); + } + + + + ContentTypeDefinition Build(ContentTypeDefinitionRecord source) { + return new ContentTypeDefinition( + source.Name, + source.ContentTypePartDefinitionRecords.Select(Build), + _settingsReader.Map(Parse(source.Settings))); + } + + ContentTypeDefinition.Part Build(ContentTypePartDefinitionRecord source) { + return new ContentTypeDefinition.Part( + Build(source.ContentPartDefinitionRecord), + _settingsReader.Map(Parse(source.Settings))); + } + + ContentPartDefinition Build(ContentPartDefinitionRecord source) { + return new ContentPartDefinition( + source.Name, + source.ContentPartFieldDefinitionRecords.Select(Build), + _settingsReader.Map(Parse(source.Settings))); + } + + ContentPartDefinition.Field Build(ContentPartFieldDefinitionRecord source) { + return new ContentPartDefinition.Field( + Build(source.ContentFieldDefinitionRecord), + source.Name, + _settingsReader.Map(Parse(source.Settings))); + } + + ContentFieldDefinition Build(ContentFieldDefinitionRecord source) { + return new ContentFieldDefinition(source.Name); + } + + XElement Parse(string settings) { + if (string.IsNullOrEmpty(settings)) + return null; + + try { + return XElement.Parse(settings); + } + catch (Exception ex) { + Logger.Error(ex, "Unable to parse settings xml"); + return null; + } + } + string Compose(XElement map) { + if (map == null) + return null; + + return map.ToString(); + } + } +} diff --git a/src/Orchard.Web/Core/Settings/Metadata/Records/ContentFieldDefinitionRecord.cs b/src/Orchard.Web/Core/Settings/Metadata/Records/ContentFieldDefinitionRecord.cs new file mode 100644 index 000000000..a8cf97aff --- /dev/null +++ b/src/Orchard.Web/Core/Settings/Metadata/Records/ContentFieldDefinitionRecord.cs @@ -0,0 +1,6 @@ +namespace Orchard.Core.Settings.Metadata.Records { + public class ContentFieldDefinitionRecord { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + } +} diff --git a/src/Orchard.Web/Core/Settings/Metadata/Records/ContentPartDefinitionRecord.cs b/src/Orchard.Web/Core/Settings/Metadata/Records/ContentPartDefinitionRecord.cs new file mode 100644 index 000000000..76045f25b --- /dev/null +++ b/src/Orchard.Web/Core/Settings/Metadata/Records/ContentPartDefinitionRecord.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Orchard.Data.Conventions; + +namespace Orchard.Core.Settings.Metadata.Records { + public class ContentPartDefinitionRecord { + public ContentPartDefinitionRecord() { + ContentPartFieldDefinitionRecords = new List(); + } + + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual bool Hidden { get; set; } + public virtual string Settings { get; set; } + + [CascadeAllDeleteOrphan] + public virtual IList ContentPartFieldDefinitionRecords { get; set; } + + } +} diff --git a/src/Orchard.Web/Core/Settings/Metadata/Records/ContentPartFieldDefinitionRecord.cs b/src/Orchard.Web/Core/Settings/Metadata/Records/ContentPartFieldDefinitionRecord.cs new file mode 100644 index 000000000..f85da77d0 --- /dev/null +++ b/src/Orchard.Web/Core/Settings/Metadata/Records/ContentPartFieldDefinitionRecord.cs @@ -0,0 +1,8 @@ +namespace Orchard.Core.Settings.Metadata.Records { + public class ContentPartFieldDefinitionRecord { + public virtual int Id { get; set; } + public virtual ContentFieldDefinitionRecord ContentFieldDefinitionRecord { get; set; } + public virtual string Name { get; set; } + public virtual string Settings { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Settings/Metadata/Records/ContentTypeDefinitionRecord.cs b/src/Orchard.Web/Core/Settings/Metadata/Records/ContentTypeDefinitionRecord.cs new file mode 100644 index 000000000..0f6806c19 --- /dev/null +++ b/src/Orchard.Web/Core/Settings/Metadata/Records/ContentTypeDefinitionRecord.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Orchard.Data.Conventions; + +namespace Orchard.Core.Settings.Metadata.Records { + public class ContentTypeDefinitionRecord { + public ContentTypeDefinitionRecord() { + ContentTypePartDefinitionRecords = new List(); + } + + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual bool Hidden { get; set; } + public virtual string Settings { get; set; } + + [CascadeAllDeleteOrphan] + public virtual IList ContentTypePartDefinitionRecords { get; set; } + } + +} diff --git a/src/Orchard.Web/Core/Settings/Metadata/Records/ContentTypePartDefinitionRecord.cs b/src/Orchard.Web/Core/Settings/Metadata/Records/ContentTypePartDefinitionRecord.cs new file mode 100644 index 000000000..72a6fe137 --- /dev/null +++ b/src/Orchard.Web/Core/Settings/Metadata/Records/ContentTypePartDefinitionRecord.cs @@ -0,0 +1,7 @@ +namespace Orchard.Core.Settings.Metadata.Records { + public class ContentTypePartDefinitionRecord { + public virtual int Id { get; set; } + public virtual ContentPartDefinitionRecord ContentPartDefinitionRecord { get; set; } + public virtual string Settings { get; set; } + } +} diff --git a/src/Orchard.Web/Core/Settings/Styles/admin.css b/src/Orchard.Web/Core/Settings/Styles/admin.css new file mode 100644 index 000000000..53fc62882 --- /dev/null +++ b/src/Orchard.Web/Core/Settings/Styles/admin.css @@ -0,0 +1,18 @@ +.site-cultures { + font-size:1.2em; + overflow:auto; +} +.site-cultures li { + clear:left; + float:left; + overflow:auto; + padding:1px 6px; + margin:0 0 0 1em; +} +.site-cultures li:hover { + background:#EAEAEA; +} +.site-cultures div { + float:left; + width:6em; +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Settings/ViewModels/SettingsIndexViewModel.cs b/src/Orchard.Web/Core/Settings/ViewModels/SettingsIndexViewModel.cs index 724487bd8..bce0c09da 100644 --- a/src/Orchard.Web/Core/Settings/ViewModels/SettingsIndexViewModel.cs +++ b/src/Orchard.Web/Core/Settings/ViewModels/SettingsIndexViewModel.cs @@ -1,4 +1,5 @@ -using System.Web.Mvc; +using System.Collections.Generic; +using System.Web.Mvc; using Orchard.ContentManagement; using Orchard.Mvc.ViewModels; using Orchard.Core.Settings.Models; @@ -6,6 +7,7 @@ using Orchard.Core.Settings.Models; namespace Orchard.Core.Settings.ViewModels { public class SettingsIndexViewModel : BaseViewModel { public SiteSettings Site { get; set; } + public IEnumerable SiteCultures { get; set; } public ContentItemViewModel ViewModel { get; set; } @@ -20,12 +22,16 @@ namespace Orchard.Core.Settings.ViewModels { set { Site.As().Record.PageTitleSeparator = value; } } - public string SiteName - { + public string SiteName { get { return Site.As().Record.SiteName; } set { Site.As().Record.SiteName = value; } } + public string SiteCulture { + get { return Site.As().Record.SiteCulture; } + set { Site.As().Record.SiteCulture = value; } + } + public string SuperUser { get { return Site.As().Record.SuperUser; } set { Site.As().Record.SuperUser = value; } diff --git a/src/Orchard.Web/Core/Settings/ViewModels/SiteCulturesViewModel.cs b/src/Orchard.Web/Core/Settings/ViewModels/SiteCulturesViewModel.cs new file mode 100644 index 000000000..98dd899d5 --- /dev/null +++ b/src/Orchard.Web/Core/Settings/ViewModels/SiteCulturesViewModel.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Orchard.Mvc.ViewModels; + +namespace Orchard.Core.Settings.ViewModels { + public class SiteCulturesViewModel : BaseViewModel { + public string CurrentCulture { get; set; } + public IEnumerable SiteCultures { get; set; } + public IEnumerable AvailableSystemCultures { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Settings/Views/Admin/Culture.ascx b/src/Orchard.Web/Core/Settings/Views/Admin/Culture.ascx new file mode 100644 index 000000000..452c8091e --- /dev/null +++ b/src/Orchard.Web/Core/Settings/Views/Admin/Culture.ascx @@ -0,0 +1,17 @@ +<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> +<%@ Import Namespace="Orchard.Core.Settings.ViewModels" %><% + Html.RegisterStyle("admin.css"); %> +

<%:Html.TitleForPage(T("Manage Settings").ToString()) %>

+

<%:T("Cultures this site supports") %>

+<%=Html.UnorderedList( + Model.SiteCultures.OrderBy(s => s), + (s, i) => Html.DisplayFor(scvm => s, s == Model.CurrentCulture ? "CurrentCulture" : "RemovableCulture", "").ToString(), + "site-cultures", "culture", "odd")%> +<% using (Html.BeginFormAntiForgeryPost("AddCulture")) { %> +<%:Html.ValidationSummary() %> +
+ <%:T("Add a culture...") %> + <%:Html.DropDownList("CultureName", new SelectList(Model.AvailableSystemCultures.OrderBy(s => s), Model.CurrentCulture)) %> + +
+<% } %> \ No newline at end of file diff --git a/src/Orchard.Web/Core/Settings/Views/Admin/Index.ascx b/src/Orchard.Web/Core/Settings/Views/Admin/Index.ascx index e2de4d1ae..c01dc960a 100644 --- a/src/Orchard.Web/Core/Settings/Views/Admin/Index.ascx +++ b/src/Orchard.Web/Core/Settings/Views/Admin/Index.ascx @@ -1,29 +1,35 @@ <%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> -<%@ Import Namespace="Orchard.Core.Settings.ViewModels"%> -

<%: Html.TitleForPage(T("Manage Settings").ToString())%>

-<%using (Html.BeginFormAntiForgeryPost()) { %> -<%: Html.ValidationSummary() %> +<%@ Import Namespace="Orchard.Core.Settings.ViewModels" %> +

<%:Html.TitleForPage(T("Manage Settings").ToString()) %>

+<% using (Html.BeginFormAntiForgeryPost()) { %> +<%:Html.ValidationSummary() %>
- <%: T("Global Settings")%> + <%:T("Global Settings") %>
- - <%: Html.EditorFor(m => m.SiteName)%> - <%: Html.ValidationMessage("SiteName", "*") %> + + <%:Html.EditorFor(m => m.SiteName) %> + <%:Html.ValidationMessage("SiteName", "*") %>
- - <%: Html.EditorFor(x => x.PageTitleSeparator)%> - <%: Html.ValidationMessage("PageTitleSeparator", "*")%> + + <%:Html.DropDownList("SiteCulture", new SelectList(Model.SiteCultures, Model.SiteCulture)) %> + <%:Html.ValidationMessage("SiteCulture", "*") %> + <%:Html.ActionLink(T("Add or remove supported cultures for the site.").ToString(), "Culture") %>
- - <%: Html.EditorFor(x=>x.SuperUser) %> - <%: Html.ValidationMessage("SuperUser", "*") %> + + <%:Html.EditorFor(x => x.PageTitleSeparator) %> + <%:Html.ValidationMessage("PageTitleSeparator", "*") %> +
+
+ + <%:Html.EditorFor(x=>x.SuperUser) %> + <%:Html.ValidationMessage("SuperUser", "*") %>
-<%: Html.EditorForItem(Model.ViewModel) %> +<%:Html.EditorForItem(Model.ViewModel) %>
- <%: Html.EditorFor(s => s.Id) %> - " /> + <%:Html.EditorFor(s => s.Id) %> + " />
<% } %> diff --git a/src/Orchard.Web/Core/Settings/Views/DisplayTemplates/CurrentCulture.ascx b/src/Orchard.Web/Core/Settings/Views/DisplayTemplates/CurrentCulture.ascx new file mode 100644 index 000000000..10d49b6ac --- /dev/null +++ b/src/Orchard.Web/Core/Settings/Views/DisplayTemplates/CurrentCulture.ascx @@ -0,0 +1,2 @@ +<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> +<%:Model %> \ No newline at end of file diff --git a/src/Orchard.Web/Core/Settings/Views/DisplayTemplates/RemovableCulture.ascx b/src/Orchard.Web/Core/Settings/Views/DisplayTemplates/RemovableCulture.ascx new file mode 100644 index 000000000..3abc6bfdf --- /dev/null +++ b/src/Orchard.Web/Core/Settings/Views/DisplayTemplates/RemovableCulture.ascx @@ -0,0 +1,6 @@ +<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> +
<%:Model %>
+<% using (Html.BeginFormAntiForgeryPost(Url.Action("DeleteCulture", "Admin", new { area = "Settings" }), FormMethod.Post, new {@class = "inline link"})) { %> + <%=Html.Hidden("cultureName", Model, new { id = "" }) %> + +<% } %> \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Futures.Widgets/Futures.Widgets.csproj b/src/Orchard.Web/Modules/Futures.Widgets/Futures.Widgets.csproj index 1ba9e0991..b4442b16c 100644 --- a/src/Orchard.Web/Modules/Futures.Widgets/Futures.Widgets.csproj +++ b/src/Orchard.Web/Modules/Futures.Widgets/Futures.Widgets.csproj @@ -12,7 +12,7 @@ Futures.Widgets Futures.Widgets v4.0 - true + false 3.5 diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Blogs/AdminMenu.cs index d692251d9..807ba6336 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Orchard.Blogs/AdminMenu.cs @@ -1,5 +1,6 @@ using System.Linq; using Orchard.Blogs.Services; +using Orchard.Localization; using Orchard.UI.Navigation; namespace Orchard.Blogs { @@ -10,10 +11,12 @@ namespace Orchard.Blogs { _blogService = blogService; } + public Localizer T { get; set; } + public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { - builder.Add("Blogs", "2", BuildMenu); + builder.Add(T("Blogs"), "2", BuildMenu); } private void BuildMenu(NavigationItemBuilder menu) { @@ -22,20 +25,20 @@ namespace Orchard.Blogs { var singleBlog = blogCount == 1 ? blogs.ElementAt(0) : null; if (blogCount > 0 && singleBlog == null) - menu.Add("Manage Blogs", "1.0", + menu.Add(T("Manage Blogs"), "1.0", item => item.Action("List", "BlogAdmin", new {area = "Orchard.Blogs"}).Permission(Permissions.MetaListBlogs)); else if (singleBlog != null) - menu.Add("Manage Blog", "1.0", + menu.Add(T("Manage Blog"), "1.0", item => item.Action("Item", "BlogAdmin", new {area = "Orchard.Blogs", blogSlug = singleBlog.Slug}).Permission(Permissions.MetaListBlogs)); - menu.Add("Add New Blog", "1.1", + menu.Add(T("Add New Blog"), "1.1", item => item.Action("Create", "BlogAdmin", new {area = "Orchard.Blogs"}).Permission(Permissions.ManageBlogs)); if (singleBlog != null) - menu.Add("Add New Post", "1.2", + menu.Add(T("Add New Post"), "1.2", item => item.Action("Create", "BlogPostAdmin", new {area = "Orchard.Blogs", blogSlug = singleBlog.Slug}).Permission(Permissions.PublishBlogPost)); } diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Commands/BlogCommands.cs b/src/Orchard.Web/Modules/Orchard.Blogs/Commands/BlogCommands.cs new file mode 100644 index 000000000..8456fc929 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Commands/BlogCommands.cs @@ -0,0 +1,128 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using Orchard.Blogs.Models; +using Orchard.Commands; +using Orchard.ContentManagement; +using Orchard.ContentManagement.Aspects; +using Orchard.Core.Common.Models; +using Orchard.Core.Navigation.Models; +using Orchard.Security; +using System.IO; +using Orchard.Blogs.Services; +using Orchard.Core.Navigation.Services; + +namespace Orchard.Blogs.Commands { + public class BlogCommands : DefaultOrchardCommandHandler { + private readonly IContentManager _contentManager; + private readonly IMembershipService _membershipService; + private readonly IBlogService _blogService; + private readonly IMenuService _menuService; + + public BlogCommands( + IContentManager contentManager, + IMembershipService membershipService, + IBlogService blogService, + IMenuService menuService) { + _contentManager = contentManager; + _membershipService = membershipService; + _blogService = blogService; + _menuService = menuService; + } + + [OrchardSwitch] + public string FeedUrl { get; set; } + + [OrchardSwitch] + public string Slug { get; set; } + + [OrchardSwitch] + public string Title { get; set; } + + [OrchardSwitch] + public string MenuText { get; set; } + + [CommandName("blog create")] + [CommandHelp("blog create /Slug: /Title: [/MenuText:<menu text>]\r\n\t" + "Creates a new Blog")] + [OrchardSwitches("Slug,Title,MenuText")] + public string Create() { + var admin = _membershipService.GetUser("admin"); + + if(!IsSlugValid(Slug)) { + return "Invalid Slug provided. Blog creation failed."; + } + + var blog = _contentManager.New("blog"); + blog.As<ICommonAspect>().Owner = admin; + blog.As<RoutableAspect>().Slug = Slug; + blog.As<RoutableAspect>().Title = Title; + if ( !String.IsNullOrWhiteSpace(MenuText) ) { + blog.As<MenuPart>().OnMainMenu = true; + blog.As<MenuPart>().MenuPosition = _menuService.Get().Select(menuPart => menuPart.MenuPosition).Max() + 1 + ".0"; + blog.As<MenuPart>().MenuText = MenuText; + } + _contentManager.Create(blog); + + return "Blog created successfully"; + } + + [CommandName("blog import")] + [CommandHelp("blog import /Slug:<slug> /FeedUrl:<feed url>\r\n\t" + "Import all items from <feed url> into the blog at the specified <slug>")] + [OrchardSwitches("FeedUrl,Slug")] + public string Import() { + var admin = _membershipService.GetUser("admin"); + + XDocument doc; + + try { + Context.Output.WriteLine("Loading feed..."); + doc = XDocument.Load(FeedUrl); + Context.Output.WriteLine("Found {0} items", doc.Descendants("item").Count()); + } + catch ( Exception ex ) { + Context.Output.WriteLine(T("An error occured while loading the file: " + ex.Message)); + return "Import terminated."; + } + + var blog = _blogService.Get(Slug); + + if ( blog == null ) { + return "Blog not found at specified slug: " + Slug; + } + + foreach ( var item in doc.Descendants("item") ) { + string postName = item.Element("title").Value; + + Context.Output.WriteLine("Adding post: {0}...", postName.Substring(0, Math.Min(postName.Length, 40))); + var post = _contentManager.New("blogpost"); + post.As<ICommonAspect>().Owner = admin; + post.As<ICommonAspect>().Container = blog; + post.As<RoutableAspect>().Slug = Slugify(postName); + post.As<RoutableAspect>().Title = postName; + post.As<BodyAspect>().Text = item.Element("description").Value; + _contentManager.Create(post); + } + + + return "Import feed completed."; + } + + private static string Slugify(string slug) { + var dissallowed = new Regex(@"[/:?#\[\]@!$&'()*+,;=\s]+"); + + slug = dissallowed.Replace(slug, "-"); + slug = slug.Trim('-'); + + if ( slug.Length > 1000 ) + slug = slug.Substring(0, 1000); + + return slug.ToLowerInvariant(); + } + + private static bool IsSlugValid(string slug) { + // see http://tools.ietf.org/html/rfc3987 for prohibited chars + return slug == null || String.IsNullOrEmpty(slug.Trim()) || Regex.IsMatch(slug, @"^[^/:?#\[\]@!$&'()*+,;=\s]+$"); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Models/Blog.cs b/src/Orchard.Web/Modules/Orchard.Blogs/Models/Blog.cs index 75362ec5f..9b80f4001 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/Models/Blog.cs +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Models/Blog.cs @@ -9,6 +9,7 @@ namespace Orchard.Blogs.Models { public string Name { get { return this.As<RoutableAspect>().Title; } + set { this.As<RoutableAspect>().Title = value; } } //TODO: (erikpo) Need a data type for slug diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Orchard.Blogs.csproj b/src/Orchard.Web/Modules/Orchard.Blogs/Orchard.Blogs.csproj index 797ed7bcc..a27f9f0af 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/Orchard.Blogs.csproj +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Orchard.Blogs.csproj @@ -12,7 +12,7 @@ <RootNamespace>Orchard.Blogs</RootNamespace> <AssemblyName>Orchard.Blogs</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> @@ -66,6 +66,7 @@ </ItemGroup> <ItemGroup> <Compile Include="AdminMenu.cs" /> + <Compile Include="Commands\BlogCommands.cs" /> <Compile Include="Controllers\BlogAdminController.cs" /> <Compile Include="Drivers\BlogDriver.cs" /> <Compile Include="Controllers\BlogPostAdminController.cs" /> @@ -122,6 +123,7 @@ <Content Include="Scripts\jquery.ui.widget.js" /> <Content Include="Scripts\jquery.utils.js" /> <Content Include="Scripts\ui.timepickr.js" /> + <Content Include="Styles\admin.css" /> <Content Include="Styles\archives.css" /> <Content Include="Styles\datetime.css" /> <Content Include="Styles\images\ui-bg_flat_0_aaaaaa_40x100.png" /> diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Styles/admin.css b/src/Orchard.Web/Modules/Orchard.Blogs/Styles/admin.css new file mode 100644 index 000000000..55925d659 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Styles/admin.css @@ -0,0 +1,3 @@ +.blogdescription { + margin-top:1em; +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Views/DisplayTemplates/Items/Blogs.Blog.DetailAdmin.ascx b/src/Orchard.Web/Modules/Orchard.Blogs/Views/DisplayTemplates/Items/Blogs.Blog.DetailAdmin.ascx index 9fdf5815c..eb5b72504 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/Views/DisplayTemplates/Items/Blogs.Blog.DetailAdmin.ascx +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Views/DisplayTemplates/Items/Blogs.Blog.DetailAdmin.ascx @@ -2,8 +2,8 @@ <%@ Import Namespace="Orchard.Mvc.ViewModels"%> <%@ Import Namespace="Orchard.Blogs.Extensions"%> <%@ Import Namespace="Orchard.Blogs.Models"%> -<h1 class="withActions"> - <a href="<%=Url.BlogForAdmin(Model.Item.Slug) %>"><%: Html.TitleForPage(Model.Item.Name) %></a> +<h1><a href="<%=Url.BlogForAdmin(Model.Item.Slug) %>"><%: Html.TitleForPage(Model.Item.Name) %></a> + </h1> <% Html.Zone("manage"); %><%-- <form> diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Views/DisplayTemplates/Items/Blogs.Blog.SummaryAdmin.ascx b/src/Orchard.Web/Modules/Orchard.Blogs/Views/DisplayTemplates/Items/Blogs.Blog.SummaryAdmin.ascx index f5cc097bc..0ea733a2a 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/Views/DisplayTemplates/Items/Blogs.Blog.SummaryAdmin.ascx +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Views/DisplayTemplates/Items/Blogs.Blog.SummaryAdmin.ascx @@ -9,7 +9,7 @@ <a href="<%=Url.BlogPostCreate(Model.Item) %>" title="<%: T("New Post") %>"><%: T("New Post") %></a><%: T(" | ")%> <a href="<%=Url.BlogEdit(Model.Item.Slug) %>" title="<%: T("Settings") %>"><%: T("Settings") %></a><%: T(" | ")%> <%-- todo: (heskew) this is waaaaa too verbose. need template helpers for all ibuttons --%> - <% using (Html.BeginFormAntiForgeryPost(Url.BlogDelete(Model.Item.Slug), FormMethod.Post, new { @class = "inline" })) { %> + <% using (Html.BeginFormAntiForgeryPost(Url.BlogDelete(Model.Item.Slug), FormMethod.Post, new { @class = "inline link" })) { %> <button type="submit" class="linkButton" title="<%: T("Remove") %>"><%: T("Remove") %></button><% } %> </div> diff --git a/src/Orchard.Web/Modules/Orchard.Blogs/Views/DisplayTemplates/Parts/Blogs.Blog.Manage.ascx b/src/Orchard.Web/Modules/Orchard.Blogs/Views/DisplayTemplates/Parts/Blogs.Blog.Manage.ascx index 3f5082f0b..d2e16c4b2 100644 --- a/src/Orchard.Web/Modules/Orchard.Blogs/Views/DisplayTemplates/Parts/Blogs.Blog.Manage.ascx +++ b/src/Orchard.Web/Modules/Orchard.Blogs/Views/DisplayTemplates/Parts/Blogs.Blog.Manage.ascx @@ -2,6 +2,7 @@ <%@ Import Namespace="Orchard.Blogs"%> <%@ Import Namespace="Orchard.Blogs.Extensions"%> <%@ Import Namespace="Orchard.Blogs.Models"%><% +Html.RegisterStyle("admin.css"); if (AuthorizedFor(Permissions.ManageBlogs)) { %> <div class="folderProperties"> <p><a href="<%=Url.BlogEdit(Model.Slug) %>" class="edit"><%: T("Edit") %></a></p> diff --git a/src/Orchard.Web/Modules/Orchard.Comments/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Comments/AdminMenu.cs index fea261884..583ae82e2 100644 --- a/src/Orchard.Web/Modules/Orchard.Comments/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Orchard.Comments/AdminMenu.cs @@ -1,13 +1,16 @@ -using Orchard.UI.Navigation; +using Orchard.Localization; +using Orchard.UI.Navigation; namespace Orchard.Comments { public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } + public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { - builder.Add("Comments", "3", + builder.Add(T("Comments"), "3", menu => menu - .Add("Manage Comments", "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.Comments" }).Permission(Permissions.ManageComments)) + .Add(T("Manage Comments"), "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.Comments" }).Permission(Permissions.ManageComments)) ); } } diff --git a/src/Orchard.Web/Modules/Orchard.Comments/Orchard.Comments.csproj b/src/Orchard.Web/Modules/Orchard.Comments/Orchard.Comments.csproj index 95e38c50c..391eb11c5 100644 --- a/src/Orchard.Web/Modules/Orchard.Comments/Orchard.Comments.csproj +++ b/src/Orchard.Web/Modules/Orchard.Comments/Orchard.Comments.csproj @@ -12,7 +12,7 @@ <RootNamespace>Orchard.Comments</RootNamespace> <AssemblyName>Orchard.Comments</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Controllers/MetadataController.cs b/src/Orchard.Web/Modules/Orchard.DevTools/Controllers/MetadataController.cs new file mode 100644 index 000000000..bc3a60e86 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Controllers/MetadataController.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using System.Xml; +using System.Xml.Linq; +using Orchard.ContentManagement.MetaData; +using Orchard.DevTools.ViewModels; + +namespace Orchard.DevTools.Controllers { + [ValidateInput(false)] + public class MetadataController : Controller { + private readonly IContentDefinitionManager _contentDefinitionManager; + private readonly IContentDefinitionWriter _contentDefinitionWriter; + private readonly IContentDefinitionReader _contentDefinitionReader; + + public MetadataController( + IContentDefinitionManager contentDefinitionManager, + IContentDefinitionWriter contentDefinitionWriter, + IContentDefinitionReader contentDefinitionReader) { + _contentDefinitionManager = contentDefinitionManager; + _contentDefinitionWriter = contentDefinitionWriter; + _contentDefinitionReader = contentDefinitionReader; + } + + public ActionResult Index() { + var model = new MetadataIndexViewModel { + TypeDefinitions = _contentDefinitionManager.ListTypeDefinitions(), + PartDefinitions = _contentDefinitionManager.ListPartDefinitions() + }; + var types = new XElement("Types"); + foreach (var type in model.TypeDefinitions) { + types.Add(_contentDefinitionWriter.Export(type)); + } + + var parts = new XElement("Parts"); + foreach (var part in model.PartDefinitions) { + parts.Add(_contentDefinitionWriter.Export(part)); + } + + var stringWriter = new StringWriter(); + using (var xmlWriter = XmlWriter.Create(stringWriter, new XmlWriterSettings { Indent = true, IndentChars = " " })) { + if (xmlWriter != null) { + new XElement("Orchard", types, parts).WriteTo(xmlWriter); + } + } + model.ExportText = stringWriter.ToString(); + + return View(model); + } + + [HttpPost] + public ActionResult Index(MetadataIndexViewModel model) { + var root = XElement.Parse(model.ExportText); + foreach (var element in root.Elements("Types").Elements()) { + var typeElement = element; + var typeName = XmlConvert.DecodeName(element.Name.LocalName); + _contentDefinitionManager.AlterTypeDefinition(typeName, alteration => _contentDefinitionReader.Merge(typeElement, alteration)); + } + return RedirectToAction("Index"); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj b/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj index 81e865d38..fd88a2bd9 100644 --- a/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Orchard.DevTools.csproj @@ -12,7 +12,7 @@ <RootNamespace>Orchard.DevTools</RootNamespace> <AssemblyName>Orchard.DevTools</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> @@ -73,6 +73,7 @@ <Compile Include="Commands\ProfilingCommands.cs" /> <Compile Include="Controllers\ContentController.cs" /> <Compile Include="Controllers\HomeController.cs" /> + <Compile Include="Controllers\MetadataController.cs" /> <Compile Include="Handlers\DebugLinkHandler.cs" /> <Compile Include="Models\ShowDebugLink.cs" /> <Compile Include="Models\Simple.cs" /> @@ -80,6 +81,7 @@ <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="ViewModels\ContentIndexViewModel.cs" /> <Compile Include="ViewModels\ContentDetailsViewModel.cs" /> + <Compile Include="ViewModels\MetadataIndexViewModel.cs" /> </ItemGroup> <ItemGroup> <Content Include="Module.txt" /> @@ -90,6 +92,7 @@ <Content Include="Views\Home\Index.aspx" /> <Content Include="Views\DisplayTemplates\Parts\DevTools.ShowDebugLink.ascx" /> <Content Include="Views\EditorTemplates\Parts\DevTools.ShowDebugLink.ascx" /> + <Content Include="Views\Metadata\Index.aspx" /> <Content Include="Web.config" /> <Content Include="Views\Web.config" /> </ItemGroup> diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/ViewModels/MetadataIndexViewModel.cs b/src/Orchard.Web/Modules/Orchard.DevTools/ViewModels/MetadataIndexViewModel.cs new file mode 100644 index 000000000..574430485 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.DevTools/ViewModels/MetadataIndexViewModel.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Orchard.ContentManagement.MetaData.Models; +using Orchard.Mvc.ViewModels; + +namespace Orchard.DevTools.ViewModels { + public class MetadataIndexViewModel : BaseViewModel { + public IEnumerable<ContentTypeDefinition> TypeDefinitions { get; set; } + public IEnumerable<ContentPartDefinition> PartDefinitions { get; set; } + public string ExportText { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Views/Home/Index.aspx b/src/Orchard.Web/Modules/Orchard.DevTools/Views/Home/Index.aspx index 10cffdbf9..bb0d16a28 100644 --- a/src/Orchard.Web/Modules/Orchard.DevTools/Views/Home/Index.aspx +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Views/Home/Index.aspx @@ -2,4 +2,7 @@ <%@ Import Namespace="Orchard.Mvc.ViewModels"%> <h1><%: Html.TitleForPage(T("Dev Tools").ToString()) %></h1> <p><%: Html.ActionLink(T("Contents").ToString(), "Index", "Content") %></p> + +<p><%: Html.ActionLink(T("Metadata").ToString(), "Index", "Metadata") %></p> <p><%: Html.ActionLink(T("Test Unauthorized Request").ToString(), "NotAuthorized", "Home")%></p> + diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Views/Metadata/Index.aspx b/src/Orchard.Web/Modules/Orchard.DevTools/Views/Metadata/Index.aspx new file mode 100644 index 000000000..7c119bf89 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Views/Metadata/Index.aspx @@ -0,0 +1,44 @@ +<%@ Page Language="C#" Inherits="Orchard.Mvc.ViewPage<MetadataIndexViewModel>" %> + +<%@ Import Namespace="Orchard.DevTools.ViewModels" %> +<style title="text/css"> + ul + { + margin-left: 12px; + } +</style> +<h1> + Metadata</h1> +<h2> + Content Type Definitions</h2> +<ul> + <%foreach (var type in Model.TypeDefinitions) {%> + <li> + <%:type.Name %> + <ul> + <%foreach (var part in type.Parts) {%> + <li> + <%:part.PartDefinition.Name %></li> + <% + }%> + </ul> + </li> + <% + }%> +</ul> +<h2> + Content Part Definitions</h2> +<ul> + <%foreach (var part in Model.PartDefinitions) {%> + <li> + <%:part.Name %></li> + <% + }%> +</ul> +<h2> +Exported as xml</h2> +<% using (Html.BeginFormAntiForgeryPost()) { %> +<%:Html.TextAreaFor(m=>m.ExportText, new{style="width:100%;height:640px;"}) %> +<br /> +<input class="button primaryAction" type="submit" value="<%=_Encoded("Merge Changes") %>" /> +<%} %> diff --git a/src/Orchard.Web/Modules/Orchard.Media/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Media/AdminMenu.cs index 8bc691067..f81ed3319 100644 --- a/src/Orchard.Web/Modules/Orchard.Media/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Orchard.Media/AdminMenu.cs @@ -1,13 +1,16 @@ -using Orchard.UI.Navigation; +using Orchard.Localization; +using Orchard.UI.Navigation; namespace Orchard.Media { public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } + public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { - builder.Add("Media", "4", + builder.Add(T("Media"), "4", menu => menu - .Add("Manage Media", "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.Media" }).Permission(Permissions.ManageMediaFiles)) + .Add(T("Manage Media"), "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.Media" }).Permission(Permissions.ManageMediaFiles)) ); } } diff --git a/src/Orchard.Web/Modules/Orchard.Media/Orchard.Media.csproj b/src/Orchard.Web/Modules/Orchard.Media/Orchard.Media.csproj index d7c4f79db..88f02adab 100644 --- a/src/Orchard.Web/Modules/Orchard.Media/Orchard.Media.csproj +++ b/src/Orchard.Web/Modules/Orchard.Media/Orchard.Media.csproj @@ -12,7 +12,7 @@ <RootNamespace>Orchard.Media</RootNamespace> <AssemblyName>Orchard.Media</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> @@ -93,6 +93,7 @@ <ItemGroup> <Content Include="Content\Admin\images\folder.gif" /> <Content Include="Module.txt" /> + <Content Include="Styles\admin.css" /> <Content Include="Views\Admin\Add.aspx" /> <Content Include="Views\Admin\Create.aspx" /> <Content Include="Views\Admin\Edit.aspx" /> diff --git a/src/Orchard.Web/Modules/Orchard.Media/Styles/admin.css b/src/Orchard.Web/Modules/Orchard.Media/Styles/admin.css new file mode 100644 index 000000000..3efdaf0de --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Media/Styles/admin.css @@ -0,0 +1,7 @@ +.breadCrumbs, .folderProperties { + float:left; + margin:-2em 0 0; +} +.folderProperties { + float:right; +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/Add.aspx b/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/Add.aspx index 987343bc7..483046d6e 100644 --- a/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/Add.aspx +++ b/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/Add.aspx @@ -2,6 +2,8 @@ <%@ Import Namespace="Orchard.Media.Helpers"%> <%@ Import Namespace="Orchard.Media.Models"%> <%@ Import Namespace="Orchard.Media.ViewModels"%> +<%Html.RegisterStyle("admin.css"); %> + <h1><%: Html.TitleForPage(T("Add Media").ToString()) %></h1> <div class="breadCrumbs"> <p><%: Html.ActionLink(T("Media Folders").ToString(), "Index") %> > diff --git a/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/Create.aspx b/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/Create.aspx index 0f2227a91..32b6aa0ef 100644 --- a/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/Create.aspx +++ b/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/Create.aspx @@ -1,7 +1,8 @@ <%@ Page Language="C#" Inherits="Orchard.Mvc.ViewPage<MediaFolderCreateViewModel>" %> <%@ Import Namespace="Orchard.Media.Helpers"%> <%@ Import Namespace="Orchard.Media.Models"%> -<%@ Import Namespace="Orchard.Media.ViewModels"%> +<%@ Import Namespace="Orchard.Media.ViewModels"%><% +Html.RegisterStyle("admin.css"); %> <h1><%: Html.TitleForPage(T("Add a Folder").ToString()) %></h1> <div class="breadCrumbs"> <p><%: Html.ActionLink(T("Media Folders").ToString(), "Index") %> > diff --git a/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/Edit.aspx b/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/Edit.aspx index f8bffa018..2ddabe0da 100644 --- a/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/Edit.aspx +++ b/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/Edit.aspx @@ -1,7 +1,8 @@ <%@ Page Language="C#" Inherits="Orchard.Mvc.ViewPage<MediaFolderEditViewModel>" %> <%@ Import Namespace="Orchard.Media.Models"%> <%@ Import Namespace="Orchard.Media.Helpers"%> -<%@ Import Namespace="Orchard.Media.ViewModels"%> +<%@ Import Namespace="Orchard.Media.ViewModels"%><% +Html.RegisterStyle("admin.css"); %> <h1><%: Html.TitleForPage(T("Manage Folder").ToString())%></h1> diff --git a/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/EditMedia.aspx b/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/EditMedia.aspx index 756b928d8..ac6692b75 100644 --- a/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/EditMedia.aspx +++ b/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/EditMedia.aspx @@ -1,7 +1,8 @@ <%@ Page Language="C#" Inherits="Orchard.Mvc.ViewPage<MediaItemEditViewModel>" %> <%@ Import Namespace="Orchard.Media.Models"%> <%@ Import Namespace="Orchard.Media.Helpers"%> -<%@ Import Namespace="Orchard.Media.ViewModels"%> +<%@ Import Namespace="Orchard.Media.ViewModels"%><% +Html.RegisterStyle("admin.css"); %> <h1><%: Html.TitleForPage(T("Edit Media - {0}", Model.Name).ToString())%></h1> <div class="breadCrumbs"> diff --git a/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/EditProperties.aspx b/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/EditProperties.aspx index ce22104e1..e7d36b85e 100644 --- a/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/EditProperties.aspx +++ b/src/Orchard.Web/Modules/Orchard.Media/Views/Admin/EditProperties.aspx @@ -1,7 +1,8 @@ <%@ Page Language="C#" Inherits="Orchard.Mvc.ViewPage<MediaFolderEditPropertiesViewModel>" %> <%@ Import Namespace="Orchard.Media.Helpers"%> <%@ Import Namespace="Orchard.Media.Models"%> -<%@ Import Namespace="Orchard.Media.ViewModels"%> +<%@ Import Namespace="Orchard.Media.ViewModels"%><% +Html.RegisterStyle("admin.css"); %> <h1><%: Html.TitleForPage(T("Folder Properties").ToString())%></h1> <div class="breadCrumbs"> <p><%: Html.ActionLink(T("Media Folders").ToString(), "Index")%> > diff --git a/src/Orchard.Web/Modules/Orchard.MetaData/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.MetaData/AdminMenu.cs index ad2c436d5..dbba1d1be 100644 --- a/src/Orchard.Web/Modules/Orchard.MetaData/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Orchard.MetaData/AdminMenu.cs @@ -1,16 +1,18 @@  +using Orchard.Localization; using Orchard.UI.Navigation; namespace Orchard.MetaData { public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { - builder.Add("Content Types", "5", + builder.Add(T("Content Types"), "5", menu => menu - .Add("Content Types", "1.0", item => item.Action("ContentTypeList", "MetaData", new { area = "Orchard.MetaData" }).Permission(Permissions.ManageMetaData)) + .Add(T("Content Types"), "1.0", item => item.Action("ContentTypeList", "Admin", new { area = "Orchard.MetaData" }).Permission(Permissions.ManageMetaData)) ); } diff --git a/src/Orchard.Web/Modules/Orchard.MetaData/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.MetaData/Controllers/AdminController.cs new file mode 100644 index 000000000..baae2d64c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MetaData/Controllers/AdminController.cs @@ -0,0 +1,82 @@ +using System.Web.Mvc; +using Orchard.ContentManagement.MetaData; +using Orchard.Localization; +using Orchard.MetaData.ViewModels; + +namespace Orchard.MetaData.Controllers { + + public class AdminController : Controller { + private readonly IContentDefinitionManager _contentDefinitionManager; + public IOrchardServices Services { get; set; } + + public AdminController(IOrchardServices services, IContentDefinitionManager contentDefinitionManager) { + _contentDefinitionManager = contentDefinitionManager; + Services = services; + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + // + // GET: /ContentTypeList/ + + public ActionResult ContentTypeList(string id) { + + if (!Services.Authorizer.Authorize(Permissions.ManageMetaData, T("Not allowed to manage MetaData"))) + return new HttpUnauthorizedResult(); + + var contentTypes = _contentDefinitionManager.ListTypeDefinitions(); + var contentParts = _contentDefinitionManager.ListPartDefinitions(); + + var model = new ContentTypesIndexViewModel(); + + foreach (var contentType in contentTypes) { + var contentTypeEntry = new ContentTypeEntry { Name = contentType.Name, DisplayName = contentType.Name }; + + if (contentType.Name == id) { + foreach (var contentTypePartNameRecord in contentParts) { + var contentTypePartEntry = new ContentTypePartEntry { Name = contentTypePartNameRecord.Name }; + foreach (var contentTypePartEntryTest in contentType.Parts) { + if (contentTypePartEntryTest.PartDefinition.Name == contentTypePartEntry.Name) { + contentTypePartEntry.Selected = true; + } + } + model.ContentTypeParts.Add(contentTypePartEntry); + } + model.SelectedContentType = contentTypeEntry; + } + model.ContentTypes.Add(contentTypeEntry); + } + return View(model); + } + + + // + // POST: /ContentTypeList/Save + [HttpPost] + public ActionResult Save(string id, FormCollection collection) { + if (!Services.Authorizer.Authorize(Permissions.ManageMetaData, T("Not allowed to manage MetaData"))) + return new HttpUnauthorizedResult(); + + var existingDefinition = _contentDefinitionManager.GetTypeDefinition(id); + + _contentDefinitionManager.AlterTypeDefinition(id, alter => { + foreach(var part in existingDefinition.Parts) { + alter.RemovePart(part.PartDefinition.Name); + } + foreach (var formKey in collection.AllKeys) { + if (formKey.Contains("part_")) { + var partName = formKey.Replace("part_", ""); + alter.WithPart(partName); + } + } + + }); + + return RedirectToAction("ContentTypeList", new { id }); + + + } + + + } +} diff --git a/src/Orchard.Web/Modules/Orchard.MetaData/Controllers/MetaDataController.cs b/src/Orchard.Web/Modules/Orchard.MetaData/Controllers/MetaDataController.cs deleted file mode 100644 index 2e6f991c5..000000000 --- a/src/Orchard.Web/Modules/Orchard.MetaData/Controllers/MetaDataController.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Web.Mvc; -using Orchard.ContentManagement.MetaData.Services; -using Orchard.Localization; -using Orchard.MetaData.ViewModels; -using Orchard.UI.Admin; - -namespace Orchard.MetaData.Controllers -{ - [Admin] - public class MetaDataController : Controller - { - private readonly IContentTypeService _contentTypeService; - public IOrchardServices Services { get; set; } - - public MetaDataController(IOrchardServices services, IContentTypeService contentTypeService) - { - _contentTypeService = contentTypeService; - Services = services; - T = NullLocalizer.Instance; - } - - public Localizer T { get; set; } - // - // GET: /ContentTypeList/ - - public ActionResult ContentTypeList(string id) { - - if (!Services.Authorizer.Authorize(Permissions.ManageMetaData, T("Not allowed to manage MetaData"))) - return new HttpUnauthorizedResult(); - - var contentTypes = _contentTypeService.GetContentTypes(); - var contentTypePartNames = _contentTypeService.GetContentTypePartNames(); - - var model = new ContentTypesIndexViewModel(); - - foreach(var contentType in contentTypes) { - var contentTypeEntry = new ContentTypeEntry {Name = contentType.Name,DisplayName = contentType.Name}; - - if (contentType.Name==id) { - foreach(var contentTypePartNameRecord in contentTypePartNames) { - var contentTypePartEntry = new ContentTypePartEntry { Name = contentTypePartNameRecord.PartName }; - foreach(var contentTypePartEntryTest in contentType.ContentParts) { - if (contentTypePartEntryTest.PartName.PartName==contentTypePartEntry.Name) { - contentTypePartEntry.Selected = true; - } - } - model.ContentTypeParts.Add(contentTypePartEntry); - } - model.SelectedContentType = contentTypeEntry; - } - model.ContentTypes.Add(contentTypeEntry); - } - return View(model); - } - - - // - // POST: /ContentTypeList/Save - [HttpPost] - public ActionResult Save(string id, FormCollection collection) - { - if (!Services.Authorizer.Authorize(Permissions.ManageMetaData, T("Not allowed to manage MetaData"))) - return new HttpUnauthorizedResult(); - - var contentTypeRecord = _contentTypeService.GetContentTypeRecord(id); - //using a while loop because we are removing items from the collection - while (contentTypeRecord.ContentParts.Count>0) { - _contentTypeService.UnMapContentTypeToContentPart(contentTypeRecord.Name, contentTypeRecord.ContentParts[0].PartName.PartName); - } - foreach(var formKey in collection.AllKeys) { - if (formKey.Contains("part_")) { - var partName = formKey.Replace("part_", ""); - _contentTypeService.MapContentTypeToContentPart(contentTypeRecord.Name,partName); - } - } - - return RedirectToAction("ContentTypeList", new { id }); - - - } - - - } -} diff --git a/src/Orchard.Web/Modules/Orchard.MetaData/Orchard.MetaData.csproj b/src/Orchard.Web/Modules/Orchard.MetaData/Orchard.MetaData.csproj index 6dd6479a1..5f110a3d1 100644 --- a/src/Orchard.Web/Modules/Orchard.MetaData/Orchard.MetaData.csproj +++ b/src/Orchard.Web/Modules/Orchard.MetaData/Orchard.MetaData.csproj @@ -12,7 +12,7 @@ <RootNamespace>Orchard.MetaData</RootNamespace> <AssemblyName>Orchard.MetaData</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> @@ -66,7 +66,7 @@ </ItemGroup> <ItemGroup> <Compile Include="AdminMenu.cs" /> - <Compile Include="Controllers\MetaDataController.cs" /> + <Compile Include="Controllers\AdminController.cs" /> <Compile Include="Permissions.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="ViewModels\ContentTypesViewModel.cs" /> @@ -74,7 +74,7 @@ <ItemGroup> <Content Include="Module.txt" /> <Content Include="Web.config" /> - <Content Include="Views\MetaData\ContentTypeList.ascx" /> + <Content Include="Views\Admin\ContentTypeList.ascx" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\..\Orchard\Orchard.Framework.csproj"> diff --git a/src/Orchard.Web/Modules/Orchard.MetaData/Views/MetaData/ContentTypeList.ascx b/src/Orchard.Web/Modules/Orchard.MetaData/Views/Admin/ContentTypeList.ascx similarity index 87% rename from src/Orchard.Web/Modules/Orchard.MetaData/Views/MetaData/ContentTypeList.ascx rename to src/Orchard.Web/Modules/Orchard.MetaData/Views/Admin/ContentTypeList.ascx index 589aacad1..c0b78eead 100644 --- a/src/Orchard.Web/Modules/Orchard.MetaData/Views/MetaData/ContentTypeList.ascx +++ b/src/Orchard.Web/Modules/Orchard.MetaData/Views/Admin/ContentTypeList.ascx @@ -25,7 +25,7 @@ %> <tr class="<%=contentTypeClass %>"> <td> - <%: Html.ActionLink(item.Name, "ContentTypeList", new {id=item.Name})%> + <%= Html.ActionLink(item.Name, "ContentTypeList", new {id=item.Name})%> </td> </tr> @@ -60,7 +60,7 @@ using (Html.BeginFormAntiForgeryPost(Url.Action("Save",new {id=Model.SelectedCon <input name="<%="part_" + item.Name%>" type="checkbox" /><%}%> </td> <td class="ContentTypePartListRowItem"> - <%: item.Name %> + <%= Html.Encode(item.Name)%> </td> </tr> @@ -68,7 +68,7 @@ using (Html.BeginFormAntiForgeryPost(Url.Action("Save",new {id=Model.SelectedCon </table> <p> - <input type="submit" value="<%: T("Save") %>" /> + <input type="submit" value="<%=_Encoded("Save") %>" /> </p> <% } %> </div> diff --git a/src/Orchard.Web/Modules/Orchard.Modules/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Modules/AdminMenu.cs index 9db153432..6ac566a6f 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Orchard.Modules/AdminMenu.cs @@ -1,15 +1,18 @@ -using Orchard.UI.Navigation; +using Orchard.Localization; +using Orchard.UI.Navigation; namespace Orchard.Modules { public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } + public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { - builder.Add("Features", "10", + builder.Add(T("Features"), "10", menu => menu - .Add("Manage Features", "1.0", item => item.Action("Features", "Admin", new { area = "Orchard.Modules" }) + .Add(T("Manage Features"), "1.0", item => item.Action("Features", "Admin", new { area = "Orchard.Modules" }) .Permission(Permissions.ManageFeatures)) - .Add("Installed Modules", "2.0", item => item.Action("Index", "Admin", new { area = "Orchard.Modules" }) + .Add(T("Installed Modules"), "2.0", item => item.Action("Index", "Admin", new { area = "Orchard.Modules" }) .Permission(Permissions.ManageModules))); } } diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Orchard.Modules.csproj b/src/Orchard.Web/Modules/Orchard.Modules/Orchard.Modules.csproj index 9e20f453b..2668aeaf5 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Orchard.Modules.csproj +++ b/src/Orchard.Web/Modules/Orchard.Modules/Orchard.Modules.csproj @@ -12,7 +12,7 @@ <RootNamespace>Orchard.Modules</RootNamespace> <AssemblyName>Orchard.Modules</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> diff --git a/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Index.ascx b/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Index.ascx index 5a259a262..3ffb531c2 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Index.ascx +++ b/src/Orchard.Web/Modules/Orchard.Modules/Views/Admin/Index.ascx @@ -3,7 +3,7 @@ <%@ Import Namespace="Orchard.Mvc.Html"%> <%@ Import Namespace="Orchard.Modules.ViewModels"%> <h1><%: Html.TitleForPage(T("Installed Modules").ToString()) %></h1> -<div class="manage"><%: Html.ActionLink(T("Install a module"), "Add", null, new { @class = "button primaryAction" })%></div> +<div class="manage"><%: Html.ActionLink(T("Install a module").ToString(), "Add", null, new { @class = "button primaryAction" })%></div> <% if (Model.Modules.Count() > 0) { %> <ul class="contentItems"><% foreach (var module in Model.Modules.OrderBy(m => m.DisplayName)) { %> diff --git a/src/Orchard.Web/Modules/Orchard.Modules/styles/admin.css b/src/Orchard.Web/Modules/Orchard.Modules/styles/admin.css index 7804ea223..8aada2d0f 100644 --- a/src/Orchard.Web/Modules/Orchard.Modules/styles/admin.css +++ b/src/Orchard.Web/Modules/Orchard.Modules/styles/admin.css @@ -1,6 +1,9 @@ #main .features h2 { margin-top:0; } +#main .features h3 { + padding:0; +} .features.detail-view .category > ul { border:1px solid #EAEAEA; margin-bottom:2em; @@ -11,6 +14,9 @@ } .features.summary-view .feature { border:1px solid #EAEAEA; + box-shadow: 0px 1px 2px #d6d6d6; + -moz-box-shadow: 0px 1px 2px #d6d6d6; + -webkit-box-shadow: 0px 1px 2px #d6d6d6; display:block; float:left; height:5em; diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.MultiTenancy/AdminMenu.cs index aab63b491..5eca44687 100644 --- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/AdminMenu.cs @@ -1,14 +1,17 @@ -using Orchard.UI.Navigation; +using Orchard.Localization; +using Orchard.UI.Navigation; namespace Orchard.MultiTenancy { public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } + public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { - builder.Add("Tenants", "22", + builder.Add(T("Tenants"), "22", menu => menu - .Add("Manage Tenants", "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.MultiTenancy" }).Permission(Permissions.ManageTenants)) - .Add("Add New Tenant", "1.1", item => item.Action("Add", "Admin", new { area = "Orchard.MultiTenancy" }).Permission(Permissions.ManageTenants))); + .Add(T("Manage Tenants"), "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.MultiTenancy" }).Permission(Permissions.ManageTenants)) + .Add(T("Add New Tenant"), "1.1", item => item.Action("Add", "Admin", new { area = "Orchard.MultiTenancy" }).Permission(Permissions.ManageTenants))); } } } diff --git a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Orchard.MultiTenancy.csproj b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Orchard.MultiTenancy.csproj index aa6a19d74..9e3facaa7 100644 --- a/src/Orchard.Web/Modules/Orchard.MultiTenancy/Orchard.MultiTenancy.csproj +++ b/src/Orchard.Web/Modules/Orchard.MultiTenancy/Orchard.MultiTenancy.csproj @@ -12,7 +12,7 @@ <RootNamespace>Orchard.MultiTenancy</RootNamespace> <AssemblyName>Orchard.MultiTenancy</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> @@ -96,6 +96,7 @@ <ProjectReference Include="..\..\..\Orchard\Orchard.Framework.csproj"> <Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project> <Name>Orchard.Framework</Name> + <Private>False</Private> </ProjectReference> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> diff --git a/src/Orchard.Web/Modules/Orchard.Pages/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Pages/AdminMenu.cs index 4a184c6c6..8467d8040 100644 --- a/src/Orchard.Web/Modules/Orchard.Pages/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Orchard.Pages/AdminMenu.cs @@ -1,4 +1,5 @@ -using Orchard.Pages.Services; +using Orchard.Localization; +using Orchard.Pages.Services; using Orchard.UI.Navigation; namespace Orchard.Pages { @@ -9,19 +10,21 @@ namespace Orchard.Pages { _pageService = pageService; } + public Localizer T { get; set; } + public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { - builder.Add("Pages", "1", BuildMenu); + builder.Add(T("Pages"), "1", BuildMenu); } private void BuildMenu(NavigationItemBuilder menu) { if (_pageService.GetCount() > 0) - menu.Add("Manage Pages", "1.0", + menu.Add(T("Manage Pages"), "1.0", item => item.Action("List", "Admin", new {area = "Orchard.Pages"}).Permission(Permissions.MetaListPages)); - menu.Add("Add New Page", "1.1", + menu.Add(T("Add New Page"), "1.1", item => item.Action("Create", "Admin", new {area = "Orchard.Pages"}).Permission(Permissions.EditPages)); } diff --git a/src/Orchard.Web/Modules/Orchard.Pages/Orchard.Pages.csproj b/src/Orchard.Web/Modules/Orchard.Pages/Orchard.Pages.csproj index be3ec1239..85af20843 100644 --- a/src/Orchard.Web/Modules/Orchard.Pages/Orchard.Pages.csproj +++ b/src/Orchard.Web/Modules/Orchard.Pages/Orchard.Pages.csproj @@ -12,7 +12,7 @@ <RootNamespace>Orchard.Pages</RootNamespace> <AssemblyName>Orchard.Pages</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> @@ -93,6 +93,7 @@ <Compile Include="ViewModels\PageViewModel.cs" /> </ItemGroup> <ItemGroup> + <Content Include="Styles\admin.css" /> <Content Include="Content\Admin\images\draft.gif" /> <Content Include="Content\Admin\images\offline.gif" /> <Content Include="Content\Admin\images\online.gif" /> diff --git a/src/Orchard.Web/Modules/Orchard.Pages/Styles/admin.css b/src/Orchard.Web/Modules/Orchard.Pages/Styles/admin.css new file mode 100644 index 000000000..5504fdc34 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Pages/Styles/admin.css @@ -0,0 +1,19 @@ +.pageList { + margin:0; + padding:0; + /*background:#FFFFFF url(images/backgroundGradient.gif) repeat-x scroll left top;*/ +} +.orchard-pages .contentItems { + margin:.5em 0 0; + padding:0; + } +.contentItems .properties ul { + margin:0.8em 0 0 2em; +} +.contentItems .properties ul li { + margin:0; + padding:0 0 .1em 0; +} +.properties h3 { + display:inline; +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Pages/Views/Admin/List.aspx b/src/Orchard.Web/Modules/Orchard.Pages/Views/Admin/List.aspx index beeff778c..8cf4613c1 100644 --- a/src/Orchard.Web/Modules/Orchard.Pages/Views/Admin/List.aspx +++ b/src/Orchard.Web/Modules/Orchard.Pages/Views/Admin/List.aspx @@ -3,8 +3,10 @@ <%@ Import Namespace="Orchard.ContentManagement"%> <%@ Import Namespace="Orchard.Core.Common.Models"%> <%@ Import Namespace="Orchard.Mvc.Html"%> -<%@ Import Namespace="Orchard.Pages.ViewModels"%> -<h1><%: Html.TitleForPage(T("Manage Pages").ToString())%></h1> +<%@ Import Namespace="Orchard.Pages.ViewModels"%><% +Html.RegisterStyle("admin.css"); %> + +<h1><%=Html.TitleForPage(T("Manage Pages").ToString())%></h1> <%-- todo: Add helper text here when ready. <p><%: T("Possible text about setting up a page goes here.")%></p>--%> <div class="manage"><%: Html.ActionLink(T("Add a page").ToString(), "Create", new { }, new { @class = "button primaryAction" })%></div><% using (Html.BeginFormAntiForgeryPost()) { %> diff --git a/src/Orchard.Web/Modules/Orchard.Roles/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Roles/AdminMenu.cs index 6792b3021..b4373bb74 100644 --- a/src/Orchard.Web/Modules/Orchard.Roles/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Orchard.Roles/AdminMenu.cs @@ -1,14 +1,16 @@ -using Orchard.UI.Navigation; +using Orchard.Localization; +using Orchard.UI.Navigation; namespace Orchard.Roles { public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { - builder.Add("Users", "5", + builder.Add(T("Users"), "5", menu => menu - .Add("Manage Roles", "2.0", item => item.Action("Index", "Admin", new { area = "Orchard.Roles" }).Permission(Permissions.ManageRoles)) - .Add("Add New Role", "2.1", item => item.Action("Create", "Admin", new { area = "Orchard.Roles" }).Permission(Permissions.ManageRoles))); + .Add(T("Manage Roles"), "2.0", item => item.Action("Index", "Admin", new { area = "Orchard.Roles" }).Permission(Permissions.ManageRoles)) + .Add(T("Add New Role"), "2.1", item => item.Action("Create", "Admin", new { area = "Orchard.Roles" }).Permission(Permissions.ManageRoles))); } } } diff --git a/src/Orchard.Web/Modules/Orchard.Roles/Models/UserSimulation.cs b/src/Orchard.Web/Modules/Orchard.Roles/Models/UserSimulation.cs index 1ce3b8d9f..144ebb212 100644 --- a/src/Orchard.Web/Modules/Orchard.Roles/Models/UserSimulation.cs +++ b/src/Orchard.Web/Modules/Orchard.Roles/Models/UserSimulation.cs @@ -1,12 +1,14 @@ using System.Collections.Generic; using Orchard.ContentManagement; using Orchard.ContentManagement.Handlers; +using Orchard.ContentManagement.MetaData.Builders; using Orchard.Security; namespace Orchard.Roles.Models { public static class UserSimulation { public static IUser Create(string role) { - var simulation = new ContentItemBuilder("user") + var simulationType = new ContentTypeDefinitionBuilder().Named("user").Build(); + var simulation = new ContentItemBuilder(simulationType) .Weld<SimulatedUser>() .Weld<SimulatedUserRoles>() .Build(); diff --git a/src/Orchard.Web/Modules/Orchard.Roles/Orchard.Roles.csproj b/src/Orchard.Web/Modules/Orchard.Roles/Orchard.Roles.csproj index 288b8fef5..671501046 100644 --- a/src/Orchard.Web/Modules/Orchard.Roles/Orchard.Roles.csproj +++ b/src/Orchard.Web/Modules/Orchard.Roles/Orchard.Roles.csproj @@ -12,7 +12,7 @@ <RootNamespace>Orchard.Roles</RootNamespace> <AssemblyName>Orchard.Roles</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> diff --git a/src/Orchard.Web/Modules/Orchard.Sandbox/Orchard.Sandbox.csproj b/src/Orchard.Web/Modules/Orchard.Sandbox/Orchard.Sandbox.csproj index b2d5bd58b..54b613b8f 100644 --- a/src/Orchard.Web/Modules/Orchard.Sandbox/Orchard.Sandbox.csproj +++ b/src/Orchard.Web/Modules/Orchard.Sandbox/Orchard.Sandbox.csproj @@ -12,7 +12,7 @@ <RootNamespace>Orchard.Sandbox</RootNamespace> <AssemblyName>Orchard.Sandbox</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> diff --git a/src/Orchard.Web/Modules/Orchard.Search/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Search/AdminMenu.cs new file mode 100644 index 000000000..369fcea1c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/AdminMenu.cs @@ -0,0 +1,16 @@ +using Orchard.Localization; +using Orchard.UI.Navigation; + +namespace Orchard.Search { + public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } + public string MenuName { get { return "admin"; } } + + public void GetNavigation(NavigationBuilder builder) { + builder.Add(T("Site"), "11", + menu => menu + .Add(T("Search Index"), "10.0", item => item.Action("Index", "Admin", new {area = "Orchard.Search"}) + .Permission(Permissions.ManageSearchIndex))); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Search/Controllers/AdminController.cs new file mode 100644 index 000000000..5d98de72c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Controllers/AdminController.cs @@ -0,0 +1,50 @@ +using System.Web.Mvc; +using Orchard.Localization; +using Orchard.Search.Services; +using Orchard.Search.ViewModels; +using Orchard.UI.Notify; + +namespace Orchard.Search.Controllers { + public class AdminController : Controller { + private readonly ISearchService _searchService; + + public AdminController(ISearchService searchService, IOrchardServices services) { + _searchService = searchService; + Services = services; + T = NullLocalizer.Instance; + } + + public IOrchardServices Services { get; private set; } + public Localizer T { get; set; } + + public ActionResult Index() { + var viewModel = new SearchIndexViewModel {HasIndexToManage = _searchService.HasIndexToManage, IndexUpdatedUtc = _searchService.GetIndexUpdatedUtc()}; + + if (!viewModel.HasIndexToManage) + Services.Notifier.Information(T("There is no search index to manage for this site.")); + + return View(viewModel); + } + + [HttpPost] + public ActionResult Update() { + if (!Services.Authorizer.Authorize(Permissions.ManageSearchIndex, T("Not allowed to manage the search index."))) + return new HttpUnauthorizedResult(); + + _searchService.UpdateIndex(); + + return RedirectToAction("Index"); + } + + [HttpPost] + public ActionResult Rebuild() { + if (!Services.Authorizer.Authorize(Permissions.ManageSearchIndex, T("Not allowed to manage the search index."))) + return new HttpUnauthorizedResult(); + + _searchService.RebuildIndex(); + _searchService.UpdateIndex(); + + return RedirectToAction("Index"); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Controllers/SearchController.cs b/src/Orchard.Web/Modules/Orchard.Search/Controllers/SearchController.cs new file mode 100644 index 000000000..f7e4d4f98 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Controllers/SearchController.cs @@ -0,0 +1,31 @@ +using System.Web.Mvc; +using Orchard.ContentManagement; +using Orchard.Search.Services; +using Orchard.Search.ViewModels; +namespace Orchard.Search.Controllers { + [ValidateInput(false)] + public class SearchController : Controller { + private readonly ISearchService _searchService; + private readonly IContentManager _contentManager; + + public SearchController(ISearchService searchService, IContentManager contentManager) { + _searchService = searchService; + _contentManager = contentManager; + } + + public ActionResult Index(string q, int page = 1, int pageSize = 10) { + var searchViewModel = new SearchViewModel { + Query = q, + DefaultPageSize = 10, // <- yeah, I know :| + PageOfResults = _searchService.Query(q, page, pageSize, searchHit => new SearchResultViewModel { + Content = _contentManager.BuildDisplayModel(_contentManager.Get(searchHit.Id), "SummaryForSearch"), + SearchHit = searchHit + }) + }; + + //todo: deal with page requests beyond result count + + return View(searchViewModel); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Filters/SearchFilter.cs b/src/Orchard.Web/Modules/Orchard.Search/Filters/SearchFilter.cs new file mode 100644 index 000000000..1808581d7 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Filters/SearchFilter.cs @@ -0,0 +1,18 @@ +using System.Web.Mvc; +using Orchard.Mvc.Filters; +using Orchard.Mvc.ViewModels; +using Orchard.Search.ViewModels; + +namespace Orchard.Search.Filters { + public class SearchFilter : FilterProvider, IResultFilter { + public void OnResultExecuting(ResultExecutingContext filterContext) { + var viewModel = filterContext.Controller.ViewData.Model as BaseViewModel; + + if (viewModel != null) + viewModel.Zones.AddRenderPartial("search", "SearchForm", viewModel is SearchViewModel ? viewModel : new SearchViewModel()); + } + + public void OnResultExecuted(ResultExecutedContext filterContext) { + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Module.txt b/src/Orchard.Web/Modules/Orchard.Search/Module.txt new file mode 100644 index 000000000..c6946e0c9 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Module.txt @@ -0,0 +1,11 @@ +name: Search +antiforgery: enabled +author: The Orchard Team +website: http://orchardproject.net +version: 0.1 +orchardversion: 0.1.2010.0312 +description: Orchard's built-in search module. +features: + Orchard.Search: + Description: Standard interface to Orchard's built-in search. + Category: Search \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Orchard.Search.csproj b/src/Orchard.Web/Modules/Orchard.Search/Orchard.Search.csproj new file mode 100644 index 000000000..5ee797d6f --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Orchard.Search.csproj @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProductVersion> + </ProductVersion> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{4BE4EB01-AC56-4048-924E-2CA77F509ABA}</ProjectGuid> + <ProjectTypeGuids>{F85E285D-A4E0-4152-9332-AB1D724D3325};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Orchard.Search</RootNamespace> + <AssemblyName>Orchard.Search</AssemblyName> + <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> + <MvcBuildViews>false</MvcBuildViews> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System" /> + <Reference Include="System.Data" /> + <Reference Include="System.Drawing" /> + <Reference Include="System.Web.DynamicData" /> + <Reference Include="System.Web.Entity" /> + <Reference Include="System.Web.ApplicationServices" /> + <Reference Include="System.ComponentModel.DataAnnotations"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Core"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Data.DataSetExtensions"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" /> + <Reference Include="System.Xml.Linq"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Web" /> + <Reference Include="System.Web.Extensions"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Web.Abstractions" /> + <Reference Include="System.Web.Routing" /> + <Reference Include="System.Xml" /> + <Reference Include="System.Configuration" /> + <Reference Include="System.Web.Services" /> + <Reference Include="System.EnterpriseServices" /> + </ItemGroup> + <ItemGroup> + <Compile Include="AdminMenu.cs" /> + <Compile Include="Controllers\AdminController.cs" /> + <Compile Include="Controllers\SearchController.cs" /> + <Compile Include="Filters\SearchFilter.cs" /> + <Compile Include="Permissions.cs" /> + <Compile Include="Routes.cs" /> + <Compile Include="Services\ISearchService.cs" /> + <Compile Include="Services\SearchService.cs" /> + <Compile Include="ViewModels\SearchIndexViewModel.cs" /> + <Compile Include="ViewModels\SearchResultViewModel.cs" /> + <Compile Include="ViewModels\SearchViewModel.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\..\Orchard\Orchard.Framework.csproj"> + <Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project> + <Name>Orchard.Framework</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Content Include="Module.txt" /> + <Content Include="Styles\admin.css" /> + <Content Include="Styles\search.css" /> + <Content Include="Views\Admin\Index.ascx" /> + <Content Include="Views\SearchForm.ascx" /> + <Content Include="Views\Search\Index.ascx" /> + <Content Include="Views\Web.config" /> + </ItemGroup> + <ItemGroup> + <Content Include="Web.config" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> + <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> --> + <Target Name="AfterBuild" Condition="'$(MvcBuildViews)'=='true'"> + <AspNetCompiler VirtualPath="temp" PhysicalPath="$(ProjectDir)" /> + </Target> + <ProjectExtensions> + <VisualStudio> + <FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}"> + <WebProjectProperties> + <UseIIS>False</UseIIS> + <AutoAssignPort>True</AutoAssignPort> + <DevelopmentServerPort>47866</DevelopmentServerPort> + <DevelopmentServerVPath>/</DevelopmentServerVPath> + <IISUrl> + </IISUrl> + <NTLMAuthentication>False</NTLMAuthentication> + <UseCustomServer>True</UseCustomServer> + <CustomServerUrl>http://orchard.codeplex.com</CustomServerUrl> + <SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile> + </WebProjectProperties> + </FlavorProperties> + </VisualStudio> + </ProjectExtensions> +</Project> \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Permissions.cs b/src/Orchard.Web/Modules/Orchard.Search/Permissions.cs new file mode 100644 index 000000000..901f4ae65 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Permissions.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using Orchard.Security.Permissions; + +namespace Orchard.Search { + public class Permissions : IPermissionProvider { + public static readonly Permission ManageSearchIndex = new Permission { Description = "Manage Search Index", Name = "ManageSearchIndex" }; + + public string ModuleName { + get { + return "Search"; + } + } + + public IEnumerable<Permission> GetPermissions() { + return new Permission[] { + ManageSearchIndex, + }; + } + + public IEnumerable<PermissionStereotype> GetDefaultStereotypes() { + return new[] { + new PermissionStereotype { + Name = "Administrator", + Permissions = new[] {ManageSearchIndex} + }, + }; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Properties/AssemblyInfo.cs b/src/Orchard.Web/Modules/Orchard.Search/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..fb34f0f84 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Orchard.Search")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Orchard.Search")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("fa37585c-b84e-4b5b-a7da-13692ff45a94")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Orchard.Web/Modules/Orchard.Search/Routes.cs b/src/Orchard.Web/Modules/Orchard.Search/Routes.cs new file mode 100644 index 000000000..f26c20a65 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Routes.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using System.Web.Routing; +using Orchard.Mvc.Routes; + +namespace Orchard.Search { + public class Routes : IRouteProvider { + + public void GetRoutes(ICollection<RouteDescriptor> routes) { + foreach (var routeDescriptor in GetRoutes()) + routes.Add(routeDescriptor); + } + + public IEnumerable<RouteDescriptor> GetRoutes() { + return new[] { + new RouteDescriptor { + Priority = 5, + Route = new Route( + "Search", + new RouteValueDictionary { + {"area", "Orchard.Search"}, + {"controller", "search"}, + {"action", "index"} + }, + null, + new RouteValueDictionary { + {"area", "Orchard.Search"} + }, + new MvcRouteHandler()) + } + }; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Services/ISearchService.cs b/src/Orchard.Web/Modules/Orchard.Search/Services/ISearchService.cs new file mode 100644 index 000000000..0437e73ba --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Services/ISearchService.cs @@ -0,0 +1,13 @@ +using System; +using Orchard.Collections; +using Orchard.Indexing; + +namespace Orchard.Search.Services { + public interface ISearchService : IDependency { + bool HasIndexToManage { get; } + IPageOfItems<T> Query<T>(string query, int skip, int? take, Func<ISearchHit, T> shapeResult); + void RebuildIndex(); + void UpdateIndex(); + DateTime GetIndexUpdatedUtc(); + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Services/SearchService.cs b/src/Orchard.Web/Modules/Orchard.Search/Services/SearchService.cs new file mode 100644 index 000000000..338d101bc --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Services/SearchService.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Orchard.Collections; +using Orchard.Indexing; +using Orchard.Localization; +using Orchard.Localization.Services; +using Orchard.UI.Notify; +using System.Web; + +namespace Orchard.Search.Services +{ + public class SearchService : ISearchService + { + private const string SearchIndexName = "Search"; + private readonly IIndexManager _indexManager; + private readonly IEnumerable<IIndexNotifierHandler> _indexNotifierHandlers; + private readonly ICultureManager _cultureManager; + + public SearchService(IOrchardServices services, IIndexManager indexManager, IEnumerable<IIndexNotifierHandler> indexNotifierHandlers, ICultureManager cultureManager) { + Services = services; + _indexManager = indexManager; + _indexNotifierHandlers = indexNotifierHandlers; + _cultureManager = cultureManager; + T = NullLocalizer.Instance; + } + + public IOrchardServices Services { get; set; } + public Localizer T { get; set; } + + public bool HasIndexToManage { + get { return _indexManager.HasIndexProvider(); } + } + + IPageOfItems<T> ISearchService.Query<T>(string query, int page, int? pageSize, Func<ISearchHit, T> shapeResult) { + if (string.IsNullOrWhiteSpace(query) || !_indexManager.HasIndexProvider()) + return null; + + var searchBuilder = _indexManager.GetSearchIndexProvider().CreateSearchBuilder(SearchIndexName) + .WithField("title", query) + .WithField("body", query); + + if(HttpContext.Current != null) { + searchBuilder.WithField("culture", _cultureManager.GetCurrentCulture(HttpContext.Current)); + } + + var totalCount = searchBuilder.Count(); + if (pageSize != null) + searchBuilder = searchBuilder + .Slice((page > 0 ? page - 1 : 0) * (int)pageSize, (int)pageSize); + + + var pageOfItems = new PageOfItems<T>(searchBuilder.Search().Select(shapeResult)) { + PageNumber = page, + PageSize = pageSize != null ? (int) pageSize : totalCount, + TotalItemCount = totalCount + }; + + return pageOfItems; + } + + void ISearchService.RebuildIndex() { + if (!_indexManager.HasIndexProvider()) { + Services.Notifier.Warning(T("There is no search index to rebuild.")); + return; + } + + var searchProvider = _indexManager.GetSearchIndexProvider(); + if (searchProvider.Exists(SearchIndexName)) + searchProvider.DeleteIndex(SearchIndexName); + + searchProvider.CreateIndex(SearchIndexName); // or just reset the updated date and let the background process recreate the index + + Services.Notifier.Information(T("The search index has been rebuilt.")); + } + + void ISearchService.UpdateIndex() { + + foreach(var handler in _indexNotifierHandlers) { + handler.UpdateIndex(SearchIndexName); + } + + Services.Notifier.Information(T("The search index has been updated.")); + } + + DateTime ISearchService.GetIndexUpdatedUtc() { + return !HasIndexToManage + ? DateTime.MinValue + : _indexManager.GetSearchIndexProvider().GetLastIndexUtc(SearchIndexName); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Styles/admin.css b/src/Orchard.Web/Modules/Orchard.Search/Styles/admin.css new file mode 100644 index 000000000..057874152 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Styles/admin.css @@ -0,0 +1,3 @@ +#main button { + display:block; +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Styles/search.css b/src/Orchard.Web/Modules/Orchard.Search/Styles/search.css new file mode 100644 index 000000000..3240b4d1c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Styles/search.css @@ -0,0 +1,18 @@ +form.search { + float:left; +} +form.search input { + display:inline; + width:20em; +} +.search-summary { + float:right; + font-style:italic; + margin-top:0; +} +.search-summary em { + font-weight:bold; +} +.search-results { + clear:both; +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchIndexViewModel.cs b/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchIndexViewModel.cs new file mode 100644 index 000000000..2a0637cda --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchIndexViewModel.cs @@ -0,0 +1,10 @@ +using System; +using Orchard.Mvc.ViewModels; + +namespace Orchard.Search.ViewModels { + public class SearchIndexViewModel : BaseViewModel { + public bool HasIndexToManage { get; set; } + //todo: hang the index updated date off here to show in the admin UI (e.g. -> index updated: June 4, 2010 [update index]) + public DateTime IndexUpdatedUtc { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchResultViewModel.cs b/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchResultViewModel.cs new file mode 100644 index 000000000..15e66c0b4 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchResultViewModel.cs @@ -0,0 +1,9 @@ +using Orchard.Indexing; +using Orchard.Mvc.ViewModels; + +namespace Orchard.Search.ViewModels { + public class SearchResultViewModel { + public ISearchHit SearchHit { get; set; } + public ContentItemViewModel Content { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchViewModel.cs b/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchViewModel.cs new file mode 100644 index 000000000..3da6fc7cb --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchViewModel.cs @@ -0,0 +1,10 @@ +using Orchard.Collections; +using Orchard.Mvc.ViewModels; + +namespace Orchard.Search.ViewModels { + public class SearchViewModel : BaseViewModel { + public string Query { get; set; } + public int DefaultPageSize { get; set; } + public IPageOfItems<SearchResultViewModel> PageOfResults { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Views/Admin/Index.ascx b/src/Orchard.Web/Modules/Orchard.Search/Views/Admin/Index.ascx new file mode 100644 index 000000000..151100a64 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Views/Admin/Index.ascx @@ -0,0 +1,16 @@ +<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<Orchard.Search.ViewModels.SearchIndexViewModel>" %> +<%@ Import Namespace="Orchard.Mvc.Html" %><% +Html.RegisterStyle("admin.css"); %> +<h1><%=Html.TitleForPage(T("Search Index Management").ToString()) %></h1><% +using (Html.BeginForm("update", "admin", FormMethod.Post, new {area = "Orchard.Search"})) { %> + <fieldset> + <p><%=T("The search index was last updated {0}. <button type=\"submit\" title=\"Update the search index.\" class=\"primaryAction\">Update</button>", Html.DateTimeRelative(Model.IndexUpdatedUtc))%></p> + <%=Html.AntiForgeryTokenOrchard() %> + </fieldset><% +} +using (Html.BeginForm("rebuild", "admin", FormMethod.Post, new {area = "Orchard.Search"})) { %> + <fieldset> + <p><%=T("Rebuild the search index for a fresh start. <button type=\"submit\" title=\"Rebuild the search index.\">Rebuild</button>") %></p> + <%=Html.AntiForgeryTokenOrchard() %> + </fieldset><% +} %> \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Views/Search/Index.ascx b/src/Orchard.Web/Modules/Orchard.Search/Views/Search/Index.ascx new file mode 100644 index 000000000..472083772 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Views/Search/Index.ascx @@ -0,0 +1,17 @@ +<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<Orchard.Search.ViewModels.SearchViewModel>" %> +<%@ Import Namespace="Orchard.Mvc.Html" %><% +Html.RegisterStyle("search.css"); %> +<h1><%=Html.TitleForPage(T("Search").Text)%></h1><% +Html.Zone("search"); +if (!string.IsNullOrWhiteSpace(Model.Query)) { + if (Model.PageOfResults.Count() == 0) { %> + <p class="search-summary"><%=T("<em>zero</em> results") %></p><% + } + else { %> + <p class="search-summary"><%=T("<em>{0} - {1}</em> of <em>{2}</em> results", Model.PageOfResults.StartPosition, Model.PageOfResults.EndPosition, Model.PageOfResults.TotalItemCount)%></p><% + } +} +if (Model.PageOfResults != null && Model.PageOfResults.Count() > 0) { %> +<%=Html.UnorderedList(Model.PageOfResults, (r, i) => Html.DisplayForItem(r.Content).ToHtmlString() , "search-results contentItems") %> +<%=Html.Pager(Model.PageOfResults, Model.PageOfResults.PageNumber, Model.DefaultPageSize, new {q = Model.Query}) %><% +} %> \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Views/SearchForm.ascx b/src/Orchard.Web/Modules/Orchard.Search/Views/SearchForm.ascx new file mode 100644 index 000000000..b1df13ea6 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Views/SearchForm.ascx @@ -0,0 +1,8 @@ +<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<SearchViewModel>" %> +<%@ Import Namespace="Orchard.Search.ViewModels" %><% +using(Html.BeginForm("index", "search", new { area = "Orchard.Search" }, FormMethod.Get, new { @class = "search" })) { %> + <fieldset> + <%=Html.TextBox("q", Model.Query) %> + <button type="submit"><%=T("Search") %></button> + </fieldset><% +} %> \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Search/Views/Web.config b/src/Orchard.Web/Modules/Orchard.Search/Views/Web.config new file mode 100644 index 000000000..e065d8735 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Views/Web.config @@ -0,0 +1,34 @@ +<?xml version="1.0"?> +<configuration> + <system.web> + <httpHandlers> + <add path="*" verb="*" + type="System.Web.HttpNotFoundHandler"/> + </httpHandlers> + + <!-- + Enabling request validation in view pages would cause validation to occur + after the input has already been processed by the controller. By default + MVC performs request validation before a controller processes the input. + To change this behavior apply the ValidateInputAttribute to a + controller or action. + --> + <pages + validateRequest="false" + pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" + pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" + userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> + <controls> + <add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" /> + </controls> + </pages> + </system.web> + + <system.webServer> + <validation validateIntegratedModeConfiguration="false"/> + <handlers> + <remove name="BlockViewHandler"/> + <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler"/> + </handlers> + </system.webServer> +</configuration> diff --git a/src/Orchard.Web/Modules/Orchard.Search/Web.config b/src/Orchard.Web/Modules/Orchard.Search/Web.config new file mode 100644 index 000000000..c654951f1 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Search/Web.config @@ -0,0 +1,60 @@ +<?xml version="1.0"?> +<!-- + Note: As an alternative to hand editing this file you can use the + web admin tool to configure settings for your application. Use + the Website->Asp.Net Configuration option in Visual Studio. + A full list of settings and comments can be found in + machine.config.comments usually located in + \Windows\Microsoft.Net\Framework\v2.x\Config +--> +<configuration> + <appSettings/> + <connectionStrings> + <add name="ApplicationServices" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" providerName="System.Data.SqlClient"/> + </connectionStrings> + <system.web> + <!-- + Set compilation debug="true" to insert debugging + symbols into the compiled page. Because this + affects performance, set this value to true only + during development. + --> + <compilation debug="false" targetFramework="4.0"> + <assemblies> + <add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> + <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> + <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> + <add assembly="System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/></assemblies> + </compilation> + <pages controlRenderingCompatibilityVersion="3.5" clientIDMode="AutoID"> + <namespaces> + <add namespace="System.Web.Mvc"/> + <add namespace="System.Web.Mvc.Ajax"/> + <add namespace="System.Web.Mvc.Html"/> + <add namespace="System.Web.Routing"/> + <add namespace="System.Linq"/> + <add namespace="System.Collections.Generic"/> + </namespaces> + </pages> + </system.web> + <system.web.extensions/> + <!-- + The system.webServer section is required for running ASP.NET AJAX under Internet + Information Services 7.0. It is not necessary for previous version of IIS. + --> + <system.webServer> + <modules runAllManagedModulesForAllRequests="true"> + </modules> + <handlers> + <remove name="UrlRoutingHandler"/> + </handlers> + </system.webServer> + <runtime> + <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> + <dependentAssembly> + <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35"/> + <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/> + </dependentAssembly> + </assemblyBinding> + </runtime> +</configuration> diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Orchard.Setup.csproj b/src/Orchard.Web/Modules/Orchard.Setup/Orchard.Setup.csproj index 0b648ae3b..47e1b5f1d 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Orchard.Setup.csproj +++ b/src/Orchard.Web/Modules/Orchard.Setup/Orchard.Setup.csproj @@ -12,7 +12,7 @@ <RootNamespace>Orchard.Setup</RootNamespace> <AssemblyName>Orchard.Setup</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs b/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs index 26e50d90a..d8ca32bbe 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs @@ -3,6 +3,8 @@ using System.Linq; using System.Web; using Orchard.Comments.Models; using Orchard.ContentManagement; +using Orchard.ContentManagement.MetaData; +using Orchard.ContentManagement.MetaData.Builders; using Orchard.Core.Common.Models; using Orchard.Core.Navigation.Models; using Orchard.Core.Settings.Models; @@ -55,12 +57,14 @@ namespace Orchard.Setup.Services { string[] hardcoded = { "Orchard.Framework", "Common", + "Contents", "Dashboard", "Feeds", "HomePage", "Navigation", "Scheduling", "Indexing", + "Localization", "Settings", "XmlRpc", "Orchard.Users", @@ -79,7 +83,7 @@ namespace Orchard.Setup.Services { } var shellSettings = new ShellSettings(_shellSettings); - + if (string.IsNullOrEmpty(shellSettings.DataProvider)) { shellSettings.DataProvider = context.DatabaseProvider; shellSettings.DataConnectionString = context.DatabaseConnectionString; @@ -144,6 +148,11 @@ namespace Orchard.Setup.Services { //var hackInstallationGenerator = environment.Resolve<IHackInstallationGenerator>(); //hackInstallationGenerator.GenerateInstallEvents(); + var contentDefinitionManager = environment.Resolve<IContentDefinitionManager>(); + contentDefinitionManager.AlterTypeDefinition("blogpost", cfg => cfg.WithPart("HasComments").WithPart("HasTags").WithPart("Localized")); + contentDefinitionManager.AlterTypeDefinition("page", cfg => cfg.WithPart("HasComments").WithPart("HasTags").WithPart("Localized")); + contentDefinitionManager.AlterTypeDefinition("sandboxpage", cfg => cfg.WithPart("HasComments").WithPart("HasTags").WithPart("Localized")); + // create home page as a CMS page var page = contentManager.Create("page", VersionOptions.Draft); page.As<BodyAspect>().Text = "<p>Welcome to Orchard!</p><p>Congratulations, you've successfully set-up your Orchard site.</p><p>This is the home page of your new site. We've taken the liberty to write here about a few things you could look at next in order to get familiar with the application. Once you feel confident you don't need this anymore, just click <a href=\"Admin/Pages/Edit/3\">Edit</a> to go into edit mode and replace this with whatever you want on your home page to make it your own.</p><p>One thing you could do (but you don't have to) is go into <a href=\"Admin/Settings\">Manage Settings</a> (follow the <a href=\"Admin\">Admin</a> link and then look for it under \"Settings\" in the menu on the left) and check that everything is configured the way you want.</p><p>You probably want to make the site your own. One of the ways you can do that is by clicking <a href=\"Admin/Themes\">Manage Themes</a> in the admin menu. A theme is a packaged look and feel that affects the whole site.</p><p>Next, you can start playing with the content types that we installed. For example, go ahead and click <a href=\"Admin/Pages/Create\">Add New Page</a> in the admin menu and create an \"about\" page. Then, add it to the navigation menu by going to <a href=\"Admin/Navigation\">Manage Menu</a>. You can also click <a href=\"Admin/Blogs/Create\">Add New Blog</a> and start posting by clicking \"Add New Post\".</p><p>Finally, Orchard has been designed to be extended. It comes with a few built-in modules such as pages and blogs or themes. You can install new themes by going to <a href=\"Admin/Themes\">Manage Themes</a> and clicking <a href=\"Admin/Themes/Install\">Install a new Theme</a>. Like for themes, modules are created by other users of Orchard just like you so if you feel up to it, please <a href=\"http://www.orchardproject.net/\">consider participating</a>.</p><p>--The Orchard Crew</p>"; @@ -168,24 +177,6 @@ namespace Orchard.Setup.Services { var authenticationService = environment.Resolve<IAuthenticationService>(); authenticationService.SignIn(user, true); } - - //Add ContentType mappings - var contentTypeService = environment.Resolve<IContentTypeService>(); - - //Add ContentTypePartNames to MetaData - contentTypeService.AddContentTypePartNameToMetaData("HasComments"); - contentTypeService.AddContentTypePartNameToMetaData("HasTags"); - - //Add mappings from ContentTypes to ContentParts to MetaData - contentTypeService.MapContentTypeToContentPart("blogpost","HasComments"); - contentTypeService.MapContentTypeToContentPart("page", "HasComments"); - contentTypeService.MapContentTypeToContentPart("sandboxpage", "HasComments"); - contentTypeService.MapContentTypeToContentPart("blogpost", "HasTags"); - contentTypeService.MapContentTypeToContentPart("page", "HasTags"); - contentTypeService.MapContentTypeToContentPart("sandboxpage", "HasTags"); - - - } catch { environment.Resolve<ITransactionManager>().Cancel(); diff --git a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs index ab66f8d0d..26c715674 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs @@ -7,6 +7,7 @@ using Orchard.Commands; using Orchard.Commands.Builtin; using Orchard.ContentManagement; using Orchard.ContentManagement.Handlers; +using Orchard.ContentManagement.MetaData.Builders; using Orchard.Data.Builders; using Orchard.Environment.Extensions; using Orchard.Localization; @@ -90,7 +91,8 @@ namespace Orchard.Setup { class SafeModeSiteService : ISiteService { public ISite GetSiteSettings() { - var site = new ContentItemBuilder("site") + var siteType = new ContentTypeDefinitionBuilder().Named("site").Build(); + var site = new ContentItemBuilder(siteType) .Weld<SafeModeSite>() .Build(); diff --git a/src/Orchard.Web/Modules/Orchard.Tags/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Tags/AdminMenu.cs index 231e0a696..43c57efce 100644 --- a/src/Orchard.Web/Modules/Orchard.Tags/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Orchard.Tags/AdminMenu.cs @@ -1,13 +1,15 @@ -using Orchard.UI.Navigation; +using Orchard.Localization; +using Orchard.UI.Navigation; namespace Orchard.Tags { public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { - builder.Add("Tags", "3", + builder.Add(T("Tags"), "3", menu => menu - .Add("Manage Tags", "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.Tags" }).Permission(Permissions.ManageTags)) + .Add(T("Manage Tags"), "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.Tags" }).Permission(Permissions.ManageTags)) ); } } diff --git a/src/Orchard.Web/Modules/Orchard.Tags/Orchard.Tags.csproj b/src/Orchard.Web/Modules/Orchard.Tags/Orchard.Tags.csproj index d5ce73dba..2c93a394a 100644 --- a/src/Orchard.Web/Modules/Orchard.Tags/Orchard.Tags.csproj +++ b/src/Orchard.Web/Modules/Orchard.Tags/Orchard.Tags.csproj @@ -12,7 +12,7 @@ <RootNamespace>Orchard.Tags</RootNamespace> <AssemblyName>Orchard.Tags</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> diff --git a/src/Orchard.Web/Modules/Orchard.Themes/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Themes/AdminMenu.cs index 80889c706..52fb1e02c 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Orchard.Themes/AdminMenu.cs @@ -1,13 +1,15 @@ -using Orchard.UI.Navigation; +using Orchard.Localization; +using Orchard.UI.Navigation; namespace Orchard.Themes { public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { - builder.Add("Site", "11", + builder.Add(T("Site"), "11", menu => menu - .Add("Manage Themes", "4.0", item => item.Action("Index", "Admin", new { area = "Orchard.Themes" }) + .Add(T("Manage Themes"), "4.0", item => item.Action("Index", "Admin", new { area = "Orchard.Themes" }) .Permission(Permissions.ManageThemes).Permission(Permissions.ApplyTheme))); } } diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Orchard.Themes.csproj b/src/Orchard.Web/Modules/Orchard.Themes/Orchard.Themes.csproj index 500dff09c..2d78f2c16 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Orchard.Themes.csproj +++ b/src/Orchard.Web/Modules/Orchard.Themes/Orchard.Themes.csproj @@ -12,7 +12,7 @@ <RootNamespace>Orchard.Themes</RootNamespace> <AssemblyName>Orchard.Themes</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> @@ -88,6 +88,7 @@ <ItemGroup> <Content Include="Content\orchard.ico" /> <Content Include="Scripts\base.js" /> + <Content Include="Styles\admin.css" /> <Content Include="Styles\Images\toolBarActiveButtonBackground.gif" /> <Content Include="Styles\Images\toolBarBackground.gif" /> <Content Include="Styles\Images\toolBarHoverButtonBackground.gif" /> diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Styles/admin.css b/src/Orchard.Web/Modules/Orchard.Themes/Styles/admin.css new file mode 100644 index 000000000..362db6edd --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Themes/Styles/admin.css @@ -0,0 +1,38 @@ +#main .templates p { + margin:0 0 .3em 0; +} +.templates li { + margin:.8em; + width:30%; + display: -moz-inline-stack; + display:inline-block; + vertical-align:top; + zoom:1; + *display: inline; +} +.templates .inline button { + font-size:1.2em; +} +.templates .wasFormInlineLink { + font-size:1.4em; +} +.templates p { + overflow:hidden; +} +.templates img, .themePreviewImage { + border:1px solid #e8e8e8; + height:200px; + margin:.27em 0 .93em 0; + display:block; +} +.previewImage { + border:1px solid #525e50; + height:50%; + width:50%; +} +.themes #main h2 { + margin:1em 0 0 0; +} +.themePreviewImage { + height:300px; +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Views/Admin/Index.aspx b/src/Orchard.Web/Modules/Orchard.Themes/Views/Admin/Index.aspx index 0177c27ff..acf81f455 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Views/Admin/Index.aspx +++ b/src/Orchard.Web/Modules/Orchard.Themes/Views/Admin/Index.aspx @@ -1,6 +1,7 @@ <%@ Page Language="C#" Inherits="Orchard.Mvc.ViewPage<ThemesIndexViewModel>" %> <%@ Import Namespace="Orchard.Mvc.Html"%> -<%@ Import Namespace="Orchard.Themes.ViewModels"%> +<%@ Import Namespace="Orchard.Themes.ViewModels"%><% + Html.RegisterStyle("admin.css"); %> <h1><%: Html.TitleForPage(T("Manage Themes").ToString()) %></h1> <% if (Model.CurrentTheme == null) { %><p><%: T("There is no current theme in the application. The built-in theme will be used.") diff --git a/src/Orchard.Web/Modules/Orchard.Themes/Views/User.ascx b/src/Orchard.Web/Modules/Orchard.Themes/Views/User.ascx index ef473bf37..47cdceaa0 100644 --- a/src/Orchard.Web/Modules/Orchard.Themes/Views/User.ascx +++ b/src/Orchard.Web/Modules/Orchard.Themes/Views/User.ascx @@ -2,7 +2,7 @@ <%@ Import Namespace="Orchard.Mvc.ViewModels"%> <div id="logindisplay"> <% if (Request.IsAuthenticated) { %> - <%: T("Welcome, <strong>{0}</strong>!", Page.User.Identity.Name) %> + <%= T("Welcome, <strong>{0}</strong>!", Page.User.Identity.Name) %> <%: Html.ActionLink(T("Log Off").ToString(), "LogOff", new { Controller = "Account", Area = "Orchard.Users", ReturnUrl = Context.Request.RawUrl })%>  | <%: Html.ActionLink("Admin", "Index", new {Area = "Dashboard", Controller = "Admin"})%> <% } else { %> diff --git a/src/Orchard.Web/Modules/Orchard.Users/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Users/AdminMenu.cs index ff3b75ad1..874e2e95a 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/AdminMenu.cs +++ b/src/Orchard.Web/Modules/Orchard.Users/AdminMenu.cs @@ -1,14 +1,16 @@ -using Orchard.UI.Navigation; +using Orchard.Localization; +using Orchard.UI.Navigation; namespace Orchard.Users { public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } public string MenuName { get { return "admin"; } } public void GetNavigation(NavigationBuilder builder) { - builder.Add("Users", "5", + builder.Add(T("Users"), "5", menu => menu - .Add("Manage Users", "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.Users" }).Permission(Permissions.ManageUsers)) - .Add("Add New User", "1.1", item => item.Action("Create", "Admin", new { area = "Orchard.Users" }).Permission(Permissions.ManageUsers))); + .Add(T("Manage Users"), "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.Users" }).Permission(Permissions.ManageUsers)) + .Add(T("Add New User"), "1.1", item => item.Action("Create", "Admin", new { area = "Orchard.Users" }).Permission(Permissions.ManageUsers))); } } } diff --git a/src/Orchard.Web/Modules/Orchard.Users/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Users/Controllers/AdminController.cs index e85de7d77..5578531ab 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Controllers/AdminController.cs +++ b/src/Orchard.Web/Modules/Orchard.Users/Controllers/AdminController.cs @@ -10,6 +10,7 @@ using Orchard.Users.Services; using Orchard.Users.ViewModels; namespace Orchard.Users.Controllers { + [ValidateInput(false)] public class AdminController : Controller, IUpdateModel { private readonly IMembershipService _membershipService; private readonly IUserService _userService; diff --git a/src/Orchard.Web/Modules/Orchard.Users/Orchard.Users.csproj b/src/Orchard.Web/Modules/Orchard.Users/Orchard.Users.csproj index faf53194c..747a36978 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Orchard.Users.csproj +++ b/src/Orchard.Web/Modules/Orchard.Users/Orchard.Users.csproj @@ -12,7 +12,7 @@ <RootNamespace>Orchard.Users</RootNamespace> <AssemblyName>Orchard.Users</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> diff --git a/src/Orchard.Web/Modules/TinyMce/TinyMce.csproj b/src/Orchard.Web/Modules/TinyMce/TinyMce.csproj index d51a80f38..9f7e48552 100644 --- a/src/Orchard.Web/Modules/TinyMce/TinyMce.csproj +++ b/src/Orchard.Web/Modules/TinyMce/TinyMce.csproj @@ -12,7 +12,7 @@ <RootNamespace>TinyMce</RootNamespace> <AssemblyName>TinyMce</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> diff --git a/src/Orchard.Web/Orchard.Web.csproj b/src/Orchard.Web/Orchard.Web.csproj index 958b9a842..2da25214d 100644 --- a/src/Orchard.Web/Orchard.Web.csproj +++ b/src/Orchard.Web/Orchard.Web.csproj @@ -12,7 +12,7 @@ <RootNamespace>Orchard.Web</RootNamespace> <AssemblyName>Orchard.Web</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> - <MvcBuildViews>true</MvcBuildViews> + <MvcBuildViews>false</MvcBuildViews> <FileUpgradeFlags> </FileUpgradeFlags> <OldToolsVersion>3.5</OldToolsVersion> @@ -155,6 +155,10 @@ <Project>{D10AD48F-407D-4DB5-A328-173EC7CB010F}</Project> <Name>Orchard.Roles</Name> </ProjectReference> + <ProjectReference Include="Modules\Orchard.Search\Orchard.Search.csproj"> + <Project>{4BE4EB01-AC56-4048-924E-2CA77F509ABA}</Project> + <Name>Orchard.Search</Name> + </ProjectReference> <ProjectReference Include="Modules\Orchard.Setup\Orchard.Setup.csproj"> <Project>{8C7FCBC2-E6E1-405E-BFB5-D8D9E67A09C4}</Project> <Name>Orchard.Setup</Name> diff --git a/src/Orchard.Web/Themes/Classic/Styles/site.css b/src/Orchard.Web/Themes/Classic/Styles/site.css index 1f1ade327..239ecd460 100644 --- a/src/Orchard.Web/Themes/Classic/Styles/site.css +++ b/src/Orchard.Web/Themes/Classic/Styles/site.css @@ -61,8 +61,14 @@ input[type="text"], #CommentText, #password, #confirmPassword { display: block; padding:3px; width:90%; - } - +} +form.search { + margin-bottom:2em; +} +.search input[type=text] { + display:inline; + width:17em; +} fieldset div {margin:1.6em 0 0 0} legend { diff --git a/src/Orchard.Web/Themes/Classic/Views/Layout.ascx b/src/Orchard.Web/Themes/Classic/Views/Layout.ascx index b0c39f539..de642df91 100644 --- a/src/Orchard.Web/Themes/Classic/Views/Layout.ascx +++ b/src/Orchard.Web/Themes/Classic/Views/Layout.ascx @@ -33,7 +33,8 @@ <%Html.ZoneBody("content");%> </div> <div id="sidebar"> - <%Html.Zone("sidebar");%> + <% Html.Zone("search"); + Html.Zone("sidebar");%> </div> <%-- End Content --%> <% Html.Include("Footer"); %> diff --git a/src/Orchard.Web/Themes/Contoso/Views/Layout.HomePage.ascx b/src/Orchard.Web/Themes/Contoso/Views/Layout.HomePage.ascx index d72869286..831aee24a 100644 --- a/src/Orchard.Web/Themes/Contoso/Views/Layout.HomePage.ascx +++ b/src/Orchard.Web/Themes/Contoso/Views/Layout.HomePage.ascx @@ -23,7 +23,7 @@ <div class="home-hero-container"> <div class="home-hero"> <%-- Init jQuery Slider --%> - <script src="/Themes/Contoso/Scripts/easySlider.js" type="text/javascript"></script> + <script src="<%= Url.Content("~/Themes/Contoso/Scripts/easySlider.js") %>" type="text/javascript"></script> <script type="text/javascript"> $(document).ready(function() { $("#slider").easySlider({ diff --git a/src/Orchard.Web/Themes/Contoso/Zones/Highlights.html b/src/Orchard.Web/Themes/Contoso/Zones/Highlights.html index d00f99423..aa4a147a0 100644 --- a/src/Orchard.Web/Themes/Contoso/Zones/Highlights.html +++ b/src/Orchard.Web/Themes/Contoso/Zones/Highlights.html @@ -16,7 +16,7 @@ </p> </div> <div class="three"> - <img src="/Themes/Contoso/Content/Images/icon-location.png" /> + <img src="Themes/Contoso/Content/Images/icon-location.png" /> <p> 1 Microsoft Way<br /> Redmond, WA 98444<br /> diff --git a/src/Orchard.Web/Themes/Contoso/Zones/Home-Hero-Gallery.html b/src/Orchard.Web/Themes/Contoso/Zones/Home-Hero-Gallery.html index 4a51637e1..7156962ff 100644 --- a/src/Orchard.Web/Themes/Contoso/Zones/Home-Hero-Gallery.html +++ b/src/Orchard.Web/Themes/Contoso/Zones/Home-Hero-Gallery.html @@ -1,7 +1,7 @@ <div id="slider"> <ul> - <li><img src="/Themes/Contoso/Content/Images/Gallery/feature01.jpg" alt="Orchard Rocks" /></li> - <li><img src="/Themes/Contoso/Content/Images/Gallery/feature02.jpg" alt="Orchard FTW" /></li> - <li><img src="/Themes/Contoso/Content/Images/Gallery/feature03.jpg" alt="Orchard Time" /></li> + <li><img src="Themes/Contoso/Content/Images/Gallery/feature01.jpg" alt="Orchard Rocks" /></li> + <li><img src="Themes/Contoso/Content/Images/Gallery/feature02.jpg" alt="Orchard FTW" /></li> + <li><img src="Themes/Contoso/Content/Images/Gallery/feature03.jpg" alt="Orchard Time" /></li> </ul> </div> diff --git a/src/Orchard.Web/Themes/Corporate/Views/Layout.HomePage.ascx b/src/Orchard.Web/Themes/Corporate/Views/Layout.HomePage.ascx index 773031601..6e334bace 100644 --- a/src/Orchard.Web/Themes/Corporate/Views/Layout.HomePage.ascx +++ b/src/Orchard.Web/Themes/Corporate/Views/Layout.HomePage.ascx @@ -32,7 +32,7 @@ <%-- Home Hero --%> <div class="main-box"> <div class="top"> - <img src="/Themes/Corporate/Content/Images/content-top.png" /></div> + <img src="<%= Url.Content("~/Themes/Corporate/Content/Images/content-top.png") %>" /></div> <div class="content group"> <% Html.Zone("home-hero-gallery"); %> @@ -41,7 +41,7 @@ </div> <div class="bottom"> - <img src="/Themes/Corporate/Content/Images/content-bottom.png" /></div> + <img src="<%= Url.Content("~/Themes/Corporate/Content/Images/content-bottom.png") %>" /></div> </div> <%-- Main Content Area --%> diff --git a/src/Orchard.Web/Themes/Corporate/Views/Layout.ascx b/src/Orchard.Web/Themes/Corporate/Views/Layout.ascx index 23f8fa043..14e429b41 100644 --- a/src/Orchard.Web/Themes/Corporate/Views/Layout.ascx +++ b/src/Orchard.Web/Themes/Corporate/Views/Layout.ascx @@ -32,7 +32,7 @@ <%-- Content Hero --%> <div class="main-box"> <div class="top"> - <img src="/Themes/Corporate/Content/Images/content-top.png" /></div> + <img src="<%= Url.Content("~/Themes/Corporate/Content/Images/content-top.png") %>" /></div> <div class="content"> <div class="subpage group"> <div class="sub-content"> @@ -57,7 +57,7 @@ </div> <div class="bottom"> - <img src="/Themes/Corporate/Content/Images/content-bottom.png" /></div> + <img src="<%= Url.Content("~/Themes/Corporate/Content/Images/content-bottom.png") %>" /></div> </div> </div> diff --git a/src/Orchard.Web/Themes/Corporate/Zones/Home-Hero-Gallery.html b/src/Orchard.Web/Themes/Corporate/Zones/Home-Hero-Gallery.html index 07ec88acd..c25ef50b4 100644 --- a/src/Orchard.Web/Themes/Corporate/Zones/Home-Hero-Gallery.html +++ b/src/Orchard.Web/Themes/Corporate/Zones/Home-Hero-Gallery.html @@ -1,2 +1,2 @@  <div id="hm-image"> - <img src="/Themes/Corporate/Content/Images/jumping-people.jpg" /></div> \ No newline at end of file + <img src="Themes/Corporate/Content/Images/jumping-people.jpg" /></div> \ No newline at end of file diff --git a/src/Orchard.Web/Themes/Corporate/Zones/Home-Hero.html b/src/Orchard.Web/Themes/Corporate/Zones/Home-Hero.html index 2cc82e6b0..e724d418b 100644 --- a/src/Orchard.Web/Themes/Corporate/Zones/Home-Hero.html +++ b/src/Orchard.Web/Themes/Corporate/Zones/Home-Hero.html @@ -9,16 +9,16 @@ </div> <ul id="hm-icons"> <li> - <img src="/Themes/Corporate/Content/Images/icons/icon-1.png" /> + <img src="Themes/Corporate/Content/Images/icons/icon-1.png" /> <a href="#" title="Bullet Point One"> Bullet Point One</a></li> - <li><img src="/Themes/Corporate/Content/Images/icons/icon-2.png" /><a href="#" title="Bullet Point One"> + <li><img src="Themes/Corporate/Content/Images/icons/icon-2.png" /><a href="#" title="Bullet Point One"> Bullet Point Two</a></li> - <li><img src="/Themes/Corporate/Content/Images/icons/icon-3.png" /><a href="#" title="Bullet Point One"> + <li><img src="Themes/Corporate/Content/Images/icons/icon-3.png" /><a href="#" title="Bullet Point One"> Bullet Point Three</a></li> - <li><img src="/Themes/Corporate/Content/Images/icons/icon-4.png" /><a href="#" title="Bullet Point One"> + <li><img src="Themes/Corporate/Content/Images/icons/icon-4.png" /><a href="#" title="Bullet Point One"> Bullet Point Four</a></li> - <li><img src="/Themes/Corporate/Content/Images/icons/icon-5.png" /><a href="#" title="Bullet Point One"> + <li><img src="Themes/Corporate/Content/Images/icons/icon-5.png" /><a href="#" title="Bullet Point One"> Bullet Point Five</a></li> </ul> </div> diff --git a/src/Orchard.Web/Themes/TheAdmin/Styles/ie.css b/src/Orchard.Web/Themes/TheAdmin/Styles/ie.css index 5f282702b..b07efeb2b 100644 --- a/src/Orchard.Web/Themes/TheAdmin/Styles/ie.css +++ b/src/Orchard.Web/Themes/TheAdmin/Styles/ie.css @@ -1 +1,7 @@ - \ No newline at end of file +.contentItems { + /* need an alternative to: + box-shadow:inset 0 1px 2px #878686; + -moz-box-shadow:inset 0 1px 3px #878686; + -webkit-box-shadow:inset 0 1px 2px #878686; + */ +} \ No newline at end of file diff --git a/src/Orchard.Web/Themes/TheAdmin/Styles/site.css b/src/Orchard.Web/Themes/TheAdmin/Styles/site.css index 00ff2c9c7..e692f8aeb 100644 --- a/src/Orchard.Web/Themes/TheAdmin/Styles/site.css +++ b/src/Orchard.Web/Themes/TheAdmin/Styles/site.css @@ -40,7 +40,6 @@ table { } /* end: reset */ - /* Base setup ----------------------------------------------------------*/ html { @@ -56,10 +55,8 @@ body { min-width:94.6em; /* 946px */ padding:0; } -/*todo: (heskew) find out why I need this...*/ button { font-family:Segoe UI,Trebuchet,Arial,Sans-Serif; - font-size:1.01em; } body#preview { min-width:0; @@ -68,7 +65,6 @@ body#preview { background:#fcfcfc; } - /* Layout number of columns: 24; actual width: 946; column width: 26; gutter width:14 @@ -160,6 +156,7 @@ table.items th, table.items td, table.items caption { font-size:1.4em; line-heig table.items p, table.items label, table.items input, table.items .button { font-size:1em; line-height:1em; } p .button { font-size:inherit; } .meta, .hint { font-size:1.2em; } /* 12px */ +form.link button { font-size:1.01em; } /* Links ----------------------------------------------------------*/ @@ -232,7 +229,6 @@ form.link button:hover { color:#ffea9b; } - /* Navigation ----------------------------------------------------------*/ #navshortcut { @@ -276,7 +272,6 @@ form.link button:hover { text-decoration:underline; } - /* Content ----------------------------------------------------------*/ #main h1 { @@ -325,7 +320,6 @@ span.message { .message a { font-weight:bold; } - .confirmation.message { background:#D1F2A5; /* green */ border:1px solid #BCD994; @@ -358,7 +352,6 @@ span.message { content:"DEBUG » "; } - /* Forms ----------------------------------------------------------*/ form.inline, form.inline fieldset { /* todo: (heskew) need something other than .inline ... */ @@ -381,7 +374,7 @@ legend span { font-weight:normal; } label { - display: block; + display:block; color:#4c4c4c; font-weight:bold; } @@ -401,9 +394,6 @@ label input { .hint { display:block; } -.hint.forcheckbox { - text-indent:3ex; /* todo: (heskew) this might look out of place */ -} /* todo: (heskew) try to get .text on stuff like .text-box */ select, textarea, input.text, input.textMedium, input.text-box { padding:2px; @@ -422,18 +412,6 @@ input.check-box { margin-left:0; vertical-align:-.1em; } -.permalink input { - background:transparent; - border-color:#EAE9D9; - border-style:dashed; - margin-left:0; - width:350px; -} -.permalink input:focus { - background:#FFF; - border-color:#666d51; - border-style:solid; -} input.large.text, textarea, fieldset { clear:both; } @@ -441,21 +419,6 @@ fieldset { margin:0 0 1.3em 0; padding:6px 0 0; } -/* Settings page styles */ -.settings fieldset { - margin:.5em 0; - padding:1em; -} -.orchard-media fieldset div, .settings fieldset div, .orchard-media .button, .settings .button { - margin:1em 0 .5em 1em; -} -.settings legend { - margin:0 0 -.4em 0; -} -/* todo: This style is the same as textMedium. Consolidate if possible */ -.settings input.text, .settings input.text-box { - width:26em; -} textarea { min-height:8em; } @@ -487,9 +450,6 @@ button, .button, .button:link, .button:visited { text-align:center; padding:0 .8em .1em; } -button { - padding-top:.08em; -} form.link button { background:inherit; border:0; @@ -542,9 +502,8 @@ button.remove:focus::-moz-focus-inner, .remove.button:focus::-moz-focus-inner { overflow:hidden; } .actions { - height:2em; - overflow:hidden; - padding:.6em 0 .1em; + clear:right; + padding:0; text-align:right; } .contentItems .actions li { @@ -608,58 +567,6 @@ button.ibutton { .ibutton.blog:hover, .ibutton.blog:active, .ibutton.blog:focus { background-position:-120px 0; } -/* todo: (heskew) needs attention */ -.withActions { - overflow:hidden; -} -.withActions a { - display:inline; - float:left; - margin-right:7px; -} - - -/* Content item lists -----------------------------------------------------------*/ -.contentItems { - background:#FFF; - box-shadow:inset 0 1px 2px #878686; - -moz-box-shadow:inset 0 1px 3px #878686; - -webkit-box-shadow:inset 0 1px 2px #878686; - margin:1.4em 0; - padding:2px; -} -.contentItems li { - border-bottom:1px solid #eaeaea; - margin:0; - overflow:hidden; - padding:0 1.4em .8em; -} -.contentItems li.last { - border-bottom:0; -} -#main .contentItems li .actions { - color:#EAE9D9; - height:auto; - margin:-1.3em 0 0; - padding:0 0 .1em; -} -#main .contentItems li .actions .ibutton { - margin-right:6px; -} -#main .contentItems li .actions .destruct .ibutton { - margin-left:8px; - margin-right:0; -} -#main .contentItems li:hover .ibutton { background-position:0 0; } -#main .contentItems li:hover .ibutton.remove { background-position:-20px 0; } -#main .contentItems li:hover .ibutton.view { background-position:-40px 0; } -#main .contentItems li:hover .ibutton.add.page { background-position:-60px 0; } -#main .contentItems li:hover .ibutton.edit { background-position:-80px 0; } -#main .contentItems li:hover .ibutton.publish { background-position:-100px 0; } -#main .contentItems li:hover .ibutton.blog { background-position:-120px 0; } - - /* (Items) Tables ----------------------------------------------------------*/ table.items { @@ -715,170 +622,106 @@ table .button { } -/* MISC. -todo: (heskew) pull out into relevant modules where appropriate +/* Content item lists ----------------------------------------------------------*/ - -/* Pages -----------------------------------------------------------*/ -#main .templates p { - margin:0 0 .3em 0; +.contentItems { + background:#FFF; + box-shadow:inset 0 1px 2px #878686; + -moz-box-shadow:inset 0 1px 3px #878686; + -webkit-box-shadow:inset 0 1px 2px #878686; + clear:both; + margin:1.4em 0; } -.templates li { - margin:.8em; - width:30%; - display: -moz-inline-stack; - display:inline-block; - vertical-align:top; - zoom:1; - *display: inline; -} -.templates .inline button { - font-size:1.2em; -} -.templates .wasFormInlineLink { - font-size:1.4em; -} -.templates p { +.contentItems li { + border-bottom:1px solid #eaeaea; + margin:0; overflow:hidden; + padding:0 1.4em .8em; } -.templates img, .themePreviewImage { - border:1px solid #e8e8e8; - height:200px; - margin:.27em 0 .93em 0; - display:block; +.contentItems li.last { + border-bottom:0; } -.previewImage { - border:1px solid #525e50; - height:50%; - width:50%; +#main .contentItems li .actions { + color:#EAE9D9; + height:auto; + margin:-1.3em 0 0; + padding:0 0 .1em; } -.themes #main h2 { - margin:1em 0 0 0; +#main .contentItems li .actions .ibutton { + margin-right:6px; } -.themePreviewImage { - height:300px; +#main .contentItems li .actions .destruct .ibutton { + margin-left:8px; + margin-right:0; } +#main .contentItems li:hover .ibutton { background-position:0 0; } +#main .contentItems li:hover .ibutton.remove { background-position:-20px 0; } +#main .contentItems li:hover .ibutton.view { background-position:-40px 0; } +#main .contentItems li:hover .ibutton.add.page { background-position:-60px 0; } +#main .contentItems li:hover .ibutton.edit { background-position:-80px 0; } +#main .contentItems li:hover .ibutton.publish { background-position:-100px 0; } +#main .contentItems li:hover .ibutton.blog { background-position:-120px 0; } -/* Rounded borders and other "works in some browsers" additions ----------------------------------------------------------- -#content, #navigation li, button, .button, -table.items, textarea, input.text, input.text-box, -.contentItems, .message, .validation-summary-errors { - -moz-border-radius:4px; - -webkit-border-radius:4px; - border-radius:4px; -} -#navigation li h4 a { - -moz-border-radius:3px 3px 0 0; - -webkit-border-radius:3px 3px 0 0; - border-radius:3px 3px 0 0; -}*/ - - - -/* Added classes for new blog list layout ----------------------------------------------------------- */ -.blogdescription { - margin-top:1em; -} -.summary { +.contentItems .summary { overflow:auto; padding:1.2em .4em; } -.actions { - clear:right; - height:auto; - overflow:visible; - padding:0; - text-align:right; -} -.contentItems { - clear:both; - padding:0; -} -.properties { +.contentItems .properties { float:left; } -.blogs.contentItems .properties { - float:none; -} -#main .contentItems .properties h3 { - border-bottom:none; - margin:0; - padding:0; -} -.related{ - font-size:1.4em; - float:right; - text-align:right; -} -/*todo: (heskew) cleanup */ -.related .button { - font-size:1em; -} -/*end todo*/ -.commentcount { - line-height:2em; -} -.contentItems .properties ul li{ +.contentItems .properties li { border:0; float:left; - padding:.8em 0; font-size:1.4em; - background:inherit; + padding:.8em 0; } -.icon { - margin:0 .2em -.2em .2em; +.contentItems .properties .icon { + margin:0 .2em -.2em; } -h3 .icon { - margin-bottom:-.05em; -} -.linkButton { - border:none; - padding:0; - background:none; - color:#1E5D7D; - } -.linkButton:hover { - background-color:Transparent; - text-decoration:underline; - color:#1E5D7D; - } - - /* Added classes for new page list layout ----------------------------------------------------------- */ -.pageList { - margin:0; - padding:0; - /*background:#FFFFFF url(images/backgroundGradient.gif) repeat-x scroll left top;*/ - } -.orchard-pages .contentItems { - margin:.5em 0 0; - padding:0; - } -.contentItems .properties ul.pageStatus { - margin:0.8em 0 0 2em; -} -.contentItems .properties ul.pageStatus li { - margin:0; - padding:0 0 .1em 0; -} -.properties h3 { - display:inline; -} - -/* Added classes for media ----------------------------------------------------------- */ -.breadCrumbs, .folderProperties { - float:left; - margin:-2em 0 0 0; -} -.folderProperties { +.contentItems .related { float:right; + font-size:1.4em; + text-align:right; +} +.contentItems .commentcount { + line-height:2em; } -/* ---------- Generic ---------- */ + +/* Core Modules +----------------------------------------------------------*/ +/* Routable */ +.permalink input { + background:transparent; + border-color:#EAE9D9; + border-style:dashed; + margin-left:0; + width:350px; +} +.permalink input:focus { + background:#FFF; + border-color:#666d51; + border-style:solid; +} +/* Settings */ +.settings fieldset { + margin:.5em 0; + padding:1em; +} +.orchard-media fieldset div, .settings fieldset div, .orchard-media .button, .settings .button { + margin:1em 0 .5em 1em; +} +.settings legend { + margin:0 0 -.4em 0; +} +/* todo: This style is the same as textMedium. Consolidate if possible */ +.settings input.text, .settings input.text-box { + width:26em; +} + + +/* ---------- Generic ---------- */ +/* todo: needed? */ .clearBoth { clear:both; } \ No newline at end of file diff --git a/src/Orchard.Web/Web.config b/src/Orchard.Web/Web.config index 8cef8bee4..df511b385 100644 --- a/src/Orchard.Web/Web.config +++ b/src/Orchard.Web/Web.config @@ -8,7 +8,8 @@ \Windows\Microsoft.Net\Framework\v2.x\Config --> <configuration> - <appSettings/> + + <appSettings/> <system.diagnostics configSource="Config\Diagnostics.config"/> <!-- Set default transaction timeout to 30 minutes so that interactive debugging @@ -26,7 +27,10 @@ during development. --> <compilation debug="true" targetFramework="4.0"> - <assemblies> + <buildProviders> + <add extension=".csproj" type="Orchard.Environment.Extensions.Compilers.CSharpExtensionBuildProvider"/> + </buildProviders> + <assemblies> <add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> @@ -114,8 +118,9 @@ </handlers> </system.webServer> <runtime> - <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> - <dependentAssembly> + <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> + <probing privatePath="App_Data/Dependencies"/> + <dependentAssembly> <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35"/> <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/> </dependentAssembly> diff --git a/src/Orchard.sln b/src/Orchard.sln index 7337aa9e1..c1b3aa847 100644 --- a/src/Orchard.sln +++ b/src/Orchard.sln @@ -65,6 +65,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Profile", "Orchard. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.MetaData", "Orchard.Web\Modules\Orchard.MetaData\Orchard.MetaData.csproj", "{23E04990-2A8D-41B8-9908-6DDB71EA3B23}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Search", "Orchard.Web\Modules\Orchard.Search\Orchard.Search.csproj", "{4BE4EB01-AC56-4048-924E-2CA77F509ABA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -183,12 +185,15 @@ Global {23E04990-2A8D-41B8-9908-6DDB71EA3B23}.Debug|Any CPU.Build.0 = Debug|Any CPU {23E04990-2A8D-41B8-9908-6DDB71EA3B23}.Release|Any CPU.ActiveCfg = Release|Any CPU {23E04990-2A8D-41B8-9908-6DDB71EA3B23}.Release|Any CPU.Build.0 = Release|Any CPU + {4BE4EB01-AC56-4048-924E-2CA77F509ABA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BE4EB01-AC56-4048-924E-2CA77F509ABA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BE4EB01-AC56-4048-924E-2CA77F509ABA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BE4EB01-AC56-4048-924E-2CA77F509ABA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {94E694A2-D140-468D-A277-C5FCE1D13E9B} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} {954CA994-D204-468B-9D69-51F6AD3E1C29} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {D9A7B330-CD22-4DA1-A95A-8DE1982AD8EB} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {79AED36E-ABD0-4747-93D3-8722B042454B} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} @@ -204,11 +209,13 @@ Global {CDE24A24-01D3-403C-84B9-37722E18DFB7} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {17F86780-9A1F-4AA1-86F1-875EEC2730C7} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {23E04990-2A8D-41B8-9908-6DDB71EA3B23} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} + {4BE4EB01-AC56-4048-924E-2CA77F509ABA} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {ABC826D4-2FA1-4F2F-87DE-E6095F653810} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} {F112851D-B023-4746-B6B1-8D2E5AD8F7AA} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} {6CB3EB30-F725-45C0-9742-42599BA8E8D2} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} {2FC1D9C8-446D-4414-B252-5E9FBE61EB63} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} {7354DF37-934B-46CF-A13C-455D5F5F5413} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} + {94E694A2-D140-468D-A277-C5FCE1D13E9B} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} {5E5E7A21-C7B2-44D8-8593-2F9541AE041D} = {383DBA32-4A3E-48D1-AAC3-75377A694452} {4AB4B5B6-277E-4FF6-B69B-7AE9E16D2A56} = {383DBA32-4A3E-48D1-AAC3-75377A694452} {33B1BC8D-E292-4972-A363-22056B207156} = {383DBA32-4A3E-48D1-AAC3-75377A694452} diff --git a/src/Orchard/Collections/IPageOfItems.cs b/src/Orchard/Collections/IPageOfItems.cs new file mode 100644 index 000000000..4ed9362bd --- /dev/null +++ b/src/Orchard/Collections/IPageOfItems.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Orchard.Collections { + public interface IPageOfItems<out T> : IEnumerable<T> { + int PageNumber { get; set; } + int PageSize { get; set; } + int TotalItemCount { get; set; } + int TotalPageCount { get; } + int StartPosition { get; } + int EndPosition { get; } + } +} \ No newline at end of file diff --git a/src/Orchard/Collections/PageOfItems.cs b/src/Orchard/Collections/PageOfItems.cs new file mode 100644 index 000000000..3e69aa696 --- /dev/null +++ b/src/Orchard/Collections/PageOfItems.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace Orchard.Collections { + public class PageOfItems<T> : List<T>, IPageOfItems<T> { + public PageOfItems(IEnumerable<T> items) { + AddRange(items); + } + + #region IPageOfItems<T> Members + + public int PageNumber { get; set; } + public int PageSize { get; set; } + public int TotalItemCount { get; set; } + + public int TotalPageCount { + get { return (int) Math.Ceiling((double) TotalItemCount/PageSize); } + } + public int StartPosition { + get { return (PageNumber - 1)*PageSize + 1; } + } + public int EndPosition { + get { return PageNumber * PageSize > TotalItemCount ? TotalItemCount : PageNumber * PageSize; } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Orchard/Commands/DefaultOrchardCommandHandler.cs b/src/Orchard/Commands/DefaultOrchardCommandHandler.cs index 1242227e4..5e591d205 100644 --- a/src/Orchard/Commands/DefaultOrchardCommandHandler.cs +++ b/src/Orchard/Commands/DefaultOrchardCommandHandler.cs @@ -45,7 +45,7 @@ namespace Orchard.Commands { propertyInfo.SetValue(this, stringValue, null); } else { - throw new InvalidOperationException(T("No property named {0} found of type bool, int or string.", commandSwitch)); + throw new InvalidOperationException(T("No property named {0} found of type bool, int or string.", commandSwitch).ToString()); } } } @@ -55,7 +55,7 @@ namespace Orchard.Commands { CheckMethodForSwitches(context.CommandDescriptor.MethodInfo, context.Switches); object[] invokeParameters = GetInvokeParametersForMethod(context.CommandDescriptor.MethodInfo, (context.Arguments ?? Enumerable.Empty<string>()).ToArray()); if (invokeParameters == null) { - throw new InvalidOperationException(T("Command arguments don't match")); + throw new InvalidOperationException(T("Command arguments don't match").ToString()); } this.Context = context; @@ -105,7 +105,7 @@ namespace Orchard.Commands { } foreach (var commandSwitch in switches.Keys) { if (!supportedSwitches.Contains(commandSwitch)) { - throw new InvalidOperationException(T("Method {0} does not support switch {1}.", methodInfo.Name, commandSwitch)); + throw new InvalidOperationException(T("Method {0} does not support switch {1}.", methodInfo.Name, commandSwitch).ToString()); } } } diff --git a/src/Orchard/ContentManagement/Aspects/IRoutableAspect.cs b/src/Orchard/ContentManagement/Aspects/IRoutableAspect.cs new file mode 100644 index 000000000..0943b76ce --- /dev/null +++ b/src/Orchard/ContentManagement/Aspects/IRoutableAspect.cs @@ -0,0 +1,6 @@ +namespace Orchard.ContentManagement.Aspects { + public interface IRoutableAspect : IContent { + string Title { get; set; } + string Slug { get; set; } + } +} diff --git a/src/Orchard/ContentManagement/ContentField.cs b/src/Orchard/ContentManagement/ContentField.cs new file mode 100644 index 000000000..671ac54fd --- /dev/null +++ b/src/Orchard/ContentManagement/ContentField.cs @@ -0,0 +1,8 @@ +using Orchard.ContentManagement.MetaData.Models; + +namespace Orchard.ContentManagement { + public class ContentField { + public string Name { get; set; } + public ContentFieldDefinition Definition { get; set; } + } +} diff --git a/src/Orchard/ContentManagement/ContentItem.cs b/src/Orchard/ContentManagement/ContentItem.cs index 42956b558..a6f751230 100644 --- a/src/Orchard/ContentManagement/ContentItem.cs +++ b/src/Orchard/ContentManagement/ContentItem.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Orchard.ContentManagement.MetaData.Models; using Orchard.ContentManagement.Records; namespace Orchard.ContentManagement { @@ -17,6 +18,7 @@ namespace Orchard.ContentManagement { public int Version { get { return VersionRecord == null ? 0 : VersionRecord.Number; } } public string ContentType { get; set; } + public ContentTypeDefinition TypeDefinition { get; set; } public ContentItemRecord Record { get { return VersionRecord == null ? null : VersionRecord.ContentItemRecord; } } public ContentItemVersionRecord VersionRecord { get; set; } diff --git a/src/Orchard/ContentManagement/ContentPart.cs b/src/Orchard/ContentManagement/ContentPart.cs index 180ea362f..8f485aa9a 100644 --- a/src/Orchard/ContentManagement/ContentPart.cs +++ b/src/Orchard/ContentManagement/ContentPart.cs @@ -1,8 +1,12 @@ +using Orchard.ContentManagement.MetaData.Models; using Orchard.ContentManagement.Utilities; namespace Orchard.ContentManagement { public abstract class ContentPart : IContent { public virtual ContentItem ContentItem { get; set; } + public ContentTypeDefinition TypeDefinition { get { return ContentItem.TypeDefinition; } } + public ContentTypeDefinition.Part TypePartDefinition { get; set; } + public ContentPartDefinition PartDefinition { get { return TypePartDefinition.PartDefinition; } } } public class ContentPart<TRecord> : ContentPart { diff --git a/src/Orchard/ContentManagement/DefaultContentManager.cs b/src/Orchard/ContentManagement/DefaultContentManager.cs index ee99a3716..e4e4a6973 100644 --- a/src/Orchard/ContentManagement/DefaultContentManager.cs +++ b/src/Orchard/ContentManagement/DefaultContentManager.cs @@ -3,6 +3,9 @@ using System.Collections.Generic; using System.Linq; using Autofac; using Orchard.ContentManagement.Handlers; +using Orchard.ContentManagement.MetaData; +using Orchard.ContentManagement.MetaData.Builders; +using Orchard.ContentManagement.MetaData.Models; using Orchard.ContentManagement.Records; using Orchard.Data; using Orchard.Mvc.ViewModels; @@ -13,6 +16,7 @@ namespace Orchard.ContentManagement { private readonly IRepository<ContentTypeRecord> _contentTypeRepository; private readonly IRepository<ContentItemRecord> _contentItemRepository; private readonly IRepository<ContentItemVersionRecord> _contentItemVersionRepository; + private readonly IContentDefinitionManager _contentDefinitionManager; private readonly Func<IContentManagerSession> _contentManagerSession; public DefaultContentManager( @@ -20,11 +24,13 @@ namespace Orchard.ContentManagement { IRepository<ContentTypeRecord> contentTypeRepository, IRepository<ContentItemRecord> contentItemRepository, IRepository<ContentItemVersionRecord> contentItemVersionRepository, + IContentDefinitionManager contentDefinitionManager, Func<IContentManagerSession> contentManagerSession) { _context = context; _contentTypeRepository = contentTypeRepository; _contentItemRepository = contentItemRepository; _contentItemVersionRepository = contentItemVersionRepository; + _contentDefinitionManager = contentDefinitionManager; _contentManagerSession = contentManagerSession; } @@ -44,11 +50,16 @@ namespace Orchard.ContentManagement { } public virtual ContentItem New(string contentType) { + var contentTypeDefinition = _contentDefinitionManager.GetTypeDefinition(contentType); + if (contentTypeDefinition == null) { + contentTypeDefinition = new ContentTypeDefinitionBuilder().Named(contentType).Build(); + } // create a new kernel for the model instance var context = new ActivatingContentContext { - ContentType = contentType, - Builder = new ContentItemBuilder(contentType) + ContentType = contentTypeDefinition.Name, + Definition = contentTypeDefinition, + Builder = new ContentItemBuilder(contentTypeDefinition) }; // invoke handlers to weld aspects onto kernel @@ -336,6 +347,20 @@ namespace Orchard.ContentManagement { foreach (var handler in Handlers) { handler.Created(context); } + + if (options.IsPublished) { + var publishContext = new PublishContentContext(contentItem, null); + + // invoke handlers to acquire state, or at least establish lazy loading callbacks + foreach (var handler in Handlers) { + handler.Publishing(publishContext); + } + + // invoke handlers to acquire state, or at least establish lazy loading callbacks + foreach (var handler in Handlers) { + handler.Published(publishContext); + } + } } public ContentItemMetadata GetItemMetadata(IContent content) { diff --git a/src/Orchard/ContentManagement/Drivers/ContentItemDriverHandler.cs b/src/Orchard/ContentManagement/Drivers/ContentItemDriverHandler.cs index ca2cbd200..f6d456c32 100644 --- a/src/Orchard/ContentManagement/Drivers/ContentItemDriverHandler.cs +++ b/src/Orchard/ContentManagement/Drivers/ContentItemDriverHandler.cs @@ -5,7 +5,7 @@ using Orchard.Logging; namespace Orchard.ContentManagement.Drivers { [UsedImplicitly] - public class ContentItemDriverHandler : IContentHandler { + public class ContentItemDriverHandler : ContentHandlerBase { private readonly IEnumerable<IContentItemDriver> _drivers; public ContentItemDriverHandler(IEnumerable<IContentItemDriver> drivers) { @@ -15,33 +15,17 @@ namespace Orchard.ContentManagement.Drivers { public ILogger Logger { get; set; } - IEnumerable<ContentType> IContentHandler.GetContentTypes() { + public override IEnumerable<ContentType> GetContentTypes() { var contentTypes = new List<ContentType>(); _drivers.Invoke(driver=>contentTypes.AddRange(driver.GetContentTypes()), Logger); return contentTypes; } - void IContentHandler.Activating(ActivatingContentContext context) { } - void IContentHandler.Activated(ActivatedContentContext context) { } - void IContentHandler.Creating(CreateContentContext context) { } - void IContentHandler.Created(CreateContentContext context) { } - void IContentHandler.Loading(LoadContentContext context) { } - void IContentHandler.Loaded(LoadContentContext context) { } - void IContentHandler.Versioning(VersionContentContext context) { } - void IContentHandler.Versioned(VersionContentContext context) { } - void IContentHandler.Publishing(PublishContentContext context) { } - void IContentHandler.Published(PublishContentContext context) { } - void IContentHandler.Removing(RemoveContentContext context) { } - void IContentHandler.Removed(RemoveContentContext context) { } - void IContentHandler.Indexing(IndexContentContext context) { } - void IContentHandler.Indexed(IndexContentContext context) { } - - - void IContentHandler.GetContentItemMetadata(GetContentItemMetadataContext context) { + public override void GetContentItemMetadata(GetContentItemMetadataContext context) { _drivers.Invoke(driver => driver.GetContentItemMetadata(context), Logger); } - void IContentHandler.BuildDisplayModel(BuildDisplayModelContext context) { + public override void BuildDisplayModel(BuildDisplayModelContext context) { _drivers.Invoke(driver => { var result = driver.BuildDisplayModel(context); if (result != null) @@ -49,7 +33,7 @@ namespace Orchard.ContentManagement.Drivers { }, Logger); } - void IContentHandler.BuildEditorModel(BuildEditorModelContext context) { + public override void BuildEditorModel(BuildEditorModelContext context) { _drivers.Invoke(driver => { var result = driver.BuildEditorModel(context); if (result != null) @@ -57,7 +41,7 @@ namespace Orchard.ContentManagement.Drivers { }, Logger); } - void IContentHandler.UpdateEditorModel(UpdateEditorModelContext context) { + public override void UpdateEditorModel(UpdateEditorModelContext context) { _drivers.Invoke(driver => { var result = driver.UpdateEditorModel(context); if (result != null) diff --git a/src/Orchard/ContentManagement/Drivers/ContentPartDriver.cs b/src/Orchard/ContentManagement/Drivers/ContentPartDriver.cs index 91b12f342..d399a19fa 100644 --- a/src/Orchard/ContentManagement/Drivers/ContentPartDriver.cs +++ b/src/Orchard/ContentManagement/Drivers/ContentPartDriver.cs @@ -31,19 +31,19 @@ namespace Orchard.ContentManagement.Drivers { return part == null ? null : Editor(part, context.Updater); } - protected virtual DriverResult Display(TContent part, string displayType) {return null;} - protected virtual DriverResult Editor(TContent part) {return null;} - protected virtual DriverResult Editor(TContent part, IUpdateModel updater) {return null;} + protected virtual DriverResult Display(TContent part, string displayType) { return null; } + protected virtual DriverResult Editor(TContent part) { return null; } + protected virtual DriverResult Editor(TContent part, IUpdateModel updater) { return null; } public ContentPartTemplateResult ContentPartTemplate(object model) { return new ContentPartTemplateResult(model, null, Prefix).Location(Zone); } - + public ContentPartTemplateResult ContentPartTemplate(object model, string template) { return new ContentPartTemplateResult(model, template, Prefix).Location(Zone); } - + public ContentPartTemplateResult ContentPartTemplate(object model, string template, string prefix) { return new ContentPartTemplateResult(model, template, prefix).Location(Zone); } @@ -52,11 +52,12 @@ namespace Orchard.ContentManagement.Drivers { return new CombinedResult(results); } - public IEnumerable<ContentPartInfo> GetPartInfo() - { - var contentPartInfo = new List<ContentPartInfo>() { - new ContentPartInfo() - {PartName = typeof(TContent).Name,Factory = () => new TContent()} + public IEnumerable<ContentPartInfo> GetPartInfo() { + var contentPartInfo = new[] { + new ContentPartInfo { + PartName = typeof (TContent).Name, + Factory = typePartDefinition => new TContent {TypePartDefinition = typePartDefinition} + } }; return contentPartInfo; diff --git a/src/Orchard/ContentManagement/Drivers/ContentPartDriverHandler.cs b/src/Orchard/ContentManagement/Drivers/ContentPartDriverHandler.cs index 3a6f67da7..eb6cc6130 100644 --- a/src/Orchard/ContentManagement/Drivers/ContentPartDriverHandler.cs +++ b/src/Orchard/ContentManagement/Drivers/ContentPartDriverHandler.cs @@ -6,7 +6,7 @@ using Orchard.Logging; namespace Orchard.ContentManagement.Drivers { [UsedImplicitly] - public class ContentPartDriverHandler : IContentHandler { + public class ContentPartDriverHandler : ContentHandlerBase { private readonly IEnumerable<IContentPartDriver> _drivers; public ContentPartDriverHandler(IEnumerable<IContentPartDriver> drivers) { @@ -16,28 +16,7 @@ namespace Orchard.ContentManagement.Drivers { public ILogger Logger { get; set; } - IEnumerable<ContentType> IContentHandler.GetContentTypes() { - return Enumerable.Empty<ContentType>(); - } - - void IContentHandler.Activating(ActivatingContentContext context) { } - void IContentHandler.Activated(ActivatedContentContext context) { } - void IContentHandler.Creating(CreateContentContext context) { } - void IContentHandler.Created(CreateContentContext context) { } - void IContentHandler.Loading(LoadContentContext context) { } - void IContentHandler.Loaded(LoadContentContext context) { } - void IContentHandler.Versioning(VersionContentContext context) { } - void IContentHandler.Versioned(VersionContentContext context) { } - void IContentHandler.Publishing(PublishContentContext context) { } - void IContentHandler.Published(PublishContentContext context) { } - void IContentHandler.Removing(RemoveContentContext context) { } - void IContentHandler.Removed(RemoveContentContext context) { } - void IContentHandler.Indexing(IndexContentContext context) { } - void IContentHandler.Indexed(IndexContentContext context) { } - - void IContentHandler.GetContentItemMetadata(GetContentItemMetadataContext context) { } - - void IContentHandler.BuildDisplayModel(BuildDisplayModelContext context) { + public override void BuildDisplayModel(BuildDisplayModelContext context) { _drivers.Invoke(driver => { var result = driver.BuildDisplayModel(context); if (result != null) @@ -45,7 +24,7 @@ namespace Orchard.ContentManagement.Drivers { }, Logger); } - void IContentHandler.BuildEditorModel(BuildEditorModelContext context) { + public override void BuildEditorModel(BuildEditorModelContext context) { _drivers.Invoke(driver => { var result = driver.BuildEditorModel(context); if (result != null) @@ -53,7 +32,7 @@ namespace Orchard.ContentManagement.Drivers { }, Logger); } - void IContentHandler.UpdateEditorModel(UpdateEditorModelContext context) { + public override void UpdateEditorModel(UpdateEditorModelContext context) { _drivers.Invoke(driver => { var result = driver.UpdateEditorModel(context); if (result != null) diff --git a/src/Orchard/ContentManagement/Handlers/ActivatingContentContext.cs b/src/Orchard/ContentManagement/Handlers/ActivatingContentContext.cs index de3bb8a8d..3d202465b 100644 --- a/src/Orchard/ContentManagement/Handlers/ActivatingContentContext.cs +++ b/src/Orchard/ContentManagement/Handlers/ActivatingContentContext.cs @@ -1,6 +1,9 @@ +using Orchard.ContentManagement.MetaData.Models; + namespace Orchard.ContentManagement.Handlers { public class ActivatingContentContext { public string ContentType { get; set; } + public ContentTypeDefinition Definition { get; set; } public ContentItemBuilder Builder { get; set; } } } diff --git a/src/Orchard/ContentManagement/Handlers/ContentHandler.cs b/src/Orchard/ContentManagement/Handlers/ContentHandler.cs index de68ae0b8..61264e60f 100644 --- a/src/Orchard/ContentManagement/Handlers/ContentHandler.cs +++ b/src/Orchard/ContentManagement/Handlers/ContentHandler.cs @@ -242,7 +242,7 @@ namespace Orchard.ContentManagement.Handlers { void IContentHandler.Indexed(IndexContentContext context) { foreach ( var filter in Filters.OfType<IContentStorageFilter>() ) filter.Indexed(context); - Indexing(context); + Indexed(context); } void IContentHandler.GetContentItemMetadata(GetContentItemMetadataContext context) { diff --git a/src/Orchard/ContentManagement/Handlers/ContentHandlerBase.cs b/src/Orchard/ContentManagement/Handlers/ContentHandlerBase.cs new file mode 100644 index 000000000..f8026393a --- /dev/null +++ b/src/Orchard/ContentManagement/Handlers/ContentHandlerBase.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Orchard.ContentManagement.Handlers { + public class ContentHandlerBase : IContentHandler { + public virtual IEnumerable<ContentType> GetContentTypes() { + return Enumerable.Empty<ContentType>(); + } + + public virtual void Activating(ActivatingContentContext context) { + } + + public virtual void Activated(ActivatedContentContext context) { + } + + public virtual void Creating(CreateContentContext context) { + } + + public virtual void Created(CreateContentContext context) { + } + + public virtual void Loading(LoadContentContext context) { + } + + public virtual void Loaded(LoadContentContext context) { + } + + public virtual void Versioning(VersionContentContext context) { + } + + public virtual void Versioned(VersionContentContext context) { + } + + public virtual void Publishing(PublishContentContext context) { + } + + public virtual void Published(PublishContentContext context) { + } + + public virtual void Removing(RemoveContentContext context) { + } + + public virtual void Removed(RemoveContentContext context) { + } + + public virtual void Indexing(IndexContentContext context) { + } + + public virtual void Indexed(IndexContentContext context) { + } + + public virtual void GetContentItemMetadata(GetContentItemMetadataContext context) { + } + + public virtual void BuildDisplayModel(BuildDisplayModelContext context) { + } + + public virtual void BuildEditorModel(BuildEditorModelContext context) { + } + + public virtual void UpdateEditorModel(UpdateEditorModelContext context) { + } + } +} \ No newline at end of file diff --git a/src/Orchard/ContentManagement/Handlers/ContentItemBuilder.cs b/src/Orchard/ContentManagement/Handlers/ContentItemBuilder.cs index fc8449d6c..619558c58 100644 --- a/src/Orchard/ContentManagement/Handlers/ContentItemBuilder.cs +++ b/src/Orchard/ContentManagement/Handlers/ContentItemBuilder.cs @@ -1,9 +1,18 @@ -namespace Orchard.ContentManagement.Handlers { +using System.Collections.Generic; +using System.Linq; +using Orchard.ContentManagement.MetaData.Models; + +namespace Orchard.ContentManagement.Handlers { public class ContentItemBuilder { + private readonly ContentTypeDefinition _definition; private readonly ContentItem _item; - public ContentItemBuilder(string contentType) { - _item = new ContentItem { ContentType = contentType }; + public ContentItemBuilder(ContentTypeDefinition definition) { + _definition = definition; + _item = new ContentItem { + ContentType = definition.Name, + TypeDefinition = definition + }; } public ContentItem Build() { @@ -11,7 +20,18 @@ } public ContentItemBuilder Weld<TPart>() where TPart : ContentPart, new() { - var part = new TPart(); + var partName = typeof(TPart).Name; + + var typePartDefinition = _definition.Parts.FirstOrDefault(p => p.PartDefinition.Name == partName); + if (typePartDefinition == null) { + typePartDefinition = new ContentTypeDefinition.Part( + new ContentPartDefinition(partName), + new Dictionary<string, string>()); + } + + var part = new TPart { + TypePartDefinition = typePartDefinition + }; _item.Weld(part); return this; } @@ -20,6 +40,5 @@ _item.Weld(contentPart); return this; } - } } diff --git a/src/Orchard/ContentManagement/Handlers/IContentHandler.cs b/src/Orchard/ContentManagement/Handlers/IContentHandler.cs index 846e6a1fd..7dc37cfa7 100644 --- a/src/Orchard/ContentManagement/Handlers/IContentHandler.cs +++ b/src/Orchard/ContentManagement/Handlers/IContentHandler.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Orchard.Events; namespace Orchard.ContentManagement.Handlers { diff --git a/src/Orchard/ContentManagement/MetaData/Builders/ContentPartDefinitionBuilder.cs b/src/Orchard/ContentManagement/MetaData/Builders/ContentPartDefinitionBuilder.cs new file mode 100644 index 000000000..4e9fc0de6 --- /dev/null +++ b/src/Orchard/ContentManagement/MetaData/Builders/ContentPartDefinitionBuilder.cs @@ -0,0 +1,19 @@ +using System; +using Orchard.ContentManagement.MetaData.Models; + +namespace Orchard.ContentManagement.MetaData.Builders { + public class ContentPartDefinitionBuilder { + public ContentPartDefinitionBuilder(ContentPartDefinition partDefinition) { + throw new NotImplementedException(); + } + + public ContentPartDefinition Build() { + throw new NotImplementedException(); + } + + public ContentPartDefinitionBuilder WithSetting(string name, string value) { + throw new NotImplementedException(); + } + + } +} \ No newline at end of file diff --git a/src/Orchard/ContentManagement/MetaData/Builders/ContentTypeDefinitionBuilder.cs b/src/Orchard/ContentManagement/MetaData/Builders/ContentTypeDefinitionBuilder.cs new file mode 100644 index 000000000..b2d997079 --- /dev/null +++ b/src/Orchard/ContentManagement/MetaData/Builders/ContentTypeDefinitionBuilder.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Orchard.ContentManagement.MetaData.Models; + +namespace Orchard.ContentManagement.MetaData.Builders { + public class ContentTypeDefinitionBuilder { + private string _name; + private readonly IList<ContentTypeDefinition.Part> _parts; + private readonly IDictionary<string, string> _settings; + + public ContentTypeDefinitionBuilder() + : this(new ContentTypeDefinition(null)) { + } + + public ContentTypeDefinitionBuilder(ContentTypeDefinition existing) { + if (existing == null) { + _parts = new List<ContentTypeDefinition.Part>(); + _settings = new Dictionary<string, string>(); + } + else { + _name = existing.Name; + _parts = existing.Parts.ToList(); + _settings = existing.Settings.ToDictionary(kv => kv.Key, kv => kv.Value); + } + } + + private void Init(ContentTypeDefinition existing) { + + } + + public ContentTypeDefinition Build() { + return new ContentTypeDefinition(_name, _parts, _settings); + } + + public ContentTypeDefinitionBuilder Named(string name) { + _name = name; + return this; + } + + public ContentTypeDefinitionBuilder WithSetting(string name, string value) { + _settings[name] = value; + return this; + } + + public ContentTypeDefinitionBuilder RemovePart(string partName) { + var existingPart = _parts.SingleOrDefault(x => x.PartDefinition.Name == partName); + if (existingPart != null) { + _parts.Remove(existingPart); + } + return this; + } + + public ContentTypeDefinitionBuilder WithPart(string partName) { + return WithPart(partName, configuration => { }); + } + + public ContentTypeDefinitionBuilder WithPart(string partName, Action<PartConfigurer> configuration) { + return WithPart(new ContentPartDefinition(partName), configuration); + } + + public ContentTypeDefinitionBuilder WithPart(ContentPartDefinition partDefinition, Action<PartConfigurer> configuration) { + var existingPart = _parts.SingleOrDefault(x => x.PartDefinition.Name == partDefinition.Name); + if (existingPart != null) { + _parts.Remove(existingPart); + } + else { + existingPart = new ContentTypeDefinition.Part(partDefinition, new Dictionary<string, string>()); + } + var configurer = new PartConfigurerImpl(existingPart); + configuration(configurer); + _parts.Add(configurer.Build()); + return this; + } + + public abstract class PartConfigurer { + protected readonly IDictionary<string, string> _settings; + + protected PartConfigurer(ContentTypeDefinition.Part part) { + _settings = part.Settings.ToDictionary(kv => kv.Key, kv => kv.Value); + } + + public PartConfigurer WithSetting(string name, string value) { + _settings[name] = value; + return this; + } + } + + class PartConfigurerImpl : PartConfigurer { + private readonly ContentPartDefinition _partDefinition; + + public PartConfigurerImpl(ContentTypeDefinition.Part part) + : base(part) { + _partDefinition = part.PartDefinition; + } + + public ContentTypeDefinition.Part Build() { + return new ContentTypeDefinition.Part(_partDefinition, _settings); + } + } + + } +} diff --git a/src/Orchard/ContentManagement/MetaData/ContentPartHandler.cs b/src/Orchard/ContentManagement/MetaData/ContentPartHandler.cs index 350e27371..465ac806d 100644 --- a/src/Orchard/ContentManagement/MetaData/ContentPartHandler.cs +++ b/src/Orchard/ContentManagement/MetaData/ContentPartHandler.cs @@ -1,32 +1,28 @@ using System.Collections.Generic; using System.Linq; using Orchard.ContentManagement.Handlers; -using Orchard.ContentManagement.MetaData.Services; using Orchard.ContentManagement.Drivers; namespace Orchard.ContentManagement.MetaData { - public class ContentPartHandler : ContentHandler { + public class ContentPartHandler : ContentHandlerBase { private readonly IEnumerable<IContentPartDriver> _contentPartDrivers; - private readonly IContentTypeService _contentTypeService; + private readonly IContentDefinitionManager _contentDefinitionManager; - public ContentPartHandler(IEnumerable<IContentPartDriver> contentPartDrivers, IContentTypeService contentTypeService) { + public ContentPartHandler(IEnumerable<IContentPartDriver> contentPartDrivers, IContentDefinitionManager contentDefinitionManager) { _contentPartDrivers = contentPartDrivers; - _contentTypeService = contentTypeService; + _contentDefinitionManager = contentDefinitionManager; } - protected override void Activating(ActivatingContentContext context) { - var contentTypeRecord = _contentTypeService.GetContentTypeRecord(context.ContentType); - if (contentTypeRecord == null) + public override void Activating(ActivatingContentContext context) { + var contentTypeDefinition = _contentDefinitionManager.GetTypeDefinition(context.ContentType); + if (contentTypeDefinition == null) return; - var contentPartInfos = _contentPartDrivers.SelectMany(cpp => cpp.GetPartInfo()).ToList(); - - foreach (var contentTypePartRecord in contentTypeRecord.ContentParts) { - // We might have a part in the database, but the corresponding feature might not - // be enabled anymore, so we need to be resilient to that situation. - var contentPartInfo = contentPartInfos.SingleOrDefault(x => x.PartName == contentTypePartRecord.PartName.PartName); - if (contentPartInfo != null) { - context.Builder.Weld(contentPartInfo.Factory()); + foreach (var partInfo in _contentPartDrivers.SelectMany(cpp => cpp.GetPartInfo())) { + var partName = partInfo.PartName; + var typePartDefinition = contentTypeDefinition.Parts.FirstOrDefault(p => p.PartDefinition.Name == partName); + if (typePartDefinition != null) { + context.Builder.Weld(partInfo.Factory(typePartDefinition)); } } } diff --git a/src/Orchard/ContentManagement/MetaData/ContentPartInfo.cs b/src/Orchard/ContentManagement/MetaData/ContentPartInfo.cs index 23a731cbf..a7c8c190a 100644 --- a/src/Orchard/ContentManagement/MetaData/ContentPartInfo.cs +++ b/src/Orchard/ContentManagement/MetaData/ContentPartInfo.cs @@ -1,10 +1,9 @@ using System; +using Orchard.ContentManagement.MetaData.Models; -namespace Orchard.ContentManagement.MetaData -{ - public class ContentPartInfo - { +namespace Orchard.ContentManagement.MetaData { + public class ContentPartInfo { public string PartName { get; set; } - public Func<ContentPart> Factory { get; set; } + public Func<ContentTypeDefinition.Part, ContentPart> Factory { get; set; } } } diff --git a/src/Orchard/ContentManagement/MetaData/IContentDefinitionManager.cs b/src/Orchard/ContentManagement/MetaData/IContentDefinitionManager.cs new file mode 100644 index 000000000..8b635f2a5 --- /dev/null +++ b/src/Orchard/ContentManagement/MetaData/IContentDefinitionManager.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using Orchard.ContentManagement.MetaData.Builders; +using Orchard.ContentManagement.MetaData.Models; + +namespace Orchard.ContentManagement.MetaData { + public interface IContentDefinitionManager : IDependency { + IEnumerable<ContentTypeDefinition> ListTypeDefinitions(); + IEnumerable<ContentPartDefinition> ListPartDefinitions(); + + ContentTypeDefinition GetTypeDefinition(string name); + ContentPartDefinition GetPartDefinition(string name); + + void StoreTypeDefinition(ContentTypeDefinition contentTypeDefinition); + void StorePartDefinition(ContentPartDefinition contentPartDefinition); + } + + public static class ContentDefinitionManagerExtensions{ + public static void AlterTypeDefinition(this IContentDefinitionManager manager, string name, Action<ContentTypeDefinitionBuilder> alteration) { + var typeDefinition = manager.GetTypeDefinition(name) ?? new ContentTypeDefinition(name); + var builder = new ContentTypeDefinitionBuilder(typeDefinition); + alteration(builder); + manager.StoreTypeDefinition(builder.Build()); + } + public static void AlterPartDefinition(this IContentDefinitionManager manager, string name, Action<ContentPartDefinitionBuilder> alteration) { + var partDefinition = manager.GetPartDefinition(name) ?? new ContentPartDefinition(name); + var builder = new ContentPartDefinitionBuilder(partDefinition); + alteration(builder); + manager.StorePartDefinition(builder.Build()); + } + } +} + diff --git a/src/Orchard/ContentManagement/MetaData/IContentDefinitionReader.cs b/src/Orchard/ContentManagement/MetaData/IContentDefinitionReader.cs new file mode 100644 index 000000000..c24691de3 --- /dev/null +++ b/src/Orchard/ContentManagement/MetaData/IContentDefinitionReader.cs @@ -0,0 +1,17 @@ +using System.Xml.Linq; +using Orchard.ContentManagement.MetaData.Builders; +using Orchard.ContentManagement.MetaData.Models; + +namespace Orchard.ContentManagement.MetaData { + public interface IContentDefinitionReader : IDependency { + void Merge(XElement source, ContentTypeDefinitionBuilder builder); + } + + public static class ContentDefinitionReaderExtensions { + public static ContentTypeDefinition Import(this IContentDefinitionReader reader, XElement source) { + var target = new ContentTypeDefinitionBuilder(); + reader.Merge(source, target); + return target.Build(); + } + } +} \ No newline at end of file diff --git a/src/Orchard/ContentManagement/MetaData/IContentDefinitionWriter.cs b/src/Orchard/ContentManagement/MetaData/IContentDefinitionWriter.cs new file mode 100644 index 000000000..bc2b6f908 --- /dev/null +++ b/src/Orchard/ContentManagement/MetaData/IContentDefinitionWriter.cs @@ -0,0 +1,9 @@ +using System.Xml.Linq; +using Orchard.ContentManagement.MetaData.Models; + +namespace Orchard.ContentManagement.MetaData { + public interface IContentDefinitionWriter : IDependency{ + XElement Export(ContentTypeDefinition typeDefinition); + XElement Export(ContentPartDefinition partDefinition); + } +} diff --git a/src/Orchard/ContentManagement/MetaData/Models/ContentFieldDefinition.cs b/src/Orchard/ContentManagement/MetaData/Models/ContentFieldDefinition.cs new file mode 100644 index 000000000..6b60c4ffd --- /dev/null +++ b/src/Orchard/ContentManagement/MetaData/Models/ContentFieldDefinition.cs @@ -0,0 +1,9 @@ +namespace Orchard.ContentManagement.MetaData.Models { + public class ContentFieldDefinition { + public ContentFieldDefinition(string name) { + Name = name; + } + + public string Name { get; private set; } + } +} diff --git a/src/Orchard/ContentManagement/MetaData/Models/ContentPartDefinition.cs b/src/Orchard/ContentManagement/MetaData/Models/ContentPartDefinition.cs new file mode 100644 index 000000000..dd8bd4e9c --- /dev/null +++ b/src/Orchard/ContentManagement/MetaData/Models/ContentPartDefinition.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Orchard.Utility.Extensions; + +namespace Orchard.ContentManagement.MetaData.Models { + public class ContentPartDefinition { + public ContentPartDefinition(string name, IEnumerable<Field> fields, IDictionary<string, string> settings) { + Name = name; + Fields = fields.ToReadOnlyCollection(); + Settings = settings; + } + + public ContentPartDefinition(string name) { + Name = name; + Fields = Enumerable.Empty<Field>(); + Settings = new Dictionary<string, string>(); + } + + public string Name { get; private set; } + public IEnumerable<Field> Fields { get; private set; } + public IDictionary<string, string> Settings { get; private set; } + + public class Field { + public Field(ContentFieldDefinition contentFieldDefinition, string name, IDictionary<string, string> settings) { + FieldDefinition = contentFieldDefinition; + Name = name; + Settings = settings; + } + + public string Name { get; private set; } + public ContentFieldDefinition FieldDefinition { get; private set; } + public IDictionary<string, string> Settings { get; private set; } + } + } +} diff --git a/src/Orchard/ContentManagement/MetaData/Models/ContentTypeDefinition.cs b/src/Orchard/ContentManagement/MetaData/Models/ContentTypeDefinition.cs new file mode 100644 index 000000000..ae9d2a114 --- /dev/null +++ b/src/Orchard/ContentManagement/MetaData/Models/ContentTypeDefinition.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Orchard.ContentManagement.MetaData.Models { + public class ContentTypeDefinition { + public ContentTypeDefinition(string name, IEnumerable<Part> parts, IDictionary<string, string> settings) { + Name = name; + Parts = parts; + Settings = settings; + } + + public ContentTypeDefinition(string name) { + Name = name; + Parts = Enumerable.Empty<Part>(); + Settings = new Dictionary<string, string>(); + } + + public string Name { get; private set; } + public IEnumerable<Part> Parts { get; private set; } + public IDictionary<string, string> Settings { get; private set; } + + public class Part { + public Part(ContentPartDefinition contentPartDefinition, IDictionary<string, string> settings) { + PartDefinition = contentPartDefinition; + Settings = settings; + } + + public ContentPartDefinition PartDefinition { get; private set; } + public IDictionary<string, string> Settings { get; private set; } + } + } +} diff --git a/src/Orchard/ContentManagement/MetaData/Records/ContentTypePartNameRecord.cs b/src/Orchard/ContentManagement/MetaData/Records/ContentTypePartNameRecord.cs deleted file mode 100644 index 7f0caddb2..000000000 --- a/src/Orchard/ContentManagement/MetaData/Records/ContentTypePartNameRecord.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Orchard.ContentManagement.MetaData.Records { - public class ContentTypePartNameRecord { - public virtual int Id { get; set; } - public virtual string PartName { get; set; } - } -} diff --git a/src/Orchard/ContentManagement/MetaData/Records/ContentTypePartRecord.cs b/src/Orchard/ContentManagement/MetaData/Records/ContentTypePartRecord.cs deleted file mode 100644 index e723d18e7..000000000 --- a/src/Orchard/ContentManagement/MetaData/Records/ContentTypePartRecord.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Orchard.ContentManagement.MetaData.Records { - public class ContentTypePartRecord { - public virtual int Id { get; set; } - public virtual ContentTypePartNameRecord PartName { get; set; } - } -} diff --git a/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionReader.cs b/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionReader.cs new file mode 100644 index 000000000..ad1037632 --- /dev/null +++ b/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionReader.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using System.Xml.Linq; +using Orchard.ContentManagement.MetaData.Builders; + +namespace Orchard.ContentManagement.MetaData.Services { + public class ContentDefinitionReader : IContentDefinitionReader { + private readonly IMapper<XElement, IDictionary<string, string>> _settingsReader; + + public ContentDefinitionReader(IMapper<XElement, IDictionary<string, string>> settingsReader) { + _settingsReader = settingsReader; + } + + public void Merge(XElement source, ContentTypeDefinitionBuilder builder) { + builder.Named(XmlConvert.DecodeName(source.Name.LocalName)); + foreach (var setting in _settingsReader.Map(source)) { + builder.WithSetting(setting.Key, setting.Value); + } + foreach (var iter in source.Elements()) { + var partElement = iter; + builder.WithPart( + XmlConvert.DecodeName(partElement.Name.LocalName), + partBuilder => { + foreach (var setting in _settingsReader.Map(partElement)) { + partBuilder.WithSetting(setting.Key, setting.Value); + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs b/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs new file mode 100644 index 000000000..fc61eb16e --- /dev/null +++ b/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using System.Xml.Linq; +using Orchard.ContentManagement.MetaData.Models; + +namespace Orchard.ContentManagement.MetaData.Services { + public class ContentDefinitionWriter : IContentDefinitionWriter { + private readonly IMapper<IDictionary<string, string>, XElement> _settingsWriter; + + public ContentDefinitionWriter(IMapper<IDictionary<string, string>, XElement> settingsWriter) { + _settingsWriter = settingsWriter; + } + + public XElement Export(ContentTypeDefinition typeDefinition) { + var typeElement = NewElement(typeDefinition.Name, typeDefinition.Settings); + + foreach(var typePart in typeDefinition.Parts) { + typeElement.Add(NewElement(typePart.PartDefinition.Name, typePart.Settings)); + } + return typeElement; + } + + public XElement Export(ContentPartDefinition partDefinition) { + var partElement = NewElement(partDefinition.Name, partDefinition.Settings); + foreach(var partField in partDefinition.Fields) { + var partFieldElement = NewElement(partField.Name, partField.Settings); + partFieldElement.SetAttributeValue("FieldType", partField.FieldDefinition.Name); + partElement.Add(partFieldElement); + } + return partElement; + } + + private XElement NewElement(string name, IDictionary<string, string> settings) { + var element = new XElement(XmlConvert.EncodeLocalName(name)); + foreach(var settingAttribute in _settingsWriter.Map(settings).Attributes()) { + element.Add(settingAttribute); + } + return element; + } + } +} diff --git a/src/Orchard/ContentManagement/MetaData/Services/ContentTypeService.cs b/src/Orchard/ContentManagement/MetaData/Services/ContentTypeService.cs deleted file mode 100644 index a0b71bb20..000000000 --- a/src/Orchard/ContentManagement/MetaData/Services/ContentTypeService.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using Orchard.ContentManagement.MetaData.Records; -using Orchard.ContentManagement.Records; -using Orchard.Data; - -namespace Orchard.ContentManagement.MetaData.Services { - [UsedImplicitly] - public class ContentTypeService : IContentTypeService { - private readonly IRepository<ContentTypeRecord> _contentTypeRepository; - private readonly IRepository<ContentTypePartNameRecord> _contentTypePartNameRepository; - private readonly IRepository<ContentTypePartRecord> _contentTypePartRepository; - - public ContentTypeService(IRepository<ContentTypePartRecord> contentTypePartRepository, IRepository<ContentTypeRecord> contentTypeRepository, IRepository<ContentTypePartNameRecord> contentTypePartNameRepository) { - _contentTypeRepository = contentTypeRepository; - _contentTypePartNameRepository = contentTypePartNameRepository; - _contentTypePartRepository = contentTypePartRepository; - } - - public ContentTypeRecord GetContentTypeRecord(string contentTypeName) { - return _contentTypeRepository.Fetch(x => x.Name == contentTypeName).SingleOrDefault(); - } - - public ContentTypePartNameRecord GetContentPartNameRecord(string name) { - return _contentTypePartNameRepository.Fetch(x => x.PartName == name).SingleOrDefault(); - } - - public IEnumerable<ContentTypeRecord> GetContentTypes() { - return _contentTypeRepository.Table.ToList(); - } - - public IEnumerable<ContentTypePartNameRecord> GetContentTypePartNames() { - return _contentTypePartNameRepository.Table.ToList(); - } - - public void MapContentTypeToContentPart(string contentType, string contentPart) { - // Create content type if needed - var contentTypeRecord = GetContentTypeRecord(contentType); - if (contentTypeRecord == null) { - contentTypeRecord = new ContentTypeRecord { Name = contentType }; - _contentTypeRepository.Create(contentTypeRecord); - } - - // Create part name if needed - var contentTypePartNameRecord = GetContentPartNameRecord(contentPart); - if (contentTypePartNameRecord == null) { - contentTypePartNameRecord = new ContentTypePartNameRecord { PartName = contentPart }; - _contentTypePartNameRepository.Create(contentTypePartNameRecord); - } - - // Add part name to content type - var contentTypePartRecord = new ContentTypePartRecord { PartName = contentTypePartNameRecord }; - contentTypeRecord.ContentParts.Add(contentTypePartRecord); - } - - public void UnMapContentTypeToContentPart(string contentType, string contentPart) { - var contentTypeRecord = GetContentTypeRecord(contentType); - var contentTypePartNameRecord = _contentTypePartNameRepository.Fetch(x => x.PartName == contentPart).Single(); - var contentTypePartRecord = contentTypeRecord.ContentParts.Single(x => x.PartName == contentTypePartNameRecord); - contentTypeRecord.ContentParts.Remove(contentTypePartRecord); - } - - public bool ValidateContentTypeToContentPartMapping(string contentType, string contentPart) { - var contentTypeRecord = GetContentTypeRecord(contentType) ?? new ContentTypeRecord(); - if (contentTypeRecord.ContentParts.Count == 0) - return false; - var contentTypePart = contentTypeRecord.ContentParts.Single(x => x.PartName.PartName == contentPart); - return contentTypePart != null; - } - - - public void AddContentTypePartNameToMetaData(string contentTypePartName) { - var contentTypePartNameRecord = new ContentTypePartNameRecord() { - PartName = contentTypePartName - }; - - _contentTypePartNameRepository.Update(contentTypePartNameRecord); - } - } -} diff --git a/src/Orchard/ContentManagement/MetaData/Services/IContentTypeService.cs b/src/Orchard/ContentManagement/MetaData/Services/IContentTypeService.cs deleted file mode 100644 index 2ab533896..000000000 --- a/src/Orchard/ContentManagement/MetaData/Services/IContentTypeService.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using Orchard.ContentManagement.MetaData.Records; -using Orchard.ContentManagement.Records; - -namespace Orchard.ContentManagement.MetaData.Services -{ - public interface IContentTypeService : IDependency { - void MapContentTypeToContentPart(string contentType, string contentPart); - void UnMapContentTypeToContentPart(string contentType, string contentPart); - void AddContentTypePartNameToMetaData(string contentTypePartName); - ContentTypeRecord GetContentTypeRecord(string contentTypeName); - bool ValidateContentTypeToContentPartMapping(string contentType, string contentPart); - IEnumerable<ContentTypeRecord> GetContentTypes(); - IEnumerable<ContentTypePartNameRecord> GetContentTypePartNames(); - ContentTypePartNameRecord GetContentPartNameRecord(string name); - } -} diff --git a/src/Orchard/ContentManagement/MetaData/Services/SettingsFormatter.cs b/src/Orchard/ContentManagement/MetaData/Services/SettingsFormatter.cs new file mode 100644 index 000000000..293a10018 --- /dev/null +++ b/src/Orchard/ContentManagement/MetaData/Services/SettingsFormatter.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using System.Xml.Linq; + +namespace Orchard.ContentManagement.MetaData.Services { + public class SettingsFormatter : + IMapper<XElement, IDictionary<string, string>>, + IMapper<IDictionary<string, string>, XElement> { + + public IDictionary<string, string> Map(XElement source) { + if (source == null) + return new Dictionary<string, string>(); + + return source.Attributes().ToDictionary(attr => XmlConvert.DecodeName(attr.Name.LocalName), attr => attr.Value); + } + + public XElement Map(IDictionary<string, string> source) { + if (source == null) + return new XElement("settings"); + + return new XElement("settings", source.Select(kv => new XAttribute(XmlConvert.EncodeLocalName(kv.Key), kv.Value))); + } + } +} diff --git a/src/Orchard/ContentManagement/Records/ContentTypeRecord.cs b/src/Orchard/ContentManagement/Records/ContentTypeRecord.cs index b21774f3f..5d05fdc54 100644 --- a/src/Orchard/ContentManagement/Records/ContentTypeRecord.cs +++ b/src/Orchard/ContentManagement/Records/ContentTypeRecord.cs @@ -1,15 +1,7 @@ -using System.Collections.Generic; -using Orchard.ContentManagement.MetaData.Records; -using Orchard.Data.Conventions; - -namespace Orchard.ContentManagement.Records { - public class ContentTypeRecord { - public ContentTypeRecord() { - ContentParts = new List<ContentTypePartRecord>(); - } +namespace Orchard.ContentManagement.Records { + public class ContentTypeRecord { public virtual int Id { get; set; } public virtual string Name { get; set; } - [CascadeAllDeleteOrphan] - public virtual IList<ContentTypePartRecord> ContentParts { get; set; } } + } diff --git a/src/Orchard/Data/SessionLocator.cs b/src/Orchard/Data/SessionLocator.cs index 55085321b..42aebec6a 100644 --- a/src/Orchard/Data/SessionLocator.cs +++ b/src/Orchard/Data/SessionLocator.cs @@ -47,15 +47,15 @@ namespace Orchard.Data { } bool IInterceptor.OnLoad(object entity, object id, object[] state, string[] propertyNames, IType[] types) { - return true; + return false; } bool IInterceptor.OnFlushDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, IType[] types) { - return true; + return false; } bool IInterceptor.OnSave(object entity, object id, object[] state, string[] propertyNames, IType[] types) { - return true; + return false; } void IInterceptor.OnDelete(object entity, object id, object[] state, string[] propertyNames, IType[] types) { diff --git a/src/Orchard/Environment/Extensions/Compilers/CSharpExtensionBuildProvider.cs b/src/Orchard/Environment/Extensions/Compilers/CSharpExtensionBuildProvider.cs new file mode 100644 index 000000000..826eccc64 --- /dev/null +++ b/src/Orchard/Environment/Extensions/Compilers/CSharpExtensionBuildProvider.cs @@ -0,0 +1,21 @@ +using System.Web.Compilation; + +namespace Orchard.Environment.Extensions.Compilers { + public class CSharpExtensionBuildProvider : BuildProvider { + private readonly CompilerType _codeCompilerType; + + public CSharpExtensionBuildProvider() { + _codeCompilerType = GetDefaultCompilerTypeForLanguage("C#"); + } + + public override CompilerType CodeCompilerType { get { return _codeCompilerType; } } + + public override void GenerateCode(AssemblyBuilder assemblyBuilder) { + var virtualPathProvider = new DefaultVirtualPathProvider(); + var compiler = new CSharpProjectMediumTrustCompiler(virtualPathProvider); + + var aspNetAssemblyBuilder = new AspNetAssemblyBuilder(assemblyBuilder, this); + compiler.CompileProject(this.VirtualPath, aspNetAssemblyBuilder); + } + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/Compilers/CSharpExtensionCompiler.cs b/src/Orchard/Environment/Extensions/Compilers/CSharpExtensionCompiler.cs new file mode 100644 index 000000000..b267831db --- /dev/null +++ b/src/Orchard/Environment/Extensions/Compilers/CSharpExtensionCompiler.cs @@ -0,0 +1,46 @@ +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Web.Compilation; + +namespace Orchard.Environment.Extensions.Compilers { + /// <summary> + /// Compile a C# extension into an assembly given a directory location + /// </summary> + public class CSharpExtensionCompiler { + public CompilerResults CompileProject(string location) { + var codeProvider = CodeDomProvider.CreateProvider("cs"); + + var references = GetAssemblyReferenceNames(); + var options = new CompilerParameters(references.ToArray()); + + var fileNames = GetSourceFileNames(location); + var results = codeProvider.CompileAssemblyFromFile(options, fileNames.ToArray()); + return results; + } + + private IEnumerable<string> GetAssemblyReferenceNames() { + return Enumerable.Distinct<string>(BuildManager.GetReferencedAssemblies() + .OfType<Assembly>() + .Select(x => x.Location) + .Where(x => !string.IsNullOrEmpty(x))); + } + + private IEnumerable<string> GetSourceFileNames(string path) { + foreach (var file in Directory.GetFiles(path, "*.cs")) { + yield return file; + } + + foreach (var folder in Directory.GetDirectories(path)) { + if (Path.GetFileName(folder).StartsWith(".")) + continue; + + foreach (var file in GetSourceFileNames(folder)) { + yield return file; + } + } + } + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/Compilers/CSharpProjectFullTrustCompiler.cs b/src/Orchard/Environment/Extensions/Compilers/CSharpProjectFullTrustCompiler.cs new file mode 100644 index 000000000..d8d0ced8c --- /dev/null +++ b/src/Orchard/Environment/Extensions/Compilers/CSharpProjectFullTrustCompiler.cs @@ -0,0 +1,51 @@ +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Orchard.Environment.Extensions.Compilers { + /// <summary> + /// Compile a C# extension into an assembly given a directory location + /// </summary> + public class CSharpProjectFullTrustCompiler { + private readonly IVirtualPathProvider _virtualPathProvider; + private readonly IBuildManager _buildManager; + + public CSharpProjectFullTrustCompiler(IVirtualPathProvider virtualPathProvider, IBuildManager buildManager) { + _virtualPathProvider = virtualPathProvider; + _buildManager = buildManager; + } + + /// <summary> + /// Compile a csproj file given its virtual path. Use the CSharp CodeDomProvider + /// class, which is only available in full trust. + /// </summary> + public CompilerResults CompileProject(string virtualPath, string outputDirectory) { + var codeProvider = CodeDomProvider.CreateProvider("cs"); + var directory = _virtualPathProvider.GetDirectoryName(virtualPath); + + using (var stream = _virtualPathProvider.OpenFile(virtualPath)) { + var descriptor = new CSharpProjectParser().Parse(stream); + + var references = GetReferencedAssembliesLocation(); + var options = new CompilerParameters(references.ToArray()); + options.GenerateExecutable = false; + options.OutputAssembly = Path.Combine(outputDirectory, descriptor.AssemblyName + ".dll"); + + var fileNames = descriptor.SourceFilenames + .Select(f => _virtualPathProvider.Combine(directory, f)) + .Select(f => _virtualPathProvider.MapPath(f)); + + var results = codeProvider.CompileAssemblyFromFile(options, fileNames.ToArray()); + return results; + } + } + + private IEnumerable<string> GetReferencedAssembliesLocation() { + return _buildManager.GetReferencedAssemblies() + .Select(a => a.Location) + .Where(a => !string.IsNullOrEmpty(a)) + .Distinct(); + } + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/Compilers/CSharpProjectMediumTrustCompiler.cs b/src/Orchard/Environment/Extensions/Compilers/CSharpProjectMediumTrustCompiler.cs new file mode 100644 index 000000000..1ab94e637 --- /dev/null +++ b/src/Orchard/Environment/Extensions/Compilers/CSharpProjectMediumTrustCompiler.cs @@ -0,0 +1,50 @@ +using System.CodeDom; +using System.IO; +using System.Linq; + +namespace Orchard.Environment.Extensions.Compilers { + /// <summary> + /// Compile a C# extension into an assembly given a directory location + /// </summary> + public class CSharpProjectMediumTrustCompiler { + private readonly IVirtualPathProvider _virtualPathProvider; + + public CSharpProjectMediumTrustCompiler(IVirtualPathProvider virtualPathProvider) { + _virtualPathProvider = virtualPathProvider; + } + /// <summary> + /// Compile a csproj file given its virtual path, a build provider and an assembly builder. + /// This method works in medium trust. + /// </summary> + public void CompileProject(string virtualPath, IAssemblyBuilder assemblyBuilder) { + using (var stream = _virtualPathProvider.OpenFile(virtualPath)) { + var descriptor = new CSharpProjectParser().Parse(stream); + + var directory = _virtualPathProvider.GetDirectoryName(virtualPath); + foreach (var filename in descriptor.SourceFilenames.Select(f => _virtualPathProvider.Combine(directory, f))) { + assemblyBuilder.AddCodeCompileUnit(CreateCompileUnit(filename)); + } + } + } + + private CodeCompileUnit CreateCompileUnit(string virtualPath) { + var contents = GetContents(virtualPath); + var unit = new CodeSnippetCompileUnit(contents); + var physicalPath = _virtualPathProvider.MapPath(virtualPath); + if (!string.IsNullOrEmpty(physicalPath)) { + unit.LinePragma = new CodeLinePragma(physicalPath, 1); + } + return unit; + } + + private string GetContents(string virtualPath) { + string contents; + using (var stream = _virtualPathProvider.OpenFile(virtualPath)) { + using (var reader = new StreamReader(stream)) { + contents = reader.ReadToEnd(); + } + } + return contents; + } + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/Compilers/CSharpProjectParser.cs b/src/Orchard/Environment/Extensions/Compilers/CSharpProjectParser.cs new file mode 100644 index 000000000..1c0288149 --- /dev/null +++ b/src/Orchard/Environment/Extensions/Compilers/CSharpProjectParser.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Linq; + +namespace Orchard.Environment.Extensions.Compilers { + public class CSharpProjectDescriptor { + public string AssemblyName { get; set; } + public IEnumerable<string> SourceFilenames { get; set; } + public IEnumerable<ReferenceDescriptor> References { get; set; } + } + + public class ReferenceDescriptor { + public string AssemblyName { get; set; } + + public override string ToString() { + return "{" + (AssemblyName ?? "") + "}"; + } + } + + public class CSharpProjectParser { + public CSharpProjectDescriptor Parse(Stream stream) { + var document = XDocument.Load(XmlReader.Create(stream)); + return new CSharpProjectDescriptor { + AssemblyName = GetAssemblyName(document), + SourceFilenames = GetSourceFilenames(document).ToArray(), + References = GetReferences(document).ToArray() + }; + } + + private string GetAssemblyName(XDocument document) { + return document + .Elements(ns("Project")) + .Elements(ns("PropertyGroup")) + .Elements(ns("AssemblyName")) + .Single() + .Value; + } + + private IEnumerable<string> GetSourceFilenames(XDocument document) { + return document + .Elements(ns("Project")) + .Elements(ns("ItemGroup")) + .Elements(ns("Compile")) + .Attributes("Include") + .Select(c => c.Value); + } + + private IEnumerable<ReferenceDescriptor> GetReferences(XDocument document) { + return document + .Elements(ns("Project")) + .Elements(ns("ItemGroup")) + .Elements(ns("Reference")) + .Attributes("Include") + .Select(c => new ReferenceDescriptor { AssemblyName = c.Value }); + } + + private static XName ns(string name) { + return XName.Get(name, "http://schemas.microsoft.com/developer/msbuild/2003"); + } + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/ExtensionManager.cs b/src/Orchard/Environment/Extensions/ExtensionManager.cs index 7fba8f53e..bf05612ba 100644 --- a/src/Orchard/Environment/Extensions/ExtensionManager.cs +++ b/src/Orchard/Environment/Extensions/ExtensionManager.cs @@ -53,7 +53,7 @@ namespace Orchard.Environment.Extensions { private Feature LoadFeature(FeatureDescriptor featureDescriptor) { var featureName = featureDescriptor.Name; string extensionName = GetExtensionForFeature(featureName); - if (extensionName == null) throw new ArgumentException(T("Feature {0} was not found in any of the installed extensions", featureName)); + if (extensionName == null) throw new ArgumentException(T("Feature {0} was not found in any of the installed extensions", featureName).ToString()); var extension = BuildActiveExtensions().Where(x => x.Descriptor.Name == extensionName).FirstOrDefault(); if (extension == null) throw new InvalidOperationException(T("Extension ") + extensionName + T(" is not active")); diff --git a/src/Orchard/Environment/Extensions/Loaders/AreaExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/AreaExtensionLoader.cs index 7168eb034..5323212b7 100644 --- a/src/Orchard/Environment/Extensions/Loaders/AreaExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/AreaExtensionLoader.cs @@ -5,17 +5,17 @@ using Orchard.Environment.Extensions.Models; namespace Orchard.Environment.Extensions.Loaders { public class AreaExtensionLoader : IExtensionLoader { - public int Order { get { return 5; } } + public int Order { get { return 50; } } public ExtensionEntry Load(ExtensionDescriptor descriptor) { if (descriptor.Location == "~/Areas") { var assembly = Assembly.Load("Orchard.Web"); return new ExtensionEntry { - Descriptor = descriptor, - Assembly = assembly, - ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, descriptor)) - }; + Descriptor = descriptor, + Assembly = assembly, + ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, descriptor)) + }; } return null; } diff --git a/src/Orchard/Environment/Extensions/Loaders/CoreExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/CoreExtensionLoader.cs index 856df10f0..ba36c7f6c 100644 --- a/src/Orchard/Environment/Extensions/Loaders/CoreExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/CoreExtensionLoader.cs @@ -4,18 +4,20 @@ using System.Reflection; using Orchard.Environment.Extensions.Models; namespace Orchard.Environment.Extensions.Loaders { + /// <summary> + /// Load an extension by looking into specific namespaces of the "Orchard.Core" assembly + /// </summary> public class CoreExtensionLoader : IExtensionLoader { - public int Order { get { return 1; } } + public int Order { get { return 10; } } public ExtensionEntry Load(ExtensionDescriptor descriptor) { if (descriptor.Location == "~/Core") { - var assembly = Assembly.Load("Orchard.Core"); return new ExtensionEntry { - Descriptor = descriptor, - Assembly = assembly, - ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, descriptor)) - }; + Descriptor = descriptor, + Assembly = assembly, + ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, descriptor)) + }; } return null; } diff --git a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs index 3178d2780..fd000783c 100644 --- a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs @@ -1,59 +1,41 @@ -using System.CodeDom.Compiler; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Web.Compilation; -using System.Web.Hosting; +using System; using Orchard.Environment.Extensions.Models; +using Orchard.FileSystems.Dependencies; namespace Orchard.Environment.Extensions.Loaders { public class DynamicExtensionLoader : IExtensionLoader { - public int Order { get { return 10; } } + private readonly IHostEnvironment _hostEnvironment; + private readonly IBuildManager _buildManager; + private readonly IVirtualPathProvider _virtualPathProvider; + private readonly IDependenciesFolder _dependenciesFolder; + + public DynamicExtensionLoader(IHostEnvironment hostEnvironment, IBuildManager buildManager, IVirtualPathProvider virtualPathProvider, IDependenciesFolder dependenciesFolder) { + _hostEnvironment = hostEnvironment; + _buildManager = buildManager; + _virtualPathProvider = virtualPathProvider; + _dependenciesFolder = dependenciesFolder; + } + + public int Order { get { return 100; } } public ExtensionEntry Load(ExtensionDescriptor descriptor) { - if (HostingEnvironment.IsHosted == false) + string projectPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Name, + descriptor.Name + ".csproj"); + if (!_virtualPathProvider.FileExists(projectPath)) { return null; + } - var codeProvider = CodeDomProvider.CreateProvider("cs"); + var assembly = _buildManager.GetCompiledAssembly(projectPath); - var references = GetAssemblyReferenceNames(); - var options = new CompilerParameters(references.ToArray()); - - var locationPath = HostingEnvironment.MapPath(descriptor.Location); - var extensionPath = Path.Combine(locationPath, descriptor.Name); - - var fileNames = GetSourceFileNames(extensionPath); - var results = codeProvider.CompileAssemblyFromFile(options, fileNames.ToArray()); + if (_hostEnvironment.IsFullTrust) { + _dependenciesFolder.StoreAssemblyFile(descriptor.Name, assembly.Location); + } return new ExtensionEntry { - Descriptor = descriptor, - Assembly = results.CompiledAssembly, - ExportedTypes = results.CompiledAssembly.GetExportedTypes(), - }; - } - - private IEnumerable<string> GetAssemblyReferenceNames() { - return BuildManager.GetReferencedAssemblies() - .OfType<Assembly>() - .Select(x => x.Location) - .Where(x => !string.IsNullOrEmpty(x)) - .Distinct(); - } - - private IEnumerable<string> GetSourceFileNames(string path) { - foreach (var file in Directory.GetFiles(path, "*.cs")) { - yield return file; - } - - foreach (var folder in Directory.GetDirectories(path)) { - if (Path.GetFileName(folder).StartsWith(".")) - continue; - - foreach (var file in GetSourceFileNames(folder)) { - yield return file; - } - } + Descriptor = descriptor, + Assembly = assembly, + ExportedTypes = assembly.GetExportedTypes(), + }; } } } \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs index 3f7a30ee2..8e65e2183 100644 --- a/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs @@ -1,17 +1,39 @@ using Orchard.Environment.Extensions.Models; +using Orchard.FileSystems.Dependencies; namespace Orchard.Environment.Extensions.Loaders { + /// <summary> + /// Load an extension by looking into the "bin" subdirectory of an + /// extension directory. + /// </summary> public class PrecompiledExtensionLoader : IExtensionLoader { - public int Order { get { return 3; } } + private readonly IDependenciesFolder _folder; + private readonly IVirtualPathProvider _virtualPathProvider; + + public PrecompiledExtensionLoader(IDependenciesFolder folder, IVirtualPathProvider virtualPathProvider) { + _folder = folder; + _virtualPathProvider = virtualPathProvider; + } + + public int Order { get { return 30; } } public ExtensionEntry Load(ExtensionDescriptor descriptor) { - //var assembly = Assembly.Load(descriptor.Name); - //return new ModuleEntry { - // Descriptor = descriptor, - // Assembly = assembly, - // ExportedTypes = assembly.GetExportedTypes() - //}; - return null; + var extensionPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Name, "bin", + descriptor.Name + ".dll"); + if (!_virtualPathProvider.FileExists(extensionPath)) + return null; + + _folder.StoreAssemblyFile(descriptor.Name, _virtualPathProvider.MapPath(extensionPath)); + + var assembly = _folder.LoadAssembly(descriptor.Name); + if (assembly == null) + return null; + + return new ExtensionEntry { + Descriptor = descriptor, + Assembly = assembly, + ExportedTypes = assembly.GetExportedTypes() + }; } } } \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/Loaders/ProbingExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/ProbingExtensionLoader.cs new file mode 100644 index 000000000..6f83506a2 --- /dev/null +++ b/src/Orchard/Environment/Extensions/Loaders/ProbingExtensionLoader.cs @@ -0,0 +1,30 @@ +using Orchard.Environment.Extensions.Models; +using Orchard.FileSystems.Dependencies; + +namespace Orchard.Environment.Extensions.Loaders { + /// <summary> + /// Load an extension using the "Assembly.Load" method if the + /// file can be found in the "App_Data/Dependencies" folder. + /// </summary> + public class ProbingExtensionLoader : IExtensionLoader { + private readonly IDependenciesFolder _folder; + + public ProbingExtensionLoader(IDependenciesFolder folder) { + _folder = folder; + } + + public int Order { get { return 40; } } + + public ExtensionEntry Load(ExtensionDescriptor descriptor) { + var assembly = _folder.LoadAssembly(descriptor.Name); + if (assembly == null) + return null; + + return new ExtensionEntry { + Descriptor = descriptor, + Assembly = assembly, + ExportedTypes = assembly.GetExportedTypes() + }; + } + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs index 4aa7ce345..39b8a19f0 100644 --- a/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs @@ -5,8 +5,11 @@ using System.Web.Hosting; using Orchard.Environment.Extensions.Models; namespace Orchard.Environment.Extensions.Loaders { + /// <summary> + /// Load an extension by looking through the BuildManager referenced assemblies + /// </summary> public class ReferencedExtensionLoader : IExtensionLoader { - public int Order { get { return 2; } } + public int Order { get { return 20; } } public ExtensionEntry Load(ExtensionDescriptor descriptor) { if (HostingEnvironment.IsHosted == false) @@ -20,10 +23,10 @@ namespace Orchard.Environment.Extensions.Loaders { return null; return new ExtensionEntry { - Descriptor = descriptor, - Assembly = assembly, - ExportedTypes = assembly.GetExportedTypes() - }; + Descriptor = descriptor, + Assembly = assembly, + ExportedTypes = assembly.GetExportedTypes() + }; } } } \ No newline at end of file diff --git a/src/Orchard/Environment/IAssemblyBuilder.cs b/src/Orchard/Environment/IAssemblyBuilder.cs new file mode 100644 index 000000000..055fb1caa --- /dev/null +++ b/src/Orchard/Environment/IAssemblyBuilder.cs @@ -0,0 +1,22 @@ +using System.CodeDom; +using System.Web.Compilation; + +namespace Orchard.Environment { + public interface IAssemblyBuilder { + void AddCodeCompileUnit(CodeCompileUnit compileUnit); + } + + public class AspNetAssemblyBuilder : IAssemblyBuilder { + private readonly AssemblyBuilder _assemblyBuilder; + private readonly BuildProvider _buildProvider; + + public AspNetAssemblyBuilder(AssemblyBuilder assemblyBuilder, BuildProvider buildProvider) { + _assemblyBuilder = assemblyBuilder; + _buildProvider = buildProvider; + } + + public void AddCodeCompileUnit(CodeCompileUnit compileUnit) { + _assemblyBuilder.AddCodeCompileUnit(_buildProvider, compileUnit); + } + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/IBuildManager.cs b/src/Orchard/Environment/IBuildManager.cs new file mode 100644 index 000000000..625e7190a --- /dev/null +++ b/src/Orchard/Environment/IBuildManager.cs @@ -0,0 +1,21 @@ +using System.Linq; +using System.Collections.Generic; +using System.Reflection; +using System.Web.Compilation; + +namespace Orchard.Environment { + public interface IBuildManager : IDependency { + IEnumerable<Assembly> GetReferencedAssemblies(); + Assembly GetCompiledAssembly(string virtualPath); + } + + public class DefaultBuildManager : IBuildManager { + public IEnumerable<Assembly> GetReferencedAssemblies() { + return BuildManager.GetReferencedAssemblies().OfType<Assembly>(); + } + + public Assembly GetCompiledAssembly(string virtualPath) { + return BuildManager.GetCompiledAssembly(virtualPath); + } + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/IHostEnvironment.cs b/src/Orchard/Environment/IHostEnvironment.cs new file mode 100644 index 000000000..694bc5cc0 --- /dev/null +++ b/src/Orchard/Environment/IHostEnvironment.cs @@ -0,0 +1,22 @@ +using System; +using System.Web.Hosting; + +namespace Orchard.Environment { + /// <summary> + /// Abstraction of the running environment + /// </summary> + public interface IHostEnvironment : IDependency { + bool IsFullTrust { get; } + string MapPath(string virtualPath); + } + + public class DefaultHostEnvironment : IHostEnvironment { + public bool IsFullTrust { + get { return AppDomain.CurrentDomain.IsFullyTrusted; } + } + + public string MapPath(string virtualPath) { + return HostingEnvironment.MapPath(virtualPath); + } + } +} diff --git a/src/Orchard/Environment/IVirtualPathProvider.cs b/src/Orchard/Environment/IVirtualPathProvider.cs new file mode 100644 index 000000000..37b824e39 --- /dev/null +++ b/src/Orchard/Environment/IVirtualPathProvider.cs @@ -0,0 +1,50 @@ +using System.IO; +using System.Web.Hosting; +using Orchard.Caching; + +namespace Orchard.Environment { + public interface IVirtualPathProvider : IVolatileProvider { + string GetDirectoryName(string virtualPath); + string Combine(params string[] paths); + Stream OpenFile(string virtualPath); + StreamWriter CreateText(string virtualPath); + string MapPath(string virtualPath); + bool FileExists(string virtualPath); + bool DirectoryExists(string virtualPath); + void CreateDirectory(string virtualPath); + } + + public class DefaultVirtualPathProvider : IVirtualPathProvider { + public string GetDirectoryName(string virtualPath) { + return Path.GetDirectoryName(virtualPath).Replace('\\', '/'); + } + + public string Combine(params string[] paths) { + return Path.Combine(paths).Replace('\\', '/'); + } + + public Stream OpenFile(string virtualPath) { + return HostingEnvironment.VirtualPathProvider.GetFile(virtualPath).Open(); + } + + public StreamWriter CreateText(string virtualPath) { + return File.CreateText(MapPath(virtualPath)); + } + + public string MapPath(string virtualPath) { + return HostingEnvironment.MapPath(virtualPath); + } + + public bool FileExists(string virtualPath) { + return HostingEnvironment.VirtualPathProvider.FileExists(virtualPath); + } + + public bool DirectoryExists(string virtualPath) { + return HostingEnvironment.VirtualPathProvider.DirectoryExists(virtualPath); + } + + public void CreateDirectory(string virtualPath) { + Directory.CreateDirectory(MapPath(virtualPath)); + } + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/OrchardStarter.cs b/src/Orchard/Environment/OrchardStarter.cs index f8e6ec8f4..a86204858 100644 --- a/src/Orchard/Environment/OrchardStarter.cs +++ b/src/Orchard/Environment/OrchardStarter.cs @@ -15,6 +15,7 @@ using Orchard.Environment.State; using Orchard.Environment.Topology; using Orchard.Events; using Orchard.FileSystems.AppData; +using Orchard.FileSystems.Dependencies; using Orchard.FileSystems.WebSite; using Orchard.Logging; using Orchard.Services; @@ -30,10 +31,14 @@ namespace Orchard.Environment { // a single default host implementation is needed for bootstrapping a web app domain builder.RegisterType<DefaultOrchardEventBus>().As<IEventBus>().SingleInstance(); builder.RegisterType<DefaultCacheHolder>().As<ICacheHolder>().SingleInstance(); + builder.RegisterType<DefaultHostEnvironment>().As<IHostEnvironment>().SingleInstance(); + builder.RegisterType<DefaultBuildManager>().As<IBuildManager>().SingleInstance(); RegisterVolatileProvider<WebSiteFolder, IWebSiteFolder>(builder); RegisterVolatileProvider<AppDataFolder, IAppDataFolder>(builder); RegisterVolatileProvider<Clock, IClock>(builder); + RegisterVolatileProvider<DefaultDependenciesFolder, IDependenciesFolder>(builder); + RegisterVolatileProvider<DefaultVirtualPathProvider, IVirtualPathProvider>(builder); builder.RegisterType<DefaultOrchardHost>().As<IOrchardHost>().As<IEventHandler>().SingleInstance(); { @@ -63,6 +68,7 @@ namespace Orchard.Environment { builder.RegisterType<CoreExtensionLoader>().As<IExtensionLoader>().SingleInstance(); builder.RegisterType<ReferencedExtensionLoader>().As<IExtensionLoader>().SingleInstance(); builder.RegisterType<PrecompiledExtensionLoader>().As<IExtensionLoader>().SingleInstance(); + builder.RegisterType<ProbingExtensionLoader>().As<IExtensionLoader>().SingleInstance(); builder.RegisterType<DynamicExtensionLoader>().As<IExtensionLoader>().SingleInstance(); } } diff --git a/src/Orchard/Environment/State/ShellStateCoordinator.cs b/src/Orchard/Environment/State/ShellStateCoordinator.cs index 1ef1d1854..3d408998f 100644 --- a/src/Orchard/Environment/State/ShellStateCoordinator.cs +++ b/src/Orchard/Environment/State/ShellStateCoordinator.cs @@ -148,13 +148,13 @@ namespace Orchard.Environment.State { })); // lower enabled states in reverse order - foreach (var entry in allEntries.Where(entry => entry.FeatureState.EnableState == ShellFeatureState.State.Falling)) { + foreach (var entry in allEntries.Reverse().Where(entry => entry.FeatureState.EnableState == ShellFeatureState.State.Falling)) { _featureEvents.Disable(entry.Feature); _stateManager.UpdateEnabledState(entry.FeatureState, ShellFeatureState.State.Down); } // lower installed states in reverse order - foreach (var entry in allEntries.Where(entry => entry.FeatureState.InstallState == ShellFeatureState.State.Falling)) { + foreach (var entry in allEntries.Reverse().Where(entry => entry.FeatureState.InstallState == ShellFeatureState.State.Falling)) { _featureEvents.Uninstall(entry.Feature); _stateManager.UpdateInstalledState(entry.FeatureState, ShellFeatureState.State.Down); } diff --git a/src/Orchard/FileSystems/Dependencies/IDependenciesFolder.cs b/src/Orchard/FileSystems/Dependencies/IDependenciesFolder.cs new file mode 100644 index 000000000..64ffa7578 --- /dev/null +++ b/src/Orchard/FileSystems/Dependencies/IDependenciesFolder.cs @@ -0,0 +1,124 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; +using Orchard.Caching; +using Orchard.Environment; + +namespace Orchard.FileSystems.Dependencies { + public interface IDependenciesFolder : IVolatileProvider { + void StoreAssemblyFile(string assemblyName, string assemblyFileName); + Assembly LoadAssembly(string assemblyName); + } + + public class DefaultDependenciesFolder : IDependenciesFolder { + private const string _basePath = "~/App_Data/Dependencies"; + private readonly IVirtualPathProvider _virtualPathProvider; + + public DefaultDependenciesFolder(IVirtualPathProvider virtualPathProvider) { + _virtualPathProvider = virtualPathProvider; + } + + private string BasePath { + get { + return _basePath; + } + } + + private string PersistencePath { + get { + return _virtualPathProvider.Combine(BasePath, "dependencies.xml"); + } + } + + public void StoreAssemblyFile(string assemblyName, string assemblyFileName) { + _virtualPathProvider.CreateDirectory(BasePath); + + // Only store assembly if it's more recent that what we have stored already (if anything) + if (IsNewerAssembly(assemblyName, assemblyFileName)) { + var destinationFileName = Path.GetFileName(assemblyFileName); + var destinationPath = _virtualPathProvider.MapPath(_virtualPathProvider.Combine(BasePath, destinationFileName)); + File.Copy(assemblyFileName, destinationPath); + + StoreDepencyInformation(assemblyName, destinationFileName); + } + } + + private bool IsNewerAssembly(string assemblyName, string assemblyFileName) { + var dependency = ReadDependencies().SingleOrDefault(d => d.Name == assemblyName); + if (dependency == null) { + return true; + } + + var existingFileName = _virtualPathProvider.MapPath(_virtualPathProvider.Combine(BasePath, dependency.FileName)); + if (!File.Exists(existingFileName)) { + return true; + } + + return (File.GetCreationTimeUtc(existingFileName) <= File.GetCreationTimeUtc(assemblyFileName)); + } + + private void StoreDepencyInformation(string name, string fileName) { + var dependencies = ReadDependencies().ToList(); + + var dependency = dependencies.SingleOrDefault(d => d.Name == name); + if (dependency == null) { + dependency = new DependencyDescritpor { Name = name, FileName = fileName }; + dependencies.Add(dependency); + } + dependency.FileName = fileName; + + WriteDependencies(dependencies); + } + + public Assembly LoadAssembly(string assemblyName) { + _virtualPathProvider.CreateDirectory(BasePath); + + var dependency = ReadDependencies().SingleOrDefault(d => d.Name == assemblyName); + if (dependency == null) + return null; + + if (!_virtualPathProvider.FileExists(_virtualPathProvider.Combine(BasePath, dependency.FileName))) + return null; + + return Assembly.Load(Path.GetFileNameWithoutExtension(dependency.FileName)); + } + + private class DependencyDescritpor { + public string Name { get; set; } + public string FileName { get; set; } + } + + private IEnumerable<DependencyDescritpor> ReadDependencies() { + if (!_virtualPathProvider.FileExists(PersistencePath)) + return Enumerable.Empty<DependencyDescritpor>(); + + using (var stream = _virtualPathProvider.OpenFile(PersistencePath)) { + XDocument document = XDocument.Load(stream); + return document + .Elements(ns("Dependencies")) + .Elements(ns("Dependency")) + .Select(e => new DependencyDescritpor { Name = e.Element("Name").Value, FileName = e.Element("FileName").Value }) + .ToList(); + } + } + + private void WriteDependencies(IEnumerable<DependencyDescritpor> dependencies) { + var document = new XDocument(); + document.Add(new XElement(ns("Dependencies"))); + var elements = dependencies.Select(d => new XElement("Dependency", + new XElement(ns("Name"), d.Name), + new XElement(ns("FileName"), d.FileName))); + document.Root.Add(elements); + + using (var stream = _virtualPathProvider.CreateText(PersistencePath)) { + document.Save(stream, SaveOptions.None); + } + } + + private static XName ns(string name) { + return XName.Get(name/*, "http://schemas.microsoft.com/developer/msbuild/2003"*/); + } + } +} diff --git a/src/Orchard/FileSystems/WebSite/WebSiteFolder.cs b/src/Orchard/FileSystems/WebSite/WebSiteFolder.cs index 14543853f..d82eec4f9 100644 --- a/src/Orchard/FileSystems/WebSite/WebSiteFolder.cs +++ b/src/Orchard/FileSystems/WebSite/WebSiteFolder.cs @@ -43,6 +43,7 @@ namespace Orchard.FileSystems.WebSite { } public IVolatileToken WhenPathChanges(string virtualPath) { + // Fix this to monitor first existing parent directory. var token = BindToken(virtualPath); BindSignal(virtualPath); return token; diff --git a/src/Orchard/Indexing/IIndexDocument.cs b/src/Orchard/Indexing/IIndexDocument.cs index 36abb1b22..574eb612b 100644 --- a/src/Orchard/Indexing/IIndexDocument.cs +++ b/src/Orchard/Indexing/IIndexDocument.cs @@ -24,6 +24,10 @@ namespace Orchard.Indexing { /// </summary> IIndexDocument Analyze(bool analyze); + /// <summary> + /// Whether some property have been added to this document, or otherwise if it's empty + /// </summary> + bool IsDirty { get; } } } \ No newline at end of file diff --git a/src/Orchard/Indexing/IIndexNotifierHandler.cs b/src/Orchard/Indexing/IIndexNotifierHandler.cs new file mode 100644 index 000000000..f115544d9 --- /dev/null +++ b/src/Orchard/Indexing/IIndexNotifierHandler.cs @@ -0,0 +1,5 @@ +namespace Orchard.Indexing { + public interface IIndexNotifierHandler : IEvents { + void UpdateIndex(string indexName); + } +} diff --git a/src/Orchard/Indexing/IIndexProvider.cs b/src/Orchard/Indexing/IIndexProvider.cs index dcd44c06a..d873b1a13 100644 --- a/src/Orchard/Indexing/IIndexProvider.cs +++ b/src/Orchard/Indexing/IIndexProvider.cs @@ -1,4 +1,7 @@ -namespace Orchard.Indexing { +using System; +using System.Collections.Generic; + +namespace Orchard.Indexing { public interface IIndexProvider : IDependency { /// <summary> /// Creates a new index @@ -16,9 +19,14 @@ void DeleteIndex(string name); /// <summary> - /// Loads an existing document + /// Whether an index is empty or not /// </summary> - IIndexDocument Get(string indexName, int documentId); + bool IsEmpty(string indexName); + + /// <summary> + /// Gets the number of indexed documents + /// </summary> + int NumDocs(string indexName); /// <summary> /// Creates an empty document @@ -31,15 +39,36 @@ /// </summary> void Store(string indexName, IIndexDocument indexDocument); + /// <summary> + /// Adds a set of new document to the index + /// </summary> + void Store(string indexName, IEnumerable<IIndexDocument> indexDocuments); + /// <summary> /// Removes an existing document from the index /// </summary> - void Delete(string indexName, int id); + void Delete(string indexName, int documentId); + + /// <summary> + /// Removes a set of existing document from the index + /// </summary> + void Delete(string indexName, IEnumerable<int> documentIds); /// <summary> /// Creates a search builder for this provider /// </summary> /// <returns>A search builder instance</returns> ISearchBuilder CreateSearchBuilder(string indexName); + + /// <summary> + /// Returns the date and time when the index was last processed + /// </summary> + DateTime GetLastIndexUtc(string indexName); + + /// <summary> + /// Sets the date and time when the index was last processed + /// </summary> + void SetLastIndexUtc(string indexName, DateTime lastIndexUtc); + } } \ No newline at end of file diff --git a/src/Orchard/Indexing/ISearchBuilder.cs b/src/Orchard/Indexing/ISearchBuilder.cs index 3994a37bd..83e3ffda4 100644 --- a/src/Orchard/Indexing/ISearchBuilder.cs +++ b/src/Orchard/Indexing/ISearchBuilder.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace Orchard.Indexing { public interface ISearchBuilder { - ISearchBuilder Parse(string query); + ISearchBuilder Parse(string defaultField, string query); ISearchBuilder WithField(string field, string value); ISearchBuilder WithField(string field, string value, bool wildcardSearch); diff --git a/src/Orchard/Localization/LocalizedString.cs b/src/Orchard/Localization/LocalizedString.cs index a9bd2d934..8197f4219 100644 --- a/src/Orchard/Localization/LocalizedString.cs +++ b/src/Orchard/Localization/LocalizedString.cs @@ -12,12 +12,12 @@ namespace Orchard.Localization { return new LocalizedString(x); } - public override string ToString() { - return _localized; + public string Text { + get { return _localized; } } - public static implicit operator string(LocalizedString x) { - return x._localized; + public override string ToString() { + return _localized; } public override int GetHashCode() { diff --git a/src/Orchard/Localization/Services/DefaultCultureManager.cs b/src/Orchard/Localization/Services/DefaultCultureManager.cs index 8983636d0..e689ab82b 100644 --- a/src/Orchard/Localization/Services/DefaultCultureManager.cs +++ b/src/Orchard/Localization/Services/DefaultCultureManager.cs @@ -3,9 +3,10 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Web; -using System.Web.Routing; +using JetBrains.Annotations; using Orchard.Data; using Orchard.Localization.Records; +using Orchard.Settings; namespace Orchard.Localization.Services { public class DefaultCultureManager : ICultureManager { @@ -17,6 +18,8 @@ namespace Orchard.Localization.Services { _cultureSelectors = cultureSelectors; } + protected virtual ISite CurrentSite { get; [UsedImplicitly] private set; } + public IEnumerable<string> ListCultures() { var query = from culture in _cultureRepository.Table select culture.Culture; return query.ToList(); @@ -29,6 +32,16 @@ namespace Orchard.Localization.Services { _cultureRepository.Create(new CultureRecord { Culture = cultureName }); } + public void DeleteCulture(string cultureName) { + if (!IsValidCulture(cultureName)) { + throw new ArgumentException("cultureName"); + } + + var culture = _cultureRepository.Get(cr => cr.Culture == cultureName); + if (culture != null) + _cultureRepository.Delete(culture); + } + public string GetCurrentCulture(HttpContext requestContext) { var requestCulture = _cultureSelectors .Select(x => x.GetCulture(requestContext)) @@ -47,6 +60,14 @@ namespace Orchard.Localization.Services { return String.Empty; } + public CultureRecord GetCultureById(int id) { + return _cultureRepository.Get(id); + } + + public string GetSiteCulture() { + return CurrentSite == null ? null : CurrentSite.SiteCulture; + } + // "<languagecode2>" or // "<languagecode2>-<country/regioncode2>" or // "<languagecode2>-<scripttag>-<country/regioncode2>" diff --git a/src/Orchard/Localization/Services/DefaultResourceManager.cs b/src/Orchard/Localization/Services/DefaultResourceManager.cs index 313de4212..3f0871041 100644 --- a/src/Orchard/Localization/Services/DefaultResourceManager.cs +++ b/src/Orchard/Localization/Services/DefaultResourceManager.cs @@ -1,27 +1,47 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; +using Orchard.Caching; +using Orchard.Environment.Configuration; +using Orchard.Environment.Extensions; using Orchard.FileSystems.WebSite; namespace Orchard.Localization.Services { public class DefaultResourceManager : IResourceManager { private readonly IWebSiteFolder _webSiteFolder; private readonly ICultureManager _cultureManager; - private readonly IList<CultureDictionary> _cultures; + private readonly IExtensionManager _extensionManager; + private readonly ICacheManager _cacheManager; + private readonly ShellSettings _shellSettings; const string CoreLocalizationFilePathFormat = "/Core/App_Data/Localization/{0}/orchard.core.po"; + const string ModulesLocalizationFilePathFormat = "/Modules/{0}/App_Data/Localization/{1}/orchard.module.po"; + const string RootLocalizationFilePathFormat = "/App_Data/Localization/{0}/orchard.root.po"; + const string TenantLocalizationFilePathFormat = "/App_Data/Sites/{0}/Localization/{1}/orchard.po"; - public DefaultResourceManager(ICultureManager cultureManager, IWebSiteFolder webSiteFolder) { + public DefaultResourceManager( + ICultureManager cultureManager, + IWebSiteFolder webSiteFolder, + IExtensionManager extensionManager, + ICacheManager cacheManager, + ShellSettings shellSettings) { _cultureManager = cultureManager; _webSiteFolder = webSiteFolder; - _cultures = new List<CultureDictionary>(); + _extensionManager = extensionManager; + _cacheManager = cacheManager; + _shellSettings = shellSettings; } + // This will translate a string into a string in the target cultureName. + // The scope portion is optional, it amounts to the location of the file containing + // the string in case it lives in a view, or the namespace name if the string lives in a binary. + // If the culture doesn't have a translation for the string, it will fallback to the + // parent culture as defined in the .net culture hierarchy. e.g. fr-FR will fallback to fr. + // In case it's not found anywhere, the text is returned as is. public string GetLocalizedString(string scope, string text, string cultureName) { - if (_cultures.Count == 0) { - LoadCultures(); - } + var cultures = LoadCultures(); - foreach (var culture in _cultures) { + foreach (var culture in cultures) { if (String.Equals(cultureName, culture.CultureName, StringComparison.OrdinalIgnoreCase)) { string scopedKey = scope + "|" + text; string genericKey = "|" + text; @@ -31,33 +51,104 @@ namespace Orchard.Localization.Services { if (culture.Translations.ContainsKey(genericKey)) { return culture.Translations[genericKey]; } - return text; + + return GetParentTranslation(scope, text, cultureName, cultures); } } return text; } - private void LoadCultures() { - foreach (var culture in _cultureManager.ListCultures()) { - _cultures.Add(new CultureDictionary { - CultureName = culture, - Translations = LoadTranslationsForCulture(culture) - }); + private static string GetParentTranslation(string scope, string text, string cultureName, IEnumerable<CultureDictionary> cultures) { + string scopedKey = scope + "|" + text; + string genericKey = "|" + text; + try { + CultureInfo cultureInfo = CultureInfo.GetCultureInfo(cultureName); + CultureInfo parentCultureInfo = cultureInfo.Parent; + if (parentCultureInfo.IsNeutralCulture) { + foreach (var culture in cultures) { + if (String.Equals(parentCultureInfo.Name, culture.CultureName, StringComparison.OrdinalIgnoreCase)) { + if (culture.Translations.ContainsKey(scopedKey)) { + return culture.Translations[scopedKey]; + } + if (culture.Translations.ContainsKey(genericKey)) { + return culture.Translations[genericKey]; + } + break; + } + } + } } + catch (CultureNotFoundException) { } + + return text; } - private IDictionary<string, string> LoadTranslationsForCulture(string culture) { - string path = string.Format(CoreLocalizationFilePathFormat, culture); - string text = _webSiteFolder.ReadFile(path); + // Loads the culture dictionaries in memory and caches them. + // Cache entry will be invalidated any time the directories hosting + // the .po files are modified. + private IEnumerable<CultureDictionary> LoadCultures() { + return _cacheManager.Get("cultures", ctx => { + var cultures = new List<CultureDictionary>(); + foreach (var culture in _cultureManager.ListCultures()) { + cultures.Add(new CultureDictionary { + CultureName = culture, + Translations = LoadTranslationsForCulture(culture, ctx) + }); + } + return cultures; + }); + + } + + // Merging occurs from multiple locations: + // In reverse priority order: + // "/Core/App_Data/Localization/<culture_name>/orchard.core.po"; + // "/Modules/<module_name>/App_Data/Localization/<culture_name>/orchard.module.po"; + // "/App_Data/Localization/<culture_name>/orchard.root.po"; + // "/App_Data/Sites/<tenant_name>/Localization/<culture_name>/orchard.po"; + // The dictionary entries from po files that live in higher priority locations will + // override the ones from lower priority locations during loading of dictionaries. + + // TODO: Add culture name in the po file name to facilitate usage. + private IDictionary<string, string> LoadTranslationsForCulture(string culture, AcquireContext<string> context) { + IDictionary<string, string> translations = new Dictionary<string, string>(); + string corePath = string.Format(CoreLocalizationFilePathFormat, culture); + string text = _webSiteFolder.ReadFile(corePath); if (text != null) { - return ParseLocalizationStream(text); + ParseLocalizationStream(text, translations, false); + context.Monitor(_webSiteFolder.WhenPathChanges(corePath)); } - return new Dictionary<string, string>(); + + foreach (var module in _extensionManager.AvailableExtensions()) { + if (String.Equals(module.ExtensionType, "Module")) { + string modulePath = string.Format(ModulesLocalizationFilePathFormat, module.Name, culture); + text = _webSiteFolder.ReadFile(modulePath); + if (text != null) { + ParseLocalizationStream(text, translations, true); + context.Monitor(_webSiteFolder.WhenPathChanges(modulePath)); + } + } + } + + string rootPath = string.Format(RootLocalizationFilePathFormat, culture); + text = _webSiteFolder.ReadFile(rootPath); + if (text != null) { + ParseLocalizationStream(text, translations, true); + context.Monitor(_webSiteFolder.WhenPathChanges(rootPath)); + } + + string tenantPath = string.Format(TenantLocalizationFilePathFormat, _shellSettings.Name, culture); + text = _webSiteFolder.ReadFile(tenantPath); + if (text != null) { + ParseLocalizationStream(text, translations, true); + context.Monitor(_webSiteFolder.WhenPathChanges(tenantPath)); + } + + return translations; } - private static IDictionary<string, string> ParseLocalizationStream(string text) { - Dictionary<string, string> translations = new Dictionary<string, string>(); + private static void ParseLocalizationStream(string text, IDictionary<string, string> translations, bool merge) { StringReader reader = new StringReader(text); string poLine, id, scope; id = scope = String.Empty; @@ -80,18 +171,26 @@ namespace Orchard.Localization.Services { if (!translations.ContainsKey(scopedKey)) { translations.Add(scopedKey, translation); } + else { + if (merge) { + translations[scopedKey] = translation; + } + } } string genericKey = "|" + id; if (!translations.ContainsKey(genericKey)) { translations.Add(genericKey, translation); } + else { + if (merge) { + translations[genericKey] = translation; + } + } } id = scope = String.Empty; } } - - return translations; } private static string ParseTranslation(string poLine) { diff --git a/src/Orchard/Localization/Services/ICultureManager.cs b/src/Orchard/Localization/Services/ICultureManager.cs index 2a55558f1..0ae822a42 100644 --- a/src/Orchard/Localization/Services/ICultureManager.cs +++ b/src/Orchard/Localization/Services/ICultureManager.cs @@ -1,10 +1,14 @@ using System.Collections.Generic; using System.Web; +using Orchard.Localization.Records; namespace Orchard.Localization.Services { public interface ICultureManager : IDependency { IEnumerable<string> ListCultures(); void AddCulture(string cultureName); + void DeleteCulture(string cultureName); string GetCurrentCulture(HttpContext requestContext); + CultureRecord GetCultureById(int id); + string GetSiteCulture(); } } diff --git a/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs b/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs index 7e97dab3f..ee4ba0d40 100644 --- a/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs +++ b/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; -using System.Text.RegularExpressions; using System.Web.Mvc; using System.Web.Mvc.Html; using System.Web.Routing; +using Orchard.Collections; using Orchard.Mvc.ViewModels; using Orchard.Services; using Orchard.Settings; @@ -41,6 +41,74 @@ namespace Orchard.Mvc.Html { return MvcHtmlString.Create(builder.ToString(TagRenderMode.Normal)); } + #region Pager + + public static string Pager<T>(this HtmlHelper html, IPageOfItems<T> pageOfItems, int currentPage, int defaultPageSize, object values = null, string previousText = "<", string nextText = ">", bool alwaysShowPreviousAndNext = false) { + if (pageOfItems.TotalPageCount < 2) + return ""; + + var sb = new StringBuilder(75); + var rvd = new RouteValueDictionary {{"q", ""},{"page", 0}}; + var viewContext = html.ViewContext; + var urlHelper = new UrlHelper(viewContext.RequestContext); + + if (pageOfItems.PageSize != defaultPageSize) + rvd.Add("pagesize", pageOfItems.PageSize); + + foreach (var item in viewContext.RouteData.Values) { + rvd.Add(item.Key, item.Value); + } + + + if (values != null) { + var rvd2 = new RouteValueDictionary(values); + + foreach (var item in rvd2) { + rvd[item.Key] = item.Value; + } + } + + sb.Append("<p class=\"pager\">"); + + if (currentPage > 1 || alwaysShowPreviousAndNext) { + if (currentPage == 2) + rvd.Remove("page"); + else + rvd["page"] = currentPage - 1; + + sb.AppendFormat(" <a href=\"{1}\" class=\"previous\">{0}</a>", previousText, + urlHelper.RouteUrl(rvd)); + } + + //todo: when there are many pages (> 15?) maybe do something like 1 2 3...6 7 8...13 14 15 + for (var p = 1; p <= pageOfItems.TotalPageCount; p++) { + if (p == currentPage) { + sb.AppendFormat(" <span>{0}</span>", p); + } + else { + if (p == 1) + rvd.Remove("page"); + else + rvd["page"] = p; + + sb.AppendFormat(" <a href=\"{1}\">{0}</a>", p, + urlHelper.RouteUrl(rvd)); + } + } + + if (currentPage < pageOfItems.TotalPageCount || alwaysShowPreviousAndNext) { + rvd["page"] = currentPage + 1; + sb.AppendFormat("<a href=\"{1}\" class=\"next\">{0}</a>", nextText, + urlHelper.RouteUrl(rvd)); + } + + sb.Append("</p>"); + + return sb.ToString(); + } + + #endregion + #region UnorderedList public static string UnorderedList<T>(this HtmlHelper htmlHelper, IEnumerable<T> items, Func<T, int, string> generateContent, string cssClass) { @@ -111,7 +179,7 @@ namespace Orchard.Mvc.Html { TimeSpan time = htmlHelper.Resolve<IClock>().UtcNow - value; if (time.TotalDays > 7) - return "at " + htmlHelper.DateTime(value); + return "on " + htmlHelper.DateTime(value, "MMM d yyyy 'at' h:mm tt"); if (time.TotalHours > 24) return string.Format("{0} day{1} ago", time.Days, time.Days == 1 ? "" : "s"); if (time.TotalMinutes > 60) diff --git a/src/Orchard/Mvc/MvcModule.cs b/src/Orchard/Mvc/MvcModule.cs index c88c941b0..76d24a5a6 100644 --- a/src/Orchard/Mvc/MvcModule.cs +++ b/src/Orchard/Mvc/MvcModule.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Specialized; using System.Web; using System.Web.Mvc; using System.Web.Routing; @@ -82,6 +83,13 @@ namespace Orchard.Mvc { public override bool IsAuthenticated { get { return false; } } + + // empty collection provided for background operation + public override NameValueCollection Form { + get { + return new NameValueCollection(); + } + } } } } \ No newline at end of file diff --git a/src/Orchard/Mvc/ViewEngines/ViewEngineFilter.cs b/src/Orchard/Mvc/ViewEngines/ViewEngineFilter.cs index a2345afaa..832eec526 100644 --- a/src/Orchard/Mvc/ViewEngines/ViewEngineFilter.cs +++ b/src/Orchard/Mvc/ViewEngines/ViewEngineFilter.cs @@ -48,7 +48,7 @@ namespace Orchard.Mvc.ViewEngines { themeViewEngines = _viewEngineProviders .Select(x => x.CreateThemeViewEngine(new CreateThemeViewEngineParams { VirtualPath = themeLocation })); - Logger.Debug("Theme location:\r\n\t-{0}", themeLocation); + //Logger.Debug("Theme location:\r\n\t-{0}", themeLocation); } @@ -58,7 +58,7 @@ namespace Orchard.Mvc.ViewEngines { var moduleLocations = modules.Select(x => Path.Combine(x.Location, x.Name)); var moduleViewEngines = _viewEngineProviders .Select(x => x.CreateModulesViewEngine(new CreateModulesViewEngineParams { VirtualPaths = moduleLocations })); - Logger.Debug("Module locations:\r\n\t-{0}", string.Join("\r\n\t-", moduleLocations.ToArray())); + //Logger.Debug("Module locations:\r\n\t-{0}", string.Join("\r\n\t-", moduleLocations.ToArray())); var requestViewEngines = new ViewEngineCollection( themeViewEngines diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index f43d1f4af..4245c8db9 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -119,6 +119,7 @@ <RequiredTargetFramework>3.5</RequiredTargetFramework> </Reference> <Reference Include="System.Xml" /> + <Reference Include="System.Xml.Linq" /> <Reference Include="Yaml, Version=1.0.3370.39839, Culture=neutral, PublicKeyToken=187a3d240e44a135, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> <HintPath>..\..\lib\yaml\Yaml.dll</HintPath> @@ -129,12 +130,235 @@ </Reference> </ItemGroup> <ItemGroup> + <Compile Include="Collections\IPageOfItems.cs" /> + <Compile Include="Collections\PageOfItems.cs" /> + <Compile Include="ContentManagement\Aspects\ICommonAspect.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Aspects\IRoutableAspect.cs" /> + <Compile Include="ContentManagement\ContentExtensions.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\ContentField.cs" /> + <Compile Include="ContentManagement\ContentItem.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\ContentItemMetadata.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\ContentModule.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\ContentPart.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\ContentType.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\DefaultContentManager.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\DefaultContentManagerSession.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\DefaultContentQuery.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Drivers\AutomaticContentPartDriver.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Drivers\CombinedResult.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Drivers\ContentItemDriver.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Drivers\ContentItemDriverHandler.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Drivers\ContentItemTemplateResult.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Drivers\ContentPartDriver.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Drivers\ContentPartDriverHandler.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Drivers\ContentPartTemplateResult.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Drivers\DriverResult.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Drivers\IContentItemDriver.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Extenstions\UrlHelperExtensions.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\ActivatedContentContext.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\ActivatingContentContext.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\ActivatingFilter.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\BuildDisplayModelContext.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\BuildEditorModelContext.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\ContentContextBase.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\ContentHandler.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\ContentHandlerBase.cs" /> + <Compile Include="ContentManagement\Handlers\ContentItemBuilder.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\ContentItemTemplates.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\CreateContentContext.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\GetContentItemMetadataContext.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\IContentActivatingFilter.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\IContentFilter.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\IContentHandler.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\IContentStorageFilter.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\IContentTemplateFilter.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\LoadContentContext.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\PublishContentContext.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\RemoveContentContext.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\StorageFilter.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\StorageFilterBase.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\StorageVersionFilter.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\TemplateFilterBase.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\TemplateFilterForRecord.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\UpdateEditorModelContext.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Handlers\VersionContentContext.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\IContent.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\IContentManager.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\IContentManagerSession.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\IContentQuery.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\IUpdateModel.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\MetaData\Builders\ContentPartDefinitionBuilder.cs" /> + <Compile Include="ContentManagement\MetaData\Builders\ContentTypeDefinitionBuilder.cs" /> + <Compile Include="ContentManagement\MetaData\ContentPartHandler.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\MetaData\ContentPartInfo.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\MetaData\Services\ContentDefinitionWriter.cs" /> + <Compile Include="ContentManagement\MetaData\IContentDefinitionManager.cs" /> + <Compile Include="ContentManagement\MetaData\IContentDefinitionReader.cs" /> + <Compile Include="ContentManagement\MetaData\IContentDefinitionWriter.cs" /> + <Compile Include="ContentManagement\MetaData\Models\ContentFieldDefinition.cs" /> + <Compile Include="ContentManagement\MetaData\Models\ContentPartDefinition.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\MetaData\Models\ContentTypeDefinition.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\MetaData\Services\ContentDefinitionReader.cs" /> + <Compile Include="ContentManagement\MetaData\Services\SettingsFormatter.cs" /> + <Compile Include="ContentManagement\Records\ContentItemAlteration.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Records\ContentItemRecord.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Records\ContentItemVersionRecord.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Records\ContentPartAlteration.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Records\ContentPartRecord.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Records\ContentPartVersionRecord.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Records\ContentTypeRecord.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Records\Utility.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\Utilities\LazyField.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="ContentManagement\ViewModels\TemplateViewModel.cs"> + <SubType>Code</SubType> + </Compile> <Compile Include="Data\SessionLocator.cs" /> <Compile Include="Data\IRepository.cs" /> <Compile Include="Data\ISessionLocator.cs" /> <Compile Include="Data\DataModule.cs" /> <Compile Include="Data\Orderable.cs" /> <Compile Include="Environment\DefaultOrchardShell.cs" /> + <Compile Include="Environment\IHostEnvironment.cs" /> + <Compile Include="FileSystems\Dependencies\IDependenciesFolder.cs" /> + <Compile Include="Environment\Extensions\Loaders\ProbingExtensionLoader.cs" /> + <Compile Include="Environment\Extensions\Compilers\CSharpExtensionBuildProvider.cs" /> + <Compile Include="Environment\Extensions\Compilers\CSharpExtensionCompiler.cs" /> + <Compile Include="Environment\Extensions\Compilers\CSharpProjectMediumTrustCompiler.cs" /> + <Compile Include="Environment\Extensions\Compilers\CSharpProjectFullTrustCompiler.cs" /> + <Compile Include="Environment\Extensions\Compilers\CSharpProjectParser.cs" /> + <Compile Include="Environment\IAssemblyBuilder.cs" /> + <Compile Include="Environment\IBuildManager.cs" /> + <Compile Include="Environment\IVirtualPathProvider.cs" /> <Compile Include="Environment\IOrchardShell.cs" /> <Compile Include="Environment\OrchardStarter.cs" /> <Compile Include="Environment\State\DefaultProcessingEngine.cs" /> @@ -143,6 +367,7 @@ <Compile Include="Environment\State\IShellStateManager.cs" /> <Compile Include="Environment\State\ShellStateCoordinator.cs" /> <Compile Include="IDependency.cs" /> + <Compile Include="Indexing\IIndexNotifierHandler.cs" /> <Compile Include="Localization\Services\DefaultCultureManager.cs" /> <Compile Include="Localization\Services\DefaultResourceManager.cs" /> <Compile Include="Indexing\DefaultIndexManager.cs" /> @@ -182,10 +407,6 @@ <Compile Include="Caching\DefaultCacheManager.cs" /> <Compile Include="Caching\ICacheHolder.cs" /> <Compile Include="Caching\Weak.cs" /> - <Compile Include="ContentManagement\DefaultContentManagerSession.cs" /> - <Compile Include="ContentManagement\IContentManagerSession.cs" /> - <Compile Include="ContentManagement\MetaData\Records\ContentTypePartNameRecord.cs" /> - <Compile Include="ContentManagement\Utilities\LazyField.cs" /> <Compile Include="Environment\State\Models\ShellState.cs" /> <Compile Include="FileSystems\WebSite\WebSiteFolder.cs" /> <Compile Include="FileSystems\AppData\IAppDataFolder.cs" /> @@ -209,18 +430,6 @@ <Compile Include="Commands\ICommandHandler.cs" /> <Compile Include="Commands\OrchardSwitchAttribute.cs" /> <Compile Include="Commands\OrchardSwitchesAttribute.cs" /> - <Compile Include="ContentManagement\Aspects\ICommonAspect.cs" /> - <Compile Include="ContentManagement\Drivers\IContentItemDriver.cs" /> - <Compile Include="ContentManagement\Extenstions\UrlHelperExtensions.cs" /> - <Compile Include="ContentManagement\Handlers\ContentContextBase.cs" /> - <Compile Include="ContentManagement\Handlers\PublishContentContext.cs" /> - <Compile Include="ContentManagement\Handlers\RemoveContentContext.cs" /> - <Compile Include="ContentManagement\Handlers\VersionContentContext.cs" /> - <Compile Include="ContentManagement\MetaData\ContentPartHandler.cs" /> - <Compile Include="ContentManagement\MetaData\ContentPartInfo.cs" /> - <Compile Include="ContentManagement\MetaData\Services\ContentTypeService.cs" /> - <Compile Include="ContentManagement\MetaData\Services\IContentTypeService.cs" /> - <Compile Include="ContentManagement\MetaData\Records\ContentTypePartRecord.cs" /> <Compile Include="Data\Conventions\RecordTableNameConvention.cs" /> <Compile Include="Data\Builders\AbstractBuilder.cs" /> <Compile Include="Data\Builders\SessionFactoryBuilder.cs" /> @@ -290,60 +499,8 @@ <Compile Include="Services\IHomePageProvider.cs" /> <Compile Include="Tasks\Scheduling\IPublishingTaskManager.cs" /> <Compile Include="Tasks\Scheduling\IScheduledTask.cs" /> - <Compile Include="ContentManagement\ContentExtensions.cs" /> - <Compile Include="ContentManagement\ContentItem.cs" /> - <Compile Include="ContentManagement\ContentItemMetadata.cs" /> - <Compile Include="ContentManagement\ContentModule.cs" /> - <Compile Include="ContentManagement\ContentPart.cs" /> - <Compile Include="ContentManagement\ContentType.cs" /> - <Compile Include="ContentManagement\DefaultContentManager.cs" /> - <Compile Include="ContentManagement\DefaultContentQuery.cs" /> - <Compile Include="ContentManagement\Drivers\AutomaticContentPartDriver.cs" /> - <Compile Include="ContentManagement\Drivers\CombinedResult.cs" /> - <Compile Include="ContentManagement\Drivers\DriverResult.cs" /> - <Compile Include="ContentManagement\Drivers\ContentItemDriver.cs" /> - <Compile Include="ContentManagement\Drivers\ContentItemDriverHandler.cs" /> - <Compile Include="ContentManagement\Drivers\ContentItemTemplateResult.cs" /> - <Compile Include="ContentManagement\Drivers\ContentPartDriverHandler.cs" /> - <Compile Include="ContentManagement\Drivers\ContentPartTemplateResult.cs" /> - <Compile Include="ContentManagement\Handlers\ActivatedContentContext.cs" /> - <Compile Include="ContentManagement\Handlers\ActivatingContentContext.cs" /> - <Compile Include="ContentManagement\Handlers\ActivatingFilter.cs" /> - <Compile Include="ContentManagement\Handlers\BuildDisplayModelContext.cs" /> - <Compile Include="ContentManagement\Handlers\BuildEditorModelContext.cs" /> - <Compile Include="ContentManagement\Handlers\ContentHandler.cs" /> - <Compile Include="ContentManagement\Handlers\ContentItemBuilder.cs" /> - <Compile Include="ContentManagement\Handlers\ContentItemTemplates.cs" /> - <Compile Include="ContentManagement\Handlers\CreateContentContext.cs" /> - <Compile Include="ContentManagement\Handlers\GetContentItemMetadataContext.cs" /> - <Compile Include="ContentManagement\Handlers\IContentActivatingFilter.cs" /> - <Compile Include="ContentManagement\Handlers\IContentFilter.cs" /> - <Compile Include="ContentManagement\Handlers\IContentHandler.cs" /> - <Compile Include="ContentManagement\Handlers\IContentStorageFilter.cs" /> - <Compile Include="ContentManagement\Handlers\IContentTemplateFilter.cs" /> - <Compile Include="ContentManagement\Handlers\LoadContentContext.cs" /> - <Compile Include="ContentManagement\Handlers\StorageFilter.cs" /> - <Compile Include="ContentManagement\Handlers\StorageFilterBase.cs" /> - <Compile Include="ContentManagement\Handlers\StorageVersionFilter.cs" /> - <Compile Include="ContentManagement\Handlers\TemplateFilterBase.cs" /> - <Compile Include="ContentManagement\Handlers\TemplateFilterForRecord.cs" /> - <Compile Include="ContentManagement\Handlers\UpdateEditorModelContext.cs" /> - <Compile Include="ContentManagement\IContent.cs" /> - <Compile Include="ContentManagement\IContentManager.cs" /> - <Compile Include="ContentManagement\IContentQuery.cs" /> - <Compile Include="ContentManagement\Drivers\ContentPartDriver.cs" /> - <Compile Include="ContentManagement\IUpdateModel.cs" /> - <Compile Include="ContentManagement\Records\ContentItemRecord.cs" /> - <Compile Include="ContentManagement\Records\ContentItemAlteration.cs" /> - <Compile Include="ContentManagement\Records\ContentItemVersionRecord.cs" /> - <Compile Include="ContentManagement\Records\ContentPartRecord.cs" /> - <Compile Include="ContentManagement\Records\ContentPartAlteration.cs" /> - <Compile Include="ContentManagement\Records\ContentPartVersionRecord.cs" /> - <Compile Include="ContentManagement\Records\ContentTypeRecord.cs" /> - <Compile Include="ContentManagement\Records\Utility.cs" /> <Compile Include="Localization\Text.cs" /> <Compile Include="Mvc\ViewModels\ContentItemViewModel.cs" /> - <Compile Include="ContentManagement\ViewModels\TemplateViewModel.cs" /> <Compile Include="Data\Conventions\AttributeCollectionConvention.cs" /> <Compile Include="Data\Conventions\CascadeAllDeleteOrphanAttribute.cs" /> <Compile Include="Data\TransactionManager.cs" /> @@ -513,9 +670,7 @@ <Install>true</Install> </BootstrapperPackage> </ItemGroup> - <ItemGroup> - <Folder Include="ContentManagement\MetaData\Models\" /> - </ItemGroup> + <ItemGroup /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. diff --git a/src/Orchard/Tasks/Indexing/IIndexingTaskManager.cs b/src/Orchard/Tasks/Indexing/IIndexingTaskManager.cs index de913e167..1bd06d54c 100644 --- a/src/Orchard/Tasks/Indexing/IIndexingTaskManager.cs +++ b/src/Orchard/Tasks/Indexing/IIndexingTaskManager.cs @@ -4,9 +4,25 @@ using Orchard.ContentManagement; namespace Orchard.Tasks.Indexing { public interface IIndexingTaskManager : IDependency { - void CreateTask(ContentItem contentItem); - IEnumerable<IIndexingTask> GetTasks(DateTime? createdAfter); - void DeleteTasks(DateTime? createdBefore); + /// <summary> + /// Adds a new entry in the index task table in order to create an index for the specified content item. + /// </summary> + void CreateUpdateIndexTask(ContentItem contentItem); + + /// <summary> + /// Adds a new entry in the index task table in order to delete an existing index for the specified content item. + /// </summary> + void CreateDeleteIndexTask(ContentItem contentItem); + + /// <summary> + /// Returns the Date Time of the last task created + /// </summary> + DateTime GetLastTaskDateTime(); + + /// <summary> + /// Deletes all indexing tasks assigned to a specific content item + /// </summary> + /// <param name="contentItem"></param> void DeleteTasks(ContentItem contentItem); } } \ No newline at end of file diff --git a/src/Orchard/Tasks/SweepGenerator.cs b/src/Orchard/Tasks/SweepGenerator.cs index b8fb5b64b..f811029b2 100644 --- a/src/Orchard/Tasks/SweepGenerator.cs +++ b/src/Orchard/Tasks/SweepGenerator.cs @@ -14,7 +14,7 @@ namespace Orchard.Tasks { _timer = new Timer(); _timer.Elapsed += Elapsed; Logger = NullLogger.Instance; - Interval = TimeSpan.FromMinutes(5); + Interval = TimeSpan.FromMinutes(1); } public ILogger Logger { get; set; } diff --git a/src/Orchard/UI/Navigation/NavigationBuilder.cs b/src/Orchard/UI/Navigation/NavigationBuilder.cs index 5848557b4..8fbb45108 100644 --- a/src/Orchard/UI/Navigation/NavigationBuilder.cs +++ b/src/Orchard/UI/Navigation/NavigationBuilder.cs @@ -1,35 +1,32 @@ using System; using System.Collections.Generic; using System.Linq; +using Orchard.Localization; namespace Orchard.UI.Navigation { public class NavigationBuilder { IEnumerable<MenuItem> Contained { get; set; } - public NavigationBuilder Add(string caption, string position, Action<NavigationItemBuilder> itemBuilder) { + public NavigationBuilder Add(LocalizedString caption, string position, Action<NavigationItemBuilder> itemBuilder) { var childBuilder = new NavigationItemBuilder(); - if (!string.IsNullOrEmpty(caption)) - childBuilder.Caption(caption); - - if (!string.IsNullOrEmpty(position)) - childBuilder.Position(position); - + childBuilder.Caption(caption); + childBuilder.Position(position); itemBuilder(childBuilder); Contained = (Contained ?? Enumerable.Empty<MenuItem>()).Concat(childBuilder.Build()); return this; } - public NavigationBuilder Add(string caption, Action<NavigationItemBuilder> itemBuilder) { + public NavigationBuilder Add(LocalizedString caption, Action<NavigationItemBuilder> itemBuilder) { return Add(caption, null, itemBuilder); } public NavigationBuilder Add(Action<NavigationItemBuilder> itemBuilder) { return Add(null, null, itemBuilder); } - public NavigationBuilder Add(string caption, string position) { + public NavigationBuilder Add(LocalizedString caption, string position) { return Add(caption, position, x=> { }); } - public NavigationBuilder Add(string caption) { + public NavigationBuilder Add(LocalizedString caption) { return Add(caption, null, x => { }); } diff --git a/src/Orchard/UI/Navigation/NavigationItemBuilder.cs b/src/Orchard/UI/Navigation/NavigationItemBuilder.cs index 52a0c9246..61bc5da3a 100644 --- a/src/Orchard/UI/Navigation/NavigationItemBuilder.cs +++ b/src/Orchard/UI/Navigation/NavigationItemBuilder.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Web.Routing; +using Orchard.Localization; using Orchard.Security.Permissions; namespace Orchard.UI.Navigation { @@ -11,8 +12,9 @@ namespace Orchard.UI.Navigation { _item = new MenuItem(); } - public NavigationItemBuilder Caption(string caption) { - _item.Text = caption; + public NavigationItemBuilder Caption(LocalizedString caption) { + if (caption != null) + _item.Text = caption.Text; return this; }