commit b8f912cc790244366481140522e9bea5d1905044 Author: Dario Solera Date: Wed Sep 30 13:47:13 2009 +0000 Fixed help files. diff --git a/AclEngine-Tests/AclChangedEventArgsTests.cs b/AclEngine-Tests/AclChangedEventArgsTests.cs new file mode 100644 index 0000000..e74b65b --- /dev/null +++ b/AclEngine-Tests/AclChangedEventArgsTests.cs @@ -0,0 +1,43 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.AclEngine.Tests { + + [TestFixture] + public class AclChangedEventArgsTests { + + [Test] + public void Constructor() { + AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant); + + AclChangedEventArgs args = new AclChangedEventArgs(new AclEntry[] { entry }, Change.EntryStored); + + Assert.AreEqual(1, args.Entries.Length, "Wrong entry count"); + Assert.AreSame(entry, args.Entries[0], "Wrong Entry instance"); + Assert.AreEqual(Change.EntryStored, args.Change, "Wrong change"); + + args = new AclChangedEventArgs(new AclEntry[] { entry }, Change.EntryDeleted); + + Assert.AreEqual(1, args.Entries.Length, "Wrong entry count"); + Assert.AreSame(entry, args.Entries[0], "Wrong Entry instance"); + Assert.AreEqual(Change.EntryDeleted, args.Change, "Wrong change"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_NullEntries() { + AclChangedEventArgs args = new AclChangedEventArgs(null, Change.EntryDeleted); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void Constructor_EmptyEntries() { + AclChangedEventArgs args = new AclChangedEventArgs(new AclEntry[0], Change.EntryDeleted); + } + + } + +} diff --git a/AclEngine-Tests/AclEngine-Tests.csproj b/AclEngine-Tests/AclEngine-Tests.csproj new file mode 100644 index 0000000..0c52a1d --- /dev/null +++ b/AclEngine-Tests/AclEngine-Tests.csproj @@ -0,0 +1,73 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {9F22D0A6-115B-4EB1-8506-65263674CEA3} + Library + Properties + ScrewTurn.Wiki.AclEngine.Tests + ScrewTurn.Wiki.AclEngine.Tests + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + none + true + bin\Release\ + TRACE + prompt + 4 + true + + + + False + ..\References\Tools\NUnit\framework\nunit.framework.dll + + + False + ..\References\Tools\Rhino.Mocks\Rhino.Mocks.dll + + + + 3.5 + + + + + AssemblyVersion.cs + + + + + + + + + + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81} + AclEngine + + + + + \ No newline at end of file diff --git a/AclEngine-Tests/AclEntryTests.cs b/AclEngine-Tests/AclEntryTests.cs new file mode 100644 index 0000000..86d7dfc --- /dev/null +++ b/AclEngine-Tests/AclEntryTests.cs @@ -0,0 +1,77 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.AclEngine.Tests { + + [TestFixture] + public class AclEntryTests { + + [Test] + public void Constructor() { + AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant); + + Assert.AreEqual("Res", entry.Resource, "Wrong resource"); + Assert.AreEqual("Action", entry.Action, "Wrong action"); + Assert.AreEqual("U.User", entry.Subject, "Wrong subject"); + Assert.AreEqual(Value.Grant, entry.Value, "Wrong value"); + + entry = new AclEntry("Res", "Action", "G.Group", Value.Deny); + + Assert.AreEqual("Res", entry.Resource, "Wrong resource"); + Assert.AreEqual("Action", entry.Action, "Wrong action"); + Assert.AreEqual("G.Group", entry.Subject, "Wrong subject"); + Assert.AreEqual(Value.Deny, entry.Value, "Wrong value"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_InvalidResource(string r) { + AclEntry entry = new AclEntry(r, "Action", "U.USer", Value.Grant); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_InvalidAction(string a) { + AclEntry entry = new AclEntry("Res", a, "G.Group", Value.Deny); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_InvalidSubject(string s) { + AclEntry entry = new AclEntry("Res", "Action", s, Value.Grant); + } + + [Test] + public void Equals() { + AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant); + + Assert.IsFalse(entry.Equals(null), "Equals should return false (testing null)"); + Assert.IsFalse(entry.Equals("blah"), "Equals should return false (testing a string)"); + Assert.IsFalse(entry.Equals(new AclEntry("Res1", "Action", "U.User", Value.Grant)), "Equals should return false"); + Assert.IsFalse(entry.Equals(new AclEntry("Res", "Action1", "U.User", Value.Grant)), "Equals should return false"); + Assert.IsFalse(entry.Equals(new AclEntry("Res", "Action", "U.User1", Value.Grant)), "Equals should return false"); + Assert.IsTrue(entry.Equals(new AclEntry("Res", "Action", "U.User", Value.Deny)), "Equals should return true"); + Assert.IsTrue(entry.Equals(new AclEntry("Res", "Action", "U.User", Value.Grant)), "Equals should return true"); + Assert.IsTrue(entry.Equals(entry), "Equals should return true"); + } + + [Test] + public void Static_Equals() { + AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant); + + Assert.IsFalse(AclEntry.Equals(entry, null), "Equals should return false (testing null)"); + Assert.IsFalse(AclEntry.Equals(entry, "blah"), "Equals should return false (testing a string)"); + Assert.IsFalse(AclEntry.Equals(entry, new AclEntry("Res1", "Action", "U.User", Value.Grant)), "Equals should return false"); + Assert.IsFalse(AclEntry.Equals(entry, new AclEntry("Res", "Action1", "U.User", Value.Grant)), "Equals should return false"); + Assert.IsFalse(AclEntry.Equals(entry, new AclEntry("Res", "Action", "U.User1", Value.Grant)), "Equals should return false"); + Assert.IsTrue(AclEntry.Equals(entry, new AclEntry("Res", "Action", "U.User", Value.Deny)), "Equals should return true"); + Assert.IsTrue(AclEntry.Equals(entry, new AclEntry("Res", "Action", "U.User", Value.Grant)), "Equals should return true"); + Assert.IsTrue(AclEntry.Equals(entry, entry), "Equals should return true"); + } + + } + +} diff --git a/AclEngine-Tests/AclEvaluatorTests.cs b/AclEngine-Tests/AclEvaluatorTests.cs new file mode 100644 index 0000000..0e65c02 --- /dev/null +++ b/AclEngine-Tests/AclEvaluatorTests.cs @@ -0,0 +1,440 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.AclEngine.Tests { + + [TestFixture] + public class AclEvaluatorTests { + + [Test] + public void AuthorizeAction_InexistentResource() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Deny)); + entries.Add(new AclEntry("Res", "Action2", "U.User", Value.Deny)); + entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant)); + entries.Add(new AclEntry("Res2", "Action", "U.User", Value.Deny)); + + Assert.AreEqual(Authorization.Unknown, AclEvaluator.AuthorizeAction("Res3", "Action", "U.User", new string[0], entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_InexistentAction() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Deny)); + entries.Add(new AclEntry("Res", "Action2", "U.User", Value.Deny)); + entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant)); + entries.Add(new AclEntry("Res2", "Action", "U.User", Value.Deny)); + + Assert.AreEqual(Authorization.Unknown, AclEvaluator.AuthorizeAction("Res", "Action3", "U.User", new string[0], entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Deny)); + entries.Add(new AclEntry("Res", "Action2", "U.User", Value.Deny)); + entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant)); + entries.Add(new AclEntry("Res2", "Action", "U.User", Value.Deny)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[0], entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "U.User", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Deny)); + entries.Add(new AclEntry("Res", "Action2", "U.User", Value.Deny)); + entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant)); + entries.Add(new AclEntry("Res2", "Action", "U.User", Value.Deny)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[0], entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Deny)); + entries.Add(new AclEntry("Res", "Action2", "U.User", Value.Deny)); + entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant)); + entries.Add(new AclEntry("Res2", "Action", "U.User", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User2", new string[0], entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant)); + entries.Add(new AclEntry("Res", "*", "U.User2", Value.Deny)); + entries.Add(new AclEntry("Res", "Action2", "U.User", Value.Deny)); + entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant)); + entries.Add(new AclEntry("Res2", "Action", "U.User", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User2", new string[0], entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantGroupExplicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Deny)); + entries.Add(new AclEntry("Res", "Action2", "G.Group", Value.Deny)); + entries.Add(new AclEntry("Res2", "Action", "G.Group", Value.Deny)); + entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Deny)); + entries.Add(new AclEntry("Res", "Action2", "G.Group", Value.Deny)); + entries.Add(new AclEntry("Res2", "Action", "G.Group", Value.Deny)); + entries.Add(new AclEntry("Res", "*", "U.User3", Value.Grant)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyGroupExplicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Deny)); + entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Grant)); + entries.Add(new AclEntry("Res", "Action2", "G.Group", Value.Grant)); + entries.Add(new AclEntry("Res2", "Action", "G.Group", Value.Grant)); + entries.Add(new AclEntry("Res", "*", "U.User3", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group", Value.Deny)); + entries.Add(new AclEntry("Res", "Action", "U.User2", Value.Grant)); + entries.Add(new AclEntry("Res", "Action2", "G.Group", Value.Grant)); + entries.Add(new AclEntry("Res2", "Action", "G.Group", Value.Grant)); + entries.Add(new AclEntry("Res", "*", "U.User3", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantGroupFullControl_DenyUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group", Value.Grant)); + entries.Add(new AclEntry("Res", "*", "U.User", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantGroupExplicit_DenyUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Grant)); + entries.Add(new AclEntry("Res", "*", "U.User", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantGroupFullControl_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyGroupFullControl_GrantUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group", Value.Deny)); + entries.Add(new AclEntry("Res", "*", "U.User", Value.Grant)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyGroupExplicit_GrantUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Deny)); + entries.Add(new AclEntry("Res", "*", "U.User", Value.Grant)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyGroupFullControl_GrantUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group", Value.Deny)); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantGroupFullControl_GrantUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group", Value.Grant)); + entries.Add(new AclEntry("Res", "*", "U.User", Value.Grant)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantGroupExplicit_GrantUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyGroupExplicit_GrantUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Deny)); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantGroupFullControl_GrantUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantGroupExplicit_GrantUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Grant)); + entries.Add(new AclEntry("Res", "*", "U.User", Value.Grant)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantGroupExplicit_DenyUserExpicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyGroupExplicit_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Deny)); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyGroupFullControl_DenyUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group", Value.Deny)); + entries.Add(new AclEntry("Res", "*", "U.User", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyGroupFullControl_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group", Value.Deny)); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyGroupExplicit_DenyUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Deny)); + entries.Add(new AclEntry("Res", "*", "U.User", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantOneGroupExplicit_GrantOtherGroupExplicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "G.Group1", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "G.Group2", Value.Grant)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyOneGroupExplicit_GrantOtherGroupExplicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "G.Group1", Value.Deny)); + entries.Add(new AclEntry("Res", "Action", "G.Group2", Value.Grant)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyOneGroupExplicit_DenyOtherGroupExplicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "G.Group1", Value.Deny)); + entries.Add(new AclEntry("Res", "Action", "G.Group2", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantOneGroupFullControl_GrantOtherGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group1", Value.Grant)); + entries.Add(new AclEntry("Res", "*", "G.Group2", Value.Grant)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyOneGroupFullControl_DenyOtherGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group1", Value.Deny)); + entries.Add(new AclEntry("Res", "*", "G.Group2", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyOneGroupFullControl_GrantOtherGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group1", Value.Grant)); + entries.Add(new AclEntry("Res", "*", "G.Group2", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyOneGroupFullControl_GrantOtherGroupExplicit() { + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "G.Group1", Value.Grant)); + entries.Add(new AclEntry("Res", "*", "G.Group2", Value.Deny)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyOneGroupExplicit_GrantOtherGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group1", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "G.Group2", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantOneGroupExplicit_GrantOtherGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group1", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "G.Group2", Value.Grant)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyOneGroupExplicit_DenyOtherGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group1", Value.Deny)); + entries.Add(new AclEntry("Res", "Action", "G.Group2", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group1", "G.Group2" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyUserExplicit_GrantUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "U.User", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[0], entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_DenyGroupExplicit_GrantGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Deny)); + + Assert.AreEqual(Authorization.Denied, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantUserExplicit_DenyUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "U.User", Value.Deny)); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[0], entries.ToArray()), "Wrong auth result"); + } + + [Test] + public void AuthorizeAction_GrantGroupExplicit_DenyGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry("Res", "*", "G.Group", Value.Deny)); + entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Grant)); + + Assert.AreEqual(Authorization.Granted, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[] { "G.Group" }, entries.ToArray()), "Wrong auth result"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AuthorizeAction_InvalidResource(string r) { + AclEvaluator.AuthorizeAction(r, "Action", "U.User", new string[0], new AclEntry[0]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase(AclEntry.FullControlAction, ExpectedException = typeof(ArgumentException))] + public void AuthorizeAction_InvalidAction(string a) { + AclEvaluator.AuthorizeAction("Res", a, "U.User", new string[0], new AclEntry[0]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AuthorizeAction_InvalidUser(string u) { + AclEvaluator.AuthorizeAction("Res", "Action", u, new string[0], new AclEntry[0]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void AuthorizeAction_NullGroups() { + AclEvaluator.AuthorizeAction("Res", "Action", "U.User", null, new AclEntry[0]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void AuthorizeAction_NullEntries() { + AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[0], null); + } + + [Test] + public void AuthorizeAction_EmptyEntries() { + Assert.AreEqual(Authorization.Unknown, AclEvaluator.AuthorizeAction("Res", "Action", "U.User", new string[0], new AclEntry[0]), "Wrong auth result"); + } + + } + +} diff --git a/AclEngine-Tests/AclManagerBaseTests.cs b/AclEngine-Tests/AclManagerBaseTests.cs new file mode 100644 index 0000000..b9b1058 --- /dev/null +++ b/AclEngine-Tests/AclManagerBaseTests.cs @@ -0,0 +1,498 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; + +namespace ScrewTurn.Wiki.AclEngine.Tests { + + [TestFixture] + public class AclManagerBaseTests { + + private MockRepository mocks = new MockRepository(); + + private AclManagerBase MockAclManager() { + AclManagerBase manager = mocks.DynamicMock(); + + return manager; + } + + private void AssertAclEntriesAreEqual(AclEntry expected, AclEntry actual) { + Assert.AreEqual(expected.Resource, actual.Resource, "Wrong resource"); + Assert.AreEqual(expected.Action, actual.Action, "Wrong action"); + Assert.AreEqual(expected.Subject, actual.Subject, "Wrong subject"); + Assert.AreEqual(expected.Value, actual.Value, "Wrong value"); + } + + [Test] + public void StoreEntry_RetrieveAllEntries() { + AclManagerBase manager = MockAclManager(); + + Assert.AreEqual(0, manager.TotalEntries, "Wrong initial entry count"); + Assert.AreEqual(0, manager.RetrieveAllEntries().Length, "Wrong initial entry count"); + + Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Grant), "StoreEntry should return true"); + Assert.IsTrue(manager.StoreEntry("Res", "Action", "G.Group", Value.Deny), "StoreEntry should return true"); + + Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count"); + + AclEntry[] allEntries = manager.RetrieveAllEntries(); + Assert.AreEqual(2, allEntries.Length, "Wrong entry count"); + + Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); }); + + AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "G.Group", Value.Deny), allEntries[0]); + AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "U.User", Value.Grant), allEntries[1]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void StoreEntry_InvalidResource(string s) { + AclManagerBase manager = MockAclManager(); + manager.StoreEntry(s, "Action", "U.User", Value.Grant); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void StoreEntry_InvalidAction(string a) { + AclManagerBase manager = MockAclManager(); + manager.StoreEntry("Res", a, "U.User", Value.Grant); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void StoreEntry_InvalidSubject(string s) { + AclManagerBase manager = MockAclManager(); + manager.StoreEntry("Res", "Action", s, Value.Grant); + } + + [Test] + public void StoreEntry_Overwrite() { + AclManagerBase manager = MockAclManager(); + + Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Grant), "StoreEntry should return true"); + Assert.IsTrue(manager.StoreEntry("Res", "Action", "G.Group", Value.Grant), "StoreEntry should return true"); + + // Overwrite with a deny + Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Deny), "StoreEntry should return true"); + + Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count"); + + AclEntry[] allEntries = manager.RetrieveAllEntries(); + Assert.AreEqual(2, allEntries.Length, "Wrong entry count"); + + Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); }); + + AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "G.Group", Value.Grant), allEntries[0]); + AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "U.User", Value.Deny), allEntries[1]); + } + + [Test] + public void DeleteEntry_RetrieveAllEntries() { + AclManagerBase manager = MockAclManager(); + + Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Grant), "StoreEntry should return true"); + Assert.IsTrue(manager.StoreEntry("Res", "Action", "G.Group", Value.Deny), "StoreEntry should return true"); + + Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count"); + + Assert.IsFalse(manager.DeleteEntry("Res1", "Action", "G.Group"), "DeleteEntry should return false"); + Assert.IsFalse(manager.DeleteEntry("Res", "Action1", "G.Group"), "DeleteEntry should return false"); + Assert.IsFalse(manager.DeleteEntry("Res", "Action", "G.Group1"), "DeleteEntry should return false"); + + Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count"); + + Assert.IsTrue(manager.DeleteEntry("Res", "Action", "G.Group"), "DeleteEntry should return true"); + + Assert.AreEqual(1, manager.TotalEntries, "Wrong entry count"); + + AclEntry[] allEntries = manager.RetrieveAllEntries(); + Assert.AreEqual(1, allEntries.Length, "Wrong entry count"); + + AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "U.User", Value.Grant), allEntries[0]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void DeleteEntry_InvalidResource(string r) { + AclManagerBase manager = MockAclManager(); + manager.DeleteEntry(r, "Action", "U.User"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void DeleteEntry_InvalidAction(string a) { + AclManagerBase manager = MockAclManager(); + manager.DeleteEntry("Res", a, "U.User"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void DeleteEntry_InvalidSubject(string s) { + AclManagerBase manager = MockAclManager(); + manager.DeleteEntry("Res", "Action", s); + } + + [Test] + public void DeleteEntriesForResource_RetrieveAllEntries() { + AclManagerBase manager = MockAclManager(); + + Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Grant), "StoreEntry should return true"); + Assert.IsTrue(manager.StoreEntry("Res", "Action", "G.Group", Value.Deny), "StoreEntry should return true"); + Assert.IsTrue(manager.StoreEntry("Res2", "Action2", "G.Group2", Value.Grant), "StoreEntry should return true"); + Assert.IsTrue(manager.StoreEntry("Res2", "Action3", "U.User", Value.Deny), "StoreEntry should return true"); + + Assert.AreEqual(4, manager.TotalEntries, "Wrong entry count"); + + Assert.IsFalse(manager.DeleteEntriesForResource("Inexistent"), "DeleteEntriesForResource should return false"); + + Assert.AreEqual(4, manager.TotalEntries, "Wrong entry count"); + + Assert.IsTrue(manager.DeleteEntriesForResource("Res2"), "DeleteEntriesForResource should return true"); + + Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count"); + + AclEntry[] allEntries = manager.RetrieveAllEntries(); + Assert.AreEqual(2, allEntries.Length, "Wrong entry count"); + + Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); }); + + AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "G.Group", Value.Deny), allEntries[0]); + AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "U.User", Value.Grant), allEntries[1]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void DeleteEntriesForResource_InvalidResource(string r) { + AclManagerBase manager = MockAclManager(); + manager.DeleteEntriesForResource(r); + } + + [Test] + public void DeleteEntriesForSubject_RetrieveAllEntries() { + AclManagerBase manager = MockAclManager(); + + Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Grant), "StoreEntry should return true"); + Assert.IsTrue(manager.StoreEntry("Res", "Action", "G.Group", Value.Deny), "StoreEntry should return true"); + Assert.IsTrue(manager.StoreEntry("Res2", "Action2", "G.Group2", Value.Grant), "StoreEntry should return true"); + Assert.IsTrue(manager.StoreEntry("Res2", "Action3", "G.Group2", Value.Deny), "StoreEntry should return true"); + + Assert.AreEqual(4, manager.TotalEntries, "Wrong entry count"); + + Assert.IsFalse(manager.DeleteEntriesForSubject("I.Inexistent"), "DeleteEntriesForSubject should return false"); + + Assert.AreEqual(4, manager.TotalEntries, "Wrong entry count"); + + Assert.IsTrue(manager.DeleteEntriesForSubject("G.Group2"), "DeleteEntriesForSubject should return true"); + + Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count"); + + AclEntry[] allEntries = manager.RetrieveAllEntries(); + Assert.AreEqual(2, allEntries.Length, "Wrong entry count"); + + Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); }); + + AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "G.Group", Value.Deny), allEntries[0]); + AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "U.User", Value.Grant), allEntries[1]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void DeleteEntriesForSubject_InvalidSubject(string s) { + AclManagerBase manager = MockAclManager(); + manager.DeleteEntriesForSubject(s); + } + + [Test] + public void RetrieveEntriesForResource() { + AclManagerBase manager = MockAclManager(); + + Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Grant), "StoreEntry should return true"); + Assert.IsTrue(manager.StoreEntry("Res", "Action", "G.Group", Value.Deny), "StoreEntry should return true"); + Assert.IsTrue(manager.StoreEntry("Res2", "Action2", "G.Group2", Value.Grant), "StoreEntry should return true"); + Assert.IsTrue(manager.StoreEntry("Res2", "Action3", "G.Group2", Value.Deny), "StoreEntry should return true"); + + Assert.AreEqual(0, manager.RetrieveEntriesForResource("Inexistent").Length, "Wrong result count"); + + AclEntry[] allEntries = manager.RetrieveEntriesForResource("Res"); + Assert.AreEqual(2, allEntries.Length, "Wrong entry count"); + + Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); }); + + AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "G.Group", Value.Deny), allEntries[0]); + AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "U.User", Value.Grant), allEntries[1]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RetrieveEntriesForResource_InvalidResource(string r) { + AclManagerBase manager = MockAclManager(); + manager.RetrieveEntriesForResource(r); + } + + [Test] + public void RetrieveEntriesForSubject() { + AclManagerBase manager = MockAclManager(); + + Assert.IsTrue(manager.StoreEntry("Res", "Action", "U.User", Value.Grant), "StoreEntry should return true"); + Assert.IsTrue(manager.StoreEntry("Res", "Action", "G.Group", Value.Deny), "StoreEntry should return true"); + Assert.IsTrue(manager.StoreEntry("Res2", "Action2", "G.Group2", Value.Grant), "StoreEntry should return true"); + Assert.IsTrue(manager.StoreEntry("Res2", "Action3", "G.Group2", Value.Deny), "StoreEntry should return true"); + + Assert.AreEqual(0, manager.RetrieveEntriesForSubject("I.Inexistent").Length, "Wrong result count"); + + AclEntry[] allEntries = manager.RetrieveEntriesForSubject("G.Group2"); + Assert.AreEqual(2, allEntries.Length, "Wrong entry count"); + + Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Action.CompareTo(y.Action); }); + + AssertAclEntriesAreEqual(new AclEntry("Res2", "Action2", "G.Group2", Value.Grant), allEntries[0]); + AssertAclEntriesAreEqual(new AclEntry("Res2", "Action3", "G.Group2", Value.Deny), allEntries[1]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RetrieveEntriesForSubject_InvalidSubject(string s) { + AclManagerBase manager = MockAclManager(); + manager.RetrieveEntriesForSubject(s); + } + + [Test] + public void InitializeData() { + AclManagerBase manager = MockAclManager(); + + List entries = new List(); + entries.Add(new AclEntry("Res", "Action", "U.User", Value.Grant)); + entries.Add(new AclEntry("Res", "Action", "G.Group", Value.Deny)); + + manager.InitializeData(entries.ToArray()); + + Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count"); + + AclEntry[] allEntries = manager.RetrieveAllEntries(); + Assert.AreEqual(2, allEntries.Length, "Wrong entry count"); + + Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); }); + + AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "G.Group", Value.Deny), allEntries[0]); + AssertAclEntriesAreEqual(new AclEntry("Res", "Action", "U.User", Value.Grant), allEntries[1]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void InitializeData_NullData() { + AclManagerBase manager = MockAclManager(); + manager.InitializeData(null); + } + + [Test] + public void Event_AclChanged_StoreEntry() { + AclManagerBase manager = MockAclManager(); + + AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant); + + bool invoked = false; + manager.AclChanged += delegate(object sender, AclChangedEventArgs e) { + invoked = true; + Assert.AreEqual(1, e.Entries.Length, "Wrong entry count"); + AssertAclEntriesAreEqual(entry, e.Entries[0]); + Assert.AreEqual(Change.EntryStored, e.Change); + }; + + manager.StoreEntry(entry.Resource, entry.Action, entry.Subject, entry.Value); + + Assert.IsTrue(invoked, "Store event not invoked"); + } + + [Test] + public void Event_AclChanged_OverwriteEntry() { + AclManagerBase manager = MockAclManager(); + + AclEntry entryOld = new AclEntry("Res", "Action", "U.User", Value.Deny); + + AclEntry entryNew = new AclEntry("Res", "Action", "U.User", Value.Grant); + + manager.StoreEntry(entryOld.Resource, entryOld.Action, entryOld.Subject, entryOld.Value); + + bool invokedStore = false; + bool invokedDelete = false; + manager.AclChanged += delegate(object sender, AclChangedEventArgs e) { + if(e.Change == Change.EntryStored) { + invokedStore = true; + Assert.AreEqual(1, e.Entries.Length, "Wrong entry count"); + AssertAclEntriesAreEqual(entryNew, e.Entries[0]); + Assert.AreEqual(Change.EntryStored, e.Change); + } + else { + invokedDelete = true; + Assert.AreEqual(1, e.Entries.Length, "Wrong entry count"); + AssertAclEntriesAreEqual(entryOld, e.Entries[0]); + Assert.AreEqual(Change.EntryDeleted, e.Change); + } + }; + + manager.StoreEntry(entryNew.Resource, entryNew.Action, entryNew.Subject, entryNew.Value); + + Assert.IsTrue(invokedStore, "Store event not invoked"); + Assert.IsTrue(invokedDelete, "Delete event not invoked"); + } + + [Test] + public void Event_AclChanged_DeleteEntry() { + AclManagerBase manager = MockAclManager(); + + AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant); + + manager.StoreEntry(entry.Resource, entry.Action, entry.Subject, entry.Value); + + bool invokedDelete = false; + manager.AclChanged += delegate(object sender, AclChangedEventArgs e) { + invokedDelete = true; + Assert.AreEqual(1, e.Entries.Length, "Wrong entry count"); + AssertAclEntriesAreEqual(entry, e.Entries[0]); + Assert.AreEqual(Change.EntryDeleted, e.Change, "Wrong change"); + }; + + manager.DeleteEntry(entry.Resource, entry.Action, entry.Subject); + + Assert.IsTrue(invokedDelete, "Delete event not invoked"); + } + + [Test] + public void Event_AclChanged_DeleteEntriesForResource() { + AclManagerBase manager = MockAclManager(); + + AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant); + + manager.StoreEntry(entry.Resource, entry.Action, entry.Subject, entry.Value); + manager.StoreEntry("Res2", "Action", "G.Group", Value.Deny); + + bool invokedDelete = false; + manager.AclChanged += delegate(object sender, AclChangedEventArgs e) { + invokedDelete = true; + Assert.AreEqual(1, e.Entries.Length, "Wrong entry count"); + AssertAclEntriesAreEqual(entry, e.Entries[0]); + Assert.AreEqual(Change.EntryDeleted, e.Change, "Wrong change"); + }; + + manager.DeleteEntriesForResource(entry.Resource); + + Assert.IsTrue(invokedDelete, "Delete event not invoked"); + } + + [Test] + public void Event_AclChanged_DeleteEntriesForSubject() { + AclManagerBase manager = MockAclManager(); + + AclEntry entry = new AclEntry("Res", "Action", "U.User", Value.Grant); + + manager.StoreEntry(entry.Resource, entry.Action, entry.Subject, entry.Value); + manager.StoreEntry("Res2", "Action", "G.Group", Value.Deny); + + bool invokedDelete = false; + manager.AclChanged += delegate(object sender, AclChangedEventArgs e) { + invokedDelete = true; + Assert.AreEqual(1, e.Entries.Length, "Wrong entry count"); + AssertAclEntriesAreEqual(entry, e.Entries[0]); + Assert.AreEqual(Change.EntryDeleted, e.Change, "Wrong change"); + }; + + manager.DeleteEntriesForSubject(entry.Subject); + + Assert.IsTrue(invokedDelete, "Delete event not invoked"); + } + + [Test] + public void RenameResource() { + AclManagerBase manager = MockAclManager(); + + Assert.IsFalse(manager.RenameResource("Res", "Res_Renamed"), "RenameResource should return false"); + + AclEntry entry1 = new AclEntry("Res", "Action", "U.User", Value.Grant); + AclEntry newEntry1 = new AclEntry("Res_Renamed", "Action", "U.User", Value.Grant); + AclEntry entry2 = new AclEntry("Res", "Action2", "U.User2", Value.Deny); + AclEntry newEntry2 = new AclEntry("Res_Renamed", "Action2", "U.User", Value.Deny); + + manager.StoreEntry(entry1.Resource, entry1.Action, entry1.Subject, entry1.Value); + manager.StoreEntry(entry2.Resource, entry2.Action, entry2.Subject, entry2.Value); + manager.StoreEntry("Res2", "Action", "G.Group", Value.Deny); + + bool invokedDelete1 = false; + bool invokedStore1 = false; + bool invokedDelete2 = false; + bool invokedStore2 = false; + manager.AclChanged += delegate(object sender, AclChangedEventArgs e) { + if(e.Change == Change.EntryDeleted) { + Assert.AreEqual(1, e.Entries.Length, "Wrong entry count"); + Assert.AreEqual("Res", e.Entries[0].Resource, "Wrong resource"); + + if(e.Entries[0].Action == entry1.Action) invokedDelete1 = true; + if(e.Entries[0].Action == entry2.Action) invokedDelete2 = true; + } + else { + Assert.AreEqual(1, e.Entries.Length, "Wrong entry count"); + Assert.AreEqual("Res_Renamed", e.Entries[0].Resource, "Wrong resource"); + + if(e.Entries[0].Action == entry1.Action) invokedStore1 = true; + if(e.Entries[0].Action == entry2.Action) invokedStore2 = true; + } + }; + + Assert.IsTrue(manager.RenameResource("Res", "Res_Renamed"), "RenameResource should return true"); + + Assert.IsTrue(invokedDelete1, "Delete event 1 not invoked"); + Assert.IsTrue(invokedStore1, "Store event 1 not invoked"); + Assert.IsTrue(invokedDelete2, "Delete event 2 not invoked"); + Assert.IsTrue(invokedStore2, "Store event 2 not invoked"); + + AclEntry[] entries = manager.RetrieveAllEntries(); + + Assert.AreEqual(3, entries.Length, "Wrong entry count"); + Array.Sort(entries, delegate(AclEntry x, AclEntry y) { return x.Resource.CompareTo(y.Resource); }); + + Assert.AreEqual("Res_Renamed", entries[0].Resource, "Wrong resource"); + if(entries[0].Value == Value.Grant) { + Assert.AreEqual("Action", entries[0].Action, "Wrong action"); + Assert.AreEqual("U.User", entries[0].Subject, "Wrong subject"); + } + else { + Assert.AreEqual("Action2", entries[0].Action, "Wrong action"); + Assert.AreEqual("U.User2", entries[0].Subject, "Wrong subject"); + } + + Assert.AreEqual("Res_Renamed", entries[1].Resource, "Wrong resource"); + if(entries[1].Value == Value.Grant) { + Assert.AreEqual("Action", entries[1].Action, "Wrong action"); + Assert.AreEqual("U.User", entries[1].Subject, "Wrong subject"); + } + else { + Assert.AreEqual("Action2", entries[1].Action, "Wrong action"); + Assert.AreEqual("U.User2", entries[1].Subject, "Wrong subject"); + } + + Assert.AreEqual("Res2", entries[2].Resource, "Wrong resource"); + Assert.AreEqual("Action", entries[2].Action, "Wrong action"); + Assert.AreEqual("G.Group", entries[2].Subject, "Wrong subject"); + Assert.AreEqual(Value.Deny, entries[2].Value, "Wrong value"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RenameResource_InvalidResource(string r) { + AclManagerBase manager = MockAclManager(); + + manager.RenameResource(r, "new_name"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RenameResource_InvalidNewName(string n) { + AclManagerBase manager = MockAclManager(); + + manager.RenameResource("res", n); + } + + } + +} diff --git a/AclEngine-Tests/Properties/AssemblyInfo.cs b/AclEngine-Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..366d5dc --- /dev/null +++ b/AclEngine-Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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("ScrewTurn Wiki ACL Engine Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// 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("aa6f62d5-284a-443f-a5c1-d75fb638a8b7")] diff --git a/AclEngine/AclChangedEventArgs.cs b/AclEngine/AclChangedEventArgs.cs new file mode 100644 index 0000000..c030a15 --- /dev/null +++ b/AclEngine/AclChangedEventArgs.cs @@ -0,0 +1,61 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.AclEngine { + + /// + /// Contains arguments for the event. + /// + public class AclChangedEventArgs : EventArgs { + + private AclEntry[] entries; + private Change change; + + /// + /// Initializes a new instance of the class. + /// + /// The entries that changed. + /// The change. + /// If is null. + /// If is empty. + public AclChangedEventArgs(AclEntry[] entries, Change change) { + if(entries == null) throw new ArgumentNullException("entry"); + if(entries.Length == 0) throw new ArgumentException("Entries cannot be empty", "entries"); + + this.entries = entries; + this.change = change; + } + + /// + /// Gets the entries that changed. + /// + public AclEntry[] Entries { + get { return entries; } + } + + /// + /// Gets the change. + /// + public Change Change { + get { return change; } + } + + } + + /// + /// Lists legal changes for ACL entries. + /// + public enum Change { + /// + /// An entry is stored. + /// + EntryStored, + /// + /// An entry is deleted. + /// + EntryDeleted + } + +} diff --git a/AclEngine/AclEngine.csproj b/AclEngine/AclEngine.csproj new file mode 100644 index 0000000..0e42dec --- /dev/null +++ b/AclEngine/AclEngine.csproj @@ -0,0 +1,63 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81} + Library + Properties + ScrewTurn.Wiki.AclEngine + ScrewTurn.Wiki.AclEngine + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + bin\Debug\ScrewTurn.Wiki.AclEngine.XML + + + none + true + bin\Release\ + TRACE + prompt + 4 + true + bin\Release\ScrewTurn.Wiki.AclEngine.xml + + + + + 3.5 + + + + + AssemblyVersion.cs + + + + + + + + + + + + \ No newline at end of file diff --git a/AclEngine/AclEntry.cs b/AclEngine/AclEntry.cs new file mode 100644 index 0000000..348ad98 --- /dev/null +++ b/AclEngine/AclEntry.cs @@ -0,0 +1,153 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.AclEngine { + + /// + /// Represents an ACL Entry. + /// + public class AclEntry { + + /// + /// The full control action. + /// + public const string FullControlAction = "*"; + + /// + /// The controlled resource. + /// + private string resource; + /// + /// The controlled action on the resource. + /// + private string action; + /// + /// The subject whose access to the resource/action is controlled. + /// + private string subject; + /// + /// The entry value. + /// + private Value value; + + /// + /// Initializes a new instance of the class. + /// + /// The controlled resource. + /// The controlled action on the resource. + /// The subject whose access to the resource/action is controlled. + /// The entry value. + /// If , or are null. + /// If , or are empty. + public AclEntry(string resource, string action, string subject, Value value) { + if(resource == null) throw new ArgumentNullException("resource"); + if(resource.Length == 0) throw new ArgumentException("Resource cannot be empty", "resource"); + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(subject == null) throw new ArgumentNullException("subject"); + if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject"); + + this.resource = resource; + this.action = action; + this.subject = subject; + this.value = value; + } + + /// + /// Gets the controlled resource. + /// + public string Resource { + get { return resource; } + } + + /// + /// Gets the controlled action on the resource. + /// + public string Action { + get { return action; } + } + + /// + /// Gets the subject of the entry. + /// + public string Subject { + get { return subject; } + } + + /// + /// Gets the value of the entry. + /// + public Value Value { + get { return value; } + } + + /// + /// Gets a string representation of the current object. + /// + /// The string representation. + public override string ToString() { + return resource + "->" + action + ": " + subject + " (" + value.ToString() + ")"; + } + + /// + /// Gets a hash code for the current object. + /// + /// The hash code. + public override int GetHashCode() { + return base.GetHashCode(); + } + + /// + /// Determines whether this object equals another (by value). + /// + /// The other object. + /// true if this object equals obj, false otherwise. + public override bool Equals(object obj) { + AclEntry other = obj as AclEntry; + if(other != null) return Equals(other); + else return false; + } + + /// + /// Determines whether this instance equals another (by value). + /// + /// The other instance. + /// true if this instance equals other, false otherwise. + public bool Equals(AclEntry other) { + if(object.ReferenceEquals(other, null)) return false; + else return resource == other.Resource && + action == other.Action && subject == other.Subject; + } + + /// + /// Determines whether two instances of are equal (by value). + /// + /// The first instance. + /// The second instance. + /// true if x equals y, false otherwise. + public static bool Equals(AclEntry x, AclEntry y) { + if(object.ReferenceEquals(x, null) && !object.ReferenceEquals(x, null)) return false; + if(!object.ReferenceEquals(x, null) && object.ReferenceEquals(x, null)) return false; + if(object.ReferenceEquals(x, null) && object.ReferenceEquals(x, null)) return true; + return x.Equals(y); + } + + } + + /// + /// Lists legal ACL Entry values. + /// + public enum Value { + /// + /// Deny the action. + /// + Deny = 0, + /// + /// Grant the action. + /// + Grant = 1 + } + +} diff --git a/AclEngine/AclEvaluator.cs b/AclEngine/AclEvaluator.cs new file mode 100644 index 0000000..5694025 --- /dev/null +++ b/AclEngine/AclEvaluator.cs @@ -0,0 +1,129 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.AclEngine { + + /// + /// Implements tools for evaluating permissions. + /// + public static class AclEvaluator { + + /// + /// Decides whether a user, member of some groups, is authorized to perform an action on a resource. + /// + /// The resource. + /// The action on the resource. + /// The user, in the form 'U.Name'. + /// The groups the user is member of, in the form 'G.Name'. + /// The available ACL entries for the resource. + /// The positive, negative, or indeterminate result. + /// If , , , or are null. + /// If , , are empty, or if equals . + public static Authorization AuthorizeAction(string resource, string action, string user, string[] groups, AclEntry[] entries) { + if(resource == null) throw new ArgumentNullException("resource"); + if(resource.Length == 0) throw new ArgumentException("Resource cannot be empty", "resource"); + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(action == AclEntry.FullControlAction) throw new ArgumentException("Action cannot be the FullControl flag", "action"); + if(user == null) throw new ArgumentNullException("user"); + if(user.Length == 0) throw new ArgumentException("User cannot be empty", "user"); + if(groups == null) throw new ArgumentNullException("groups"); + if(entries == null) throw new ArgumentNullException("entries"); + + // Simple ACL model + // Sort entries so that FullControl ones are at the bottom + // First look for an entry specific for the user + // If not found, look for a group that denies the permission + + AclEntry[] sortedEntries = new AclEntry[entries.Length]; + Array.Copy(entries, sortedEntries, entries.Length); + + Array.Sort(sortedEntries, delegate(AclEntry x, AclEntry y) { + return x.Action.CompareTo(y.Action); + }); + Array.Reverse(sortedEntries); + + foreach(AclEntry entry in sortedEntries) { + if(entry.Resource == resource && (entry.Action == action || entry.Action == AclEntry.FullControlAction) && entry.Subject == user) { + if(entry.Value == Value.Grant) return Authorization.Granted; + else if(entry.Value == Value.Deny) return Authorization.Denied; + else throw new NotSupportedException("Entry value not supported"); + } + } + + // For each group, a decision is made + Dictionary groupFullControlGrant = new Dictionary(); + Dictionary groupExplicitGrant = new Dictionary(); + Dictionary groupFullControlDeny = new Dictionary(); + + foreach(string group in groups) { + foreach(AclEntry entry in entries) { + + if(entry.Resource == resource && entry.Subject == group) { + if(!groupFullControlGrant.ContainsKey(group)) { + groupFullControlGrant.Add(group, false); + groupExplicitGrant.Add(group, false); + groupFullControlDeny.Add(group, false); + } + + if(entry.Action == action) { + // Explicit action + if(entry.Value == Value.Grant) { + // An explicit grant only wins if there are no other explicit deny + groupExplicitGrant[group] = true; + } + else if(entry.Value == Value.Deny) { + // An explicit deny wins over all other entries + return Authorization.Denied; + } + } + else if(entry.Action == AclEntry.FullControlAction) { + // Full control, lower priority + if(entry.Value == Value.Deny) { + groupFullControlDeny[group] = true; + } + else if(entry.Value == Value.Grant) { + groupFullControlGrant[group] = true; + } + } + } + } + } + + // Any explicit grant found at this step wins, because all explicit deny have been processed previously + bool tentativeGrant = false; + bool tentativeDeny = false; + foreach(string group in groupFullControlGrant.Keys) { + if(groupExplicitGrant[group]) return Authorization.Granted; + + if(groupFullControlGrant[group] && !groupFullControlDeny[group]) tentativeGrant = true; + if(!groupFullControlGrant[group] && groupFullControlDeny[group]) tentativeDeny = true; + } + if(tentativeGrant && !tentativeDeny) return Authorization.Granted; + else if(tentativeDeny) return Authorization.Denied; + else return Authorization.Unknown; + } + + } + + /// + /// Lists legal authorization values. + /// + public enum Authorization { + /// + /// Authorization granted. + /// + Granted, + /// + /// Authorization denied. + /// + Denied, + /// + /// No information available. + /// + Unknown + } + +} diff --git a/AclEngine/AclManagerBase.cs b/AclEngine/AclManagerBase.cs new file mode 100644 index 0000000..de845f3 --- /dev/null +++ b/AclEngine/AclManagerBase.cs @@ -0,0 +1,287 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.AclEngine { + + /// + /// Implements a base class for an ACL Manager. + /// + /// All instance and static members are thread-safe. + public abstract class AclManagerBase : IAclManager { + + private List entries; + + /// + /// Initializes a new instance of the abstract class. + /// + public AclManagerBase() { + entries = new List(100); + } + + /// + /// Handles the invokation of event. + /// + /// The changed entries. + /// The change. + protected void OnAclChanged(AclEntry[] entries, Change change) { + if(AclChanged != null) { + AclChanged(this, new AclChangedEventArgs(entries, change)); + } + } + + /// + /// Stores a new ACL entry. + /// + /// The controlled resource. + /// The action on the controlled resource. + /// The subject whose access to the resource/action is controlled. + /// The value of the entry. + /// true if the entry is stored, false otherwise. + /// If , or are null. + /// If , or are empty. + public bool StoreEntry(string resource, string action, string subject, Value value) { + if(resource == null) throw new ArgumentNullException("resource"); + if(resource.Length == 0) throw new ArgumentException("Resource cannot be empty", "resource"); + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(subject == null) throw new ArgumentNullException("subject"); + if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject"); + + AclEntry result = new AclEntry(resource, action, subject, value); + + lock(this) { + int index = entries.FindIndex(delegate(AclEntry x) { return AclEntry.Equals(x, result); }); + if(index >= 0) { + AclEntry removed = entries[index]; + entries.RemoveAt(index); + OnAclChanged(new AclEntry[] { removed }, Change.EntryDeleted); + } + entries.Add(result); + OnAclChanged(new AclEntry[] { result }, Change.EntryStored); + } + + return true; + } + + /// + /// Deletes an ACL entry. + /// + /// The controlled resource. + /// The action on the controlled resource. + /// The subject whose access to the resource/action is controlled. + /// true if the entry is deleted, false otherwise. + /// If , or are null. + /// If , or are empty. + public bool DeleteEntry(string resource, string action, string subject) { + if(resource == null) throw new ArgumentNullException("resource"); + if(resource.Length == 0) throw new ArgumentException("Resource cannot be empty", "resource"); + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(subject == null) throw new ArgumentNullException("subject"); + if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject"); + + AclEntry result = new AclEntry(resource, action, subject, Value.Deny); + + lock(this) { + int index = entries.FindIndex(delegate(AclEntry x) { return AclEntry.Equals(x, result); }); + if(index >= 0) { + AclEntry entry = entries[index]; + entries.RemoveAt(index); + OnAclChanged(new AclEntry[] { entry }, Change.EntryDeleted); + return true; + } + else return false; + } + } + + /// + /// Deletes all the ACL entries for a resource. + /// + /// The controlled resource. + /// true if the entries are deleted, false otherwise. + /// If is null. + /// If is empty. + public bool DeleteEntriesForResource(string resource) { + if(resource == null) throw new ArgumentNullException("resource"); + if(resource.Length == 0) throw new ArgumentException("Resource cannot be empty", "resource"); + + lock(this) { + List indexesToRemove = new List(30); + List entriesToRemove = new List(30); + for(int i = 0; i < entries.Count; i++) { + if(entries[i].Resource == resource) { + indexesToRemove.Add(i); + entriesToRemove.Add(entries[i]); + } + } + + if(indexesToRemove.Count > 0) { + // Work in opposite direction to preserve smaller indexes + for(int i = indexesToRemove.Count - 1; i >= 0; i--) { + entries.RemoveAt(indexesToRemove[i]); + } + + OnAclChanged(entriesToRemove.ToArray(), Change.EntryDeleted); + + return true; + } + else return false; + } + } + + /// + /// Deletes all the ACL entries for a subject. + /// + /// The subject. + /// true if the entries are deleted, false otherwise. + /// If is null. + /// If is empty. + public bool DeleteEntriesForSubject(string subject) { + if(subject == null) throw new ArgumentNullException("subject"); + if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject"); + + lock(this) { + List indexesToRemove = new List(30); + List entriesToRemove = new List(30); + for(int i = 0; i < entries.Count; i++) { + if(entries[i].Subject == subject) { + indexesToRemove.Add(i); + entriesToRemove.Add(entries[i]); + } + } + + if(indexesToRemove.Count > 0) { + // Work in opposite direction to preserve smaller indexes + for(int i = indexesToRemove.Count - 1; i >= 0; i--) { + entries.RemoveAt(indexesToRemove[i]); + } + + OnAclChanged(entriesToRemove.ToArray(), Change.EntryDeleted); + + return true; + } + else return false; + } + } + + /// + /// Renames a resource. + /// + /// The resource. + /// The new name of the resource. + /// true if the resource is renamed, false otherwise. + /// If or are null. + /// If or are empty. + public bool RenameResource(string resource, string newName) { + if(resource == null) throw new ArgumentNullException("resource"); + if(resource.Length == 0) throw new ArgumentException("Resource cannot be empty", "resource"); + + if(newName == null) throw new ArgumentNullException("newName"); + if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); + + lock(this) { + AclEntry[] entries = RetrieveEntriesForResource(resource); + bool renamed = false; + + foreach(AclEntry entry in entries) { + bool deleted = DeleteEntry(entry.Resource, entry.Action, entry.Subject); + if(deleted) { + bool stored = StoreEntry(newName, entry.Action, entry.Subject, entry.Value); + if(stored) renamed = true; + else return false; + } + else return false; + } + + return renamed; + } + } + + /// + /// Retrieves all the ACL entries for a resource. + /// + /// The entries. + public AclEntry[] RetrieveAllEntries() { + lock(this) { + return entries.ToArray(); + } + } + + /// + /// Retrieves all the ACL entries for a resource. + /// + /// The resource. + /// The entries. + /// If is null. + /// If is empty. + public AclEntry[] RetrieveEntriesForResource(string resource) { + if(resource == null) throw new ArgumentNullException("resource"); + if(resource.Length == 0) throw new ArgumentException("Resource cannot be empty", "resource"); + + lock(this) { + List result = new List(10); + + foreach(AclEntry e in entries) { + if(e.Resource == resource) result.Add(e); + } + + return result.ToArray(); + } + } + + /// + /// Retrieves all the ACL entries for a subject. + /// + /// The subject. + /// The entries. + /// If is null. + /// If is empty. + public AclEntry[] RetrieveEntriesForSubject(string subject) { + if(subject == null) throw new ArgumentNullException("subject"); + if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject"); + + lock(this) { + List result = new List(10); + + foreach(AclEntry e in entries) { + if(e.Subject == subject) result.Add(e); + } + + return result.ToArray(); + } + } + + /// + /// Initializes the manager data. + /// + /// The ACL entries. + /// If is null. + public void InitializeData(AclEntry[] entries) { + if(entries == null) throw new ArgumentNullException("entries"); + + lock(this) { + this.entries = new List(entries); + } + } + + /// + /// Gets the total number of ACL entries. + /// + public int TotalEntries { + get { + lock(this) { + return entries.Count; + } + } + } + + /// + /// Event fired when an ACL entry is stored or deleted. + /// + public event EventHandler AclChanged; + + } + +} diff --git a/AclEngine/AclStorerBase.cs b/AclEngine/AclStorerBase.cs new file mode 100644 index 0000000..2ed6e24 --- /dev/null +++ b/AclEngine/AclStorerBase.cs @@ -0,0 +1,107 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.AclEngine { + + /// + /// Implements a base class for an ACL Storer. + /// + public abstract class AclStorerBase : IDisposable { + + /// + /// Indicates whether the object was disposed. + /// + protected bool disposed = false; + + /// + /// The instance of the ACL Manager to handle. + /// + protected IAclManager aclManager; + + /// + /// The event handler for the event. + /// + protected EventHandler aclChangedHandler; + + /// + /// Initializes a new instance of the abstract class. + /// + /// The instance of the ACL Manager to handle. + /// If is null. + public AclStorerBase(IAclManager aclManager) { + if(aclManager == null) throw new ArgumentNullException("aclManager"); + + this.aclManager = aclManager; + + aclChangedHandler = new EventHandler(aclManager_AclChanged); + + this.aclManager.AclChanged += aclChangedHandler; + } + + /// + /// Handles the event. + /// + /// The sender. + /// The event arguments. + private void aclManager_AclChanged(object sender, AclChangedEventArgs e) { + if(e.Change == Change.EntryDeleted) DeleteEntries(e.Entries); + else if(e.Change == Change.EntryStored) StoreEntries(e.Entries); + else throw new NotSupportedException("Change type not supported"); + } + + /// + /// Loads data from storage. + /// + /// The loaded ACL entries. + protected abstract AclEntry[] LoadDataInternal(); + + /// + /// Deletes some entries. + /// + /// The entries to delete. + protected abstract void DeleteEntries(AclEntry[] entries); + + /// + /// Stores some entries. + /// + /// The entries to store. + protected abstract void StoreEntries(AclEntry[] entries); + + /// + /// Loads the data and injects it in the instance of . + /// + public void LoadData() { + lock(this) { + AclEntry[] entries = LoadDataInternal(); + aclManager.InitializeData(entries); + } + } + + /// + /// Gets the instance of the ACL Manager. + /// + public IAclManager AclManager { + get { + lock(this) { + return aclManager; + } + } + } + + /// + /// Disposes the current object. + /// + public void Dispose() { + lock(this) { + if(!disposed) { + disposed = true; + aclManager.AclChanged -= aclChangedHandler; + } + } + } + + } + +} diff --git a/AclEngine/IAclManager.cs b/AclEngine/IAclManager.cs new file mode 100644 index 0000000..2752736 --- /dev/null +++ b/AclEngine/IAclManager.cs @@ -0,0 +1,107 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.AclEngine { + + /// + /// Defines an interface for an ACL manager. + /// + public interface IAclManager { + + /// + /// Stores a new ACL entry. + /// + /// The controlled resource. + /// The action on the controlled resource. + /// The subject whose access to the resource/action is controlled. + /// The value of the entry. + /// true if the entry is stored, false otherwise. + /// If , or are null. + /// If , or are empty. + bool StoreEntry(string resource, string action, string subject, Value value); + + /// + /// Deletes an ACL entry. + /// + /// The controlled resource. + /// The action on the controlled resource. + /// The subject whose access to the resource/action is controlled. + /// true if the entry is deleted, false otherwise. + /// If , or are null. + /// If , or are empty. + bool DeleteEntry(string resource, string action, string subject); + + /// + /// Deletes all the ACL entries for a resource. + /// + /// The controlled resource. + /// true if the entries are deleted, false otherwise. + /// If is null. + /// If is empty. + bool DeleteEntriesForResource(string resource); + + /// + /// Deletes all the ACL entries for a subject. + /// + /// The subject. + /// true if the entries are deleted, false otherwise. + /// If is null. + /// If is empty. + bool DeleteEntriesForSubject(string subject); + + /// + /// Renames a resource. + /// + /// The resource. + /// The new name of the resource. + /// true if the resource is renamed, false otherwise. + /// If or are null. + /// If or are empty. + bool RenameResource(string resource, string newName); + + /// + /// Retrieves all the ACL entries for a resource. + /// + /// The entries. + AclEntry[] RetrieveAllEntries(); + + /// + /// Retrieves all the ACL entries for a resource. + /// + /// The resource. + /// The entries. + /// If is null. + /// If is empty. + AclEntry[] RetrieveEntriesForResource(string resource); + + /// + /// Retrieves all the ACL entries for a subject. + /// + /// The subject. + /// The entries. + /// If is null. + /// If is empty. + AclEntry[] RetrieveEntriesForSubject(string subject); + + /// + /// Initializes the manager data. + /// + /// The ACL entries. + /// If is null. + void InitializeData(AclEntry[] entries); + + /// + /// Gets the total number of ACL entries. + /// + int TotalEntries { get; } + + /// + /// Event fired when an ACL entry is stored or deleted. + /// + event EventHandler AclChanged; + + } + +} diff --git a/AclEngine/Properties/AssemblyInfo.cs b/AclEngine/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4375ff5 --- /dev/null +++ b/AclEngine/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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("ScrewTurn Wiki ACL Engine")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// 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("3129a8a6-4955-47a3-9a88-3ea42a08b34b")] diff --git a/AssemblyVersion.cs b/AssemblyVersion.cs new file mode 100644 index 0000000..7be0f85 --- /dev/null +++ b/AssemblyVersion.cs @@ -0,0 +1,20 @@ + +using System.Reflection; + +[assembly: AssemblyCompany("Dario Solera")] +[assembly: AssemblyProduct("ScrewTurn Wiki")] +[assembly: AssemblyCopyright("Copyright Dario Solera 2006-2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 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("3.0.0.333")] +[assembly: AssemblyFileVersion("3.0.0.333")] diff --git a/Build - Readme.txt b/Build - Readme.txt new file mode 100644 index 0000000..6d0acfe --- /dev/null +++ b/Build - Readme.txt @@ -0,0 +1,22 @@ + +ScrewTurn Wiki is written in Microsoft Visual Studio 2008 Professional. The project is structured +as a Visual Studio solution (ScrewTurnWiki.sln). + +In order to compile the application you can either build the solution in Visual Studio, or +follow the instructions included in the Build directory. + +The CHM documentation file (Help directory) is built using Microsoft Sandcastle [1] and +Sandcastle Help File Builder [2]. The documentation is not necessary to compile or run the application. + +Note: if you are using a 64-bit Windows edition, Sandcastle as well as HTML Help Workshop +(distributed with Visual Studio 2008) are installed by default in "C:\Program Files (x86)\". +The SHFB project assumes that the installation directory is "C:\Program Files\". +In order to fix this issue without re-installing the applications or modifying the project file, +you can create a symbolic link using the "mklink" command-line tool (only supported in +Windows Server 2003/2008 and Windows Vista and 7 (you may need to open an elevated command prompt): + +mklink /D Sandcastle "C:\Program Files (x86)\Sandcastle" +mklink /D "HTML Help Workshop" "C:\Program Files (x86)\HTML Help Workshop" + +[1] http://www.codeplex.com/Sandcastle +[2] http://www.codeplex.com/SHFB diff --git a/Build/Build.bat b/Build/Build.bat new file mode 100644 index 0000000..4aef16e --- /dev/null +++ b/Build/Build.bat @@ -0,0 +1,2 @@ + +"%WINDIR%\Microsoft.NET\Framework\v3.5\MSBuild.exe" ScrewTurnWiki.msbuild diff --git a/Build/BuildAndPackage.bat b/Build/BuildAndPackage.bat new file mode 100644 index 0000000..f30d176 --- /dev/null +++ b/Build/BuildAndPackage.bat @@ -0,0 +1,2 @@ + +"%WINDIR%\Microsoft.NET\Framework\v3.5\MSBuild.exe" ScrewTurnWiki.msbuild /t:Package diff --git a/Build/BuildAndTest.bat b/Build/BuildAndTest.bat new file mode 100644 index 0000000..661d9f6 --- /dev/null +++ b/Build/BuildAndTest.bat @@ -0,0 +1,2 @@ + +"%WINDIR%\Microsoft.NET\Framework\v3.5\MSBuild.exe" ScrewTurnWiki.msbuild /t:Test /p:Configuration=Debug diff --git a/Build/Install-SqlServer.txt b/Build/Install-SqlServer.txt new file mode 100644 index 0000000..ecee0f8 --- /dev/null +++ b/Build/Install-SqlServer.txt @@ -0,0 +1,98 @@ + +Installing or Updating ScrewTurn Wiki +------------------------------------- + +This document explains how to install or update ScrewTurn Wiki when using the +SQL Server-based data providers. + +INPUT_DIR refers to the WebApplication directory supplied along this file. +TARGET_DIR refers to the directory the application will live in on your server. + +This document assumes that you have at least basic knowledge about managing a Windows Server +machine, IIS (Internet Information Services) and Microsoft SQL Server. + +If you need help, you can ask questions in the project forum [1]. + +If you want to host ScrewTurn Wiki using a shared hosting service, or you don't have full +administrative access to your server, please contact either the service's support or the +server administrator. + +Please also take a look at other installation packages that are available at the project +website [2], which might better suit your needs. + + + +Clean Installation +------------------ + +Note: depending on the environment you are running, the following steps might require +minor adjustments. + + 1. Create TARGET_DIR if it does not already exist; be sure that the directory is empty. + + 2. Copy all the files from INPUT_DIR into TARGET_DIR. + + 3. Open the "web.config" file with a text editor and set the MasterPassword field with a password + that will be used for the built-in "admin" account, for example: + + + 4. In the "web.config" file, complete the SQL Server connection string, specifying server address, + database name and authentication information (either user/password or integrated authentication), + for example: + + + 5. Make sure that the "web.config" file specifies the correct Settings Storage Provider: + + + 6. Create a database in SQL Server with the name specified in the connection string, making sure that + the specified user can create, alter and drop tables as well as select, update, insert and delete rows. + Make sure that the database collation is NOT case sensitive. + + 7. Setup a Web Site or Application in IIS, setting the root directory to TARGET_DIRECTORY. + + 8. Navigate to the Web Site or Application using a web browser and verify that everything works properly + (the data providers, if the connection string is correct, will automatically create the required + tables in the database). + + +Updating From a Previous v3 Release +----------------------------------- + +If you upgrading from v2, please refer to our online documentation [3]. + +Note: depending on the environment you are running, the following steps might require +minor adjustments. If you made modifications to the application, the following steps +might cause issues and make the application unusable and/or unstable. + + 1. Navigate to TARGET_DIR and verify that the "public" directory only contains the "Plugins" directory. + + 2. Backup all the content of TARGET_DIR (seriously). + Backup your database (seriously). + + 3. Rename "app_online.htm" to "app_offline.htm": this will take the application offline. + + 4. Delete all the content of TARGET_DIR, ***except***: + - "web.config" file + - "app_offline.htm" file. + + 5. Copy "SqlServerProviders.dll" from the distribution package into "TARGET_DIR\public\Plugins", + replacing the existing file. + + 6. Select all files in INPUT_DIR, ***except***: + - "web.config" file. + + 7. Copy all the selected files into TARGET_DIR. + + 8. Delete "app_offline.htm". + + 9. Navigate to the wiki using a web browser and verify that everything works properly, + (the data providers, if the connection string is correct, will automatically update the required + tables in the database). + +10. Check for plugin updates in the administration interface. + + + +[1] http://www.screwturn.eu/forum +[2] http://www.screwturn.eu +[3] http://www.screwturn.eu/Help.MainPage.ashx diff --git a/Build/Install.txt b/Build/Install.txt new file mode 100644 index 0000000..a78e4f1 --- /dev/null +++ b/Build/Install.txt @@ -0,0 +1,84 @@ + +Installing or Updating ScrewTurn Wiki +------------------------------------- + +This document explains how to install or update ScrewTurn Wiki when using the built-in +file-based data providers. + +INPUT_DIR refers to the WebApplication directory supplied along this file. +TARGET_DIR refers to the directory the application will live in on your server. + +This document assumes that you have at least basic knowledge about managing a Windows Server +machine and IIS (Internet Information Services). + +If you need help, you can ask questions in the project forum [1]. + +If you want to host ScrewTurn Wiki using a shared hosting service, or you don't have full +administrative access to your server, please contact either the service's support or the +server administrator. + +Please also take a look at other installation packages that are available at the project +website [2], which might better suit your needs. + + + +Clean Installation +------------------ + +Note: depending on the environment you are running, the following steps might require +minor adjustments. + + 1. Create TARGET_DIR if it does not already exist; be sure that the directory is empty. + + 2. Copy all the files from INPUT_DIR into TARGET_DIR. + + 3. Open the "web.config" file with a text editor and set the MasterPassword field with a password + that will be used for the built-in "admin" account, for example: + + + 4. Set the permissions of the "public" directory so that the ASP.NET Worker Process has "Modify" + permissions on it (usually, the ASP.NET Worker Process runs as "NETWORK SERVICE" or "ASPNET", + depending on the Operating System version). + + 5. Setup a Web Site or Application in IIS, setting the root directory to TARGET_DIRECTORY. + + 6. Navigate to the Web Site or Application using a web browser and verify that everything works properly. + + +Updating From a Previous v3 Release +----------------------------------- + +If you upgrading from v2, please refer to our online documentation [3]. + +Note: depending on the environment you are running, the following steps might require +minor adjustments. If you made modifications to the application, the following steps +might cause issues and make the application unusable and/or unstable. + + 1. Navigate to TARGET_DIR. + + 2. Backup all the content of TARGET_DIR (seriously). + + 3. Rename "app_online.htm" to "app_offline.htm": this will take the application offline. + + 4. Delete all the content of TARGET_DIR, ***except***: + - "public" directory + - "web.config" file + - "app_offline.htm" file. + + 5. Select all files in INPUT_DIR, ***except***: + - "public" directory + - "web.config" file. + + 6. Copy all the selected files into TARGET_DIR. + + 7. Delete "app_offline.htm". + + 8. Navigate to the wiki using a web browser and verify that everything works properly. + + 9. Check for plugin updates in the administration interface. + + + +[1] http://www.screwturn.eu/forum +[2] http://www.screwturn.eu +[3] http://www.screwturn.eu/Help.MainPage.ashx diff --git a/Build/Readme.txt b/Build/Readme.txt new file mode 100644 index 0000000..42bf871 --- /dev/null +++ b/Build/Readme.txt @@ -0,0 +1,16 @@ + +In order to build ScrewTurn Wiki, you need Microsoft Visual Studio 2008 (any edition, including Express) +installed on the machine. + +ScrewTurnWiki.msbuild contains the build script, written for MSBuild 3.5. + +To build the application more easily, use the Build.bad batch file, which compiles the application +and the plugins. + +BuildAndTest.bat compiles the application and runs all the unit tests. SQL Server 2005/2008 is required +on the machine and it must allow the necessary access rights to the current Windows user. + +BuildAndPackage.bat creates a ZIP archive containing the compiled application and the plugins, +but it requires 7-zip [1] installed on the system. + +[1] http://www.7-zip.org diff --git a/Build/ScrewTurnWiki.msbuild b/Build/ScrewTurnWiki.msbuild new file mode 100644 index 0000000..e3ef249 --- /dev/null +++ b/Build/ScrewTurnWiki.msbuild @@ -0,0 +1,108 @@ + + + + + Release + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core-Tests/AclStorerTests.cs b/Core-Tests/AclStorerTests.cs new file mode 100644 index 0000000..0ea1ec7 --- /dev/null +++ b/Core-Tests/AclStorerTests.cs @@ -0,0 +1,148 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.AclEngine; + +namespace ScrewTurn.Wiki.Tests { + + [TestFixture] + public class AclStorerTests { + + private string testFile = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), "__ACL_File.dat"); + + private MockRepository mocks = new MockRepository(); + + private IAclManager MockAclManager() { + IAclManager manager = mocks.DynamicMock(); + + return manager; + } + + [TearDown] + public void TearDown() { + try { + File.Delete(testFile); + } + catch { } + } + + [Test] + public void Constructor() { + IAclManager manager = MockAclManager(); + + AclStorer storer = new AclStorer(manager, testFile); + Assert.AreSame(manager, storer.AclManager, "Wrong ACL Manager instance"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_NullAclManager() { + AclStorer storer = new AclStorer(null, testFile); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_InvalidFile(string f) { + AclStorer storer = new AclStorer(MockAclManager(), f); + } + + private void AssertAclEntriesAreEqual(AclEntry expected, AclEntry actual) { + Assert.AreEqual(expected.Resource, actual.Resource, "Wrong resource"); + Assert.AreEqual(expected.Action, actual.Action, "Wrong action"); + Assert.AreEqual(expected.Subject, actual.Subject, "Wrong subject"); + Assert.AreEqual(expected.Value, actual.Value, "Wrong value"); + } + + [Test] + public void Store_LoadData() { + IAclManager manager = MockAclManager(); + + AclStorer storer = new AclStorer(manager, testFile); + + manager.StoreEntry("Res1", "Action1", "U.User", Value.Grant); + manager.StoreEntry("Res2", "Action2", "G.Group", Value.Deny); + + storer.Dispose(); + storer = null; + + manager = MockAclManager(); + + storer = new AclStorer(manager, testFile); + storer.LoadData(); + + Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count"); + + AclEntry[] allEntries = manager.RetrieveAllEntries(); + Assert.AreEqual(2, allEntries.Length, "Wrong entry count"); + + Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); }); + AssertAclEntriesAreEqual(new AclEntry("Res2", "Action2", "G.Group", Value.Deny), allEntries[0]); + AssertAclEntriesAreEqual(new AclEntry("Res1", "Action1", "U.User", Value.Grant), allEntries[1]); + } + + [Test] + public void Delete_LoadData() { + IAclManager manager = MockAclManager(); + + AclStorer storer = new AclStorer(manager, testFile); + + manager.StoreEntry("Res1", "Action1", "U.User", Value.Grant); + manager.StoreEntry("Res2", "Action2", "G.Group", Value.Deny); + manager.StoreEntry("Res3", "Action1", "U.User", Value.Grant); + manager.StoreEntry("Res3", "Action2", "G.Group", Value.Deny); + + manager.DeleteEntriesForResource("Res3"); + + storer.Dispose(); + storer = null; + + manager = MockAclManager(); + + storer = new AclStorer(manager, testFile); + storer.LoadData(); + + Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count"); + + AclEntry[] allEntries = manager.RetrieveAllEntries(); + Assert.AreEqual(2, allEntries.Length, "Wrong entry count"); + + Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); }); + AssertAclEntriesAreEqual(new AclEntry("Res2", "Action2", "G.Group", Value.Deny), allEntries[0]); + AssertAclEntriesAreEqual(new AclEntry("Res1", "Action1", "U.User", Value.Grant), allEntries[1]); + } + + [Test] + public void Overwrite_LoadData() { + IAclManager manager = MockAclManager(); + + AclStorer storer = new AclStorer(manager, testFile); + + manager.StoreEntry("Res1", "Action1", "U.User", Value.Grant); + manager.StoreEntry("Res2", "Action2", "G.Group", Value.Grant); + manager.StoreEntry("Res2", "Action2", "G.Group", Value.Deny); // Overwrite + + storer.Dispose(); + storer = null; + + manager = MockAclManager(); + + storer = new AclStorer(manager, testFile); + storer.LoadData(); + + Assert.AreEqual(2, manager.TotalEntries, "Wrong entry count"); + + AclEntry[] allEntries = manager.RetrieveAllEntries(); + Assert.AreEqual(2, allEntries.Length, "Wrong entry count"); + + Array.Sort(allEntries, delegate(AclEntry x, AclEntry y) { return x.Subject.CompareTo(y.Subject); }); + AssertAclEntriesAreEqual(new AclEntry("Res2", "Action2", "G.Group", Value.Deny), allEntries[0]); + AssertAclEntriesAreEqual(new AclEntry("Res1", "Action1", "U.User", Value.Grant), allEntries[1]); + } + + } + +} diff --git a/Core-Tests/AuthCheckerTests.cs b/Core-Tests/AuthCheckerTests.cs new file mode 100644 index 0000000..3092cff --- /dev/null +++ b/Core-Tests/AuthCheckerTests.cs @@ -0,0 +1,1736 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.AclEngine; + +namespace ScrewTurn.Wiki.Tests { + + [TestFixture] + public class AuthCheckerTests { + + private MockRepository mocks = null; + private string testDir = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Guid.NewGuid().ToString()); + + [SetUp] + public void SetUp() { + mocks = new MockRepository(); + // TODO: Verify if this is really needed + Collectors.SettingsProvider = MockProvider(); + } + + [TearDown] + public void TearDown() { + try { + Directory.Delete(testDir, true); + } + catch { + //Console.WriteLine("Test: could not delete temp directory"); + } + mocks.VerifyAll(); + } + + protected IHostV30 MockHost() { + if(!Directory.Exists(testDir)) Directory.CreateDirectory(testDir); + + IHostV30 host = mocks.DynamicMock(); + Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.Any(); + + mocks.Replay(host); + + return host; + } + + private ISettingsStorageProviderV30 MockProvider(List entries) { + ISettingsStorageProviderV30 provider = mocks.DynamicMock(); + provider.Init(MockHost(), ""); + LastCall.On(provider).Repeat.Any(); + + AclManagerBase aclManager = new StandardAclManager(); + Expect.Call(provider.AclManager).Return(aclManager).Repeat.Any(); + + mocks.Replay(provider); + + foreach(AclEntry entry in entries) { + aclManager.StoreEntry(entry.Resource, entry.Action, entry.Subject, entry.Value); + } + + return provider; + } + + private ISettingsStorageProviderV30 MockProvider() { + return MockProvider(new List()); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase("Blah", ExpectedException = typeof(ArgumentException))] + [TestCase("*", ExpectedException = typeof(ArgumentException))] + public void CheckActionForGlobals_InvalidAction(string a) { + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForGlobals(a, "User", new string[0]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void CheckActionForGlobals_InvalidUser(string u) { + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageAccounts, u, new string[0]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void CheckActionForGlobals_NullGroups() { + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageAccounts, "User", null); + } + + [Test] + public void CheckActionForGlobals_AdminBypass() { + Collectors.SettingsProvider = MockProvider(); + Assert.IsTrue(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageAccounts, "admin", new string[0]), "Admin account should bypass security"); + } + + [Test] + public void CheckActionForGlobals_GrantUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageFiles, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageFiles, "U.User10", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageGroups, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsTrue(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageFiles, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageFiles, "User2", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForGlobals_GrantUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "U.User10", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageGroups, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsTrue(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageFiles, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageGroups, "User2", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForGlobals_GrantGroupExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageFiles, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageFiles, "G.Group10", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageGroups, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsTrue(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageFiles, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageFiles, "User", new string[] { "Group2" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForGlobals_GrantGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "G.Group10", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageGroups, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsTrue(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageFiles, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageGroups, "User", new string[] { "Group2" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForGlobals_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageFiles, "U.User", Value.Deny)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageFiles, "U.User10", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageGroups, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageFiles, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageFiles, "User2", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForGlobals_DenyUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "U.User", Value.Deny)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "U.User10", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageGroups, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageFiles, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageFiles, "User2", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForGlobals_DenyGroupExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageFiles, "G.Group", Value.Deny)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageFiles, "G.Group10", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageGroups, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageFiles, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageFiles, "User2", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForGlobals_DenyGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "G.Group", Value.Deny)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "G.Group10", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageGroups, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageFiles, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageFiles, "User2", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_NullNamespace() { + // No exceptions should be thrown + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForNamespace(null, Actions.ForNamespaces.CreatePages, "User", new string[0]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase("Blah", ExpectedException = typeof(ArgumentException))] + [TestCase("*", ExpectedException = typeof(ArgumentException))] + public void CheckActionForNamespace_InvalidAction(string a) { + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForNamespace(new NamespaceInfo("NS", null, null), a, "User", new string[0]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void CheckActionForNamespace_InvalidUser(string u) { + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForNamespace(new NamespaceInfo("NS", null, null), Actions.ForNamespaces.ManagePages, u, new string[0]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void CheckActionForNamespace_NullGroups() { + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForNamespace(new NamespaceInfo("NS", null, null), Actions.ForNamespaces.ManagePages, "User", null); + } + + [Test] + public void CheckActionForNamespace_AdminBypass() { + Collectors.SettingsProvider = MockProvider(); + Assert.IsTrue(AuthChecker.CheckActionForNamespace(null, Actions.ForNamespaces.CreatePages, "admin", new string[0]), "Admin account should bypass security"); + } + + [Test] + public void CheckActionForNamespace_GrantUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.ForNamespaces.ManagePages, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.ForNamespaces.ManagePages, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManageCategories, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User2", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace2", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.ForNamespaces.ManagePages, "U.User", Value.Deny)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.ForNamespaces.ManagePages, "U.User100", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManageCategories, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User2", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace2", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_GrantUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.FullControl, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.FullControl, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManageCategories, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User2", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace2", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_DenyUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.FullControl, "U.User", Value.Deny)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.FullControl, "U.User100", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManageCategories, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User2", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace2", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_GrantGroupExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.ForNamespaces.ManagePages, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.ForNamespaces.ManagePages, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManageCategories, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group2" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace2", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_DenyGroupExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.ForNamespaces.ManagePages, "G.Group", Value.Deny)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.ForNamespaces.ManagePages, "G.Group100", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManageCategories, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group2" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace2", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_GrantGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.FullControl, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.FullControl, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManageCategories, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group2" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace2", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_DenyGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.FullControl, "G.Group", Value.Deny)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.FullControl, "G.Group100", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManageCategories, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group2" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace2", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_GrantUserGlobalEscalator() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManagePagesAndCategories, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManagePagesAndCategories, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManageCategories, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User2", new string[] { "Group" }), "Permission should be denied"); + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace2", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForNamespace_GrantGroupGlobalEscalator() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManagePagesAndCategories, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManagePagesAndCategories, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManageCategories, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group2" }), "Permission should be denied"); + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace2", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForNamespace_GrantUserGlobalFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManageCategories, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User2", new string[] { "Group" }), "Permission should be denied"); + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace2", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForNamespace_GrantGroupGlobalFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManageCategories, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group2" }), "Permission should be denied"); + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace2", null, null), Actions.ForNamespaces.ManagePages, "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForNamespace_GrantUserLocalEscalator() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.ForNamespaces.ManagePages, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.ForNamespaces.ManagePages, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ReadPages, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ReadPages, "User2", new string[] { "Group" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace2", null, null), Actions.ForNamespaces.ReadPages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_GrantGroupLocalEscalator() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.ForNamespaces.ManagePages, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Namespace", Actions.ForNamespaces.ManagePages, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ReadPages, "User", new string[] { "Group" }), "Permission should be granted"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace", null, null), Actions.ForNamespaces.ReadPages, "User", new string[] { "Group2" }), "Permission should be denied"); + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Namespace2", null, null), Actions.ForNamespaces.ReadPages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_GrantUserGlobalEscalator_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageNamespaces, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "NS", Actions.ForNamespaces.ModifyPages, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("NS", null, null), Actions.ForNamespaces.ModifyPages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_GrantGroupGlobalEscalator_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageNamespaces, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "NS", Actions.ForNamespaces.ModifyPages, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("NS", null, null), Actions.ForNamespaces.ModifyPages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_GrantUserGlobalFullControl_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "NS", Actions.ForNamespaces.ModifyPages, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("NS", null, null), Actions.ForNamespaces.ModifyPages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_GrantGroupGlobalFullControl_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "NS", Actions.ForNamespaces.ModifyPages, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("NS", null, null), Actions.ForNamespaces.ModifyPages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_GrantUserLocalEscalator_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "NS", Actions.ForNamespaces.ManagePages, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "NS", Actions.ForNamespaces.ReadPages, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("NS", null, null), Actions.ForNamespaces.ReadPages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_GrantGroupLocalEscalator_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "NS", Actions.ForNamespaces.ManagePages, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "NS", Actions.ForNamespaces.ReadPages, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("NS", null, null), Actions.ForNamespaces.ReadPages, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_GrantUserRootEscalator() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ManagePages, "U.User", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Sub", null, null), Actions.ForNamespaces.ManagePages, + "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForNamespace_GrantUserRootEscalator_DenyUserExplicitSub() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ManagePages, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", + Actions.ForNamespaces.ManagePages, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Sub", null, null), Actions.ForNamespaces.ManagePages, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_GrantGroupRootEscalator_DenyUserExplicitSub() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ManagePages, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", + Actions.ForNamespaces.ManagePages, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Sub", null, null), Actions.ForNamespaces.ManagePages, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForNamespace_GrantUserRootEscalator_DenyGroupExplicitSub() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ManagePages, "G.Group", Value.Deny)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", + Actions.ForNamespaces.ManagePages, "U.User", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("Sub", null, null), Actions.ForNamespaces.ManagePages, + "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForNamespace_RandomTestForRootNamespace() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ReadPages, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ReadPages, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(null, Actions.ForNamespaces.ManageCategories, + "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsTrue(AuthChecker.CheckActionForNamespace(null, Actions.ForNamespaces.ReadPages, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForNamespace(null, Actions.ForNamespaces.ReadPages, + "User2", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsTrue(AuthChecker.CheckActionForNamespace(new NamespaceInfo("NS", null, null), Actions.ForNamespaces.ReadPages, + "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void CheckActionForPage_NullPage() { + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForPage(null, Actions.ForPages.DeleteAttachments, "User", new string[0]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase("Blah", ExpectedException = typeof(ArgumentException))] + [TestCase("*", ExpectedException = typeof(ArgumentException))] + public void CheckActionForPage_InvalidAction(string a) { + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), a, "User", new string[0]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void CheckActionForPage_InvalidUser(string u) { + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, u, new string[0]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void CheckActionForPage_NullGroups() { + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, "User", null); + } + + [Test] + public void CheckActionForPage_AdminBypass() { + Collectors.SettingsProvider = MockProvider(); + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ManagePage, "admin", new string[0]), "Admin account should bypass security"); + } + + [Test] + public void CheckActionForPage_GrantUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User2", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Inexistent", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User", Value.Deny)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User100", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User2", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Inexistent", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.FullControl, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.FullControl, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User2", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Inexistent", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_DenyUserFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.FullControl, "U.User", Value.Deny)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.FullControl, "U.User100", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User2", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Inexistent", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantGroupExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User", new string[] { "Group2" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Inexistent", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_DenyGroupExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "G.Group", Value.Deny)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "G.Group100", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User", new string[] { "Group2" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Inexistent", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.FullControl, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.FullControl, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User", new string[] { "Group2" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Inexistent", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_DenyGroupFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.FullControl, "G.Group", Value.Deny)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.FullControl, "G.Group100", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.DeleteAttachments, + "User", new string[] { "Group2" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Inexistent", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantUserGlobalEscalator() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManagePagesAndCategories, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManagePagesAndCategories, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User2", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page2", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForPage_GrantGroupGlobalEscalator() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManagePagesAndCategories, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManagePagesAndCategories, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group2" }), "Permission should be denied"); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page2", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForPage_GrantUserGlobalFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User2", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page2", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForPage_GrantGroupGlobalFullControl() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group2" }), "Permission should be denied"); + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page2", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForPage_GrantUserLocalEscalator() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User2", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page2", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForPage_GrantGroupLocalEscalator() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group2" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page2", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForPage_GrantUserNamespaceEscalator() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ModifyPages, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ModifyPages, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User2", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantGroupNamespaceEscalator() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ModifyPages, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ModifyPages, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group2" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantUserNamespaceEscalator_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ModifyPages, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantGroupNamespaceEscalator_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ModifyPages, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantUserGlobalEscalator_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManagePagesAndCategories, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantGroupGlobalEscalator_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManagePagesAndCategories, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantUserGlobalFullControl_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantGroupGlobalFullControl_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.FullControl, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantUserLocalEscalator_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ReadPage, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantGroupLocalEscalator_DenyUserExplicit() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ReadPage, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ReadPage, + "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantUserRootEscalator() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ModifyPages, "U.User", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo(NameTools.GetFullName("Sub", "Page"), null, DateTime.Now), + Actions.ForPages.ModifyPage, "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForPage_GrantUserRootEscalator_DenyUserExplicitSub() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ModifyPages, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", + Actions.ForNamespaces.ModifyPages, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo(NameTools.GetFullName("Sub", "Page"), null, DateTime.Now), + Actions.ForPages.ModifyPage, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantGroupRootEscalator_DenyUserExplicitSub() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ModifyPages, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", + Actions.ForNamespaces.ModifyPages, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo(NameTools.GetFullName("Sub", "Page"), null, DateTime.Now), + Actions.ForPages.ModifyPage, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantUserRootEscalator_DenyGroupExplicitSub() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ModifyPages, "G.Group", Value.Deny)); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", + Actions.ForNamespaces.ModifyPages, "U.User", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo(NameTools.GetFullName("Sub", "Page"), null, DateTime.Now), + Actions.ForPages.ModifyPage, "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForPage_GrantUserRootEscalator_DenyUserExplicitPage() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ModifyPages, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + NameTools.GetFullName("Sub", "Page"), + Actions.ForPages.ModifyPage, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo(NameTools.GetFullName("Sub", "Page"), null, DateTime.Now), + Actions.ForPages.ModifyPage, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantGroupRootEscalator_DenyUserExplicitPage() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ModifyPages, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + NameTools.GetFullName("Sub", "Page"), + Actions.ForPages.ModifyPage, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo(NameTools.GetFullName("Sub", "Page"), null, DateTime.Now), + Actions.ForPages.ModifyPage, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForPage_GrantUserRootEscalator_DenyGroupExplicitPage() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ModifyPages, "G.Group", Value.Deny)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + NameTools.GetFullName("Sub", "Page"), + Actions.ForPages.ModifyPage, "U.User", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo(NameTools.GetFullName("Sub", "Page"), null, DateTime.Now), + Actions.ForPages.ModifyPage, "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForPage_RandomTestForSubNamespace() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "P.Page", Actions.ForPages.ModifyPage, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForPage(new PageInfo("P.Page", null, DateTime.Now), Actions.ForPages.ModifyPage, "User", new string[0]), "Permission should be granted"); + Assert.IsFalse(AuthChecker.CheckActionForPage(new PageInfo("Page", null, DateTime.Now), Actions.ForPages.ModifyPage, "User", new string[0]), "Permission should be denied"); + } + + private IFilesStorageProviderV30 MockFilesProvider() { + IFilesStorageProviderV30 prov = mocks.DynamicMock(); + mocks.Replay(prov); + return prov; + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void CheckActionForDirectory_NullProvider() { + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForDirectory(null, "/Dir", Actions.ForDirectories.CreateDirectories, "User", new string[0]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void CheckActionForDirectory_InvalidDirectory(string d) { + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForDirectory(MockFilesProvider(), d, Actions.ForDirectories.CreateDirectories, "User", new string[0]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase("Blah", ExpectedException = typeof(ArgumentException))] + [TestCase("*", ExpectedException = typeof(ArgumentException))] + public void CheckActionForDirectory_InvalidAction(string a) { + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForDirectory(MockFilesProvider(), "/Dir", a, "User", new string[0]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void CheckActionForDirectory_InvalidUser(string u) { + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForDirectory(MockFilesProvider(), "/Dir", Actions.ForDirectories.CreateDirectories, u, new string[0]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void CheckActionForDirectory_NullGroups() { + Collectors.SettingsProvider = MockProvider(); + AuthChecker.CheckActionForDirectory(MockFilesProvider(), "/Dir", Actions.ForDirectories.CreateDirectories, "User", null); + } + + [Test] + public void CheckActionForDirectory_AdminBypass() { + Collectors.SettingsProvider = MockProvider(); + Assert.IsTrue(AuthChecker.CheckActionForDirectory(MockFilesProvider(), "/", Actions.ForDirectories.DeleteFiles, "admin", new string[0]), "Admin account should bypass security"); + } + + [Test] + public void CheckActionForDirectory_GrantUserExplicit() { + + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.UploadFiles, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.UploadFiles, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.DeleteFiles, "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User2", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir2", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_DenyUserExplicit() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.UploadFiles, "U.User", Value.Deny)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.UploadFiles, "U.User100", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.DeleteFiles, "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User2", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir2", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_GrantUserFullControl() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.FullControl, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.FullControl, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.DeleteFiles, "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User2", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir2", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_DenyUserFullControl() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.FullControl, "U.User", Value.Deny)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.FullControl, "U.User100", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.DeleteFiles, "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User2", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir2", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_GrantGroupExplicit() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.UploadFiles, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.UploadFiles, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.DeleteFiles, "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group2" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir2", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_DenyGroupExplicit() { + + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.UploadFiles, "G.Group", Value.Deny)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.UploadFiles, "G.Group100", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.DeleteFiles, "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group2" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir2", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_GrantGroupFullControl() { + + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.FullControl, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.FullControl, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.DeleteFiles, "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group2" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir2", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_DenyGroupFullControl() { + + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.FullControl, "G.Group", Value.Deny)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.FullControl, "G.Group100", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.DeleteFiles, "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group2" }), "Permission should be denied"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir2", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_GrantUserGlobalEscalator() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageFiles, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageFiles, "U.User100", Value.Deny)); + + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForDirectory_GrantGroupGlobalEscalator() { + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageFiles, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageFiles, "G.Group100", Value.Deny)); + + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.UploadFiles, "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForDirectory_GrantUserLocalEscalator() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.DownloadFiles, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.DownloadFiles, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.List, "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForDirectory_GrantGroupLocalEscalator() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.DownloadFiles, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.DownloadFiles, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.List, "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForDirectory_GrantUserGlobalFullControl() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.FullControl, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.FullControl, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.List, "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForDirectory_GrantGroupGlobalFullControl() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.FullControl, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.FullControl, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.List, "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForDirectory_GrantUserDirectoryEscalator() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.DownloadFiles, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.DownloadFiles, "U.User100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Dir/Sub", + Actions.ForDirectories.DownloadFiles, "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForDirectory_GrantGroupDirectoryEscalator() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.DownloadFiles, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.DownloadFiles, "G.Group100", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Dir/Sub", + Actions.ForDirectories.DownloadFiles, "User", new string[] { "Group" }), "Permission should be granted"); + } + + [Test] + public void CheckActionForDirectory_GrantUserGlobalEscalator_DenyUserExplicit() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageFiles, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), Actions.ForDirectories.DownloadFiles, + "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.DownloadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_GrantGroupGlobalEscalator_DenyUserExplicit() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageFiles, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), Actions.ForDirectories.DownloadFiles, + "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.DownloadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_GrantUserLocalEscalator_DenyUserExplicit() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), Actions.ForDirectories.DownloadFiles, + "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), Actions.ForDirectories.List, + "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.List, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_GrantGroupLocalEscalator_DenyUserExplicit() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), Actions.ForDirectories.DownloadFiles, + "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), Actions.ForDirectories.List, + "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.List, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_GrantUserGlobalFullControl_DenyUserExplicit() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.FullControl, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), Actions.ForDirectories.DownloadFiles, + "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.DownloadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_GrantGroupGlobalFullControl_DenyUserExplicit() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.FullControl, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), Actions.ForDirectories.DownloadFiles, + "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir", + Actions.ForDirectories.DownloadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_GrantUserDirectoryEscalator_DenyUserExplicit() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.DownloadFiles, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir/Sub"), + Actions.ForDirectories.DownloadFiles, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir/Sub", + Actions.ForDirectories.DownloadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_GrantGroupDirectoryEscalator_DenyUserExplicit() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir"), + Actions.ForDirectories.DownloadFiles, "G.Group", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Dir/Sub"), + Actions.ForDirectories.DownloadFiles, "U.User", Value.Deny)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Dir/Sub", + Actions.ForDirectories.DownloadFiles, "User", new string[] { "Group" }), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_RandomTestForRootDirectory() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/"), + Actions.ForDirectories.List, "U.User", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/", Actions.ForDirectories.List, + "User", new string[0]), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/", Actions.ForDirectories.List, + "User2", new string[0]), "Permission should be denied"); + } + + [Test] + public void CheckActionForDirectory_GrantUserDirectoryEscalator_RecursiveName() { + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + List entries = new List(); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/"), + Actions.ForDirectories.List, "U.User", Value.Grant)); + entries.Add(new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + + AuthTools.GetDirectoryName(filesProv, "/Sub/Sub/"), + Actions.ForDirectories.List, "U.User2", Value.Grant)); + + Collectors.SettingsProvider = MockProvider(entries); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Sub/Sub/Sub", Actions.ForDirectories.List, + "User", new string[0]), "Permission should be granted"); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Sub/Sub/Sub/", Actions.ForDirectories.List, + "User", new string[0]), "Permission should be granted"); + + Assert.IsTrue(AuthChecker.CheckActionForDirectory(filesProv, "/Sub/Sub/Sub/", Actions.ForDirectories.List, + "User2", new string[0]), "Permission should be granted"); + + Assert.IsFalse(AuthChecker.CheckActionForDirectory(filesProv, "/Sub/Sub/Sub", Actions.ForDirectories.List, + "User2", new string[0]), "Permission should be granted"); + } + + } + +} diff --git a/Core-Tests/AuthReaderTests.cs b/Core-Tests/AuthReaderTests.cs new file mode 100644 index 0000000..17953ff --- /dev/null +++ b/Core-Tests/AuthReaderTests.cs @@ -0,0 +1,1169 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.AclEngine; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Tests { + + [TestFixture] + public class AuthReaderTests { + + private MockRepository mocks = null; + private string testDir = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Guid.NewGuid().ToString()); + + [SetUp] + public void SetUp() { + mocks = new MockRepository(); + // TODO: Verify if this is really needed + Collectors.SettingsProvider = MockProvider(); + } + + [TearDown] + public void TearDown() { + try { + Directory.Delete(testDir, true); + } + catch { + //Console.WriteLine("Test: could not delete temp directory"); + } + mocks.VerifyAll(); + } + + protected IHostV30 MockHost() { + if(!Directory.Exists(testDir)) Directory.CreateDirectory(testDir); + + IHostV30 host = mocks.DynamicMock(); + Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.Any(); + + mocks.Replay(host); + + return host; + } + + private ISettingsStorageProviderV30 MockProvider(List entries) { + ISettingsStorageProviderV30 provider = mocks.DynamicMock(); + provider.Init(MockHost(), ""); + LastCall.On(provider).Repeat.Any(); + + AclManagerBase aclManager = new StandardAclManager(); + Expect.Call(provider.AclManager).Return(aclManager).Repeat.Any(); + + mocks.Replay(provider); + + foreach(AclEntry entry in entries) { + aclManager.StoreEntry(entry.Resource, entry.Action, entry.Subject, entry.Value); + } + + return provider; + } + + private ISettingsStorageProviderV30 MockProvider() { + return MockProvider(new List()); + } + + [Test] + public void RetrieveGrantsForGlobals_Group() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageFiles, "G.Group", Value.Deny), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageAccounts, "G.Group", Value.Grant), + new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + "/", Actions.ForDirectories.UploadFiles, "G.Group", Value.Grant) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + string[] grants = AuthReader.RetrieveGrantsForGlobals(new UserGroup("Group", "Group", null)); + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForGlobals.ManageAccounts, grants[0], "Wrong grant"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveGrantsForGlobals_Group_NullGroup() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveGrantsForGlobals(null as UserGroup); + } + + [Test] + public void RetrieveGrantsForGlobals_User() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageFiles, "U.User", Value.Deny), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageAccounts, "U.User", Value.Grant), + new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + "/", Actions.ForDirectories.UploadFiles, "U.User", Value.Grant) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + string[] grants = AuthReader.RetrieveGrantsForGlobals(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)); + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForGlobals.ManageAccounts, grants[0], "Wrong grant"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveGrantsForGlobals_User_NullUser() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveGrantsForGlobals(null as UserInfo); + } + + [Test] + public void RetrieveDenialsForGlobals_Group() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageFiles, "G.Group", Value.Deny), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageConfiguration, "G.Group", Value.Grant), + new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + "/", Actions.ForDirectories.UploadFiles, "G.Group", Value.Deny) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + string[] grants = AuthReader.RetrieveDenialsForGlobals(new UserGroup("Group", "Group", null)); + Assert.AreEqual(1, grants.Length, "Wrong denial count"); + Assert.AreEqual(Actions.ForGlobals.ManageFiles, grants[0], "Wrong denial"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveDenialsForGlobals_Group_NullGroup() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveDenialsForGlobals(null as UserGroup); + } + + [Test] + public void RetrieveDenialsForGlobals_User() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageFiles, "U.User", Value.Deny), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageAccounts, "U.User", Value.Grant), + new AclEntry(Actions.ForDirectories.ResourceMasterPrefix + "/", Actions.ForDirectories.UploadFiles, "U.User", Value.Deny) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + string[] grants = AuthReader.RetrieveDenialsForGlobals(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)); + Assert.AreEqual(1, grants.Length, "Wrong denial count"); + Assert.AreEqual(Actions.ForGlobals.ManageFiles, grants[0], "Wrong denial"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveDenialsForGlobals_User_NullUser() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveDenialsForGlobals(null as UserInfo); + } + + [Test] + public void RetrieveSubjectsForNamespace_Root() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForResource("N.")).Return( + new AclEntry[] { + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.DeletePages, "U.User1", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ReadPages, "U.User", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "U.User", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "G.Group", Value.Deny) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + SubjectInfo[] infos = AuthReader.RetrieveSubjectsForNamespace(null); + + Assert.AreEqual(3, infos.Length, "Wrong info count"); + + Array.Sort(infos, delegate(SubjectInfo x, SubjectInfo y) { return x.Name.CompareTo(y.Name); }); + Assert.AreEqual("Group", infos[0].Name, "Wrong subject name"); + Assert.AreEqual(SubjectType.Group, infos[0].Type, "Wrong subject type"); + Assert.AreEqual("User", infos[1].Name, "Wrong subject name"); + Assert.AreEqual(SubjectType.User, infos[1].Type, "Wrong subject type"); + Assert.AreEqual("User1", infos[2].Name, "Wrong subject name"); + Assert.AreEqual(SubjectType.User, infos[2].Type, "Wrong subject type"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void RetrieveSubjectsForNamespace_Sub() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForResource("N.Sub")).Return( + new AclEntry[] { + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.DeletePages, "U.User1", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ReadPages, "U.User", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "U.User", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "G.Group", Value.Deny) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + SubjectInfo[] infos = AuthReader.RetrieveSubjectsForNamespace(new NamespaceInfo("Sub", null, null)); + + Assert.AreEqual(3, infos.Length, "Wrong info count"); + + Array.Sort(infos, delegate(SubjectInfo x, SubjectInfo y) { return x.Name.CompareTo(y.Name); }); + Assert.AreEqual("Group", infos[0].Name, "Wrong subject name"); + Assert.AreEqual(SubjectType.Group, infos[0].Type, "Wrong subject type"); + Assert.AreEqual("User", infos[1].Name, "Wrong subject name"); + Assert.AreEqual(SubjectType.User, infos[1].Type, "Wrong subject type"); + Assert.AreEqual("User1", infos[2].Name, "Wrong subject name"); + Assert.AreEqual(SubjectType.User, infos[2].Type, "Wrong subject type"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void RetrieveGrantsForNamespace_Group_Root() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "G.Group", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManageCategories, "G.Group", Value.Deny), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManagePages, "G.Group", Value.Grant) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveGrantsForNamespace(new UserGroup("Group", "Group", null), null); + + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForNamespaces.ManagePages, grants[0], "Wrong grant"); + } + + [Test] + public void RetrieveGrantsForNamespace_Group_Sub() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManagePages, "G.Group", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManageCategories, "G.Group", Value.Deny), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "G.Group", Value.Grant) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveGrantsForNamespace(new UserGroup("Group", "Group", null), new NamespaceInfo("Sub", null, null)); + + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForNamespaces.ManagePages, grants[0], "Wrong grant"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveGrantsForNamespace_Group_NullGroup() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveGrantsForNamespace(null as UserGroup, new NamespaceInfo("Sub", null, null)); + } + + [Test] + public void RetrieveGrantsForNamespace_User_Root() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "U.User", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManageCategories, "U.User", Value.Deny), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManagePages, "U.User", Value.Grant) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveGrantsForNamespace(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), null); + + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForNamespaces.ManagePages, grants[0], "Wrong grant"); + } + + [Test] + public void RetrieveGrantsForNamespace_User_Sub() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManagePages, "U.User", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManageCategories, "U.User", Value.Deny), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "U.User", Value.Grant) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveGrantsForNamespace(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), + new NamespaceInfo("Sub", null, null)); + + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForNamespaces.ManagePages, grants[0], "Wrong grant"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveGrantsForNamespace_User_NullUser() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveGrantsForNamespace(null as UserInfo, new NamespaceInfo("Sub", null, null)); + } + + [Test] + public void RetrieveDenialsForNamespace_Group_Root() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "G.Group", Value.Deny), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManageCategories, "G.Group", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManagePages, "G.Group", Value.Deny) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveDenialsForNamespace(new UserGroup("Group", "Group", null), null); + + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForNamespaces.ManagePages, grants[0], "Wrong grant"); + } + + [Test] + public void RetrieveDenialsForNamespace_Group_Sub() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManagePages, "G.Group", Value.Deny), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManageCategories, "G.Group", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "G.Group", Value.Deny) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveDenialsForNamespace(new UserGroup("Group", "Group", null), new NamespaceInfo("Sub", null, null)); + + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForNamespaces.ManagePages, grants[0], "Wrong grant"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveDenialsForNamespace_Group_NullGroup() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveDenialsForNamespace(null as UserGroup, new NamespaceInfo("Sub", null, null)); + } + + [Test] + public void RetrieveDenialsForNamespace_User_Root() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "U.User", Value.Deny), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManageCategories, "U.User", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManagePages, "U.User", Value.Deny) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveDenialsForNamespace(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), null); + + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForNamespaces.ManagePages, grants[0], "Wrong grant"); + } + + [Test] + public void RetrieveDenialsForNamespace_User_Sub() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManagePages, "U.User", Value.Deny), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManageCategories, "U.User", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "U.User", Value.Deny) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveDenialsForNamespace(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), + new NamespaceInfo("Sub", null, null)); + + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForNamespaces.ManagePages, grants[0], "Wrong grant"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveDenialsForNamespace_User_NullUser() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveDenialsForNamespace(null as UserInfo, new NamespaceInfo("Sub", null, null)); + } + + [Test] + public void RetrieveSubjectsForPage() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForResource("P.NS.Page")).Return( + new AclEntry[] { + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User1", Value.Grant), + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ManageCategories, "U.User", Value.Grant), + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.DeleteAttachments, "G.Group", Value.Grant), + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.DeleteAttachments, "U.User", Value.Deny) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + SubjectInfo[] infos = AuthReader.RetrieveSubjectsForPage(new PageInfo("NS.Page", null, DateTime.Now)); + + Assert.AreEqual(3, infos.Length, "Wrong info count"); + + Array.Sort(infos, delegate(SubjectInfo x, SubjectInfo y) { return x.Name.CompareTo(y.Name); }); + Assert.AreEqual("Group", infos[0].Name, "Wrong subject name"); + Assert.AreEqual(SubjectType.Group, infos[0].Type, "Wrong subject type"); + Assert.AreEqual("User", infos[1].Name, "Wrong subject name"); + Assert.AreEqual(SubjectType.User, infos[1].Type, "Wrong subject type"); + Assert.AreEqual("User1", infos[2].Name, "Wrong subject name"); + Assert.AreEqual(SubjectType.User, infos[2].Type, "Wrong subject type"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveSubjectsForPage_NullPage() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveSubjectsForPage(null); + } + + [Test] + public void RetrieveGrantsForPage_Group() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "G.Group", Value.Grant), + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.FullControl, "G.Group", Value.Deny), + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "NS.Blah", Actions.ForPages.ManagePage, "G.Group", Value.Grant) + }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveGrantsForPage(new UserGroup("Group", "Group", null), + new PageInfo("Page", null, DateTime.Now)); + + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForPages.ModifyPage, grants[0], "Wrong grant"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveGrantsForPage_Group_NullGroup() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveGrantsForPage(null as UserGroup, new PageInfo("Page", null, DateTime.Now)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveGrantsForPage_Group_NullPage() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveGrantsForPage(new UserGroup("Group", "Group", null), null); + } + + [Test] + public void RetrieveGrantsForPage_User() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User", Value.Grant), + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.FullControl, "U.User", Value.Deny), + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "NS.Blah", Actions.ForPages.ManagePage, "U.User", Value.Grant) + }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveGrantsForPage(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), + new PageInfo("Page", null, DateTime.Now)); + + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForPages.ModifyPage, grants[0], "Wrong grant"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveGrantsForPage_User_NullUser() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveGrantsForPage(null as UserInfo, new PageInfo("Page", null, DateTime.Now)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveGrantsForPage_User_NullPage() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveGrantsForPage(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), null); + } + + [Test] + public void RetrieveDenialsForPage_Group() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "G.Group", Value.Deny), + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.FullControl, "G.Group", Value.Grant), + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "NS.Blah", Actions.ForPages.ManagePage, "G.Group", Value.Deny) + }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveDenialsForPage(new UserGroup("Group", "Group", null), + new PageInfo("Page", null, DateTime.Now)); + + Assert.AreEqual(1, grants.Length, "Wrong denial count"); + Assert.AreEqual(Actions.ForPages.ModifyPage, grants[0], "Wrong denial"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveDenialsForPage_Group_NullGroup() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveDenialsForPage(null as UserGroup, new PageInfo("Page", null, DateTime.Now)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveDenialsForPage_Group_NullPage() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveDenialsForPage(new UserGroup("Group", "Group", null), null); + } + + [Test] + public void RetrieveDenialsForPage_User() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ModifyPage, "U.User", Value.Deny), + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.FullControl, "U.User", Value.Grant), + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "NS.Blah", Actions.ForPages.ManagePage, "U.User", Value.Deny) + }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveDenialsForPage(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), + new PageInfo("Page", null, DateTime.Now)); + + Assert.AreEqual(1, grants.Length, "Wrong denial count"); + Assert.AreEqual(Actions.ForPages.ModifyPage, grants[0], "Wrong denial"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveDenialsForPage_User_NullUser() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveDenialsForPage(null as UserInfo, new PageInfo("Page", null, DateTime.Now)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveDenialsForPage_User_NullPage() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveDenialsForPage(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), null); + } + + [Test] + public void RetrieveSubjectsForDirectory_Root() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + string dirName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/"); + Expect.Call(aclManager.RetrieveEntriesForResource(dirName)).Return( + new AclEntry[] { + new AclEntry(dirName, Actions.ForDirectories.List, "U.User1", Value.Grant), + new AclEntry(dirName, Actions.ForDirectories.UploadFiles, "U.User", Value.Grant), + new AclEntry(dirName, Actions.ForDirectories.DownloadFiles, "G.Group", Value.Grant), + new AclEntry(dirName, Actions.ForDirectories.CreateDirectories, "U.User", Value.Deny) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + SubjectInfo[] infos = AuthReader.RetrieveSubjectsForDirectory(filesProv, "/"); + + Assert.AreEqual(3, infos.Length, "Wrong info count"); + + Array.Sort(infos, delegate(SubjectInfo x, SubjectInfo y) { return x.Name.CompareTo(y.Name); }); + Assert.AreEqual("Group", infos[0].Name, "Wrong subject name"); + Assert.AreEqual(SubjectType.Group, infos[0].Type, "Wrong subject type"); + Assert.AreEqual("User", infos[1].Name, "Wrong subject name"); + Assert.AreEqual(SubjectType.User, infos[1].Type, "Wrong subject type"); + Assert.AreEqual("User1", infos[2].Name, "Wrong subject name"); + Assert.AreEqual(SubjectType.User, infos[2].Type, "Wrong subject type"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void RetrieveSubjectsForDirectory_Sub() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + string dirName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/Dir/Sub/"); + Expect.Call(aclManager.RetrieveEntriesForResource(dirName)).Return( + new AclEntry[] { + new AclEntry(dirName, Actions.ForDirectories.List, "U.User1", Value.Grant), + new AclEntry(dirName, Actions.ForDirectories.UploadFiles, "U.User", Value.Grant), + new AclEntry(dirName, Actions.ForDirectories.DownloadFiles, "G.Group", Value.Grant), + new AclEntry(dirName, Actions.ForDirectories.CreateDirectories, "U.User", Value.Deny) }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + SubjectInfo[] infos = AuthReader.RetrieveSubjectsForDirectory(filesProv, "/Dir/Sub/"); + + Assert.AreEqual(3, infos.Length, "Wrong info count"); + + Array.Sort(infos, delegate(SubjectInfo x, SubjectInfo y) { return x.Name.CompareTo(y.Name); }); + Assert.AreEqual("Group", infos[0].Name, "Wrong subject name"); + Assert.AreEqual(SubjectType.Group, infos[0].Type, "Wrong subject type"); + Assert.AreEqual("User", infos[1].Name, "Wrong subject name"); + Assert.AreEqual(SubjectType.User, infos[1].Type, "Wrong subject type"); + Assert.AreEqual("User1", infos[2].Name, "Wrong subject name"); + Assert.AreEqual(SubjectType.User, infos[2].Type, "Wrong subject type"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveSubjectsForDirectory_NullProvider() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveSubjectsForDirectory(null, "/"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RetrieveSubjectsForDirectory_InvalidDirectory(string d) { + Collectors.SettingsProvider = MockProvider(); + + IFilesStorageProviderV30 fProv = mocks.DynamicMock(); + mocks.Replay(fProv); + AuthReader.RetrieveSubjectsForDirectory(fProv, d); + } + + [Test] + public void RetrieveGrantsForDirectory_Root_Group() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + string dirName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/"); + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(dirName, Actions.ForDirectories.List, "G.Group", Value.Grant), + new AclEntry(dirName, Actions.FullControl, "G.Group", Value.Deny), + new AclEntry("D." + AuthTools.GetDirectoryName(filesProv, "/Other/"), Actions.ForDirectories.UploadFiles, "G.Group", Value.Grant) + }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveGrantsForDirectory(new UserGroup("Group", "Group", null), + filesProv, "/"); + + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForDirectories.List, grants[0], "Wrong grant"); + } + + [Test] + public void RetrieveGrantsForDirectory_Sub_Group() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + string dirName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/Dir/Sub/"); + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(dirName, Actions.ForDirectories.List, "G.Group", Value.Grant), + new AclEntry(dirName, Actions.FullControl, "G.Group", Value.Deny), + new AclEntry("D." + AuthTools.GetDirectoryName(filesProv, "/"), Actions.ForDirectories.UploadFiles, "G.Group", Value.Grant) + }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveGrantsForDirectory(new UserGroup("Group", "Group", null), + filesProv, "/Dir/Sub/"); + + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForDirectories.List, grants[0], "Wrong grant"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveGrantsForDirectory_Group_NullGroup() { + Collectors.SettingsProvider = MockProvider(); + + IFilesStorageProviderV30 fProv = mocks.DynamicMock(); + mocks.Replay(fProv); + AuthReader.RetrieveGrantsForDirectory(null as UserGroup, fProv, "/"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveGrantsForDirectory_Group_NullProvider() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveGrantsForDirectory(new UserGroup("Group", "Group", null), null, "/"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RetrieveGrantsForDirectory_Group_InvalidDirectory(string d) { + Collectors.SettingsProvider = MockProvider(); + + IFilesStorageProviderV30 fProv = mocks.DynamicMock(); + mocks.Replay(fProv); + AuthReader.RetrieveGrantsForDirectory(new UserGroup("Group", "Group", null), fProv, d); + } + + [Test] + public void RetrieveGrantsForDirectory_Root_User() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + string dirName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/"); + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(dirName, Actions.ForDirectories.UploadFiles, "U.User", Value.Grant), + new AclEntry(dirName, Actions.FullControl, "U.User", Value.Deny), + new AclEntry("D." + AuthTools.GetDirectoryName(filesProv, "/Other/"), Actions.ForDirectories.List, "U.User", Value.Grant) + }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveGrantsForDirectory(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), + filesProv, "/"); + + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForDirectories.UploadFiles, grants[0], "Wrong grant"); + } + + [Test] + public void RetrieveGrantsForDirectory_Sub_User() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + string dirName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/Dir/Sub/"); + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(dirName, Actions.ForDirectories.UploadFiles, "U.User", Value.Grant), + new AclEntry(dirName, Actions.FullControl, "U.User", Value.Deny), + new AclEntry("D." + AuthTools.GetDirectoryName(filesProv, "/"), Actions.ForDirectories.List, "U.User", Value.Grant) + }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveGrantsForDirectory(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), + filesProv, "/Dir/Sub/"); + + Assert.AreEqual(1, grants.Length, "Wrong grant count"); + Assert.AreEqual(Actions.ForDirectories.UploadFiles, grants[0], "Wrong grant"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveGrantsForDirectory_User_NullUser() { + Collectors.SettingsProvider = MockProvider(); + + IFilesStorageProviderV30 fProv = mocks.DynamicMock(); + mocks.Replay(fProv); + AuthReader.RetrieveGrantsForDirectory(null as UserInfo, fProv, "/"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveGrantsForDirectory_User_NullProvider() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveGrantsForDirectory(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), + null, "/"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RetrieveGrantsForDirectory_User_InvalidDirectory(string d) { + Collectors.SettingsProvider = MockProvider(); + + IFilesStorageProviderV30 fProv = mocks.DynamicMock(); + mocks.Replay(fProv); + AuthReader.RetrieveGrantsForDirectory(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), fProv, d); + } + + [Test] + public void RetrieveDenialsForDirectory_Root_Group() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + string dirName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/"); + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(dirName, Actions.ForDirectories.List, "G.Group", Value.Deny), + new AclEntry(dirName, Actions.FullControl, "G.Group", Value.Grant), + new AclEntry("D." + AuthTools.GetDirectoryName(filesProv, "/Other/"), Actions.ForDirectories.UploadFiles, "G.Group", Value.Deny) + }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveDenialsForDirectory(new UserGroup("Group", "Group", null), + filesProv, "/"); + + Assert.AreEqual(1, grants.Length, "Wrong denial count"); + Assert.AreEqual(Actions.ForDirectories.List, grants[0], "Wrong denial"); + } + + [Test] + public void RetrieveDenialsForDirectory_Sub_Group() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + string dirName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/Dir/Sub/"); + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(dirName, Actions.ForDirectories.List, "G.Group", Value.Deny), + new AclEntry(dirName, Actions.FullControl, "G.Group", Value.Grant), + new AclEntry("D." + AuthTools.GetDirectoryName(filesProv, "/"), Actions.ForDirectories.UploadFiles, "G.Group", Value.Deny) + }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveDenialsForDirectory(new UserGroup("Group", "Group", null), + filesProv, "/Dir/Sub/"); + + Assert.AreEqual(1, grants.Length, "Wrong denial count"); + Assert.AreEqual(Actions.ForDirectories.List, grants[0], "Wrong denial"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveDenialsForDirectory_Group_NullGroup() { + Collectors.SettingsProvider = MockProvider(); + + IFilesStorageProviderV30 fProv = mocks.DynamicMock(); + mocks.Replay(fProv); + AuthReader.RetrieveDenialsForDirectory(null as UserGroup, fProv, "/"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveDenialsForDirectory_Group_NullProvider() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveDenialsForDirectory(new UserGroup("Group", "Group", null), null, "/"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RetrieveDenialsForDirectory_Group_InvalidDirectory(string d) { + Collectors.SettingsProvider = MockProvider(); + + IFilesStorageProviderV30 fProv = mocks.DynamicMock(); + mocks.Replay(fProv); + AuthReader.RetrieveDenialsForDirectory(new UserGroup("Group", "Group", null), fProv, d); + } + + [Test] + public void RetrieveDenialsForDirectory_Root_User() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + string dirName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/"); + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(dirName, Actions.ForDirectories.UploadFiles, "U.User", Value.Deny), + new AclEntry(dirName, Actions.FullControl, "U.User", Value.Grant), + new AclEntry("D." + AuthTools.GetDirectoryName(filesProv, "/Other/"), Actions.ForDirectories.List, "U.User", Value.Deny) + }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveDenialsForDirectory(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), + filesProv, "/"); + + Assert.AreEqual(1, grants.Length, "Wrong denial count"); + Assert.AreEqual(Actions.ForDirectories.UploadFiles, grants[0], "Wrong denial"); + } + + [Test] + public void RetrieveDenialsForDirectory_Sub_User() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + string dirName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/Dir/Sub/"); + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(dirName, Actions.ForDirectories.UploadFiles, "U.User", Value.Deny), + new AclEntry(dirName, Actions.FullControl, "U.User", Value.Grant), + new AclEntry("D." + AuthTools.GetDirectoryName(filesProv, "/"), Actions.ForDirectories.List, "U.User", Value.Deny) + }); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + string[] grants = AuthReader.RetrieveDenialsForDirectory(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), + filesProv, "/Dir/Sub/"); + + Assert.AreEqual(1, grants.Length, "Wrong denial count"); + Assert.AreEqual(Actions.ForDirectories.UploadFiles, grants[0], "Wrong denial"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveDenialsForDirectory_User_NullUser() { + Collectors.SettingsProvider = MockProvider(); + + IFilesStorageProviderV30 fProv = mocks.DynamicMock(); + mocks.Replay(fProv); + AuthReader.RetrieveDenialsForDirectory(null as UserInfo, fProv, "/"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveDenialsForDirectory_User_NullProvider() { + Collectors.SettingsProvider = MockProvider(); + + AuthReader.RetrieveDenialsForDirectory(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), + null, "/"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RetrieveDenialsForDirectory_User_InvalidDirectory(string d) { + Collectors.SettingsProvider = MockProvider(); + + IFilesStorageProviderV30 fProv = mocks.DynamicMock(); + mocks.Replay(fProv); + AuthReader.RetrieveDenialsForDirectory(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), fProv, d); + } + + } + +} diff --git a/Core-Tests/AuthToolsTests.cs b/Core-Tests/AuthToolsTests.cs new file mode 100644 index 0000000..739a371 --- /dev/null +++ b/Core-Tests/AuthToolsTests.cs @@ -0,0 +1,66 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.Tests { + + [TestFixture] + public class AuthToolsTests { + + [Test] + public void Static_PrepareUsername() { + Assert.AreEqual("U.User", AuthTools.PrepareUsername("User"), "Wrong result"); + Assert.AreEqual("U.U.User", AuthTools.PrepareUsername("U.User"), "Wrong result"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Static_PrepareUsername_InvalidUsername(string s) { + AuthTools.PrepareUsername(s); + } + + [Test] + public void Static_PrepareGroups() { + Assert.AreEqual(0, AuthTools.PrepareGroups(new string[0]).Length, "Wrong result length"); + + string[] input = new string[] { "Group", "G.Group" }; + string[] output = AuthTools.PrepareGroups(input); + + Assert.AreEqual(input.Length, output.Length, "Wrong result length"); + for(int i = 0; i < input.Length; i++) { + Assert.AreEqual("G." + input[i], output[i], "Wrong value"); + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Static_PrepareGroups_NullGroups() { + AuthTools.PrepareGroups(null); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Static_PrepareGroups_InvalidElement(string e) { + AuthTools.PrepareGroups(new string[] { e }); + } + + [TestCase(null, false, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", false, ExpectedException = typeof(ArgumentException))] + [TestCase("G", false, ExpectedException = typeof(ArgumentException))] + [TestCase("G.", true)] + [TestCase("g.", true)] + [TestCase("G.Blah", true)] + [TestCase("g.Blah", true)] + [TestCase("U.", false)] + [TestCase("u.", false)] + [TestCase("U.Blah", false)] + [TestCase("u.Blah", false)] + public void Static_IsGroup(string subject, bool result) { + Assert.AreEqual(result, AuthTools.IsGroup(subject), "Wrong result"); + } + + } + +} diff --git a/Core-Tests/AuthWriterTests.cs b/Core-Tests/AuthWriterTests.cs new file mode 100644 index 0000000..19e36e2 --- /dev/null +++ b/Core-Tests/AuthWriterTests.cs @@ -0,0 +1,1629 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.AclEngine; + +namespace ScrewTurn.Wiki.Tests { + + [TestFixture] + public class AuthWriterTests { + + private MockRepository mocks = null; + private string testDir = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Guid.NewGuid().ToString()); + + [SetUp] + public void SetUp() { + mocks = new MockRepository(); + // TODO: Verify if this is really needed + Collectors.SettingsProvider = MockProvider(); + } + + [TearDown] + public void TearDown() { + try { + Directory.Delete(testDir, true); + } + catch { + //Console.WriteLine("Test: could not delete temp directory"); + } + mocks.VerifyAll(); + } + + protected IHostV30 MockHost() { + if(!Directory.Exists(testDir)) Directory.CreateDirectory(testDir); + + IHostV30 host = mocks.DynamicMock(); + Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.Any(); + + mocks.Replay(host); + + return host; + } + + private ISettingsStorageProviderV30 MockProvider(List entries) { + ISettingsStorageProviderV30 provider = mocks.DynamicMock(); + provider.Init(MockHost(), ""); + LastCall.On(provider).Repeat.Any(); + + AclManagerBase aclManager = new StandardAclManager(); + Expect.Call(provider.AclManager).Return(aclManager).Repeat.Any(); + + mocks.Replay(provider); + + foreach(AclEntry entry in entries) { + aclManager.StoreEntry(entry.Resource, entry.Action, entry.Subject, entry.Value); + } + + return provider; + } + + private ISettingsStorageProviderV30 MockProvider() { + return MockProvider(new List()); + } + + private IFilesStorageProviderV30 MockFilesProvider() { + IFilesStorageProviderV30 prov = mocks.DynamicMock(); + mocks.Replay(prov); + return prov; + } + + [Test] + public void SetPermissionForGlobals_Group_Grant() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageAccounts, "G.Group", Value.Grant)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForGlobals(AuthStatus.Grant, Actions.ForGlobals.ManageAccounts, + new UserGroup("Group", "Descr", null)), "SetPermissionForGlobals should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForGlobals_Group_Deny() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageProviders, "G.Group", Value.Deny)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForGlobals(AuthStatus.Deny, Actions.ForGlobals.ManageProviders, + new UserGroup("Group", "Descr", null)), "SetPermissionForGlobals should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForGlobals_Group_Delete() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.DeleteEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageMetaFiles, "G.Group")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForGlobals(AuthStatus.Delete, Actions.ForGlobals.ManageMetaFiles, + new UserGroup("Group", "Descr", null)), "SetPermissionForGlobals should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase("Blah", ExpectedException = typeof(ArgumentException))] + [TestCase("*")] + public void SetPermissionForGlobals_Group_InvalidAction(string a) { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForGlobals(AuthStatus.Grant, a, new UserGroup("Group", "Desc", null)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetPermissionForGlobals_Group_NullGroup() { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForGlobals(AuthStatus.Grant, Actions.ForGlobals.ManageAccounts, null as UserGroup); + } + + [Test] + public void SetPermissionForGlobals_User_Grant() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageAccounts, "U.User", Value.Grant)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForGlobals(AuthStatus.Grant, Actions.ForGlobals.ManageAccounts, + new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)), + "SetPermissionForGlobals should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForGlobals_User_Deny() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageProviders, "U.User", Value.Deny)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForGlobals(AuthStatus.Deny, Actions.ForGlobals.ManageProviders, + new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)), + "SetPermissionForGlobals should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForGlobals_User_Delete() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.DeleteEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageMetaFiles, "U.User")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForGlobals(AuthStatus.Delete, Actions.ForGlobals.ManageMetaFiles, + new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)), + "SetPermissionForGlobals should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase("Blah", ExpectedException = typeof(ArgumentException))] + [TestCase("*")] + public void SetPermissionForGlobals_User_InvalidAction(string a) { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForGlobals(AuthStatus.Grant, a, + new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetPermissionForGlobals_User_NullUser() { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForGlobals(AuthStatus.Grant, Actions.ForGlobals.ManageAccounts, null as UserInfo); + } + + [Test] + public void SetPermissionForNamespace_Group_Grant() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.CreatePages, "G.Group", Value.Grant)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, + Actions.ForNamespaces.CreatePages, new UserGroup("Group", "Descr", null)), + "SetPermissionForNamespace should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForNamespace_Group_Deny() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ModifyPages, "G.Group", Value.Deny)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForNamespace(AuthStatus.Deny, null, + Actions.ForNamespaces.ModifyPages, new UserGroup("Group", "Descr", null)), + "SetPermissionForNamespace should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForNamespace_Group_Delete() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.DeleteEntry(Actions.ForNamespaces.ResourceMasterPrefix + "NS", + Actions.ForNamespaces.ReadPages, "G.Group")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForNamespace(AuthStatus.Delete, new NamespaceInfo("NS", null, null), + Actions.ForNamespaces.ReadPages, new UserGroup("Group", "Descr", null)), + "SetPermissionForNamespace should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase("Blah", ExpectedException = typeof(ArgumentException))] + [TestCase("*")] + public void SetPermissionForNamespace_Group_InvalidAction(string a) { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, a, new UserGroup("Group", "Descr", null)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetPermissionForNamespace_Group_NullGroup() { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.ModifyPages, null as UserGroup); + } + + [Test] + public void SetPermissionForNamespace_User_Grant() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.CreatePages, "U.User", Value.Grant)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, + Actions.ForNamespaces.CreatePages, new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)), + "SetPermissionForNamespace should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForNamespace_User_Deny() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ModifyPages, "U.User", Value.Deny)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForNamespace(AuthStatus.Deny, null, + Actions.ForNamespaces.ModifyPages, new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)), + "SetPermissionForNamespace should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForNamespace_User_Delete() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.DeleteEntry(Actions.ForNamespaces.ResourceMasterPrefix + "NS", + Actions.ForNamespaces.ReadPages, "U.User")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForNamespace(AuthStatus.Delete, new NamespaceInfo("NS", null, null), + Actions.ForNamespaces.ReadPages, new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)), + "SetPermissionForNamespace should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase("Blah", ExpectedException = typeof(ArgumentException))] + [TestCase("*")] + public void SetPermissionForNamespace_User_InvalidAction(string a) { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, a, + new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetPermissionForNamespace_User_NullUser() { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.ModifyPages, null as UserInfo); + } + + [Test] + public void SetPermissionForDirectory_Group_Grant() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry( + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/"), + Actions.ForDirectories.DownloadFiles, "G.Group", Value.Grant)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, filesProv, "/", Actions.ForDirectories.DownloadFiles, + new UserGroup("Group", "Group", null)), "SetPermissionForDirectory should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForDirectory_Group_Deny() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry( + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/"), + Actions.ForDirectories.CreateDirectories, "G.Group", Value.Deny)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForDirectory(AuthStatus.Deny, filesProv, "/", Actions.ForDirectories.CreateDirectories, + new UserGroup("Group", "Group", null)), "SetPermissionForDirectory should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForDirectory_Group_Delete() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.DeleteEntry(Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/Sub/"), + Actions.ForDirectories.List, "G.Group")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForDirectory(AuthStatus.Delete, filesProv, "/Sub/", Actions.ForDirectories.List, + new UserGroup("Group", "Group", null)), "SetPermissionForDirectory should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetPermissionForDirectory_Group_NullProvider() { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, null, "/", Actions.ForDirectories.DeleteFiles, new UserGroup("Group", "Group", null)); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void SetPermissionForDirectory_Group_InvalidDirectory(string d) { + ISettingsStorageProviderV30 prov = MockProvider(); + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, filesProv, d, Actions.ForDirectories.DownloadFiles, new UserGroup("Group", "Group", null)); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase("Blah", ExpectedException = typeof(ArgumentException))] + [TestCase("*")] + public void SetPermissionForDirectory_Group_InvalidAction(string a) { + ISettingsStorageProviderV30 prov = MockProvider(); + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, filesProv, "/", a, new UserGroup("Group", "Group", null)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetPermissionForDirectory_Group_NullGroup() { + ISettingsStorageProviderV30 prov = MockProvider(); + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, filesProv, "/", Actions.ForDirectories.DownloadFiles, null as UserGroup); + } + + [Test] + public void SetPermissionForDirectory_User_Grant() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry( + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/"), + Actions.ForDirectories.DownloadFiles, "U.User", Value.Grant)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, filesProv, "/", Actions.ForDirectories.DownloadFiles, + new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)), + "SetPermissionForDirectory should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForDirectory_User_Deny() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry( + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/"), + Actions.ForDirectories.CreateDirectories, "U.User", Value.Deny)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForDirectory(AuthStatus.Deny, filesProv, "/", Actions.ForDirectories.CreateDirectories, + new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)), + "SetPermissionForDirectory should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForDirectory_User_Delete() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.DeleteEntry(Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/Sub/"), + Actions.ForDirectories.List, "U.User")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForDirectory(AuthStatus.Delete, filesProv, "/Sub/", Actions.ForDirectories.List, + new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)), + "SetPermissionForDirectory should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetPermissionForDirectory_User_NullProvider() { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, null, "/", Actions.ForDirectories.DeleteFiles, + new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void SetPermissionForDirectory_User_InvalidDirectory(string d) { + ISettingsStorageProviderV30 prov = MockProvider(); + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, filesProv, d, Actions.ForDirectories.DownloadFiles, + new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase("Blah", ExpectedException = typeof(ArgumentException))] + [TestCase("*")] + public void SetPermissionForDirectory_User_InvalidAction(string a) { + ISettingsStorageProviderV30 prov = MockProvider(); + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, filesProv, "/", a, + new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetPermissionForDirectory_User_NullUser() { + ISettingsStorageProviderV30 prov = MockProvider(); + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, filesProv, "/", Actions.ForDirectories.DownloadFiles, null as UserInfo); + } + + [Test] + public void SetPermissionForPage_Group_Grant() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry(Actions.ForPages.ResourceMasterPrefix + "Page", + Actions.ForPages.ModifyPage, "G.Group", Value.Grant)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForPage(AuthStatus.Grant, new PageInfo("Page", null, DateTime.Now), + Actions.ForPages.ModifyPage, new UserGroup("Group", "Group", null)), "SetPermissionForPage should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForPage_Group_Deny() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry(Actions.ForPages.ResourceMasterPrefix + "Page", + Actions.ForPages.ManageCategories, "G.Group", Value.Deny)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForPage(AuthStatus.Deny, new PageInfo("Page", null, DateTime.Now), + Actions.ForPages.ManageCategories, new UserGroup("Group", "Group", null)), "SetPermissionForPage should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForPage_Group_Delete() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.DeleteEntry(Actions.ForPages.ResourceMasterPrefix + "NS.Page", + Actions.ForPages.UploadAttachments, "G.Group")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForPage(AuthStatus.Delete, new PageInfo("NS.Page", null, DateTime.Now), + Actions.ForPages.UploadAttachments, new UserGroup("Group", "Group", null)), "SetPermissionForPage should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetPermissionForPage_Group_NullPage() { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForPage(AuthStatus.Grant, null, Actions.ForPages.ModifyPage, new UserGroup("Group", "Group", null)); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase("Blah", ExpectedException = typeof(ArgumentException))] + [TestCase("*")] + public void SetPermissionForPage_Group_InvalidAction(string a) { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForPage(AuthStatus.Grant, new PageInfo("Page", null, DateTime.Now), + a, new UserGroup("Group", "Group", null)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetPermissionForPage_Group_NullGroup() { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForPage(AuthStatus.Grant, new PageInfo("Page", null, DateTime.Now), + Actions.ForPages.ModifyPage, null as UserGroup); + } + + [Test] + public void SetPermissionForPage_User_Grant() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry(Actions.ForPages.ResourceMasterPrefix + "Page", + Actions.ForPages.ModifyPage, "U.User", Value.Grant)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForPage(AuthStatus.Grant, new PageInfo("Page", null, DateTime.Now), + Actions.ForPages.ModifyPage, new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)), + "SetPermissionForPage should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForPage_User_Deny() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.StoreEntry(Actions.ForPages.ResourceMasterPrefix + "Page", + Actions.ForPages.ManageCategories, "U.User", Value.Deny)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForPage(AuthStatus.Deny, new PageInfo("Page", null, DateTime.Now), + Actions.ForPages.ManageCategories, new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)), + "SetPermissionForPage should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void SetPermissionForPage_User_Delete() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.DeleteEntry(Actions.ForPages.ResourceMasterPrefix + "NS.Page", + Actions.ForPages.UploadAttachments, "U.User")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.SetPermissionForPage(AuthStatus.Delete, new PageInfo("NS.Page", null, DateTime.Now), + Actions.ForPages.UploadAttachments, new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)), + "SetPermissionForPage should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetPermissionForPage_User_NullPage() { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForPage(AuthStatus.Grant, null, Actions.ForPages.ModifyPage, + new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase("Blah", ExpectedException = typeof(ArgumentException))] + [TestCase("*")] + public void SetPermissionForPage_User_InvalidAction(string a) { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForPage(AuthStatus.Grant, new PageInfo("Page", null, DateTime.Now), + a, new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetPermissionForPage_User_NullUser() { + ISettingsStorageProviderV30 prov = MockProvider(); + + Collectors.SettingsProvider = prov; + + AuthWriter.SetPermissionForPage(AuthStatus.Grant, new PageInfo("Page", null, DateTime.Now), + Actions.ForPages.ModifyPage, null as UserInfo); + } + + [Test] + public void RemoveEntriesForGlobals_Group() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageNamespaces, "G.Group", Value.Grant), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageGroups, "G.Group", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "G.Group", Value.Grant) }); + + Expect.Call(aclManager.DeleteEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageNamespaces, "G.Group")).Return(true); + Expect.Call(aclManager.DeleteEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageGroups, "G.Group")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.RemoveEntriesForGlobals(new UserGroup("Group", "Group", null)), "RemoveEntriesForGlobals should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveEntriesForGlobals_Group_NullGroup() { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.RemoveEntriesForGlobals(null as UserGroup); + } + + [Test] + public void RemoveEntriesForGlobals_User() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageNamespaces, "U.User", Value.Grant), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageGroups, "U.User", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "U.User", Value.Grant) }); + + Expect.Call(aclManager.DeleteEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageNamespaces, "U.User")).Return(true); + Expect.Call(aclManager.DeleteEntry(Actions.ForGlobals.ResourceMasterPrefix, + Actions.ForGlobals.ManageGroups, "U.User")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.RemoveEntriesForGlobals(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null)), "RemoveEntriesForGlobals should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveEntriesForGlobals_User_NullUser() { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.RemoveEntriesForGlobals(null as UserInfo); + } + + [Test] + public void RemoveEntriesForNamespace_Group_Root() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "G.Group", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManageCategories, "G.Group", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ModifyPages, "G.Group", Value.Grant), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageNavigationPaths, "G.Group", Value.Grant) }); + + Expect.Call(aclManager.DeleteEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ManagePages, "G.Group")).Return(true); + Expect.Call(aclManager.DeleteEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ManageCategories, "G.Group")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + Assert.IsTrue(AuthWriter.RemoveEntriesForNamespace(new UserGroup("Group", "Group", null), null), "RemoveEntriesForNamespace should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void RemoveEntriesForNamespace_Group_Sub() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManagePages, "G.Group", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManageCategories, "G.Group", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ModifyPages, "G.Group", Value.Grant), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageNavigationPaths, "G.Group", Value.Grant) }); + + Expect.Call(aclManager.DeleteEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", + Actions.ForNamespaces.ManagePages, "G.Group")).Return(true); + Expect.Call(aclManager.DeleteEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", + Actions.ForNamespaces.ManageCategories, "G.Group")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + Assert.IsTrue(AuthWriter.RemoveEntriesForNamespace(new UserGroup("Group", "Group", null), new NamespaceInfo("Sub", null, null)), "RemoveEntriesForNamespace should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveEntriesForNamespace_Group_NullGroup() { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.RemoveEntriesForNamespace(null as UserGroup, new NamespaceInfo("Sub", null, null)); + } + + [Test] + public void RemoveEntriesForNamespace_User_Root() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManagePages, "U.User", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ManageCategories, "U.User", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ModifyPages, "U.User", Value.Grant), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageNavigationPaths, "U.User", Value.Grant) }); + + Expect.Call(aclManager.DeleteEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ManagePages, "U.User")).Return(true); + Expect.Call(aclManager.DeleteEntry(Actions.ForNamespaces.ResourceMasterPrefix, + Actions.ForNamespaces.ManageCategories, "U.User")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + Assert.IsTrue(AuthWriter.RemoveEntriesForNamespace(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), null), + "RemoveEntriesForNamespace should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void RemoveEntriesForNamespace_User_Sub() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManagePages, "U.User", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", Actions.ForNamespaces.ManageCategories, "U.User", Value.Grant), + new AclEntry(Actions.ForNamespaces.ResourceMasterPrefix, Actions.ForNamespaces.ModifyPages, "U.User", Value.Grant), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageNavigationPaths, "U.User", Value.Grant) }); + + Expect.Call(aclManager.DeleteEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", + Actions.ForNamespaces.ManagePages, "U.User")).Return(true); + Expect.Call(aclManager.DeleteEntry(Actions.ForNamespaces.ResourceMasterPrefix + "Sub", + Actions.ForNamespaces.ManageCategories, "U.User")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + Assert.IsTrue(AuthWriter.RemoveEntriesForNamespace(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), + new NamespaceInfo("Sub", null, null)), "RemoveEntriesForNamespace should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveEntriesForNamespace_User_NullUser() { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.RemoveEntriesForNamespace(null as UserInfo, new NamespaceInfo("Sub", null, null)); + } + + [Test] + public void RemoveEntriesForPage_Group() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ManagePage, "G.Group", Value.Grant), + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ManageCategories, "G.Group", Value.Deny), + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Sub.Page", Actions.ForPages.ManagePage, "G.Group", Value.Grant), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageNavigationPaths, "G.Group", Value.Grant) }); + + Expect.Call(aclManager.DeleteEntry(Actions.ForPages.ResourceMasterPrefix + "Page", + Actions.ForPages.ManagePage, "G.Group")).Return(true); + Expect.Call(aclManager.DeleteEntry(Actions.ForPages.ResourceMasterPrefix + "Page", + Actions.ForPages.ManageCategories, "G.Group")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + Assert.IsTrue(AuthWriter.RemoveEntriesForPage(new UserGroup("Group", "Group", null), + new PageInfo("Page", null, DateTime.Now)), "RemoveEntriesForPage should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveEntriesForPage_Group_NullGroup() { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.RemoveEntriesForPage(null as UserGroup, new PageInfo("Page", null, DateTime.Now)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveEntriesForPage_Group_NullPage() { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.RemoveEntriesForPage(new UserGroup("Group", "Group", null), null); + } + + [Test] + public void RemoveEntriesForPage_User() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ManagePage, "U.User", Value.Grant), + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Page", Actions.ForPages.ManageCategories, "U.User", Value.Deny), + new AclEntry(Actions.ForPages.ResourceMasterPrefix + "Sub.Page", Actions.ForPages.ManagePage, "U.User", Value.Grant), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageNavigationPaths, "U.User", Value.Grant) }); + + Expect.Call(aclManager.DeleteEntry(Actions.ForPages.ResourceMasterPrefix + "Page", + Actions.ForPages.ManagePage, "U.User")).Return(true); + Expect.Call(aclManager.DeleteEntry(Actions.ForPages.ResourceMasterPrefix + "Page", + Actions.ForPages.ManageCategories, "U.User")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + Assert.IsTrue(AuthWriter.RemoveEntriesForPage(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), + new PageInfo("Page", null, DateTime.Now)), "RemoveEntriesForPage should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveEntriesForPage_User_NullUser() { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.RemoveEntriesForPage(null as UserInfo, new PageInfo("Page", null, DateTime.Now)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveEntriesForPage_User_NullPage() { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.RemoveEntriesForPage(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), null); + } + + [Test] + public void RemoveEntriesForDirectory_Root_Group() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filedProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + string dirName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filedProv, "/"); + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(dirName, Actions.ForDirectories.List, "G.Group", Value.Grant), + new AclEntry(dirName, Actions.ForDirectories.UploadFiles, "G.Group", Value.Deny), + new AclEntry("D." + AuthTools.GetDirectoryName(filedProv, "/Other/"), Actions.ForDirectories.UploadFiles, "G.Group", Value.Grant), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageNavigationPaths, "G.Group", Value.Grant) }); + + Expect.Call(aclManager.DeleteEntry(dirName, Actions.ForDirectories.List, "G.Group")).Return(true); + Expect.Call(aclManager.DeleteEntry(dirName, Actions.ForDirectories.UploadFiles, "G.Group")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + Assert.IsTrue(AuthWriter.RemoveEntriesForDirectory(new UserGroup("Group", "Group", null), + filedProv, "/"), "RemoveEntriesForPage should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void RemoveEntriesForDirectory_Sub_Group() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filedProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + string dirName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filedProv, "/Dir/Sub/"); + Expect.Call(aclManager.RetrieveEntriesForSubject("G.Group")).Return( + new AclEntry[] { + new AclEntry(dirName, Actions.ForDirectories.List, "G.Group", Value.Grant), + new AclEntry(dirName, Actions.ForDirectories.UploadFiles, "G.Group", Value.Deny), + new AclEntry("D." + AuthTools.GetDirectoryName(filedProv, "/"), Actions.ForDirectories.UploadFiles, "G.Group", Value.Grant), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageNavigationPaths, "G.Group", Value.Grant) }); + + Expect.Call(aclManager.DeleteEntry(dirName, Actions.ForDirectories.List, "G.Group")).Return(true); + Expect.Call(aclManager.DeleteEntry(dirName, Actions.ForDirectories.UploadFiles, "G.Group")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + Assert.IsTrue(AuthWriter.RemoveEntriesForDirectory(new UserGroup("Group", "Group", null), + filedProv, "/Dir/Sub/"), "RemoveEntriesForPage should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveEntriesForDirectory_Group_NullGroup() { + Collectors.SettingsProvider = MockProvider(); + + IFilesStorageProviderV30 fProv = mocks.DynamicMock(); + mocks.Replay(fProv); + AuthWriter.RemoveEntriesForDirectory(null as UserGroup, fProv, "/"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveEntriesForDirectory_Group_NullProvider() { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.RemoveEntriesForDirectory(new UserGroup("Group", "Group", null), null, "/"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RemoveEntriesForDirectory_Group_InvalidDirectory(string d) { + Collectors.SettingsProvider = MockProvider(); + + IFilesStorageProviderV30 fProv = mocks.DynamicMock(); + mocks.Replay(fProv); + AuthWriter.RemoveEntriesForDirectory(new UserGroup("Group", "Group", null), fProv, d); + } + + [Test] + public void RemoveEntriesForDirectory_Root_User() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + string dirName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/"); + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(dirName, Actions.ForDirectories.List, "U.User", Value.Grant), + new AclEntry(dirName, Actions.ForDirectories.UploadFiles, "U.User", Value.Deny), + new AclEntry("D." + AuthTools.GetDirectoryName(filesProv, "/Other/"), Actions.ForDirectories.UploadFiles, "U.User", Value.Grant), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageNavigationPaths, "U.User", Value.Grant) }); + + Expect.Call(aclManager.DeleteEntry(dirName, Actions.ForDirectories.List, "U.User")).Return(true); + Expect.Call(aclManager.DeleteEntry(dirName, Actions.ForDirectories.UploadFiles, "U.User")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + Assert.IsTrue(AuthWriter.RemoveEntriesForDirectory(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), + filesProv, "/"), "RemoveEntriesForPage should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + public void RemoveEntriesForDirectory_Sub_User() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + string dirName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/Dir/Sub/"); + Expect.Call(aclManager.RetrieveEntriesForSubject("U.User")).Return( + new AclEntry[] { + new AclEntry(dirName, Actions.ForDirectories.List, "U.User", Value.Grant), + new AclEntry(dirName, Actions.ForDirectories.UploadFiles, "U.User", Value.Deny), + new AclEntry("D." + AuthTools.GetDirectoryName(filesProv, "/"), Actions.ForDirectories.UploadFiles, "U.User", Value.Grant), + new AclEntry(Actions.ForGlobals.ResourceMasterPrefix, Actions.ForGlobals.ManageNavigationPaths, "U.User", Value.Grant) }); + + Expect.Call(aclManager.DeleteEntry(dirName, Actions.ForDirectories.List, "U.User")).Return(true); + Expect.Call(aclManager.DeleteEntry(dirName, Actions.ForDirectories.UploadFiles, "U.User")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + + Assert.IsTrue(AuthWriter.RemoveEntriesForDirectory(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), + filesProv, "/Dir/Sub/"), "RemoveEntriesForPage should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveEntriesForDirectory_User_NullUser() { + Collectors.SettingsProvider = MockProvider(); + + IFilesStorageProviderV30 fProv = mocks.DynamicMock(); + mocks.Replay(fProv); + AuthWriter.RemoveEntriesForDirectory(null as UserInfo, fProv, "/"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveEntriesForDirectory_User_NullProvider() { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.RemoveEntriesForDirectory(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), null, "/"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RemoveEntriesForDirectory_User_InvalidDirectory(string d) { + Collectors.SettingsProvider = MockProvider(); + + IFilesStorageProviderV30 fProv = mocks.DynamicMock(); + mocks.Replay(fProv); + AuthWriter.RemoveEntriesForDirectory(new UserInfo("User", "User", "user@users.com", true, DateTime.Now, null), fProv, d); + } + + [Test] + public void ClearEntriesForDirectory() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + string dirName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/Dir/Sub/"); + Expect.Call(aclManager.DeleteEntriesForResource(dirName)).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + AuthWriter.ClearEntriesForDirectory(filesProv, "/Dir/Sub/"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ClearEntriesForDirectory_NullProvider() { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.ClearEntriesForDirectory(null, "/dir/"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ClearEntriesForDirectory_InvalidDirectory(string d) { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.ClearEntriesForDirectory(MockFilesProvider(), d); + } + + [Test] + public void ClearEntriesForNamespace() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.DeleteEntriesForResource(Actions.ForNamespaces.ResourceMasterPrefix + "NS")).Return(true); + Expect.Call(aclManager.DeleteEntriesForResource(Actions.ForPages.ResourceMasterPrefix + "NS.Page1")).Return(true); + Expect.Call(aclManager.DeleteEntriesForResource(Actions.ForPages.ResourceMasterPrefix + "NS.Page2")).Return(true); + Expect.Call(aclManager.DeleteEntriesForResource(Actions.ForPages.ResourceMasterPrefix + "NS.Page3")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + AuthWriter.ClearEntriesForNamespace("NS", new List() { "Page1", "Page2", "Page3" }); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ClearEntriesForNamespace_InvalidNamespace(string n) { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.ClearEntriesForNamespace(n, new List()); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ClearEntriesForNamespace_NullPages() { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.ClearEntriesForNamespace("NS", null); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ClearEntriesForNamespace_InvalidPageElement(string p) { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.ClearEntriesForNamespace("NS", new List() { "Page", p, "Page3" }); + } + + [Test] + public void ClearEntriesForPage() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.DeleteEntriesForResource(Actions.ForPages.ResourceMasterPrefix + "Page")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + AuthWriter.ClearEntriesForPage("Page"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ClearEntriesForPage_InvalidPage(string p) { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.ClearEntriesForPage(p); + } + + [Test] + public void ProcessDirectoryRenaming() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IFilesStorageProviderV30 filesProv = MockFilesProvider(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RenameResource( + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/Dir/"), + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(filesProv, "/Dir2/"))).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.ProcessDirectoryRenaming(filesProv, "/Dir/", "/Dir2/"), "ProcessDirectoryRenaming should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ProcessDirectoryRenaming_NullProvider() { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.ProcessDirectoryRenaming(null, "/Dir/", "/Dir2/"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ProcessDirectoryRenaming_InvalidOldName(string n) { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.ProcessDirectoryRenaming(MockFilesProvider(), n, "/Dir/"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ProcessDirectoryRenaming_InvalidNewName(string n) { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.ProcessDirectoryRenaming(MockFilesProvider(), "/Dir/", n); + } + + [Test] + public void ProcessNamespaceRenaming() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RenameResource(Actions.ForNamespaces.ResourceMasterPrefix + "NS", Actions.ForNamespaces.ResourceMasterPrefix + "NS_Renamed")).Return(true); + Expect.Call(aclManager.RenameResource(Actions.ForPages.ResourceMasterPrefix + "NS.Page1", Actions.ForPages.ResourceMasterPrefix + "NS_Renamed.Page1")).Return(true); + Expect.Call(aclManager.RenameResource(Actions.ForPages.ResourceMasterPrefix + "NS.Page2", Actions.ForPages.ResourceMasterPrefix + "NS_Renamed.Page2")).Return(true); + Expect.Call(aclManager.RenameResource(Actions.ForPages.ResourceMasterPrefix + "NS.Page3", Actions.ForPages.ResourceMasterPrefix + "NS_Renamed.Page3")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.ProcessNamespaceRenaming("NS", new List() { "Page1", "Page2", "Page3" }, "NS_Renamed")); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ProcessNamespaceRenaming_InvalidOldName(string n) { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.ProcessNamespaceRenaming(n, new List(), "NS_Renamed"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ProcessNamespaceRenaming_NullPages() { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.ProcessNamespaceRenaming("NS", null, "NS_Renamed"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ProcessNamespaceRenaming_InvalidPageElement(string p) { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.ProcessNamespaceRenaming("NS", new List() { "Page", p, "Page3" }, "NS_Renamed"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ProcessNamespaceRenaming_InvalidNewName(string n) { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.ProcessNamespaceRenaming("NS", new List(), n); + } + + [Test] + public void ProcessPageRenaming() { + MockRepository mocks = new MockRepository(); + ISettingsStorageProviderV30 prov = mocks.DynamicMock(); + IAclManager aclManager = mocks.DynamicMock(); + + Expect.Call(prov.AclManager).Return(aclManager).Repeat.Any(); + + Expect.Call(aclManager.RenameResource(Actions.ForPages.ResourceMasterPrefix + "NS.Page", Actions.ForPages.ResourceMasterPrefix + "NS.Renamed")).Return(true); + + mocks.Replay(prov); + mocks.Replay(aclManager); + + Collectors.SettingsProvider = prov; + Assert.IsTrue(AuthWriter.ProcessPageRenaming("NS.Page", "NS.Renamed"), "ProcessPageRenaming should return true"); + + mocks.Verify(prov); + mocks.Verify(aclManager); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ProcessPageRenaming_InvalidOldName(string n) { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.ProcessPageRenaming(n, "NS.Renamed"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ProcessPageRenaming_InvalidNewName(string n) { + Collectors.SettingsProvider = MockProvider(); + + AuthWriter.ProcessPageRenaming("NS.Original", n); + } + + } + +} diff --git a/Core-Tests/CacheProviderTests.cs b/Core-Tests/CacheProviderTests.cs new file mode 100644 index 0000000..9e3f058 --- /dev/null +++ b/Core-Tests/CacheProviderTests.cs @@ -0,0 +1,21 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Tests { + + public class CacheProviderTests : CacheProviderTestScaffolding { + + public override ICacheProviderV30 GetProvider() { + CacheProvider prov = new CacheProvider(); + prov.Init(MockHost(), ""); + return prov; + } + + } + +} diff --git a/Core-Tests/Core-Tests.csproj b/Core-Tests/Core-Tests.csproj new file mode 100644 index 0000000..2560516 --- /dev/null +++ b/Core-Tests/Core-Tests.csproj @@ -0,0 +1,106 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {013B5DA5-76F9-4D7F-A174-4926BF51E24B} + Library + Properties + ScrewTurn.Wiki.Tests + ScrewTurn.Wiki.Core.Tests + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + none + true + bin\Release\ + TRACE + prompt + 4 + true + + + + False + ..\References\Tools\NUnit\framework\nunit.framework.dll + + + False + ..\References\Tools\Rhino.Mocks\Rhino.Mocks.dll + + + + 3.5 + + + + + + + + AssemblyVersion.cs + + + TestsBase.cs + + + + + + + + + + + + + + + + + + + + + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81} + AclEngine + + + {C353A35C-86D0-4154-9500-4F88CAAB29C3} + Core + + + {531A83D6-76F9-4014-91C5-295818E2D948} + PluginFramework + + + {2DF980A6-4742-49B1-A090-DE79314644D0} + SearchEngine + + + {F865670A-DEDE-41B5-B426-48D73C3B5B1C} + TestScaffolding + + + + + \ No newline at end of file diff --git a/Core-Tests/DataMigratorTests.cs b/Core-Tests/DataMigratorTests.cs new file mode 100644 index 0000000..8f2a7e0 --- /dev/null +++ b/Core-Tests/DataMigratorTests.cs @@ -0,0 +1,647 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using RMC = Rhino.Mocks.Constraints; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.AclEngine; + +namespace ScrewTurn.Wiki.Tests { + + [TestFixture] + public class DataMigratorTests { + + [Test] + public void MigratePagesStorageProviderData() { + MockRepository mocks = new MockRepository(); + + IPagesStorageProviderV30 source = mocks.StrictMock(); + IPagesStorageProviderV30 destination = mocks.StrictMock(); + + // Setup SOURCE ------------------------- + + // Setup snippets + Snippet s1 = new Snippet("S1", "Blah1", source); + Snippet s2 = new Snippet("S2", "Blah2", source); + Expect.Call(source.GetSnippets()).Return(new Snippet[] { s1, s2 }); + + // Setup content templates + ContentTemplate ct1 = new ContentTemplate("CT1", "Template 1", source); + ContentTemplate ct2 = new ContentTemplate("CT2", "Template 2", source); + Expect.Call(source.GetContentTemplates()).Return(new ContentTemplate[] { ct1, ct2 }); + + // Setup namespaces + NamespaceInfo ns1 = new NamespaceInfo("NS1", source, null); + NamespaceInfo ns2 = new NamespaceInfo("NS2", source, null); + Expect.Call(source.GetNamespaces()).Return(new NamespaceInfo[] { ns1, ns2 }); + + // Setup pages + PageInfo p1 = new PageInfo("Page", source, DateTime.Now); + PageInfo p2 = new PageInfo(NameTools.GetFullName(ns1.Name, "Page"), source, DateTime.Now); + PageInfo p3 = new PageInfo(NameTools.GetFullName(ns1.Name, "Page1"), source, DateTime.Now); + Expect.Call(source.GetPages(null)).Return(new PageInfo[] { p1 }); + Expect.Call(source.GetPages(ns1)).Return(new PageInfo[] { p2, p3 }); + Expect.Call(source.GetPages(ns2)).Return(new PageInfo[0]); + + // Set default page for NS1 + ns1.DefaultPage = p2; + + // Setup categories/bindings + CategoryInfo c1 = new CategoryInfo("Cat", source); + c1.Pages = new string[] { p1.FullName }; + CategoryInfo c2 = new CategoryInfo(NameTools.GetFullName(ns1.Name, "Cat"), source); + c2.Pages = new string[] { p2.FullName }; + CategoryInfo c3 = new CategoryInfo(NameTools.GetFullName(ns1.Name, "Cat1"), source); + c3.Pages = new string[0]; + Expect.Call(source.GetCategories(null)).Return(new CategoryInfo[] { c1 }); + Expect.Call(source.GetCategories(ns1)).Return(new CategoryInfo[] { c2, c3 }); + Expect.Call(source.GetCategories(ns2)).Return(new CategoryInfo[0]); + + // Setup drafts + PageContent d1 = new PageContent(p1, "Draft", "NUnit", DateTime.Now, "Comm", "Cont", new string[] { "k1", "k2" }, "Descr"); + Expect.Call(source.GetDraft(p1)).Return(d1); + Expect.Call(source.GetDraft(p2)).Return(null); + Expect.Call(source.GetDraft(p3)).Return(null); + + // Setup content + PageContent ctn1 = new PageContent(p1, "Title1", "User1", DateTime.Now, "Comm1", "Cont1", null, "Descr1"); + PageContent ctn2 = new PageContent(p2, "Title2", "User2", DateTime.Now, "Comm2", "Cont2", null, "Descr2"); + PageContent ctn3 = new PageContent(p3, "Title3", "User3", DateTime.Now, "Comm3", "Cont3", null, "Descr3"); + Expect.Call(source.GetContent(p1)).Return(ctn1); + Expect.Call(source.GetContent(p2)).Return(ctn2); + Expect.Call(source.GetContent(p3)).Return(ctn3); + + // Setup backups + Expect.Call(source.GetBackups(p1)).Return(new int[] { 0, 1 }); + Expect.Call(source.GetBackups(p2)).Return(new int[] { 0 }); + Expect.Call(source.GetBackups(p3)).Return(new int[0]); + PageContent bak1_0 = new PageContent(p1, "K1_0", "U1_0", DateTime.Now, "", "Cont", null, null); + PageContent bak1_1 = new PageContent(p1, "K1_1", "U1_1", DateTime.Now, "", "Cont", null, null); + PageContent bak2_0 = new PageContent(p2, "K2_0", "U2_0", DateTime.Now, "", "Cont", null, null); + Expect.Call(source.GetBackupContent(p1, 0)).Return(bak1_0); + Expect.Call(source.GetBackupContent(p1, 1)).Return(bak1_1); + Expect.Call(source.GetBackupContent(p2, 0)).Return(bak2_0); + + // Messages + Message m1 = new Message(1, "User1", "Subject1", DateTime.Now, "Body1"); + m1.Replies = new Message[] { new Message(2, "User2", "Subject2", DateTime.Now, "Body2") }; + Message[] p1m = new Message[] { m1 }; + Message[] p2m = new Message[0]; + Message[] p3m = new Message[0]; + Expect.Call(source.GetMessages(p1)).Return(p1m); + Expect.Call(source.GetMessages(p2)).Return(p2m); + Expect.Call(source.GetMessages(p3)).Return(p3m); + + // Setup navigation paths + NavigationPath n1 = new NavigationPath("N1", source); + n1.Pages = new string[] { p1.FullName }; + NavigationPath n2 = new NavigationPath(NameTools.GetFullName(ns1.Name, "N1"), source); + n2.Pages = new string[] { p2.FullName, p3.FullName }; + Expect.Call(source.GetNavigationPaths(null)).Return(new NavigationPath[] { n1 }); + Expect.Call(source.GetNavigationPaths(ns1)).Return(new NavigationPath[] { n2 }); + Expect.Call(source.GetNavigationPaths(ns2)).Return(new NavigationPath[0]); + + // Setup DESTINATION -------------------------- + + // Snippets + Expect.Call(destination.AddSnippet(s1.Name, s1.Content)).Return(new Snippet(s1.Name, s1.Content, destination)); + Expect.Call(source.RemoveSnippet(s1.Name)).Return(true); + Expect.Call(destination.AddSnippet(s2.Name, s2.Content)).Return(new Snippet(s2.Name, s2.Content, destination)); + Expect.Call(source.RemoveSnippet(s2.Name)).Return(true); + + // Content templates + Expect.Call(destination.AddContentTemplate(ct1.Name, ct1.Content)).Return(new ContentTemplate(ct1.Name, ct1.Name, destination)); + Expect.Call(source.RemoveContentTemplate(ct1.Name)).Return(true); + Expect.Call(destination.AddContentTemplate(ct2.Name, ct2.Content)).Return(new ContentTemplate(ct2.Name, ct2.Name, destination)); + Expect.Call(source.RemoveContentTemplate(ct2.Name)).Return(true); + + // Namespaces + NamespaceInfo ns1Out = new NamespaceInfo(ns1.Name, destination, null); + NamespaceInfo ns2Out = new NamespaceInfo(ns2.Name, destination, null); + Expect.Call(destination.AddNamespace(ns1.Name)).Return(ns1Out); + Expect.Call(source.RemoveNamespace(ns1)).Return(true); + Expect.Call(destination.AddNamespace(ns2.Name)).Return(ns2Out); + Expect.Call(source.RemoveNamespace(ns2)).Return(true); + + // Pages/drafts/content/backups/messages + PageInfo p1Out = new PageInfo(p1.FullName, destination, p1.CreationDateTime); + Expect.Call(destination.AddPage(null, p1.FullName, p1.CreationDateTime)).Return(p1Out); + Expect.Call(destination.ModifyPage(p1Out, ctn1.Title, ctn1.User, ctn1.LastModified, ctn1.Comment, ctn1.Content, ctn1.Keywords, ctn1.Description, SaveMode.Normal)).Return(true); + Expect.Call(destination.ModifyPage(p1Out, d1.Title, d1.User, d1.LastModified, d1.Comment, d1.Content, d1.Keywords, d1.Description, SaveMode.Draft)).Return(true); + Expect.Call(destination.SetBackupContent(bak1_0, 0)).Return(true); + Expect.Call(destination.SetBackupContent(bak1_1, 1)).Return(true); + Expect.Call(destination.BulkStoreMessages(p1Out, p1m)).Return(true); + Expect.Call(source.RemovePage(p1)).Return(true); + + PageInfo p2Out = new PageInfo(p2.FullName, destination, p2.CreationDateTime); + Expect.Call(destination.AddPage(NameTools.GetNamespace(p2.FullName), NameTools.GetLocalName(p2.FullName), + p2.CreationDateTime)).Return(p2Out); + Expect.Call(destination.ModifyPage(p2Out, ctn2.Title, ctn2.User, ctn2.LastModified, ctn2.Comment, ctn2.Content, ctn2.Keywords, ctn2.Description, SaveMode.Normal)).Return(true); + Expect.Call(destination.SetBackupContent(bak2_0, 0)).Return(true); + Expect.Call(destination.BulkStoreMessages(p2Out, p2m)).Return(true); + Expect.Call(source.RemovePage(p2)).Return(true); + + PageInfo p3Out = new PageInfo(p3.FullName, destination, p3.CreationDateTime); + Expect.Call(destination.AddPage(NameTools.GetNamespace(p3.FullName), NameTools.GetLocalName(p3.FullName), + p3.CreationDateTime)).Return(p3Out); + Expect.Call(destination.ModifyPage(p3Out, ctn3.Title, ctn3.User, ctn3.LastModified, ctn3.Comment, ctn3.Content, ctn3.Keywords, ctn3.Description, SaveMode.Normal)).Return(true); + Expect.Call(destination.BulkStoreMessages(p3Out, p3m)).Return(true); + Expect.Call(source.RemovePage(p3)).Return(true); + + // Categories/bindings + CategoryInfo c1Out = new CategoryInfo(c1.FullName, destination); + CategoryInfo c2Out = new CategoryInfo(c2.FullName, destination); + CategoryInfo c3Out = new CategoryInfo(c3.FullName, destination); + Expect.Call(destination.AddCategory(null, c1.FullName)).Return(c1Out); + Expect.Call(destination.AddCategory(NameTools.GetNamespace(c2.FullName), NameTools.GetLocalName(c2.FullName))).Return(c2Out); + Expect.Call(destination.AddCategory(NameTools.GetNamespace(c3.FullName), NameTools.GetLocalName(c3.FullName))).Return(c3Out); + Expect.Call(destination.RebindPage(p1Out, new string[] { c1.FullName })).Return(true); + Expect.Call(destination.RebindPage(p2Out, new string[] { c2.FullName })).Return(true); + Expect.Call(destination.RebindPage(p3Out, new string[0])).Return(true); + Expect.Call(source.RemoveCategory(c1)).Return(true); + Expect.Call(source.RemoveCategory(c2)).Return(true); + Expect.Call(source.RemoveCategory(c3)).Return(true); + + // Navigation paths + NavigationPath n1Out = new NavigationPath(n1.FullName, destination); + n1Out.Pages = n1.Pages; + NavigationPath n2Out = new NavigationPath(n2.FullName, destination); + n2Out.Pages = n2.Pages; + + Expect.Call(destination.AddNavigationPath(null, n1.FullName, new PageInfo[] { p1 })).Return(n1Out).Constraints( + RMC.Is.Null(), RMC.Is.Equal(n1.FullName), + RMC.Is.Matching(delegate(PageInfo[] array) { + return array[0].FullName == p1.FullName; + })); + + Expect.Call(destination.AddNavigationPath(NameTools.GetNamespace(n2.FullName), NameTools.GetLocalName(n2.FullName), new PageInfo[] { p2, p3 })).Return(n2Out).Constraints( + RMC.Is.Equal(NameTools.GetNamespace(n2.FullName)), RMC.Is.Equal(NameTools.GetLocalName(n2.FullName)), + RMC.Is.Matching(delegate(PageInfo[] array) { + return array[0].FullName == p2.FullName && array[1].FullName == p3.FullName; + })); + + Expect.Call(source.RemoveNavigationPath(n1)).Return(true); + Expect.Call(source.RemoveNavigationPath(n2)).Return(true); + + Expect.Call(destination.SetNamespaceDefaultPage(ns1Out, p2Out)).Return(ns1Out); + Expect.Call(destination.SetNamespaceDefaultPage(ns2Out, null)).Return(ns2Out); + + // Used for navigation paths + Expect.Call(destination.GetPages(null)).Return(new PageInfo[] { p1Out }); + Expect.Call(destination.GetPages(ns1Out)).Return(new PageInfo[] { p2Out, p3Out }); + Expect.Call(destination.GetPages(ns2Out)).Return(new PageInfo[0]); + + mocks.Replay(source); + mocks.Replay(destination); + + DataMigrator.MigratePagesStorageProviderData(source, destination); + + mocks.Verify(source); + mocks.Verify(destination); + } + + [Test] + public void MigrateUsersStorageProviderData() { + MockRepository mocks = new MockRepository(); + + IUsersStorageProviderV30 source = mocks.StrictMock(); + IUsersStorageProviderV30 destination = mocks.StrictMock(); + + // Setup SOURCE -------------------- + + // User groups + UserGroup g1 = new UserGroup("G1", "G1", source); + UserGroup g2 = new UserGroup("G2", "G2", source); + Expect.Call(source.GetUserGroups()).Return(new UserGroup[] { g1, g2 }); + + // Users + UserInfo u1 = new UserInfo("U1", "U1", "u1@users.com", true, DateTime.Now, source); + UserInfo u2 = new UserInfo("U2", "U2", "u2@users.com", true, DateTime.Now, source); + UserInfo u3 = new UserInfo("U3", "U3", "u3@users.com", true, DateTime.Now, source); + Expect.Call(source.GetUsers()).Return(new UserInfo[] { u1, u2, u3 }); + + // Membership + g1.Users = new string[] { u1.Username, u2.Username }; + g2.Users = new string[] { u2.Username, u3.Username }; + u1.Groups = new string[] { g1.Name }; + u2.Groups = new string[] { g1.Name, g2.Name }; + u3.Groups = new string[] { g2.Name }; + + // User data + IDictionary u1Data = new Dictionary() { + { "Key1", "Value1" }, + { "Key2", "Value2" } + }; + Expect.Call(source.RetrieveAllUserData(u1)).Return(u1Data); + Expect.Call(source.RetrieveAllUserData(u2)).Return(new Dictionary()); + Expect.Call(source.RetrieveAllUserData(u3)).Return(new Dictionary()); + + // Setup DESTINATION ------------------ + + // User groups + UserGroup g1Out = new UserGroup(g1.Name, g1.Description, destination); + UserGroup g2Out = new UserGroup(g2.Name, g2.Description, destination); + Expect.Call(destination.AddUserGroup(g1.Name, g1.Description)).Return(g1Out); + Expect.Call(destination.AddUserGroup(g2.Name, g2.Description)).Return(g2Out); + + // Users + UserInfo u1Out = new UserInfo(u1.Username, u1.DisplayName, u1.Email, u1.Active, u1.DateTime, destination); + UserInfo u2Out = new UserInfo(u2.Username, u2.DisplayName, u2.Email, u2.Active, u2.DateTime, destination); + UserInfo u3Out = new UserInfo(u3.Username, u3.DisplayName, u3.Email, u3.Active, u3.DateTime, destination); + Expect.Call(destination.AddUser(u1.Username, u1.DisplayName, null, u1.Email, u1.Active, u1.DateTime)).Return(u1Out).Constraints( + RMC.Is.Equal(u1.Username), RMC.Is.Equal(u1.DisplayName), RMC.Is.Anything(), RMC.Is.Equal(u1.Email), RMC.Is.Equal(u1.Active), RMC.Is.Equal(u1.DateTime)); + Expect.Call(destination.AddUser(u2.Username, u2.DisplayName, null, u2.Email, u2.Active, u2.DateTime)).Return(u2Out).Constraints( + RMC.Is.Equal(u2.Username), RMC.Is.Equal(u2.DisplayName), RMC.Is.Anything(), RMC.Is.Equal(u2.Email), RMC.Is.Equal(u2.Active), RMC.Is.Equal(u2.DateTime)); + Expect.Call(destination.AddUser(u3.Username, u3.DisplayName, null, u3.Email, u3.Active, u3.DateTime)).Return(u3Out).Constraints( + RMC.Is.Equal(u3.Username), RMC.Is.Equal(u3.DisplayName), RMC.Is.Anything(), RMC.Is.Equal(u3.Email), RMC.Is.Equal(u3.Active), RMC.Is.Equal(u3.DateTime)); + + // Membership + Expect.Call(destination.SetUserMembership(u1Out, u1.Groups)).Return(u1Out); + Expect.Call(destination.SetUserMembership(u2Out, u2.Groups)).Return(u2Out); + Expect.Call(destination.SetUserMembership(u3Out, u3.Groups)).Return(u3Out); + + // User data + Expect.Call(destination.StoreUserData(u1Out, "Key1", "Value1")).Return(true); + Expect.Call(destination.StoreUserData(u1Out, "Key2", "Value2")).Return(true); + + // Delete source data + Expect.Call(source.RemoveUser(u1)).Return(true); + Expect.Call(source.RemoveUser(u2)).Return(true); + Expect.Call(source.RemoveUser(u3)).Return(true); + Expect.Call(source.RemoveUserGroup(g1)).Return(true); + Expect.Call(source.RemoveUserGroup(g2)).Return(true); + + mocks.Replay(source); + mocks.Replay(destination); + + DataMigrator.MigrateUsersStorageProviderData(source, destination, false); + + mocks.Verify(source); + mocks.Verify(destination); + } + + private delegate bool RetrieveFile(string file, Stream stream, bool count); + private delegate bool RetrieveAttachment(PageInfo page, string file, Stream stream, bool count); + private delegate bool StoreFile(string file, Stream stream, bool overwrite); + private delegate bool StoreAttachment(PageInfo page, string file, Stream stream, bool overwrite); + + [Test] + public void MigrateFilesStorageProviderData() { + MockRepository mocks = new MockRepository(); + + IFilesStorageProviderV30 source = mocks.StrictMock(); + IFilesStorageProviderV30 destination = mocks.StrictMock(); + ISettingsStorageProviderV30 settingsProvider = mocks.StrictMock(); + IAclManager aclManager = mocks.StrictMock(); + Expect.Call(settingsProvider.AclManager).Return(aclManager).Repeat.Any(); + + // Setup SOURCE ----------------- + + // Directories + Expect.Call(source.ListDirectories("/")).Return(new string[] { "/Dir1/", "/Dir2/" }); + Expect.Call(source.ListDirectories("/Dir1/")).Return(new string[] { "/Dir1/Sub/" }); + Expect.Call(source.ListDirectories("/Dir2/")).Return(new string[0]); + Expect.Call(source.ListDirectories("/Dir1/Sub/")).Return(new string[0]); + + // Settings (permissions) + Expect.Call(aclManager.RenameResource( + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(source, "/"), + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(destination, "/"))).Return(true); + Expect.Call(aclManager.RenameResource( + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(source, "/Dir1/"), + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(destination, "/Dir1/"))).Return(true); + Expect.Call(aclManager.RenameResource( + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(source, "/Dir2/"), + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(destination, "/Dir2/"))).Return(true); + Expect.Call(aclManager.RenameResource( + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(source, "/Dir1/Sub/"), + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(destination, "/Dir1/Sub/"))).Return(true); + + // Filenames + Expect.Call(source.ListFiles("/")).Return(new string[] { "/File1.txt", "/File2.txt" }); + Expect.Call(source.ListFiles("/Dir1/")).Return(new string[] { "/Dir1/File.txt" }); + Expect.Call(source.ListFiles("/Dir2/")).Return(new string[0]); + Expect.Call(source.ListFiles("/Dir1/Sub/")).Return(new string[] { "/Dir1/Sub/File.txt" }); + + // File content + Expect.Call(source.RetrieveFile("/File1.txt", null, false)).Constraints( + RMC.Is.Equal("/File1.txt"), RMC.Is.TypeOf(), RMC.Is.Equal(false)).Do( + new RetrieveFile( + delegate(string file, Stream stream, bool count) { + byte[] stuff = Encoding.Unicode.GetBytes("content1"); + stream.Write(stuff, 0, stuff.Length); + return true; + })); + + Expect.Call(source.RetrieveFile("/File2.txt", null, false)).Constraints( + RMC.Is.Equal("/File2.txt"), RMC.Is.TypeOf(), RMC.Is.Equal(false)).Do( + new RetrieveFile( + delegate(string file, Stream stream, bool count) { + byte[] stuff = Encoding.Unicode.GetBytes("content2"); + stream.Write(stuff, 0, stuff.Length); + return true; + })); + + Expect.Call(source.RetrieveFile("/Dir1/File.txt", null, false)).Constraints( + RMC.Is.Equal("/Dir1/File.txt"), RMC.Is.TypeOf(), RMC.Is.Equal(false)).Do( + new RetrieveFile( + delegate(string file, Stream stream, bool count) { + byte[] stuff = Encoding.Unicode.GetBytes("content3"); + stream.Write(stuff, 0, stuff.Length); + return true; + })); + + Expect.Call(source.RetrieveFile("/Dir1/Sub/File.txt", null, false)).Constraints( + RMC.Is.Equal("/Dir1/Sub/File.txt"), RMC.Is.TypeOf(), RMC.Is.Equal(false)).Do( + new RetrieveFile( + delegate(string file, Stream stream, bool count) { + byte[] stuff = Encoding.Unicode.GetBytes("content4"); + stream.Write(stuff, 0, stuff.Length); + return true; + })); + + // File details + Expect.Call(source.GetFileDetails("/File1.txt")).Return(new FileDetails(8, DateTime.Now, 52)); + Expect.Call(source.GetFileDetails("/File2.txt")).Return(new FileDetails(8, DateTime.Now, 0)); + Expect.Call(source.GetFileDetails("/Dir1/File.txt")).Return(new FileDetails(8, DateTime.Now, 21)); + Expect.Call(source.GetFileDetails("/Dir1/Sub/File.txt")).Return(new FileDetails(8, DateTime.Now, 123)); + + // Page attachments + Expect.Call(source.GetPagesWithAttachments()).Return(new string[] { "MainPage", "Sub.Page", "Sub.Another" }); + Expect.Call(source.ListPageAttachments(null)).Constraints(RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "MainPage"; })).Return(new string[] { "Attachment.txt" }); + Expect.Call(source.ListPageAttachments(null)).Constraints(RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "Sub.Page"; })).Return(new string[] { "Attachment2.txt" }); + Expect.Call(source.ListPageAttachments(null)).Constraints(RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "Sub.Another"; })).Return(new string[0]); + + // Page attachment content + Expect.Call(source.RetrievePageAttachment(null, "Attachment.txt", null, false)).Constraints( + RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "MainPage"; }), RMC.Is.Equal("Attachment.txt"), RMC.Is.TypeOf(), RMC.Is.Equal(false)).Do( + new RetrieveAttachment( + delegate(PageInfo page, string name, Stream stream, bool count) { + byte[] stuff = Encoding.Unicode.GetBytes("content5"); + stream.Write(stuff, 0, stuff.Length); + return true; + })); + + Expect.Call(source.RetrievePageAttachment(null, "Attachment2.txt", null, false)).Constraints( + RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "Sub.Page"; }), RMC.Is.Equal("Attachment2.txt"), RMC.Is.TypeOf(), RMC.Is.Equal(false)).Do( + new RetrieveAttachment( + delegate(PageInfo page, string name, Stream stream, bool count) { + byte[] stuff = Encoding.Unicode.GetBytes("content6"); + stream.Write(stuff, 0, stuff.Length); + return true; + })); + + // Attachment details + Expect.Call(source.GetPageAttachmentDetails(null, "Attachment.txt")).Constraints( + RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "MainPage"; }), RMC.Is.Equal("Attachment.txt")).Return(new FileDetails(8, DateTime.Now, 8)); + Expect.Call(source.GetPageAttachmentDetails(null, "Attachment2.txt")).Constraints( + RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "Sub.Page"; }), RMC.Is.Equal("Attachment2.txt")).Return(new FileDetails(8, DateTime.Now, 29)); + + // Setup DESTINATION ------------------------ + + // Directories + Expect.Call(destination.CreateDirectory("/", "Dir1")).Return(true); + Expect.Call(destination.CreateDirectory("/", "Dir2")).Return(true); + Expect.Call(destination.CreateDirectory("/Dir1/", "Sub")).Return(true); + + // Files + Expect.Call(destination.StoreFile("/File1.txt", null, false)).Constraints( + RMC.Is.Equal("/File1.txt"), RMC.Is.TypeOf(), RMC.Is.Equal(false)).Do(new StoreFile( + delegate(string name, Stream stream, bool overwrite) { + byte[] buff = new byte[512]; + int read = stream.Read(buff, 0, (int)stream.Length); + Assert.AreEqual("content1", Encoding.Unicode.GetString(buff, 0, read), "Wrong data"); + return true; + })); + + Expect.Call(destination.StoreFile("/File2.txt", null, false)).Constraints( + RMC.Is.Equal("/File2.txt"), RMC.Is.TypeOf(), RMC.Is.Equal(false)).Do(new StoreFile( + delegate(string name, Stream stream, bool overwrite) { + byte[] buff = new byte[512]; + int read = stream.Read(buff, 0, (int)stream.Length); + Assert.AreEqual("content2", Encoding.Unicode.GetString(buff, 0, read), "Wrong data"); + return true; + })); + + Expect.Call(destination.StoreFile("/Dir1/File.txt", null, false)).Constraints( + RMC.Is.Equal("/Dir1/File.txt"), RMC.Is.TypeOf(), RMC.Is.Equal(false)).Do(new StoreFile( + delegate(string name, Stream stream, bool overwrite) { + byte[] buff = new byte[512]; + int read = stream.Read(buff, 0, (int)stream.Length); + Assert.AreEqual("content3", Encoding.Unicode.GetString(buff, 0, read), "Wrong data"); + return true; + })); + + Expect.Call(destination.StoreFile("/Dir1/Sub/File.txt", null, false)).Constraints( + RMC.Is.Equal("/Dir1/Sub/File.txt"), RMC.Is.TypeOf(), RMC.Is.Equal(false)).Do(new StoreFile( + delegate(string name, Stream stream, bool overwrite) { + byte[] buff = new byte[512]; + int read = stream.Read(buff, 0, (int)stream.Length); + Assert.AreEqual("content4", Encoding.Unicode.GetString(buff, 0, read), "Wrong data"); + return true; + })); + + // File retrieval count + destination.SetFileRetrievalCount("/File1.txt", 52); + LastCall.On(destination).Repeat.Once(); + destination.SetFileRetrievalCount("/File2.txt", 0); + LastCall.On(destination).Repeat.Once(); + destination.SetFileRetrievalCount("/Dir1/File.txt", 21); + LastCall.On(destination).Repeat.Once(); + destination.SetFileRetrievalCount("/Dir1/Sub/File.txt", 123); + LastCall.On(destination).Repeat.Once(); + + // Page attachments + Expect.Call(destination.StorePageAttachment(null, "Attachment.txt", null, false)).Constraints( + RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "MainPage"; }), RMC.Is.Equal("Attachment.txt"), RMC.Is.TypeOf(), RMC.Is.Equal(false)).Do(new StoreAttachment( + delegate(PageInfo page, string name, Stream stream, bool overwrite) { + byte[] buff = new byte[512]; + int read = stream.Read(buff, 0, (int)stream.Length); + Assert.AreEqual("content5", Encoding.Unicode.GetString(buff, 0, read), "Wrong data"); + return true; + })); + + Expect.Call(destination.StorePageAttachment(null, "Attachment2.txt", null, false)).Constraints( + RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "Sub.Page"; }), RMC.Is.Equal("Attachment2.txt"), RMC.Is.TypeOf(), RMC.Is.Equal(false)).Do(new StoreAttachment( + delegate(PageInfo page, string name, Stream stream, bool overwrite) { + byte[] buff = new byte[512]; + int read = stream.Read(buff, 0, (int)stream.Length); + Assert.AreEqual("content6", Encoding.Unicode.GetString(buff, 0, read), "Wrong data"); + return true; + })); + + // Attachment retrieval count + destination.SetPageAttachmentRetrievalCount(null, "Attachment.txt", 8); + LastCall.On(destination).Constraints(RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "MainPage"; }), RMC.Is.Equal("Attachment.txt"), RMC.Is.Equal(8)).Repeat.Once(); + destination.SetPageAttachmentRetrievalCount(null, "Attachment2.txt", 29); + LastCall.On(destination).Constraints(RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "Sub.Page"; }), RMC.Is.Equal("Attachment2.txt"), RMC.Is.Equal(29)).Repeat.Once(); + + // Delete source content + Expect.Call(source.DeleteFile("/File1.txt")).Return(true); + Expect.Call(source.DeleteFile("/File2.txt")).Return(true); + Expect.Call(source.DeleteDirectory("/Dir1/")).Return(true); + Expect.Call(source.DeleteDirectory("/Dir2/")).Return(true); + + Expect.Call(source.DeletePageAttachment(null, "Attachment.aspx")).Constraints(RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "MainPage"; }), RMC.Is.Equal("Attachment.txt")).Return(true); + Expect.Call(source.DeletePageAttachment(null, "Attachment2.aspx")).Constraints(RMC.Is.Matching(delegate(PageInfo p) { return p.FullName == "Sub.Page"; }), RMC.Is.Equal("Attachment2.txt")).Return(true); + + mocks.Replay(source); + mocks.Replay(destination); + mocks.Replay(settingsProvider); + mocks.Replay(aclManager); + + DataMigrator.MigrateFilesStorageProviderData(source, destination, settingsProvider); + + mocks.Verify(source); + mocks.Verify(destination); + mocks.Verify(settingsProvider); + mocks.Verify(aclManager); + } + + [Test] + public void CopySettingsStorageProviderData() { + MockRepository mocks = new MockRepository(); + + ISettingsStorageProviderV30 source = mocks.StrictMock(); + ISettingsStorageProviderV30 destination = mocks.StrictMock(); + IAclManager sourceAclManager = mocks.StrictMock(); + IAclManager destinationAclManager = mocks.StrictMock(); + + // Setup SOURCE --------------------- + + // Settings + Dictionary settings = new Dictionary() { + { "Set1", "Value1" }, + { "Set2", "Value2" } + }; + Expect.Call(source.GetAllSettings()).Return(settings); + + // Meta-data (global) + Expect.Call(source.GetMetaDataItem(MetaDataItem.AccountActivationMessage, null)).Return("AAM"); + Expect.Call(source.GetMetaDataItem(MetaDataItem.PasswordResetProcedureMessage, null)).Return("PRM"); + Expect.Call(source.GetMetaDataItem(MetaDataItem.LoginNotice, null)).Return(""); + Expect.Call(source.GetMetaDataItem(MetaDataItem.PageChangeMessage, null)).Return("PCM"); + Expect.Call(source.GetMetaDataItem(MetaDataItem.DiscussionChangeMessage, null)).Return("DCM"); + + // Meta-data (root) + Expect.Call(source.GetMetaDataItem(MetaDataItem.EditNotice, null)).Return(""); + Expect.Call(source.GetMetaDataItem(MetaDataItem.Footer, null)).Return("FOOT"); + Expect.Call(source.GetMetaDataItem(MetaDataItem.Header, null)).Return("HEADER"); + Expect.Call(source.GetMetaDataItem(MetaDataItem.HtmlHead, null)).Return("HTML"); + Expect.Call(source.GetMetaDataItem(MetaDataItem.PageFooter, null)).Return("P_FOOT"); + Expect.Call(source.GetMetaDataItem(MetaDataItem.PageHeader, null)).Return("P_HEADER"); + Expect.Call(source.GetMetaDataItem(MetaDataItem.Sidebar, null)).Return("SIDEBAR"); + + // Meta-data ("NS" namespace) + Expect.Call(source.GetMetaDataItem(MetaDataItem.EditNotice, "NS")).Return("NS_EDIT"); + Expect.Call(source.GetMetaDataItem(MetaDataItem.Footer, "NS")).Return("NS_FOOT"); + Expect.Call(source.GetMetaDataItem(MetaDataItem.Header, "NS")).Return("NS_HEADER"); + Expect.Call(source.GetMetaDataItem(MetaDataItem.HtmlHead, "NS")).Return("NS_HTML"); + Expect.Call(source.GetMetaDataItem(MetaDataItem.PageFooter, "NS")).Return("NS_P_FOOT"); + Expect.Call(source.GetMetaDataItem(MetaDataItem.PageHeader, "NS")).Return("NS_P_HEADER"); + Expect.Call(source.GetMetaDataItem(MetaDataItem.Sidebar, "NS")).Return("NS_SIDEBAR"); + + // Plugin assemblies + byte[] asm1 = new byte[] { 1, 2, 3, 4, 5 }; + byte[] asm2 = new byte[] { 6, 7, 8, 9, 10, 11, 12 }; + Expect.Call(source.ListPluginAssemblies()).Return(new string[] { "Plugins1.dll", "Plugins2.dll" }); + Expect.Call(source.RetrievePluginAssembly("Plugins1.dll")).Return(asm1); + Expect.Call(source.RetrievePluginAssembly("Plugins2.dll")).Return(asm2); + + // Plugin status + Expect.Call(source.GetPluginStatus("Test1.Plugin1")).Return(true); + Expect.Call(source.GetPluginStatus("Test2.Plugin2")).Return(false); + + // Plugin config + Expect.Call(source.GetPluginConfiguration("Test1.Plugin1")).Return("Config1"); + Expect.Call(source.GetPluginConfiguration("Test2.Plugin2")).Return(""); + + // Outgoing links + Dictionary outgoingLinks = new Dictionary() { + { "Page1", new string[] { "Page2", "Page3" } }, + { "Page2", new string[] { "Page3" } }, + { "Page3", new string[] { "Page4", "Page3" } } + }; + Expect.Call(source.GetAllOutgoingLinks()).Return(outgoingLinks); + + // ACLs + Expect.Call(source.AclManager).Return(sourceAclManager); + AclEntry[] entries = new AclEntry[] { + new AclEntry("Res1", "Act1", "Subj1", Value.Grant), + new AclEntry("Res2", "Act2", "Subj2", Value.Deny) + }; + Expect.Call(sourceAclManager.RetrieveAllEntries()).Return(entries); + + // Setup DESTINATION ----------------- + + // Settings + destination.BeginBulkUpdate(); + LastCall.On(destination).Repeat.Once(); + foreach(KeyValuePair pair in settings) { + Expect.Call(destination.SetSetting(pair.Key, pair.Value)).Return(true); + } + destination.EndBulkUpdate(); + LastCall.On(destination).Repeat.Once(); + + // Meta-data (global) + Expect.Call(destination.SetMetaDataItem(MetaDataItem.AccountActivationMessage, null, "AAM")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.PasswordResetProcedureMessage, null, "PRM")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.LoginNotice, null, "")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.PageChangeMessage, null, "PCM")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.DiscussionChangeMessage, null, "DCM")).Return(true); + + // Meta-data (root) + Expect.Call(destination.SetMetaDataItem(MetaDataItem.EditNotice, null, "")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.Footer, null, "FOOT")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.Header, null, "HEADER")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.HtmlHead, null, "HTML")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.PageFooter, null, "P_FOOT")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.PageHeader, null, "P_HEADER")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.Sidebar, null, "SIDEBAR")).Return(true); + + // Meta-data ("NS" namespace) + Expect.Call(destination.SetMetaDataItem(MetaDataItem.EditNotice, "NS", "NS_EDIT")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.Footer, "NS", "NS_FOOT")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.Header, "NS", "NS_HEADER")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.HtmlHead, "NS", "NS_HTML")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.PageFooter, "NS", "NS_P_FOOT")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.PageHeader, "NS", "NS_P_HEADER")).Return(true); + Expect.Call(destination.SetMetaDataItem(MetaDataItem.Sidebar, "NS", "NS_SIDEBAR")).Return(true); + + // Plugin assemblies + Expect.Call(destination.StorePluginAssembly("Plugins1.dll", asm1)).Return(true); + Expect.Call(destination.StorePluginAssembly("Plugins2.dll", asm2)).Return(true); + + // Plugin status + Expect.Call(destination.SetPluginStatus("Test1.Plugin1", true)).Return(true); + Expect.Call(destination.SetPluginStatus("Test2.Plugin2", false)).Return(true); + + // Plugin config + Expect.Call(destination.SetPluginConfiguration("Test1.Plugin1", "Config1")).Return(true); + Expect.Call(destination.SetPluginConfiguration("Test2.Plugin2", "")).Return(true); + + // Outgoing links + foreach(KeyValuePair pair in outgoingLinks) { + Expect.Call(destination.StoreOutgoingLinks(pair.Key, pair.Value)).Return(true); + } + + // ACLs + Expect.Call(destination.AclManager).Return(destinationAclManager).Repeat.Any(); + foreach(AclEntry e in entries) { + Expect.Call(destinationAclManager.StoreEntry(e.Resource, e.Action, e.Subject, e.Value)).Return(true); + } + + mocks.ReplayAll(); + + DataMigrator.CopySettingsStorageProviderData(source, destination, + new string[] { "NS" }, new string[] { "Test1.Plugin1", "Test2.Plugin2" }); + + mocks.VerifyAll(); + } + + } + +} diff --git a/Core-Tests/FilesStorageProviderTests.cs b/Core-Tests/FilesStorageProviderTests.cs new file mode 100644 index 0000000..9e3bf37 --- /dev/null +++ b/Core-Tests/FilesStorageProviderTests.cs @@ -0,0 +1,30 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Tests { + + public class FilesStorageProviderTests : FilesStorageProviderTestScaffolding { + + public override IFilesStorageProviderV30 GetProvider() { + FilesStorageProvider prov = new FilesStorageProvider(); + prov.Init(MockHost(), ""); + return prov; + } + + [Test] + public void Init() { + IFilesStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), ""); + + Assert.IsNotNull(prov.Information, "Information should not be null"); + } + + } + +} diff --git a/Core-Tests/FormatterTests.cs b/Core-Tests/FormatterTests.cs new file mode 100644 index 0000000..f7c492b --- /dev/null +++ b/Core-Tests/FormatterTests.cs @@ -0,0 +1,167 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Tests { + + [TestFixture] + public class FormatterTests { + + private MockRepository mocks; + + [Test] + public void Format() { + FormattingContext context = FormattingContext.PageContent; + PageInfo currentPage = null; + string[] linkedPages = null; + + string output = Formatter.Format(Input, false, context, currentPage, out linkedPages, false); + + // Ignore \r characters + Assert.AreEqual(ExpectedOutput.Replace("\r", ""), output.Replace("\r", ""), "Formatter output is different from expected output"); + } + + [SetUp] + public void SetUp() { + mocks = new MockRepository(); + + ISettingsStorageProviderV30 settingsProvider = mocks.StrictMock(); + Expect.Call(settingsProvider.GetSetting("ProcessSingleLineBreaks")).Return("false").Repeat.Any(); + + Collectors.SettingsProvider = settingsProvider; + + IPagesStorageProviderV30 pagesProvider = mocks.StrictMock(); + Collectors.PagesProviderCollector = new ProviderCollector(); + Collectors.PagesProviderCollector.AddProvider(pagesProvider); + + Expect.Call(settingsProvider.GetSetting("DefaultPagesProvider")).Return(pagesProvider.GetType().FullName).Repeat.Any(); + + PageInfo page1 = new PageInfo("page1", pagesProvider, DateTime.Now); + PageContent page1Content = new PageContent(page1, "Page 1", "User", DateTime.Now, "Comment", "Content", null, null); + Expect.Call(pagesProvider.GetPage("page1")).Return(page1).Repeat.Any(); + Expect.Call(pagesProvider.GetContent(page1)).Return(page1Content).Repeat.Any(); + + Expect.Call(pagesProvider.GetPage("page2")).Return(null).Repeat.Any(); + + //Pages.Instance = new Pages(); + + Host.Instance = new Host(); + + Expect.Call(settingsProvider.GetSetting("CacheSize")).Return("100").Repeat.Any(); + Expect.Call(settingsProvider.GetSetting("CacheCutSize")).Return("20").Repeat.Any(); + + Expect.Call(settingsProvider.GetSetting("DefaultCacheProvider")).Return(typeof(CacheProvider).FullName).Repeat.Any(); + + // Cache needs setting to init + mocks.Replay(settingsProvider); + + ICacheProviderV30 cacheProvider = new CacheProvider(); + cacheProvider.Init(Host.Instance, ""); + Collectors.CacheProviderCollector = new ProviderCollector(); + Collectors.CacheProviderCollector.AddProvider(cacheProvider); + + mocks.Replay(pagesProvider); + + Collectors.FormatterProviderCollector = new ProviderCollector(); + + //System.Web.UI.HtmlTextWriter writer = new System.Web.UI.HtmlTextWriter(new System.IO.StreamWriter(new System.IO.MemoryStream())); + //System.Web.Hosting.SimpleWorkerRequest request = new System.Web.Hosting.SimpleWorkerRequest("Default.aspx", "?Page=MainPage", writer); + System.Web.HttpContext.Current = new System.Web.HttpContext(new DummyRequest()); + } + + [TearDown] + public void TearDown() { + mocks.VerifyAll(); + } + + private const string Input = +@"'''bold''' ''italic'' __underlined__ --striked-- +[page1] [page2|title] + +@@* item 1 +* item 2 +second line@@ + +{| +| cell || other cell +|}"; + + private const string ExpectedOutput = +@"bold italic underlined striked +page1 title

* item 1
+* item 2
+second line

cellother cell
+"; + + } + + public class DummyRequest : System.Web.HttpWorkerRequest { + + public override void EndOfRequest() { + } + + public override void FlushResponse(bool finalFlush) { + } + + public override string GetHttpVerbName() { + return "GET"; + } + + public override string GetHttpVersion() { + return "1.1"; + } + + public override string GetLocalAddress() { + return "http://localhost/"; + } + + public override int GetLocalPort() { + return 80; + } + + public override string GetQueryString() { + return ""; + } + + public override string GetRawUrl() { + return "http://localhost/Default.aspx"; + } + + public override string GetRemoteAddress() { + return "127.0.0.1"; + } + + public override int GetRemotePort() { + return 45695; + } + + public override string GetUriPath() { + return "/"; + } + + public override void SendKnownResponseHeader(int index, string value) { + } + + public override void SendResponseFromFile(IntPtr handle, long offset, long length) { + } + + public override void SendResponseFromFile(string filename, long offset, long length) { + } + + public override void SendResponseFromMemory(byte[] data, int length) { + } + + public override void SendStatus(int statusCode, string statusDescription) { + } + + public override void SendUnknownResponseHeader(string name, string value) { + } + + } + +} diff --git a/Core-Tests/IndexStorerTests.cs b/Core-Tests/IndexStorerTests.cs new file mode 100644 index 0000000..73585b1 --- /dev/null +++ b/Core-Tests/IndexStorerTests.cs @@ -0,0 +1,237 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using ScrewTurn.Wiki.SearchEngine.Tests; +using NUnit.Framework; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki.Tests { + + [TestFixture] + public class IndexStorerTests : TestsBase { + + private string documentsFile = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), "__SE_Documents.dat"); + private string wordsFile = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), "__SE_Words.dat"); + private string mappingsFile = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), "__SE_Mappings.dat"); + + [TearDown] + public void RemoveFiles() { + try { + File.Delete(documentsFile); + } + catch { } + try { + File.Delete(wordsFile); + } + catch { } + try { + File.Delete(mappingsFile); + } + catch { } + } + + [Test] + public void Constructor() { + IInMemoryIndex index = MockInMemoryIndex(); + IndexStorer storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index); + Assert.AreEqual(index, storer.Index, "Wrong index"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_InvalidDocumentsFile(string file) { + IndexStorer storer = new IndexStorer(file, wordsFile, mappingsFile, MockInMemoryIndex()); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_InvalidWordsFile(string file) { + IndexStorer storer = new IndexStorer(documentsFile, file, mappingsFile, MockInMemoryIndex()); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_InvalidMappingsFile(string file) { + IndexStorer storer = new IndexStorer(documentsFile, wordsFile, file, MockInMemoryIndex()); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_NullIndex() { + IndexStorer storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, null); + } + + [Test] + public void Size() { + IInMemoryIndex index = MockInMemoryIndex(); + index.SetBuildDocumentDelegate(delegate(DumpedDocument d) { return null; }); + + IndexStorer storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index); + storer.LoadIndex(); + + Assert.IsTrue(storer.Size > 0, "Size should be greater than zero"); + } + + [Test] + public void LoadIndexAndModifications() { + IInMemoryIndex index = MockInMemoryIndex(); + IDocument doc1 = MockDocument("doc1", "Document", "doc", DateTime.Now); + IDocument doc2 = MockDocument2("doc2", "Article", "doc", DateTime.Now); + + BuildDocument buildDoc = new BuildDocument(delegate(DumpedDocument d) { + return d.Name == doc1.Name ? doc1 : doc2; + }); + + index.SetBuildDocumentDelegate(buildDoc); + + IndexStorer storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index); + storer.LoadIndex(); + + index.StoreDocument(doc1, null, "", null); + index.StoreDocument(doc2, null, "", null); + + Assert.AreEqual(2, index.TotalDocuments, "Wrong document count"); + Assert.AreEqual(12, index.TotalWords, "Wrong word count"); + Assert.AreEqual(12, index.TotalOccurrences, "Wrong occurrence count"); + + storer.Dispose(); + storer = null; + + index = MockInMemoryIndex(); + index.SetBuildDocumentDelegate(buildDoc); + + storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index); + storer.LoadIndex(); + + Assert.AreEqual(2, index.TotalDocuments, "Wrong document count"); + Assert.AreEqual(12, index.TotalWords, "Wrong word count"); + Assert.AreEqual(12, index.TotalOccurrences, "Wrong occurrence count"); + + SearchResultCollection res = index.Search(new SearchParameters("document content article")); + Assert.AreEqual(2, res.Count, "Wrong result count"); + Assert.AreEqual(2, res[0].Matches.Count, "Wrong matches count"); + Assert.AreEqual(1, res[1].Matches.Count, "Wrong matches count"); + + Assert.AreEqual("document", res[0].Matches[0].Text, "Wrong match text"); + Assert.AreEqual(0, res[0].Matches[0].FirstCharIndex, "Wrong match first char index"); + Assert.AreEqual(0, res[0].Matches[0].WordIndex, "Wrong match word index"); + Assert.AreEqual(WordLocation.Title, res[0].Matches[0].Location, "Wrong match location"); + + Assert.AreEqual("content", res[0].Matches[1].Text, "Wrong match text"); + Assert.AreEqual(13, res[0].Matches[1].FirstCharIndex, "Wrong match first char index"); + Assert.AreEqual(3, res[0].Matches[1].WordIndex, "Wrong match word index"); + Assert.AreEqual(WordLocation.Content, res[0].Matches[1].Location, "Wrong match location"); + + Assert.AreEqual("article", res[1].Matches[0].Text, "Wrong match text"); + Assert.AreEqual(0, res[1].Matches[0].FirstCharIndex, "Wrong match first char index"); + Assert.AreEqual(0, res[1].Matches[0].WordIndex, "Wrong match word index"); + Assert.AreEqual(WordLocation.Title, res[1].Matches[0].Location, "Wrong match location"); + + index.RemoveDocument(doc1, null); + + storer.Dispose(); + storer = null; + + index = MockInMemoryIndex(); + index.SetBuildDocumentDelegate(buildDoc); + + storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index); + storer.LoadIndex(); + + Assert.AreEqual(1, index.TotalDocuments, "Wrong document count"); + Assert.AreEqual(7, index.TotalWords, "Wrong word count"); + Assert.AreEqual(7, index.TotalOccurrences, "Wrong occurrence count"); + + res = index.Search(new SearchParameters("document content article")); + Assert.AreEqual(1, res.Count, "Wrong result count"); + Assert.AreEqual(1, res[0].Matches.Count, "Wrong matches count"); + + Assert.AreEqual("article", res[0].Matches[0].Text, "Wrong match text"); + Assert.AreEqual(0, res[0].Matches[0].FirstCharIndex, "Wrong match first char index"); + Assert.AreEqual(0, res[0].Matches[0].WordIndex, "Wrong match word index"); + Assert.AreEqual(WordLocation.Title, res[0].Matches[0].Location, "Wrong match location"); + + index.Clear(null); + + storer.Dispose(); + storer = null; + + index = MockInMemoryIndex(); + index.SetBuildDocumentDelegate(buildDoc); + + storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index); + storer.LoadIndex(); + + Assert.AreEqual(0, index.TotalDocuments, "Wrong document count"); + Assert.AreEqual(0, index.TotalWords, "Wrong word count"); + Assert.AreEqual(0, index.TotalOccurrences, "Wrong occurrence count"); + Assert.AreEqual(0, index.Search(new SearchParameters("document")).Count, "Wrong result count"); + } + + [Test] + public void DataCorrupted() { + IInMemoryIndex index = MockInMemoryIndex(); + IDocument doc1 = MockDocument("doc1", "Document", "doc", DateTime.Now); + IDocument doc2 = MockDocument2("doc2", "Article", "doc", DateTime.Now); + index.SetBuildDocumentDelegate(delegate(DumpedDocument d) { return d.Name == doc1.Name ? doc1 : doc2; }); + + IndexStorer storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index); + storer.LoadIndex(); + + index.StoreDocument(doc1, null, "", null); + index.StoreDocument(doc2, null, "", null); + + Assert.AreEqual(2, index.TotalDocuments, "Wrong document count"); + Assert.AreEqual(12, index.TotalWords, "Wrong word count"); + Assert.AreEqual(12, index.TotalOccurrences, "Wrong occurrence count"); + + storer.Dispose(); + storer = null; + + File.Create(documentsFile).Close(); + + index = MockInMemoryIndex(); + + storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index); + storer.LoadIndex(); + + Assert.IsTrue(storer.DataCorrupted, "DataCorrupted should be true"); + Assert.AreEqual(0, index.TotalDocuments, "Wrong document count"); + } + + [Test] + public void ReplaceDocument() { + // This test checks that IndexStorer properly discards the old document when a new copy is stored, + // even when the title and date/time are different + + IInMemoryIndex index = MockInMemoryIndex(); + DateTime dt1 = DateTime.Now.AddDays(-1); + IDocument doc1 = MockDocument("doc1", "Document", "doc", dt1); + IDocument doc2 = MockDocument2("doc1", "Article", "doc", DateTime.Now); + index.SetBuildDocumentDelegate(delegate(DumpedDocument d) { return d.DateTime == dt1 ? doc1 : doc2; }); + + IndexStorer storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index); + storer.LoadIndex(); + + index.StoreDocument(doc1, null, "", null); + Assert.AreEqual(1, index.TotalDocuments, "Wrong document count"); + index.StoreDocument(doc2, null, "", null); + Assert.AreEqual(1, index.TotalDocuments, "Wrong document count"); + + storer.Dispose(); + storer = null; + + index = MockInMemoryIndex(); + index.SetBuildDocumentDelegate(delegate(DumpedDocument d) { return d.DateTime == dt1 ? doc1 : doc2; }); + + storer = new IndexStorer(documentsFile, wordsFile, mappingsFile, index); + storer.LoadIndex(); + + Assert.AreEqual(1, index.TotalDocuments, "Wrong document count"); + } + + } + +} diff --git a/Core-Tests/PagesStorageProviderTests.cs b/Core-Tests/PagesStorageProviderTests.cs new file mode 100644 index 0000000..2828de2 --- /dev/null +++ b/Core-Tests/PagesStorageProviderTests.cs @@ -0,0 +1,163 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using NUnit.Framework; +using Rhino.Mocks; + +namespace ScrewTurn.Wiki.Tests { + + public class PagesStorageProviderTests : PagesStorageProviderTestScaffolding { + + public override IPagesStorageProviderV30 GetProvider() { + PagesStorageProvider prov = new PagesStorageProvider(); + prov.Init(MockHost(), ""); + return prov; + } + + [Test] + public void Init() { + IPagesStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), ""); + + Assert.IsNotNull(prov.Information, "Information should not be null"); + } + + [Test] + public void Init_Upgrade() { + string testDir = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Guid.NewGuid().ToString()); + Directory.CreateDirectory(testDir); + + MockRepository mocks = new MockRepository(); + IHostV30 host = mocks.DynamicMock(); + Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.AtLeastOnce(); + + Expect.Call(host.UpgradePageStatusToAcl(null, 'L')).IgnoreArguments().Repeat.Twice().Return(true); + + mocks.Replay(host); + + string file = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Pages.cs"); + string categoriesFile = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Categories.cs"); + string navPathsFile = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "NavigationPaths.cs"); + string directory = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Pages"); + string messagesDirectory = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Messages"); + Directory.CreateDirectory(directory); + Directory.CreateDirectory(messagesDirectory); + + // Structure (Keywords and Description are new in v3) + // Page Title + // Username|DateTime[|Comment] --- Comment is optional + // ##PAGE## + // Content... + + File.WriteAllText(Path.Combine(directory, "Page1.cs"), "Title1\r\nSYSTEM|2008/10/30 20:20:20|Comment\r\n##PAGE##\r\nContent..."); + File.WriteAllText(Path.Combine(directory, "Page2.cs"), "Title2\r\nSYSTEM|2008/10/30 20:20:20\r\n##PAGE\r\nContent. [[Page.3]] [Page.3|Link to update]."); + File.WriteAllText(Path.Combine(directory, "Page.3.cs"), "Title3\r\nSYSTEM|2008/10/30 20:20:20|Comment\r\n##PAGE\r\nContent..."); + + // ID|Username|Subject|DateTime|ParentID|Body + File.WriteAllText(Path.Combine(messagesDirectory, "Page.3.cs"), "0|User|Hello|2008/10/30 21:21:21|-1|Blah\r\n"); + + // Structure + // [Namespace.]PageName|PageFile|Status|DateTime + File.WriteAllText(file, "Page1|Page1.cs|NORMAL|2008/10/30 20:20:20\r\nPage2|Page2.cs|PUBLIC\r\nPage.3|Page.3.cs|LOCKED"); + + File.WriteAllText(categoriesFile, "Cat1|Page.3\r\nCat.2|Page1|Page2\r\n"); + + File.WriteAllText(navPathsFile, "Path1|Page1|Page.3\r\nPath2|Page2\r\n"); + + PagesStorageProvider prov = new PagesStorageProvider(); + prov.Init(host, ""); + + PageInfo[] pages = prov.GetPages(null); + + Assert.AreEqual(3, pages.Length, "Wrong page count"); + Assert.AreEqual("Page1", pages[0].FullName, "Wrong name"); + Assert.AreEqual("Page2", pages[1].FullName, "Wrong name"); + Assert.AreEqual("Page_3", pages[2].FullName, "Wrong name"); + //Assert.IsFalse(prov.GetContent(pages[1]).Content.Contains("Page.3"), "Content should not contain 'Page.3'"); + //Assert.IsTrue(prov.GetContent(pages[1]).Content.Contains("Page_3"), "Content should contain 'Page_3'"); + + Message[] messages = prov.GetMessages(pages[2]); + Assert.AreEqual(1, messages.Length, "Wrong message count"); + Assert.AreEqual("Hello", messages[0].Subject, "Wrong subject"); + + CategoryInfo[] categories = prov.GetCategories(null); + + Assert.AreEqual(2, categories.Length, "Wrong category count"); + Assert.AreEqual("Cat1", categories[0].FullName, "Wrong name"); + Assert.AreEqual(1, categories[0].Pages.Length, "Wrong page count"); + Assert.AreEqual("Page_3", categories[0].Pages[0], "Wrong page"); + Assert.AreEqual("Cat_2", categories[1].FullName, "Wrong name"); + Assert.AreEqual(2, categories[1].Pages.Length, "Wrong page count"); + Assert.AreEqual("Page1", categories[1].Pages[0], "Wrong page"); + Assert.AreEqual("Page2", categories[1].Pages[1], "Wrong page"); + + NavigationPath[] navPaths = prov.GetNavigationPaths(null); + + Assert.AreEqual(2, navPaths.Length, "Wrong nav path count"); + Assert.AreEqual("Path1", navPaths[0].FullName, "Wrong name"); + Assert.AreEqual(2, navPaths[0].Pages.Length, "Wrong page count"); + Assert.AreEqual("Page1", navPaths[0].Pages[0], "Wrong page"); + Assert.AreEqual("Page_3", navPaths[0].Pages[1], "Wrong page"); + Assert.AreEqual(1, navPaths[1].Pages.Length, "Wrong page count"); + Assert.AreEqual("Page2", navPaths[1].Pages[0], "Wrong page"); + + mocks.Verify(host); + + // Simulate another startup - upgrade not needed anymore + + mocks.BackToRecord(host); + Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.AtLeastOnce(); + Expect.Call(host.UpgradePageStatusToAcl(null, 'L')).IgnoreArguments().Repeat.Times(0).Return(false); + + mocks.Replay(host); + + prov = new PagesStorageProvider(); + prov.Init(host, ""); + + mocks.Verify(host); + + Directory.Delete(testDir, true); + } + + /*[Test] + public void Init_UpgradeCategories() { + string testDir = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Guid.NewGuid().ToString()); + Directory.CreateDirectory(testDir); + + MockRepository mocks = new MockRepository(); + IHost host = mocks.DynamicMock(); + Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.AtLeastOnce(); + + mocks.Replay(host); + + string file = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Categories.cs"); + string filePages = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Pages.cs"); + string directory = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Pages"); + Directory.CreateDirectory(directory); + + File.WriteAllText(file, "Cat1|Page1\r\nCat.2|Page2|Page3\r\n"); + + File.WriteAllText(Path.Combine(directory, "Page1.cs"), "Title1\r\nSYSTEM|2008/10/30 20:20:20|Comment\r\n##PAGE##\r\nContent..."); + File.WriteAllText(Path.Combine(directory, "Page2.cs"), "Title2\r\nSYSTEM|2008/10/30 20:20:20\r\n##PAGE\r\nContent..."); + File.WriteAllText(Path.Combine(directory, "Page3.cs"), "Title3\r\nSYSTEM|2008/10/30 20:20:20|Comment\r\n##PAGE\r\nContent..."); + + File.WriteAllText(filePages, "Page1|Page1.cs|NORMAL|2008/10/30 20:20:20\r\nPage2|Page2.cs\r\nPage3|Page3.cs|LOCKED"); + + PagesStorageProvider prov = new PagesStorageProvider(); + prov.Init(host, ""); + + CategoryInfo[] categories = prov.GetCategories(null); + Assert.AreEqual("Cat1", categories[0].FullName, "Wrong name"); + Assert.AreEqual("Cat_2", categories[1].FullName, "Wrong name"); + + Assert.AreEqual(2, categories.Length, "Wrong page count"); + + mocks.Verify(host); + }*/ + + } + +} diff --git a/Core-Tests/Properties/AssemblyInfo.cs b/Core-Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..fcafa53 --- /dev/null +++ b/Core-Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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("ScrewTurn Wiki Core Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// 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("c0deeef4-19d7-4b11-9255-f5d7e9ddd16e")] diff --git a/Core-Tests/ProviderLoaderTests.cs b/Core-Tests/ProviderLoaderTests.cs new file mode 100644 index 0000000..cbe95af --- /dev/null +++ b/Core-Tests/ProviderLoaderTests.cs @@ -0,0 +1,30 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Tests { + + [TestFixture] + public class ProviderLoaderTests { + + [TestCase(null, typeof(SettingsStorageProvider))] + [TestCase("", typeof(SettingsStorageProvider))] + [TestCase("default", typeof(SettingsStorageProvider))] + [TestCase("DEfaulT", typeof(SettingsStorageProvider))] + [TestCase("ScrewTurn.Wiki.SettingsStorageProvider, ScrewTurn.Wiki.Core", typeof(SettingsStorageProvider))] + [TestCase("ScrewTurn.Wiki.SettingsStorageProvider, ScrewTurn.Wiki.Core.dll", typeof(SettingsStorageProvider))] + [TestCase("ScrewTurn.Wiki.Tests.TestSettingsStorageProvider, ScrewTurn.Wiki.Core.Tests.dll", typeof(TestSettingsStorageProvider))] + [TestCase("glglglglglglg, gfgfgfgfggf.dll", typeof(string), ExpectedException = typeof(ArgumentException))] + public void Static_LoadSettingsStorageProvider(string p, Type type) { + ISettingsStorageProviderV30 prov = ProviderLoader.LoadSettingsStorageProvider(p); + Assert.IsNotNull(prov, "Provider should not be null"); + // type == prov.GetType() seems to fail due to reflection + Assert.AreEqual(type.ToString(), prov.GetType().FullName, "Wrong return type"); + } + + } + +} diff --git a/Core-Tests/SettingsStorageProviderTests.cs b/Core-Tests/SettingsStorageProviderTests.cs new file mode 100644 index 0000000..a8a249a --- /dev/null +++ b/Core-Tests/SettingsStorageProviderTests.cs @@ -0,0 +1,30 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using NUnit.Framework; +using Rhino.Mocks; + +namespace ScrewTurn.Wiki.Tests { + + public class SettingsStorageProviderTests : SettingsStorageProviderTestScaffolding { + + public override ISettingsStorageProviderV30 GetProvider() { + SettingsStorageProvider prov = new SettingsStorageProvider(); + prov.Init(MockHost(), ""); + return prov; + } + + [Test] + public void Init() { + ISettingsStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), ""); + + Assert.IsNotNull(prov.Information, "Information should not be null"); + } + + } + +} diff --git a/Core-Tests/TestSettingsStorageProvider.cs b/Core-Tests/TestSettingsStorageProvider.cs new file mode 100644 index 0000000..28c3580 --- /dev/null +++ b/Core-Tests/TestSettingsStorageProvider.cs @@ -0,0 +1,147 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.AclEngine; + +namespace ScrewTurn.Wiki.Tests { + + /// + /// Implements a dummy Settings Storage Provider to use for testing. + /// + public class TestSettingsStorageProvider : ISettingsStorageProviderV30 { + + public string GetSetting(string name) { + throw new NotImplementedException(); + } + + public bool SetSetting(string name, string value) { + throw new NotImplementedException(); + } + + public void BeginBulkUpdate() { + throw new NotImplementedException(); + } + + public void EndBulkUpdate() { + throw new NotImplementedException(); + } + + public void LogEntry(string message, EntryType entryType, string user) { + throw new NotImplementedException(); + } + + public LogEntry[] GetLogEntries() { + throw new NotImplementedException(); + } + + public void ClearLog() { + throw new NotImplementedException(); + } + + public void CutLog(int size) { + throw new NotImplementedException(); + } + + public int LogSize { + get { throw new NotImplementedException(); } + } + + public string GetMetaDataItem(MetaDataItem item, string tag) { + throw new NotImplementedException(); + } + + public bool SetMetaDataItem(MetaDataItem item, string tag, string content) { + throw new NotImplementedException(); + } + + public RecentChange[] GetRecentChanges() { + throw new NotImplementedException(); + } + + public bool AddRecentChange(string page, string title, string messageSubject, DateTime dateTime, string user, ScrewTurn.Wiki.PluginFramework.Change change, string descr) { + throw new NotImplementedException(); + } + + public void Init(IHostV30 host, string config) { + throw new NotImplementedException(); + } + + public void Shutdown() { + throw new NotImplementedException(); + } + + public ComponentInformation Information { + get { throw new NotImplementedException(); } + } + + public string ConfigHelpHtml { + get { throw new NotImplementedException(); } + } + + public string[] ListPluginAssemblies() { + throw new NotImplementedException(); + } + + public bool StorePluginAssembly(string filename, byte[] assembly) { + throw new NotImplementedException(); + } + + public byte[] RetrievePluginAssembly(string filename) { + throw new NotImplementedException(); + } + + public bool DeletePluginAssembly(string filename) { + throw new NotImplementedException(); + } + + public bool SetPluginStatus(string typeName, bool enabled) { + throw new NotImplementedException(); + } + + public bool GetPluginStatus(string typeName) { + throw new NotImplementedException(); + } + + public bool SetPluginConfiguration(string typeName, string config) { + throw new NotImplementedException(); + } + + public string GetPluginConfiguration(string typeName) { + throw new NotImplementedException(); + } + + public IAclManager AclManager { + get { + throw new NotImplementedException(); + } + } + + public bool StoreOutgoingLinks(string page, string[] outgoingLinks) { + throw new NotImplementedException(); + } + + public string[] GetOutgoingLinks(string page) { + throw new NotImplementedException(); + } + + public IDictionary GetAllOutgoingLinks() { + throw new NotImplementedException(); + } + + public bool DeleteOutgoingLinks(string page) { + throw new NotImplementedException(); + } + + public bool UpdateOutgoingLinksForRename(string oldName, string newName) { + throw new NotImplementedException(); + } + + public IDictionary GetAllSettings() { + throw new NotImplementedException(); + } + + } + +} diff --git a/Core-Tests/UsersStorageProviderTests.cs b/Core-Tests/UsersStorageProviderTests.cs new file mode 100644 index 0000000..e539175 --- /dev/null +++ b/Core-Tests/UsersStorageProviderTests.cs @@ -0,0 +1,75 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Tests { + + public class UsersStorageProviderTests : UsersStorageProviderTestScaffolding { + + public override IUsersStorageProviderV30 GetProvider() { + UsersStorageProvider prov = new UsersStorageProvider(); + prov.Init(MockHost(), ""); + return prov; + } + + [Test] + public void Init() { + IUsersStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), ""); + + Assert.IsNotNull(prov.Information, "Information should not be null"); + } + + [Test] + public void Init_Upgrade() { + string testDir = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Guid.NewGuid().ToString()); + Directory.CreateDirectory(testDir); + + MockRepository mocks = new MockRepository(); + IHostV30 host = mocks.DynamicMock(); + Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.AtLeastOnce(); + Expect.Call(host.GetSettingValue(SettingName.AdministratorsGroup)).Return("Administrators").Repeat.Once(); + Expect.Call(host.GetSettingValue(SettingName.UsersGroup)).Return("Users").Repeat.Once(); + + Expect.Call(host.UpgradeSecurityFlagsToGroupsAcl(null, null)).IgnoreArguments().Repeat.Times(1).Return(true); + + mocks.Replay(host); + + string file = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), "Users.cs"); + + File.WriteAllText(file, "user|PASSHASH|user@users.com|Inactive|2008/10/31 15:15:15|USER\r\nsuperuser|SUPERPASSHASH|superuser@users.com|Active|2008/10/31 15:15:16|ADMIN"); + + UsersStorageProvider prov = new UsersStorageProvider(); + prov.Init(host, ""); + + UserInfo[] users = prov.GetUsers(); + + Assert.AreEqual(2, users.Length, "Wrong user count"); + + Assert.AreEqual("superuser", users[0].Username, "Wrong username"); + Assert.AreEqual("superuser@users.com", users[0].Email, "Wrong email"); + Assert.IsTrue(users[0].Active, "User should be active"); + Assert.AreEqual("2008/10/31 15:15:16", users[0].DateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss"), "Wrong date/time"); + Assert.AreEqual(1, users[0].Groups.Length, "Wrong user count"); + Assert.AreEqual("Administrators", users[0].Groups[0], "Wrong group"); + + Assert.AreEqual("user", users[1].Username, "Wrong username"); + Assert.AreEqual("user@users.com", users[1].Email, "Wrong email"); + Assert.IsFalse(users[1].Active, "User should be inactive"); + Assert.AreEqual("2008/10/31 15:15:15", users[1].DateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss"), "Wrong date/time"); + Assert.AreEqual(1, users[1].Groups.Length, "Wrong user count"); + Assert.AreEqual("Users", users[1].Groups[0], "Wrong group"); + + mocks.Verify(host); + + Directory.Delete(testDir, true); + } + + } + +} diff --git a/Core/AclStorer.cs b/Core/AclStorer.cs new file mode 100644 index 0000000..87becee --- /dev/null +++ b/Core/AclStorer.cs @@ -0,0 +1,116 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using ScrewTurn.Wiki.AclEngine; + +namespace ScrewTurn.Wiki { + + /// + /// Implements a file-based ACL Storer. + /// + public class AclStorer : AclStorerBase { + + private string file; + + /// + /// Initializes a new instance of the class. + /// + /// The instance of the ACL Manager to handle. + /// The storage file. + public AclStorer(IAclManager aclManager, string file) + : base(aclManager) { + + if(file == null) throw new ArgumentNullException("file"); + if(file.Length == 0) throw new ArgumentException("File cannot be empty", "file"); + + this.file = file; + } + + /// + /// Loads data from storage. + /// + /// The loaded ACL entries. + protected override AclEntry[] LoadDataInternal() { + lock(this) { + if(!File.Exists(file)) { + File.Create(file).Close(); + return new AclEntry[0]; + } + + // Format + // Resource|Action|Subject|(1|0) + + string[] lines = File.ReadAllLines(file); + + AclEntry[] result = new AclEntry[lines.Length]; + + string[] fields; + for(int i = 0; i < lines.Length; i++) { + fields = lines[i].Split('|'); + + result[i] = new AclEntry(fields[0], fields[1], fields[2], (fields[3] == "1" ? Value.Grant : Value.Deny)); + } + + return result; + } + } + + /// + /// Dumps a into a string. + /// + /// The entry to dump. + /// The resulting string. + private static string DumpAclEntry(AclEntry entry) { + return string.Format("{0}|{1}|{2}|{3}", entry.Resource, entry.Action, entry.Subject, (entry.Value == Value.Grant ? "1" : "0")); + } + + /// + /// Deletes some entries. + /// + /// The entries to delete. + protected override void DeleteEntries(AclEntry[] entries) { + lock(this) { + AclEntry[] allEntries = LoadDataInternal(); + + StringBuilder sb = new StringBuilder(10000); + foreach(AclEntry originalEntry in allEntries) { + // If the current entry is not contained in the entries array, then preserve it + bool delete = false; + foreach(AclEntry entryToDelete in entries) { + if(AclEntry.Equals(originalEntry, entryToDelete)) { + delete = true; + break; + } + } + + if(!delete) { + sb.Append(DumpAclEntry(originalEntry)); + sb.Append("\r\n"); + } + } + + File.WriteAllText(file, sb.ToString()); + } + } + + /// + /// Stores some entries. + /// + /// The entries to store. + protected override void StoreEntries(AclEntry[] entries) { + lock(this) { + StringBuilder sb = new StringBuilder(100); + foreach(AclEntry entry in entries) { + sb.Append(DumpAclEntry(entry)); + sb.Append("\r\n"); + } + + File.AppendAllText(file, sb.ToString()); + } + } + + } + +} diff --git a/Core/AuthChecker.cs b/Core/AuthChecker.cs new file mode 100644 index 0000000..eb3f266 --- /dev/null +++ b/Core/AuthChecker.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.AclEngine; + +namespace ScrewTurn.Wiki { + + /// + /// Utility class for checking permissions and authorizations. + /// + /// All the methods in this class implement a security bypass for the admin user. + public static class AuthChecker { + + /// + /// Gets the settings storage provider. + /// + private static ISettingsStorageProviderV30 SettingsProvider { + get { return Collectors.SettingsProvider; } + } + + /// + /// Checks whether an action is allowed for the global resources. + /// + /// The action the user is attempting to perform. + /// The current user. + /// The groups the user is member of. + /// true if the action is allowed. + public static bool CheckActionForGlobals(string action, string currentUser, string[] groups) { + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(!AuthTools.IsValidAction(action, Actions.ForGlobals.All)) throw new ArgumentException("Invalid action", "action"); + + if(currentUser == null) throw new ArgumentNullException("currentUser"); + if(currentUser.Length == 0) throw new ArgumentException("Current User cannot be empty", "currentUser"); + + if(groups == null) throw new ArgumentNullException("groups"); + + if(currentUser == "admin") return true; + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForResource(Actions.ForGlobals.ResourceMasterPrefix); + Authorization auth = AclEvaluator.AuthorizeAction(Actions.ForGlobals.ResourceMasterPrefix, action, + AuthTools.PrepareUsername(currentUser), AuthTools.PrepareGroups(groups), entries); + + return auth == Authorization.Granted; + } + + /// + /// Checks whether an action is allowed for a namespace. + /// + /// The current namespace (null for the root). + /// The action the user is attempting to perform. + /// The current user. + /// The groups the user is member of. + /// true if the action is allowed, false otherwise. + public static bool CheckActionForNamespace(NamespaceInfo nspace, string action, string currentUser, string[] groups) { + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(!AuthTools.IsValidAction(action, Actions.ForNamespaces.All)) throw new ArgumentException("Invalid action", "action"); + + if(currentUser == null) throw new ArgumentNullException("currentUser"); + if(currentUser.Length == 0) throw new ArgumentException("Current User cannot be empty", "currentUser"); + + if(groups == null) throw new ArgumentNullException("groups"); + + if(currentUser == "admin") return true; + + string namespaceName = nspace != null ? nspace.Name : ""; + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForResource( + Actions.ForNamespaces.ResourceMasterPrefix + namespaceName); + + Authorization auth = AclEvaluator.AuthorizeAction(Actions.ForNamespaces.ResourceMasterPrefix + namespaceName, + action, AuthTools.PrepareUsername(currentUser), AuthTools.PrepareGroups(groups), entries); + + if(auth != Authorization.Unknown) return auth == Authorization.Granted; + + // Try local escalators + string[] localEscalators = null; + if(Actions.ForNamespaces.LocalEscalators.TryGetValue(action, out localEscalators)) { + foreach(string localAction in localEscalators) { + bool authorized = CheckActionForNamespace(nspace, localAction, currentUser, groups); + if(authorized) return true; + } + } + + // Try root escalation + if(nspace != null) { + bool authorized = CheckActionForNamespace(null, action, currentUser, groups); + if(authorized) return true; + } + + // Try global escalators + string[] globalEscalators = null; + if(Actions.ForNamespaces.GlobalEscalators.TryGetValue(action, out globalEscalators)) { + foreach(string globalAction in globalEscalators) { + bool authorized = CheckActionForGlobals(globalAction, currentUser, groups); + if(authorized) return true; + } + } + + return false; + } + + /// + /// Checks whether an action is allowed for a page. + /// + /// The current page. + /// The action the user is attempting to perform. + /// The current user. + /// The groups the user is member of. + /// true if the action is allowed, false otherwise. + public static bool CheckActionForPage(PageInfo page, string action, string currentUser, string[] groups) { + if(page == null) throw new ArgumentNullException("page"); + + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(!AuthTools.IsValidAction(action, Actions.ForPages.All)) throw new ArgumentException("Invalid action", "action"); + + if(currentUser == null) throw new ArgumentNullException("currentUser"); + if(currentUser.Length == 0) throw new ArgumentException("Current User cannot be empty", "currentUser"); + + if(groups == null) throw new ArgumentNullException("groups"); + + if(currentUser == "admin") return true; + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForResource(Actions.ForPages.ResourceMasterPrefix + page.FullName); + Authorization auth = AclEvaluator.AuthorizeAction(Actions.ForPages.ResourceMasterPrefix + page.FullName, action, + AuthTools.PrepareUsername(currentUser), AuthTools.PrepareGroups(groups), entries); + + if(auth != Authorization.Unknown) return auth == Authorization.Granted; + + // Try local escalators + string[] localEscalators = null; + if(Actions.ForPages.LocalEscalators.TryGetValue(action, out localEscalators)) { + foreach(string localAction in localEscalators) { + bool authorized = CheckActionForPage(page, localAction, currentUser, groups); + if(authorized) return true; + } + } + + // Try namespace escalators + string[] namespaceEscalators = null; + string nsName = NameTools.GetNamespace(page.FullName); + NamespaceInfo ns = string.IsNullOrEmpty(nsName) ? null : new NamespaceInfo(nsName, null, null); + if(Actions.ForPages.NamespaceEscalators.TryGetValue(action, out namespaceEscalators)) { + foreach(string namespaceAction in namespaceEscalators) { + bool authorized = CheckActionForNamespace(ns, namespaceAction, currentUser, groups); + if(authorized) return true; + } + } + + // Try global escalators + string[] globalEscalators = null; + if(Actions.ForPages.GlobalEscalators.TryGetValue(action, out globalEscalators)) { + foreach(string globalAction in globalEscalators) { + bool authorized = CheckActionForGlobals(globalAction, currentUser, groups); + if(authorized) return true; + } + } + + return false; + } + + /// + /// Checks whether an action is allowed for a directory. + /// + /// The provider that manages the directory. + /// The full path of the directory. + /// The action the user is attempting to perform. + /// The current user. + /// The groups the user is member of. + /// true if the action is allowed, false otherwise. + public static bool CheckActionForDirectory(IFilesStorageProviderV30 provider, string directory, string action, string currentUser, string[] groups) { + if(provider == null) throw new ArgumentNullException("provider"); + + if(directory == null) throw new ArgumentNullException("directory"); + if(directory.Length == 0) throw new ArgumentException("Directory cannot be empty", "directory"); + + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(!AuthTools.IsValidAction(action, Actions.ForDirectories.All)) throw new ArgumentException("Invalid action", "action"); + + if(currentUser == null) throw new ArgumentNullException("currentUser"); + if(currentUser.Length == 0) throw new ArgumentException("Current User cannot be empty", "currentUser"); + + if(groups == null) throw new ArgumentNullException("groups"); + + if(currentUser == "admin") return true; + + string resourceName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, directory); + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForResource(resourceName); + + Authorization auth = AclEvaluator.AuthorizeAction(resourceName, action, + AuthTools.PrepareUsername(currentUser), AuthTools.PrepareGroups(groups), entries); + + if(auth != Authorization.Unknown) return auth == Authorization.Granted; + + // Try local escalators + string[] localEscalators = null; + if(Actions.ForDirectories.LocalEscalators.TryGetValue(action, out localEscalators)) { + foreach(string localAction in localEscalators) { + bool authorized = CheckActionForDirectory(provider, directory, localAction, currentUser, groups); + if(authorized) return true; + } + } + + // Try directory escalation (extract parent directory and check its permissions) + // Path manipulation keeps the format used by the caller (leading and trailing slashes are preserved if appropriate) + string trimmedDirectory = directory.Trim('/'); + if(trimmedDirectory.Length > 0) { + int slashIndex = trimmedDirectory.LastIndexOf('/'); + string parentDir = ""; + if(slashIndex > 0) { + // Navigate one level up, using the same slash format as the current one + parentDir = (directory.StartsWith("/") ? "/" : "") + + trimmedDirectory.Substring(0, slashIndex) + (directory.EndsWith("/") ? "/" : ""); + } + else { + // This is the root + parentDir = directory.StartsWith("/") ? "/" : ""; + } + bool authorized = CheckActionForDirectory(provider, parentDir, action, currentUser, groups); + if(authorized) return true; + } + + // Try global escalators + string[] globalEscalators = null; + if(Actions.ForDirectories.GlobalEscalators.TryGetValue(action, out globalEscalators)) { + foreach(string globalAction in globalEscalators) { + bool authorized = CheckActionForGlobals(globalAction, currentUser, groups); + if(authorized) return true; + } + } + + return false; + } + + } + +} diff --git a/Core/AuthReader.cs b/Core/AuthReader.cs new file mode 100644 index 0000000..6fe2b42 --- /dev/null +++ b/Core/AuthReader.cs @@ -0,0 +1,481 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.AclEngine; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Utility class for reading permissions and authorizations. + /// + public static class AuthReader { + + /// + /// Gets the settings storage provider. + /// + private static ISettingsStorageProviderV30 SettingsProvider { + get { return Collectors.SettingsProvider; } + } + + /// + /// Gets all the actions for global resources that are granted to a group. + /// + /// The user group. + /// The granted actions. + public static string[] RetrieveGrantsForGlobals(UserGroup group) { + if(group == null) throw new ArgumentNullException("group"); + + return RetrieveGrantsForGlobals(AuthTools.PrepareGroup(group.Name)); + } + + /// + /// Gets all the actions for global resources that are granted to a user. + /// + /// The user. + /// The granted actions. + public static string[] RetrieveGrantsForGlobals(UserInfo user) { + if(user == null) throw new ArgumentNullException("user"); + + return RetrieveGrantsForGlobals(AuthTools.PrepareUsername(user.Username)); + } + + /// + /// Gets all the actions for global resources that are granted to a subject. + /// + /// The subject. + /// The granted actions. + private static string[] RetrieveGrantsForGlobals(string subject) { + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject); + + List result = new List(entries.Length); + foreach(AclEntry entry in entries) { + if(entry.Value == Value.Grant && entry.Resource == Actions.ForGlobals.ResourceMasterPrefix) { + result.Add(entry.Action); + } + } + + return result.ToArray(); + } + + /// + /// Gets all the actions for global resources that are denied to a group. + /// + /// The user group. + /// The denied actions. + public static string[] RetrieveDenialsForGlobals(UserGroup group) { + if(group == null) throw new ArgumentNullException("group"); + + return RetrieveDenialsForGlobals(AuthTools.PrepareGroup(group.Name)); + } + + /// + /// Gets all the actions for global resources that are denied to a user. + /// + /// The user. + /// The denied actions. + public static string[] RetrieveDenialsForGlobals(UserInfo user) { + if(user == null) throw new ArgumentNullException("user"); + + return RetrieveDenialsForGlobals(AuthTools.PrepareUsername(user.Username)); + } + + /// + /// Gets all the actions for global resources that are denied to a subject. + /// + /// The subject. + /// The denied actions. + private static string[] RetrieveDenialsForGlobals(string subject) { + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject); + + List result = new List(entries.Length); + foreach(AclEntry entry in entries) { + if(entry.Value == Value.Deny && entry.Resource == Actions.ForGlobals.ResourceMasterPrefix) { + result.Add(entry.Action); + } + } + return result.ToArray(); + } + + /// + /// Retrieves the subjects that have ACL entries set for a namespace. + /// + /// The namespace (null for the root). + /// The subjects. + public static SubjectInfo[] RetrieveSubjectsForNamespace(NamespaceInfo nspace) { + string resourceName = Actions.ForNamespaces.ResourceMasterPrefix; + if(nspace != null) resourceName += nspace.Name; + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForResource(resourceName); + + List result = new List(entries.Length); + + for(int i = 0; i < entries.Length; i++) { + SubjectType type = AuthTools.IsGroup(entries[i].Subject) ? SubjectType.Group : SubjectType.User; + + // Remove the subject qualifier ('U.' or 'G.') + string name = entries[i].Subject.Substring(2); + + if(result.Find(delegate(SubjectInfo x) { return x.Name == name && x.Type == type; }) == null) { + result.Add(new SubjectInfo(name, type)); + } + } + + return result.ToArray(); + } + + /// + /// Gets all the actions for a namespace that are granted to a group. + /// + /// The user group. + /// The namespace (null for the root). + /// The granted actions. + public static string[] RetrieveGrantsForNamespace(UserGroup group, NamespaceInfo nspace) { + if(group == null) throw new ArgumentNullException("group"); + + return RetrieveGrantsForNamespace(AuthTools.PrepareGroup(group.Name), nspace); + } + + /// + /// Gets all the actions for a namespace that are granted to a user. + /// + /// The user. + /// The namespace (null for the root). + /// The granted actions. + public static string[] RetrieveGrantsForNamespace(UserInfo user, NamespaceInfo nspace) { + if(user == null) throw new ArgumentNullException("user"); + + return RetrieveGrantsForNamespace(AuthTools.PrepareUsername(user.Username), nspace); + } + + /// + /// Gets all the actions for a namespace that are granted to a subject. + /// + /// The subject. + /// The namespace (null for the root). + /// The granted actions. + private static string[] RetrieveGrantsForNamespace(string subject, NamespaceInfo nspace) { + string resourceName = Actions.ForNamespaces.ResourceMasterPrefix; + if(nspace != null) resourceName += nspace.Name; + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject); + + List result = new List(entries.Length); + + foreach(AclEntry entry in entries) { + if(entry.Value == Value.Grant && entry.Resource == resourceName) { + result.Add(entry.Action); + } + } + + return result.ToArray(); + } + + /// + /// Gets all the actions for a namespace that are denied to a group. + /// + /// The user group. + /// The namespace (null for the root). + /// The denied actions. + public static string[] RetrieveDenialsForNamespace(UserGroup group, NamespaceInfo nspace) { + if(group == null) throw new ArgumentNullException("group"); + + return RetrieveDenialsForNamespace(AuthTools.PrepareGroup(group.Name), nspace); + } + + /// + /// Gets all the actions for a namespace that are denied to a user. + /// + /// The user. + /// The namespace (null for the root). + /// The denied actions. + public static string[] RetrieveDenialsForNamespace(UserInfo user, NamespaceInfo nspace) { + if(user == null) throw new ArgumentNullException("user"); + + return RetrieveDenialsForNamespace(AuthTools.PrepareUsername(user.Username), nspace); + } + + /// + /// Gets all the actions for a namespace that are denied to a subject. + /// + /// The subject. + /// The namespace (null for the root). + /// The denied actions. + private static string[] RetrieveDenialsForNamespace(string subject, NamespaceInfo nspace) { + string resourceName = Actions.ForNamespaces.ResourceMasterPrefix; + if(nspace != null) resourceName += nspace.Name; + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject); + + List result = new List(entries.Length); + + foreach(AclEntry entry in entries) { + if(entry.Value == Value.Deny && entry.Resource == resourceName) { + result.Add(entry.Action); + } + } + + return result.ToArray(); + } + + /// + /// Retrieves the subjects that have ACL entries set for a page. + /// + /// The page. + /// The subjects. + public static SubjectInfo[] RetrieveSubjectsForPage(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForResource(Actions.ForPages.ResourceMasterPrefix + page.FullName); + + List result = new List(entries.Length); + + for(int i = 0; i < entries.Length; i++) { + SubjectType type = AuthTools.IsGroup(entries[i].Subject) ? SubjectType.Group : SubjectType.User; + + // Remove the subject qualifier ('U.' or 'G.') + string name = entries[i].Subject.Substring(2); + + if(result.Find(delegate(SubjectInfo x) { return x.Name == name && x.Type == type; }) == null) { + result.Add(new SubjectInfo(name, type)); + } + } + + return result.ToArray(); + } + + /// + /// Gets all the actions for a page that are granted to a group. + /// + /// The user group. + /// The page. + /// The granted actions. + public static string[] RetrieveGrantsForPage(UserGroup group, PageInfo page) { + if(group == null) throw new ArgumentNullException("group"); + + return RetrieveGrantsForPage(AuthTools.PrepareGroup(group.Name), page); + } + + /// + /// Gets all the actions for a page that are granted to a user. + /// + /// The user. + /// The page. + /// The granted actions. + public static string[] RetrieveGrantsForPage(UserInfo user, PageInfo page) { + if(user == null) throw new ArgumentNullException("user"); + + return RetrieveGrantsForPage(AuthTools.PrepareUsername(user.Username), page); + } + + /// + /// Gets all the actions for a page that are granted to a subject. + /// + /// The subject. + /// The page. + /// The granted actions. + private static string[] RetrieveGrantsForPage(string subject, PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + string resourceName = Actions.ForPages.ResourceMasterPrefix + page.FullName; + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject); + + List result = new List(entries.Length); + + foreach(AclEntry entry in entries) { + if(entry.Value == Value.Grant && entry.Resource == resourceName) { + result.Add(entry.Action); + } + } + + return result.ToArray(); + } + + /// + /// Gets all the actions for a page that are denied to a group. + /// + /// The user group. + /// The page. + /// The granted actions. + public static string[] RetrieveDenialsForPage(UserGroup group, PageInfo page) { + if(group == null) throw new ArgumentNullException("group"); + + return RetrieveDenialsForPage(AuthTools.PrepareGroup(group.Name), page); + } + + /// + /// Gets all the actions for a page that are denied to a user. + /// + /// The user. + /// The page. + /// The granted actions. + public static string[] RetrieveDenialsForPage(UserInfo user, PageInfo page) { + if(user == null) throw new ArgumentNullException("user"); + + return RetrieveDenialsForPage(AuthTools.PrepareUsername(user.Username), page); + } + + /// + /// Gets all the actions for a page that are denied to a subject. + /// + /// The subject. + /// The page. + /// The granted actions. + private static string[] RetrieveDenialsForPage(string subject, PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + string resourceName = Actions.ForPages.ResourceMasterPrefix + page.FullName; + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject); + + List result = new List(entries.Length); + + foreach(AclEntry entry in entries) { + if(entry.Value == Value.Deny && entry.Resource == resourceName) { + result.Add(entry.Action); + } + } + + return result.ToArray(); + } + + /// + /// Retrieves the subjects that have ACL entries set for a directory. + /// + /// The provider. + /// The directory. + /// The subjects. + public static SubjectInfo[] RetrieveSubjectsForDirectory(IFilesStorageProviderV30 provider, string directory) { + if(provider == null) throw new ArgumentNullException("provider"); + if(directory == null) throw new ArgumentNullException("directory"); + if(directory.Length == 0) throw new ArgumentException("Directory cannot be empty", "directory"); + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForResource(Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, directory)); + + List result = new List(entries.Length); + + for(int i = 0; i < entries.Length; i++) { + SubjectType type = AuthTools.IsGroup(entries[i].Subject) ? SubjectType.Group : SubjectType.User; + + // Remove the subject qualifier ('U.' or 'G.') + string name = entries[i].Subject.Substring(2); + + if(result.Find(delegate(SubjectInfo x) { return x.Name == name && x.Type == type; }) == null) { + result.Add(new SubjectInfo(name, type)); + } + } + + return result.ToArray(); + } + + /// + /// Gets all the actions for a directory that are granted to a group. + /// + /// The user group. + /// The provider. + /// The directory. + /// The granted actions. + public static string[] RetrieveGrantsForDirectory(UserGroup group, IFilesStorageProviderV30 provider, string directory) { + if(group == null) throw new ArgumentNullException("group"); + + return RetrieveGrantsForDirectory(AuthTools.PrepareGroup(group.Name), provider, directory); + } + + /// + /// Gets all the actions for a directory that are granted to a user. + /// + /// The user. + /// The provider. + /// The directory. + /// The granted actions. + public static string[] RetrieveGrantsForDirectory(UserInfo user, IFilesStorageProviderV30 provider, string directory) { + if(user == null) throw new ArgumentNullException("user"); + + return RetrieveGrantsForDirectory(AuthTools.PrepareUsername(user.Username), provider, directory); + } + + /// + /// Gets all the actions for a directory that are granted to a subject. + /// + /// The subject. + /// The provider. + /// The directory. + /// The granted actions. + private static string[] RetrieveGrantsForDirectory(string subject, IFilesStorageProviderV30 provider, string directory) { + if(provider == null) throw new ArgumentNullException("provider"); + if(directory == null) throw new ArgumentNullException("directory"); + if(directory.Length == 0) throw new ArgumentException("Directory cannot be empty", "directory"); + + string resourceName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, directory); + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject); + + List result = new List(entries.Length); + + foreach(AclEntry entry in entries) { + if(entry.Value == Value.Grant && entry.Resource == resourceName) { + result.Add(entry.Action); + } + } + + return result.ToArray(); + } + + /// + /// Gets all the actions for a directory that are denied to a group. + /// + /// The user group. + /// The provider. + /// The directory. + /// The denied actions. + public static string[] RetrieveDenialsForDirectory(UserGroup group, IFilesStorageProviderV30 provider, string directory) { + if(group == null) throw new ArgumentNullException("group"); + + return RetrieveDenialsForDirectory(AuthTools.PrepareGroup(group.Name), provider, directory); + } + + /// + /// Gets all the actions for a directory that are denied to a user. + /// + /// The user. + /// The provider. + /// The directory. + /// The denied actions. + public static string[] RetrieveDenialsForDirectory(UserInfo user, IFilesStorageProviderV30 provider, string directory) { + if(user == null) throw new ArgumentNullException("user"); + + return RetrieveDenialsForDirectory(AuthTools.PrepareUsername(user.Username), provider, directory); + } + + /// + /// Gets all the actions for a directory that are denied to a subject. + /// + /// The subject. + /// The provider. + /// The directory. + /// The denied actions. + private static string[] RetrieveDenialsForDirectory(string subject, IFilesStorageProviderV30 provider, string directory) { + if(provider == null) throw new ArgumentNullException("provider"); + if(directory == null) throw new ArgumentNullException("directory"); + if(directory.Length == 0) throw new ArgumentException("Directory cannot be empty", "directory"); + + string resourceName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, directory); + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject); + + List result = new List(entries.Length); + + foreach(AclEntry entry in entries) { + if(entry.Value == Value.Deny && entry.Resource == resourceName) { + result.Add(entry.Action); + } + } + + return result.ToArray(); + } + + } + +} diff --git a/Core/AuthStatus.cs b/Core/AuthStatus.cs new file mode 100644 index 0000000..7858ba6 --- /dev/null +++ b/Core/AuthStatus.cs @@ -0,0 +1,26 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki { + + /// + /// Lists legal values for authorizations. + /// + public enum AuthStatus { + /// + /// Grant the action. + /// + Grant, + /// + /// Deny the action. + /// + Deny, + /// + /// Delete the permission entry. + /// + Delete + } + +} diff --git a/Core/AuthTools.cs b/Core/AuthTools.cs new file mode 100644 index 0000000..aeb7255 --- /dev/null +++ b/Core/AuthTools.cs @@ -0,0 +1,95 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Implements tools supporting athorization management. + /// + public static class AuthTools { + + /// + /// Determines whether an action is valid. + /// + /// The action to validate. + /// The list of valid actions. + /// true if the action is valid, false otherwise. + public static bool IsValidAction(string action, string[] validActions) { + return Array.Find(validActions, delegate(string s) { return s == action; }) != null; + } + + /// + /// Determines whether a subject is a group. + /// + /// The subject to test. + /// true if the subject is a group, false if it is a user. + public static bool IsGroup(string subject) { + if(subject == null) throw new ArgumentNullException("subject"); + if(subject.Length < 2) throw new ArgumentException("Subject must contain at least 2 characters", "subject"); + + return subject.ToUpperInvariant().StartsWith("G."); + } + + /// + /// Prepends the proper string to a username. + /// + /// The username. + /// The resulting username. + public static string PrepareUsername(string username) { + if(username == null) throw new ArgumentNullException("username"); + if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username"); + + return "U." + username; + } + + /// + /// Prepends the proper string to each group name in an array. + /// + /// The group array. + /// The resulting group array. + public static string[] PrepareGroups(string[] groups) { + if(groups == null) throw new ArgumentNullException("groups"); + + if(groups.Length == 0) return groups; + + string[] result = new string[groups.Length]; + + for(int i = 0; i < groups.Length; i++) { + if(groups[i] == null) throw new ArgumentNullException("groups"); + if(groups[i].Length == 0) throw new ArgumentException("Groups cannot contain empty elements", "groups"); + + result[i] = PrepareGroup(groups[i]); + } + + return result; + } + + /// + /// Prepends the proper string to the group name. + /// + /// The group name. + /// The result string. + public static string PrepareGroup(string group) { + return "G." + group; + } + + /// + /// Gets the proper full name for a directory. + /// + /// The provider. + /// The directory name. + /// The full name (not prepended with . + public static string GetDirectoryName(IFilesStorageProviderV30 prov, string name) { + if(prov == null) throw new ArgumentNullException("prov"); + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + return "(" + prov.GetType().FullName + ")" + name; + } + + } + +} diff --git a/Core/AuthWriter.cs b/Core/AuthWriter.cs new file mode 100644 index 0000000..76213bf --- /dev/null +++ b/Core/AuthWriter.cs @@ -0,0 +1,668 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.AclEngine; + +namespace ScrewTurn.Wiki { + + /// + /// Utility class for writing permissions and authorizations. + /// + public static class AuthWriter { + + private const string Delete = "DELETE"; + private const string Set = "SET-"; + + private const string MessageDeleteSuccess = "Deleted ACL Entry: "; + private const string MessageDeleteFailure = "Deletion failed for ACL Entry: "; + private const string MessageSetSuccess = "Set ACL Entry: "; + private const string MessageSetFailure = "Setting failed for ACL Entry: "; + + /// + /// Gets the settings storage provider. + /// + private static ISettingsStorageProviderV30 SettingsProvider { + get { return Collectors.SettingsProvider; } + } + + /// + /// Sets a permission for a global resource. + /// + /// The authorization status. + /// The action of which to modify the authorization status. + /// The group subject of the authorization change. + /// true if the authorization status is changed, false otherwise. + public static bool SetPermissionForGlobals(AuthStatus status, string action, UserGroup group) { + if(group == null) throw new ArgumentNullException("group"); + + return SetPermissionForGlobals(status, action, AuthTools.PrepareGroup(group.Name)); + } + + /// + /// Sets a permission for a global resource. + /// + /// The authorization status. + /// The action of which to modify the authorization status. + /// The user subject of the authorization change. + /// true if the authorization status is changed, false otherwise. + public static bool SetPermissionForGlobals(AuthStatus status, string action, UserInfo user) { + if(user == null) throw new ArgumentNullException("user"); + + return SetPermissionForGlobals(status, action, AuthTools.PrepareUsername(user.Username)); + } + + /// + /// Sets a permission for a global resource. + /// + /// The authorization status. + /// The action of which to modify the authorization status. + /// The subject of the authorization change. + /// true if the authorization status is changed, false otherwise. + private static bool SetPermissionForGlobals(AuthStatus status, string action, string subject) { + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(action != Actions.FullControl && !AuthTools.IsValidAction(action, Actions.ForGlobals.All)) { + throw new ArgumentException("Invalid action", "action"); + } + + if(status == AuthStatus.Delete) { + bool done = SettingsProvider.AclManager.DeleteEntry(Actions.ForGlobals.ResourceMasterPrefix, action, subject); + + if(done) { + Log.LogEntry(MessageDeleteSuccess + GetLogMessage(Actions.ForGlobals.ResourceMasterPrefix, "", + action, subject, Delete), EntryType.General, Log.SystemUsername); + } + else { + Log.LogEntry(MessageDeleteFailure + GetLogMessage(Actions.ForGlobals.ResourceMasterPrefix, "", + action, subject, Delete), EntryType.Error, Log.SystemUsername); + } + + return done; + } + else { + bool done = SettingsProvider.AclManager.StoreEntry(Actions.ForGlobals.ResourceMasterPrefix, + action, subject, status == AuthStatus.Grant ? Value.Grant : Value.Deny); + + if(done) { + Log.LogEntry(MessageSetSuccess + GetLogMessage(Actions.ForGlobals.ResourceMasterPrefix, "", + action, subject, Set + status.ToString()), EntryType.General, Log.SystemUsername); + } + else { + Log.LogEntry(MessageSetFailure + GetLogMessage(Actions.ForGlobals.ResourceMasterPrefix, "", + action, subject, Set + status.ToString()), EntryType.Error, Log.SystemUsername); + } + + return done; + } + } + + /// + /// Sets a permission for a namespace. + /// + /// The authorization status. + /// The namespace (null for the root). + /// The action of which to modify the authorization status. + /// The group subject of the authorization change. + /// true if the authorization status is changed, false otherwise. + public static bool SetPermissionForNamespace(AuthStatus status, NamespaceInfo nspace, string action, UserGroup group) { + if(group == null) throw new ArgumentNullException("group"); + + return SetPermissionForNamespace(status, nspace, action, AuthTools.PrepareGroup(group.Name)); + } + + /// + /// Sets a permission for a namespace. + /// + /// The authorization status. + /// The namespace (null for the root). + /// The action of which to modify the authorization status. + /// The user subject of the authorization change. + /// true if the authorization status is changed, false otherwise. + public static bool SetPermissionForNamespace(AuthStatus status, NamespaceInfo nspace, string action, UserInfo user) { + if(user == null) throw new ArgumentNullException("user"); + + return SetPermissionForNamespace(status, nspace, action, AuthTools.PrepareUsername(user.Username)); + } + + /// + /// Sets a permission for a namespace. + /// + /// The authorization status. + /// The namespace (null for the root). + /// The action of which to modify the authorization status. + /// The subject of the authorization change. + /// true if the authorization status is changed, false otherwise. + private static bool SetPermissionForNamespace(AuthStatus status, NamespaceInfo nspace, string action, string subject) { + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(action != Actions.FullControl && !AuthTools.IsValidAction(action, Actions.ForNamespaces.All)) { + throw new ArgumentException("Invalid action", "action"); + } + + string namespaceName = nspace != null ? nspace.Name : ""; + + if(status == AuthStatus.Delete) { + bool done = SettingsProvider.AclManager.DeleteEntry(Actions.ForNamespaces.ResourceMasterPrefix + namespaceName, + action, subject); + + if(done) { + Log.LogEntry(MessageDeleteSuccess + GetLogMessage(Actions.ForNamespaces.ResourceMasterPrefix, namespaceName, + action, subject, Delete), EntryType.General, Log.SystemUsername); + } + else { + Log.LogEntry(MessageDeleteFailure + GetLogMessage(Actions.ForNamespaces.ResourceMasterPrefix, namespaceName, + action, subject, Delete), EntryType.Error, Log.SystemUsername); + } + + return done; + } + else { + bool done = SettingsProvider.AclManager.StoreEntry(Actions.ForNamespaces.ResourceMasterPrefix + namespaceName, + action, subject, status == AuthStatus.Grant ? Value.Grant : Value.Deny); + + if(done) { + Log.LogEntry(MessageSetSuccess + GetLogMessage(Actions.ForNamespaces.ResourceMasterPrefix, namespaceName, + action, subject, Set + status.ToString()), EntryType.General, Log.SystemUsername); + } + else { + Log.LogEntry(MessageSetFailure + GetLogMessage(Actions.ForNamespaces.ResourceMasterPrefix, namespaceName, + action, subject, Set + status.ToString()), EntryType.Error, Log.SystemUsername); + } + + return done; + } + } + + /// + /// Sets a permission for a directory. + /// + /// The authorization status. + /// The provider that handles the directory. + /// The directory. + /// The action of which to modify the authorization status. + /// The group subject of the authorization change. + /// true if the authorization status is changed, false otherwise. + public static bool SetPermissionForDirectory(AuthStatus status, IFilesStorageProviderV30 provider, string directory, string action, UserGroup group) { + if(group == null) throw new ArgumentNullException("group"); + + return SetPermissionForDirectory(status, provider, directory, action, AuthTools.PrepareGroup(group.Name)); + } + + /// + /// Sets a permission for a directory. + /// + /// The authorization status. + /// The provider that handles the directory. + /// The directory. + /// The action of which to modify the authorization status. + /// The user subject of the authorization change. + /// true if the authorization status is changed, false otherwise. + public static bool SetPermissionForDirectory(AuthStatus status, IFilesStorageProviderV30 provider, string directory, string action, UserInfo user) { + if(user == null) throw new ArgumentNullException("user"); + + return SetPermissionForDirectory(status, provider, directory, action, AuthTools.PrepareUsername(user.Username)); + } + + /// + /// Sets a permission for a directory. + /// + /// The authorization status. + /// The provider that handles the directory. + /// The directory. + /// The action of which to modify the authorization status. + /// The subject of the authorization change. + /// true if the authorization status is changed, false otherwise. + private static bool SetPermissionForDirectory(AuthStatus status, IFilesStorageProviderV30 provider, string directory, string action, string subject) { + if(provider == null) throw new ArgumentNullException("provider"); + if(directory == null) throw new ArgumentNullException("directory"); + if(directory.Length == 0) throw new ArgumentException("Directory cannot be empty", "directory"); + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(action != Actions.FullControl && !AuthTools.IsValidAction(action, Actions.ForDirectories.All)) { + throw new ArgumentException("Invalid action", "action"); + } + + string directoryName = AuthTools.GetDirectoryName(provider, directory); + + if(status == AuthStatus.Delete) { + bool done = SettingsProvider.AclManager.DeleteEntry(Actions.ForDirectories.ResourceMasterPrefix + directoryName, + action, subject); + + if(done) { + Log.LogEntry(MessageDeleteSuccess + GetLogMessage(Actions.ForDirectories.ResourceMasterPrefix, directoryName, + action, subject, Delete), EntryType.General, Log.SystemUsername); + } + else { + Log.LogEntry(MessageDeleteFailure + GetLogMessage(Actions.ForDirectories.ResourceMasterPrefix, directoryName, + action, subject, Delete), EntryType.Error, Log.SystemUsername); + } + + return done; + } + else { + bool done = SettingsProvider.AclManager.StoreEntry(Actions.ForDirectories.ResourceMasterPrefix + directoryName, + action, subject, status == AuthStatus.Grant ? Value.Grant : Value.Deny); + + if(done) { + Log.LogEntry(MessageSetSuccess + GetLogMessage(Actions.ForDirectories.ResourceMasterPrefix, directoryName, + action, subject, Set + status.ToString()), EntryType.General, Log.SystemUsername); + } + else { + Log.LogEntry(MessageSetFailure + GetLogMessage(Actions.ForDirectories.ResourceMasterPrefix, directoryName, + action, subject, Set + status.ToString()), EntryType.Error, Log.SystemUsername); + } + + return done; + } + } + + /// + /// Sets a permission for a page. + /// + /// The authorization status. + /// The page. + /// The action of which to modify the authorization status. + /// The group subject of the authorization change. + /// true if the authorization status is changed, false otherwise. + public static bool SetPermissionForPage(AuthStatus status, PageInfo page, string action, UserGroup group) { + if(group == null) throw new ArgumentNullException("group"); + + return SetPermissionForPage(status, page, action, AuthTools.PrepareGroup(group.Name)); + } + + /// + /// Sets a permission for a page. + /// + /// The authorization status. + /// The page. + /// The action of which to modify the authorization status. + /// The user subject of the authorization change. + /// true if the authorization status is changed, false otherwise. + public static bool SetPermissionForPage(AuthStatus status, PageInfo page, string action, UserInfo user) { + if(user == null) throw new ArgumentNullException("user"); + + return SetPermissionForPage(status, page, action, AuthTools.PrepareUsername(user.Username)); + } + + /// + /// Sets a permission for a page. + /// + /// The authorization status. + /// The page. + /// The action of which to modify the authorization status. + /// The subject of the authorization change. + /// true if the authorization status is changed, false otherwise. + private static bool SetPermissionForPage(AuthStatus status, PageInfo page, string action, string subject) { + if(page == null) throw new ArgumentNullException("page"); + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(action != Actions.FullControl && !AuthTools.IsValidAction(action, Actions.ForPages.All)) { + throw new ArgumentException("Invalid action", "action"); + } + + if(status == AuthStatus.Delete) { + bool done = SettingsProvider.AclManager.DeleteEntry(Actions.ForPages.ResourceMasterPrefix + page.FullName, + action, subject); + + if(done) { + Log.LogEntry(MessageDeleteSuccess + GetLogMessage(Actions.ForPages.ResourceMasterPrefix, page.FullName, + action, subject, Delete), EntryType.General, Log.SystemUsername); + } + else { + Log.LogEntry(MessageDeleteFailure + GetLogMessage(Actions.ForPages.ResourceMasterPrefix, page.FullName, + action, subject, Delete), EntryType.Error, Log.SystemUsername); + } + + return done; + } + else { + bool done = SettingsProvider.AclManager.StoreEntry(Actions.ForPages.ResourceMasterPrefix + page.FullName, + action, subject, status == AuthStatus.Grant ? Value.Grant : Value.Deny); + + if(done) { + Log.LogEntry(MessageSetSuccess + GetLogMessage(Actions.ForPages.ResourceMasterPrefix, page.FullName, + action, subject, Set + status.ToString()), EntryType.General, Log.SystemUsername); + } + else { + Log.LogEntry(MessageSetFailure + GetLogMessage(Actions.ForPages.ResourceMasterPrefix, page.FullName, + action, subject, Set + status.ToString()), EntryType.Error, Log.SystemUsername); + } + + return done; + } + } + + /// + /// Removes all the ACL Entries for global resources that are bound to a user group. + /// + /// The user group. + /// true if the operation succeeded, false otherwise. + public static bool RemoveEntriesForGlobals(UserGroup group) { + if(group == null) throw new ArgumentNullException("group"); + + return RemoveEntriesForGlobals(AuthTools.PrepareGroup(group.Name)); + } + + /// + /// Removes all the ACL Entries for global resources that are bound to a user. + /// + /// The user. + /// true if the operation succeeded, false otherwise. + public static bool RemoveEntriesForGlobals(UserInfo user) { + if(user == null) throw new ArgumentNullException("user"); + + return RemoveEntriesForGlobals(AuthTools.PrepareUsername(user.Username)); + } + + /// + /// Removes all the ACL Entries for global resources that are bound to a subject. + /// + /// The subject. + /// true if the operation succeeded, false otherwise. + private static bool RemoveEntriesForGlobals(string subject) { + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject); + + foreach(AclEntry entry in entries) { + if(entry.Resource == Actions.ForGlobals.ResourceMasterPrefix) { + // This call automatically logs the operation result + bool done = SetPermissionForGlobals(AuthStatus.Delete, entry.Action, subject); + if(!done) return false; + } + } + + return true; + } + + /// + /// Removes all the ACL Entries for a namespace that are bound to a user group. + /// + /// The user group. + /// The namespace (null for the root). + /// true if the operation succeeded, false otherwise. + public static bool RemoveEntriesForNamespace(UserGroup group, NamespaceInfo nspace) { + if(group == null) throw new ArgumentNullException("group"); + + return RemoveEntriesForNamespace(AuthTools.PrepareGroup(group.Name), nspace); + } + + /// + /// Removes all the ACL Entries for a namespace that are bound to a user. + /// + /// The user. + /// The namespace (null for the root). + /// true if the operation succeeded, false otherwise. + public static bool RemoveEntriesForNamespace(UserInfo user, NamespaceInfo nspace) { + if(user == null) throw new ArgumentNullException("user"); + + return RemoveEntriesForNamespace(AuthTools.PrepareUsername(user.Username), nspace); + } + + /// + /// Removes all the ACL Entries for a namespace that are bound to a subject. + /// + /// The subject. + /// The namespace (null for the root). + /// true if the operation succeeded, false otherwise. + private static bool RemoveEntriesForNamespace(string subject, NamespaceInfo nspace) { + string resourceName = Actions.ForNamespaces.ResourceMasterPrefix; + if(nspace != null) resourceName += nspace.Name; + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject); + + foreach(AclEntry entry in entries) { + if(entry.Resource == resourceName) { + // This call automatically logs the operation result + bool done = SetPermissionForNamespace(AuthStatus.Delete, nspace, entry.Action, subject); + if(!done) return false; + } + } + + return true; + } + + /// + /// Removes all the ACL Entries for a page that are bound to a user group. + /// + /// The user group. + /// The page. + /// true if the operation succeeded, false otherwise. + public static bool RemoveEntriesForPage(UserGroup group, PageInfo page) { + if(group == null) throw new ArgumentNullException("group"); + + return RemoveEntriesForPage(AuthTools.PrepareGroup(group.Name), page); + } + + /// + /// Removes all the ACL Entries for a page that are bound to a user. + /// + /// The user. + /// The page. + /// true if the operation succeeded, false otherwise. + public static bool RemoveEntriesForPage(UserInfo user, PageInfo page) { + if(user == null) throw new ArgumentNullException("user"); + + return RemoveEntriesForPage(AuthTools.PrepareUsername(user.Username), page); + } + + /// + /// Removes all the ACL Entries for a page that are bound to a subject. + /// + /// The subject. + /// The page. + /// true if the operation succeeded, false otherwise. + private static bool RemoveEntriesForPage(string subject, PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + string resourceName = Actions.ForPages.ResourceMasterPrefix + page.FullName; + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject); + + foreach(AclEntry entry in entries) { + if(entry.Resource == resourceName) { + // This call automatically logs the operation result + bool done = SetPermissionForPage(AuthStatus.Delete, page, entry.Action, subject); + if(!done) return false; + } + } + + return true; + } + + /// + /// Removes all the ACL Entries for a directory that are bound to a user group. + /// + /// The group. + /// The provider. + /// The directory. + /// true if the operation succeeded, false otherwise. + public static bool RemoveEntriesForDirectory(UserGroup group, IFilesStorageProviderV30 provider, string directory) { + if(group == null) throw new ArgumentNullException("group"); + + return RemoveEntriesForDirectory(AuthTools.PrepareGroup(group.Name), provider, directory); + } + + /// + /// Removes all the ACL Entries for a directory that are bound to a user. + /// + /// The user. + /// The provider. + /// The directory. + /// true if the operation succeeded, false otherwise. + public static bool RemoveEntriesForDirectory(UserInfo user, IFilesStorageProviderV30 provider, string directory) { + if(user == null) throw new ArgumentNullException("user"); + + return RemoveEntriesForDirectory(AuthTools.PrepareUsername(user.Username), provider, directory); + } + + /// + /// Removes all the ACL Entries for a directory that are bound to a subject. + /// + /// The subject. + /// The provider. + /// The directory. + /// true if the operation succeeded, false otherwise. + private static bool RemoveEntriesForDirectory(string subject, IFilesStorageProviderV30 provider, string directory) { + if(provider == null) throw new ArgumentNullException("provider"); + if(directory == null) throw new ArgumentNullException("directory"); + if(directory.Length == 0) throw new ArgumentException("Directory cannot be empty", "directory"); + + string resourceName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, directory); + + AclEntry[] entries = SettingsProvider.AclManager.RetrieveEntriesForSubject(subject); + + foreach(AclEntry entry in entries) { + if(entry.Resource == resourceName) { + // This call automatically logs the operation result + bool done = SetPermissionForDirectory(AuthStatus.Delete, provider, directory, entry.Action, subject); + if(!done) return false; + } + } + + return true; + } + + /// + /// Clears all the ACL entries for a directory. + /// + /// The provider. + /// The directory. + public static void ClearEntriesForDirectory(IFilesStorageProviderV30 provider, string directory) { + if(provider == null) throw new ArgumentNullException("provider"); + + if(directory == null) throw new ArgumentNullException("directory"); + if(directory.Length == 0) throw new ArgumentException("Directory cannot be empty", "directory"); + + string resourceName = Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, directory); + + SettingsProvider.AclManager.DeleteEntriesForResource(resourceName); + } + + /// + /// Clears all the ACL entries for a namespace. + /// + /// The namespace. + /// The local names of the pages in the namespace. + public static void ClearEntriesForNamespace(string nspace, List pages) { + if(nspace == null) throw new ArgumentNullException("nspac"); + if(nspace.Length == 0) throw new ArgumentException("Namespace cannot be empty", "nspace"); + + if(pages == null) throw new ArgumentNullException("pages"); + + foreach(string p in pages) { + if(p == null) throw new ArgumentNullException("pages"); + if(p.Length == 0) throw new ArgumentException("Page Element cannot be empty", "pages"); + } + + string resourceName; + + foreach(string p in pages) { + resourceName = Actions.ForPages.ResourceMasterPrefix + NameTools.GetFullName(nspace, p); + SettingsProvider.AclManager.DeleteEntriesForResource(resourceName); + } + + resourceName = Actions.ForNamespaces.ResourceMasterPrefix + nspace; + SettingsProvider.AclManager.DeleteEntriesForResource(resourceName); + } + + /// + /// Clears all the ACL entries for a page. + /// + /// The page full name. + public static void ClearEntriesForPage(string page) { + if(page == null) throw new ArgumentNullException("page"); + if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); + + string resourceName = Actions.ForPages.ResourceMasterPrefix + page; + + SettingsProvider.AclManager.DeleteEntriesForResource(resourceName); + } + + /// + /// Processes the renaming of a directory. + /// + /// The provider. + /// The old directory name (full path). + /// The new directory name (full path). + /// true if the operation completed successfully, false otherwise. + /// The method does not recurse in sub-directories. + public static bool ProcessDirectoryRenaming(IFilesStorageProviderV30 provider, string oldName, string newName) { + if(provider == null) throw new ArgumentNullException("provider"); + + if(oldName == null) throw new ArgumentNullException("oldName"); + if(oldName.Length == 0) throw new ArgumentException("Old Name cannot be empty", "oldName"); + + if(newName == null) throw new ArgumentNullException("newName"); + if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); + + return SettingsProvider.AclManager.RenameResource( + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, oldName), + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(provider, newName)); + } + + /// + /// Processes the renaming of a namespace. + /// + /// The old name of the namespace. + /// The list of local names of the pages in the renamed namespace. + /// The new name of the namespace. + /// true if the operation completed successfully, false otherwise. + public static bool ProcessNamespaceRenaming(string oldName, List oldPages, string newName) { + if(oldName == null) throw new ArgumentNullException("oldName"); + if(oldName.Length == 0) throw new ArgumentException("Old Name cannot be empty", "oldName"); + + if(oldPages == null) throw new ArgumentNullException("oldPages"); + foreach(string p in oldPages) { + if(p == null) throw new ArgumentNullException("oldPages"); + if(p.Length == 0) throw new ArgumentException("Page cannot be empty", "oldPages"); + } + + if(newName == null) throw new ArgumentNullException("newName"); + if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); + + foreach(string p in oldPages) { + SettingsProvider.AclManager.RenameResource( + Actions.ForPages.ResourceMasterPrefix + NameTools.GetFullName(oldName, p), + Actions.ForPages.ResourceMasterPrefix + NameTools.GetFullName(newName, p)); + } + + return SettingsProvider.AclManager.RenameResource( + Actions.ForNamespaces.ResourceMasterPrefix + oldName, + Actions.ForNamespaces.ResourceMasterPrefix + newName); + } + + /// + /// Processes the renaming of a page. + /// + /// The old full page name. + /// The new full page name. + /// true if the operation completed successfully, false otherwise. + public static bool ProcessPageRenaming(string oldName, string newName) { + if(oldName == null) throw new ArgumentNullException("oldName"); + if(oldName.Length == 0) throw new ArgumentException("Old Name cannot be empty", "oldName"); + + if(newName == null) throw new ArgumentNullException("newName"); + if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); + + return SettingsProvider.AclManager.RenameResource( + Actions.ForPages.ResourceMasterPrefix + oldName, + Actions.ForPages.ResourceMasterPrefix + newName); + } + + /// + /// Gets the log message for an ACL entry change. + /// + /// The resource prefix. + /// The resource name. + /// The action. + /// The subject. + /// The status. + /// The message. + private static string GetLogMessage(string resourcePrefix, string resource, string action, string subject, string status) { + return resourcePrefix + resource + ":" + action + ":" + subject + "->" + status; + } + + } + +} diff --git a/Core/BreadcrumbsManager.cs b/Core/BreadcrumbsManager.cs new file mode 100644 index 0000000..1ca641b --- /dev/null +++ b/Core/BreadcrumbsManager.cs @@ -0,0 +1,151 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using System.Web; + +namespace ScrewTurn.Wiki { + + /// + /// Manages navigation Breadcrumbs. + /// + public class BreadcrumbsManager { + + private const int MaxPages = 10; + private const string CookieName = "ScrewTurnWikiBreadcrumbs3"; + private const string CookieValue = "B"; + + private List pages; + + /// + /// Gets the cookie. + /// + /// The cookie, or null. + private HttpCookie GetCookie() { + if(HttpContext.Current.Request != null) { + HttpCookie cookie = HttpContext.Current.Request.Cookies[CookieName]; + return cookie; + } + else return null; + } + + /// + /// Initializes a new instance of the BreadcrumbsManager class. + /// + public BreadcrumbsManager() { + pages = new List(MaxPages); + + HttpCookie cookie = GetCookie(); + if(cookie != null && !string.IsNullOrEmpty(cookie.Values[CookieValue])) { + try { + foreach(string p in cookie.Values[CookieValue].Split('|')) { + PageInfo page = Pages.FindPage(p); + if(page != null) pages.Add(page); + } + } + catch { } + } + } + + /// + /// Updates the cookie. + /// + private void UpdateCookie() { + HttpCookie cookie = GetCookie(); + if(cookie == null) { + cookie = new HttpCookie(CookieName); + } + cookie.Path = Settings.CookiePath; + + StringBuilder sb = new StringBuilder(MaxPages * 20); + for(int i = 0; i < pages.Count; i++) { + sb.Append(pages[i].FullName); + if(i != pages.Count - 1) sb.Append("|"); + } + + cookie.Values[CookieValue] = sb.ToString(); + if(HttpContext.Current.Response != null) { + HttpContext.Current.Response.Cookies.Set(cookie); + } + if(HttpContext.Current.Request != null) { + HttpContext.Current.Request.Cookies.Set(cookie); + } + } + + /// + /// Adds a Page to the Breadcrumbs trail. + /// + /// The Page to add. + public void AddPage(PageInfo page) { + lock(this) { + int index = FindPage(page); + if(index != -1) pages.RemoveAt(index); + pages.Add(page); + if(pages.Count > MaxPages) pages.RemoveRange(0, pages.Count - MaxPages); + + UpdateCookie(); + } + } + + /// + /// Finds a page by name. + /// + /// The page. + /// The index in the collection. + private int FindPage(PageInfo page) { + lock(this) { + if(pages == null || pages.Count == 0) return -1; + + PageNameComparer comp = new PageNameComparer(); + for(int i = 0; i < pages.Count; i++) { + if(comp.Compare(pages[i], page) == 0) return i; + } + + return -1; + } + } + + /// + /// Removes a Page from the Breadcrumbs trail. + /// + /// The Page to remove. + public void RemovePage(PageInfo page) { + lock(this) { + int index = FindPage(page); + if(index >= 0) pages.RemoveAt(index); + + UpdateCookie(); + } + } + + /// + /// Clears the Breadcrumbs trail. + /// + public void Clear() { + lock(this) { + pages.Clear(); + + UpdateCookie(); + } + } + + /// + /// Gets all the Pages in the trail that still exist. + /// + public PageInfo[] AllPages { + get { + lock(this) { + List newPages = new List(pages.Count); + foreach(PageInfo p in pages) { + if(Pages.FindPage(p.FullName) != null) newPages.Add(p); + } + + return newPages.ToArray(); + } + } + } + + } + +} diff --git a/Core/Cache.cs b/Core/Cache.cs new file mode 100644 index 0000000..1783cc9 --- /dev/null +++ b/Core/Cache.cs @@ -0,0 +1,126 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Manages data cache. + /// + public static class Cache { + + /// + /// Gets the cache provider. + /// + public static ICacheProviderV30 Provider { + get { return Collectors.CacheProviderCollector.GetProvider(Settings.DefaultCacheProvider); } + } + + /// + /// Gets or sets the number of users online. + /// + public static int OnlineUsers { + get { return Provider.OnlineUsers; } + set { Provider.OnlineUsers = value; } + } + + /// + /// Clears the pages cache. + /// + public static void ClearPageCache() { + Provider.ClearPageContentCache(); + } + + /// + /// Clears the pseudo cache. + /// + public static void ClearPseudoCache() { + Provider.ClearPseudoCache(); + } + + /// + /// Gets a cached . + /// + /// The page to get the content of. + /// The page content, or null. + public static PageContent GetPageContent(PageInfo page) { + if(page == null) return null; + return Provider.GetPageContent(page); + } + + /// + /// Gets a cached formatted page content. + /// + /// The page to get the formatted content of. + /// The formatted page content, or null. + public static string GetFormattedPageContent(PageInfo page) { + if(page == null) return null; + return Provider.GetFormattedPageContent(page); + } + + /// + /// Sets the page content in cache. + /// + /// The page to set the content of. + /// The content. + public static void SetPageContent(PageInfo page, PageContent content) { + Provider.SetPageContent(page, content); + if(Provider.PageCacheUsage > Settings.CacheSize) { + Provider.CutCache(Settings.CacheCutSize); + } + } + + /// + /// Sets the formatted page content in cache. + /// + /// The page to set the content of. + /// The content. + public static void SetFormattedPageContent(PageInfo page, string content) { + Provider.SetFormattedPageContent(page, content); + } + + /// + /// Removes a page from the cache. + /// + /// The page to remove. + public static void RemovePage(PageInfo page) { + Provider.RemovePage(page); + } + + /// + /// Gets a pseudo cache item value. + /// + /// The name of the item to get the value of. + /// The value of the item, or null. + public static string GetPseudoCacheValue(string name) { + return Provider.GetPseudoCacheValue(name); + } + + /// + /// Sets a pseudo cache item value. + /// + /// The name of the item to set the value of. + /// The value of the item. + public static void SetPseudoCacheValue(string name, string value) { + Provider.SetPseudoCacheValue(name, value); + } + + /// + /// Gets the number of pages currently in the page cache. + /// + public static int PageCacheUsage { + get { return Provider.PageCacheUsage; } + } + + /// + /// Gets the number of formatted pages currently in the page cache. + /// + public static int FormattedPageCacheUsage { + get { return Provider.FormatterPageCacheUsage; } + } + + } + +} diff --git a/Core/CacheProvider.cs b/Core/CacheProvider.cs new file mode 100644 index 0000000..dc565d9 --- /dev/null +++ b/Core/CacheProvider.cs @@ -0,0 +1,539 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Implements a local cache provider. All instance members are thread-safe. + /// + public class CacheProvider : ICacheProviderV30 { + + private readonly ComponentInformation _info = + new ComponentInformation("Local Cache Provider", "ScrewTurn Software", Settings.WikiVersion, "http://www.screwturn.eu", null); + + private IHostV30 _host; + + // Implements the pseudo cache + private Dictionary _pseudoCache; + + // Contains the page contents + private Dictionary _pageContentCache; + + // Contains the partially-formatted page content + private Dictionary _formattedContentCache; + + // Records, for each page, how many times a page has been requested, + // limited to page contents (not formatted content) + private Dictionary _pageCacheUsage; + + private int _onlineUsers = 0; + + private List _sessions; + + // Key is lowercase, invariant culture + private Dictionary _redirections; + + /// + /// Initializes the Storage Provider. + /// + /// The Host of the Component. + /// The Configuration data, if any. + /// If host or config are null. + /// If config is not valid or is incorrect. + public void Init(IHostV30 host, string config) { + if(host == null) throw new ArgumentNullException("host"); + if(config == null) throw new ArgumentNullException("config"); + + _host = host; + + int s = int.Parse(host.GetSettingValue(SettingName.CacheSize)); + + // Initialize pseudo cache + _pseudoCache = new Dictionary(10); + + // Initialize page content cache + _pageContentCache = new Dictionary(s); + _pageCacheUsage = new Dictionary(s); + + // Initialize formatted page content cache + _formattedContentCache = new Dictionary(s); + + _sessions = new List(50); + + _redirections = new Dictionary(50); + } + + /// + /// Method invoked on shutdown. + /// + /// This method might not be invoked in some cases. + public void Shutdown() { + ClearPseudoCache(); + ClearPageContentCache(); + } + + /// + /// Gets or sets the number of users online. + /// + public int OnlineUsers { + get { + lock(this) { + return _onlineUsers; + } + } + set { + lock(this) { + _onlineUsers = value; + } + } + } + + /// + /// Gets the Information about the Provider. + /// + public ComponentInformation Information { + get { return _info; } + } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + public string ConfigHelpHtml { + get { return null; } + } + + /// + /// Gets the value of a Pseudo-cache item, previously stored in the cache. + /// + /// The name of the item being requested. + /// The value of the item, or null if the item is not found. + /// If name is null. + /// If name is empty. + public string GetPseudoCacheValue(string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + string value = null; + lock(_pseudoCache) { + _pseudoCache.TryGetValue(name, out value); + } + return value; + } + + /// + /// Sets the value of a Pseudo-cache item. + /// + /// The name of the item being stored. + /// The value of the item. If the value is null, then the item should be removed from the cache. + /// If name is null. + /// If name is empty. + public void SetPseudoCacheValue(string name, string value) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + lock(_pseudoCache) { + if(value == null) _pseudoCache.Remove(name); + else _pseudoCache[name] = value; + } + } + + /// + /// Gets the Content of a Page, previously stored in cache. + /// + /// The Page Info object related to the Content being requested. + /// The Page Content object, or null if the item is not found. + /// If pageInfo is null. + public PageContent GetPageContent(PageInfo pageInfo) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + + PageContent value = null; + lock(_pageContentCache) { + if(_pageContentCache.TryGetValue(pageInfo, out value)) { + _pageCacheUsage[pageInfo]++; + } + } + return value; + } + + /// + /// Sets the Content of a Page. + /// + /// The Page Info object related to the Content being stored. + /// The Content of the Page. + /// If pageInfo or content are null. + public void SetPageContent(PageInfo pageInfo, PageContent content) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + if(content == null) throw new ArgumentNullException("content"); + + lock(_pageContentCache) { + _pageContentCache[pageInfo] = content; + lock(_pageCacheUsage) { + _pageCacheUsage[pageInfo] = 0; + } + } + } + + /// + /// Gets the partially-formatted content (text) of a Page, previously stored in the cache. + /// + /// The Page Info object related to the content being requested. + /// The partially-formatted content, or null if the item is not found. + /// If pageInfo is null. + public string GetFormattedPageContent(PageInfo pageInfo) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + + string value = null; + lock(_formattedContentCache) { + _formattedContentCache.TryGetValue(pageInfo, out value); + } + return value; + } + + /// + /// Sets the partially-preformatted content (text) of a Page. + /// + /// The Page Info object related to the content being stored. + /// The partially-preformatted content. + /// If pageInfo or content are null. + public void SetFormattedPageContent(PageInfo pageInfo, string content) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + if(content == null) throw new ArgumentNullException("content"); + + lock(_formattedContentCache) { + _formattedContentCache[pageInfo] = content; + } + } + + /// + /// Removes a Page from the cache. + /// + /// The Page Info object related to the Page that has to be removed. + /// If pageInfo is null. + public void RemovePage(PageInfo pageInfo) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + + // In this order + lock(_formattedContentCache) { + _formattedContentCache.Remove(pageInfo); + } + lock(_pageContentCache) { + _pageContentCache.Remove(pageInfo); + } + lock(_pageCacheUsage) { + _pageCacheUsage.Remove(pageInfo); + } + } + + /// + /// Clears the Page Content cache. + /// + public void ClearPageContentCache() { + // In this order + lock(_formattedContentCache) { + _formattedContentCache.Clear(); + } + lock(_pageContentCache) { + _pageContentCache.Clear(); + } + lock(_pageCacheUsage) { + _pageCacheUsage.Clear(); + } + } + + /// + /// Clears the Pseudo-Cache. + /// + public void ClearPseudoCache() { + lock(_pseudoCache) { + _pseudoCache.Clear(); + } + } + + /// + /// Reduces the size of the Page Content cache, removing the least-recently used items. + /// + /// The number of Pages to remove. + /// If cutSize is less than or equal to zero. + public void CutCache(int cutSize) { + if(cutSize <= 0) throw new ArgumentOutOfRangeException("cutSize", "Cut Size should be greater than zero"); + + lock(_pageContentCache) { + + // TODO: improve performance - now the operation is O(cache_size^2) + for(int i = 0; i < cutSize; i++) { + PageInfo key = null; + int min = int.MaxValue; + + // Find the page that has been requested the least times + foreach(PageInfo p in _pageCacheUsage.Keys) { + if(_pageCacheUsage[p] < min) { + key = p; + min = _pageCacheUsage[p]; + } + } + + // This is necessary to avoid infinite loops + if(key == null) { + break; + } + // Remove the page from cache + RemovePage(key); + } + } + } + + /// + /// Gets the number of Pages whose content is currently stored in the cache. + /// + public int PageCacheUsage { + get { + lock(_pageContentCache) { + return _pageContentCache.Count; + } + } + } + + /// + /// Gets the numer of Pages whose formatted content is currently stored in the cache. + /// + public int FormatterPageCacheUsage { + get { + lock(_formattedContentCache) { + return _formattedContentCache.Count; + } + } + } + + /// + /// Adds or updates an editing session. + /// + /// The edited Page. + /// The User who is editing the Page. + /// If page or user are null. + /// If page or user are empty. + public void RenewEditingSession(string page, string user) { + if(page == null) throw new ArgumentNullException("page"); + if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); + if(user == null) throw new ArgumentNullException("user"); + if(user.Length == 0) throw new ArgumentException("User cannot be empty", "user"); + + lock(this) { + bool found = false; + for(int i = 0; i < _sessions.Count; i++) { + if(_sessions[i].Page == page && _sessions[i].User.Equals(user)) { + _sessions[i].Renew(); + found = true; + break; + } + } + if(!found) _sessions.Add(new EditingSession(page, user)); + } + } + + /// + /// Cancels an editing session. + /// + /// The Page. + /// The User. + /// If page or user are null. + /// If page or user are empty. + public void CancelEditingSession(string page, string user) { + if(page == null) throw new ArgumentNullException("page"); + if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); + if(user == null) throw new ArgumentNullException("user"); + if(user.Length == 0) throw new ArgumentException("User cannot be empty", "user"); + + lock(this) { + for(int i = 0; i < _sessions.Count; i++) { + if(_sessions[i].Page == page && _sessions[i].User.Equals(user)) { + _sessions.Remove(_sessions[i]); + break; + } + } + } + } + + /// + /// Finds whether a Page is being edited by a different user. + /// + /// The Page. + /// The User who is requesting the status of the Page. + /// True if the Page is being edited by another User. + /// If page or currentUser are null. + /// If page or currentUser are empty. + public bool IsPageBeingEdited(string page, string currentUser) { + if(page == null) throw new ArgumentNullException("page"); + if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); + if(currentUser == null) throw new ArgumentNullException("currentUser"); + if(currentUser.Length == 0) throw new ArgumentException("Current User cannot be empty", "currentUser"); + + lock(this) { + int timeout = int.Parse(_host.GetSettingValue(SettingName.EditingSessionTimeout)); + DateTime limit = DateTime.Now.AddSeconds(-(timeout + 5)); // Allow 5 seconds for network delays + + for(int i = 0; i < _sessions.Count; i++) { + if(_sessions[i].Page == page && !_sessions[i].User.Equals(currentUser)) { + if(_sessions[i].LastContact.CompareTo(limit) >= 0) { + // Page is being edited + return true; + } + else { + // Lost contact + _sessions.Remove(_sessions[i]); + return false; + } + } + } + } + return false; + } + + /// + /// Gets the username of the user who's editing a page. + /// + /// The page. + /// The username. + /// If page is null. + /// If page is empty. + public string WhosEditing(string page) { + if(page == null) throw new ArgumentNullException("page"); + if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); + + lock(this) { + foreach(EditingSession s in _sessions) { + if(s.Page == page) return s.User; + } + } + return ""; + } + + /// + /// Adds the redirection information for a page (overwrites the previous value, if any). + /// + /// The source page. + /// The destination page. + /// If source or destination are null. + /// If source or destination are empty. + public void AddRedirection(string source, string destination) { + if(source == null) throw new ArgumentNullException("source"); + if(source.Length == 0) throw new ArgumentException("Source cannot be empty", "source"); + if(destination == null) throw new ArgumentNullException("destination"); + if(destination.Length == 0) throw new ArgumentException("Destination cannot be empty", "destination"); + + lock(_redirections) { + _redirections[source.ToLowerInvariant()] = destination; + } + } + + /// + /// Gets the destination of a redirection. + /// + /// The source page. + /// The destination page, if any, null otherwise. + /// If source is null. + /// If source is empty. + public string GetRedirectionDestination(string source) { + if(source == null) throw new ArgumentNullException("source"); + if(source.Length == 0) throw new ArgumentException("Source cannot be empty", "source"); + + string dest = null; + + lock(_redirections) { + _redirections.TryGetValue(source.ToLowerInvariant(), out dest); + } + + return dest; + } + + /// + /// Removes a pge from both sources and destinations. + /// + /// The name of the page. + /// If name is null. + /// If name is empty. + public void RemovePageFromRedirections(string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + name = name.ToLowerInvariant(); + + lock(_redirections) { + _redirections.Remove(name); + + List keysToRemove = new List(10); + + foreach(KeyValuePair pair in _redirections) { + if(pair.Value.ToLowerInvariant() == name) keysToRemove.Add(pair.Key); + } + + foreach(string key in keysToRemove) { + _redirections.Remove(key); + } + } + } + + /// + /// Clears all the redirections information. + /// + public void ClearRedirections() { + lock(_redirections) { + _redirections.Clear(); + } + } + + } + + /// + /// Represents an Editing Session. + /// + public class EditingSession { + + private string page; + private string user; + private DateTime lastContact; + + /// + /// Initializes a new instance of the EditingSession class. + /// + /// The edited Page. + /// The User who is editing the Page. + public EditingSession(string page, string user) { + this.page = page; + this.user = user; + lastContact = DateTime.Now; + } + + /// + /// Gets the edited Page. + /// + public string Page { + get { return page; } + } + + /// + /// Gets the User. + /// + public string User { + get { return user; } + } + + /// + /// Sets the Last Contact to now. + /// + public void Renew() { + lastContact = DateTime.Now; + } + + /// + /// Gets the Last Contact Date/Time. + /// + public DateTime LastContact { + get { return lastContact; } + } + + } + +} diff --git a/Core/Collectors.cs b/Core/Collectors.cs new file mode 100644 index 0000000..0120b80 --- /dev/null +++ b/Core/Collectors.cs @@ -0,0 +1,268 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Contains instances of the Providers Collectors. + /// + public static class Collectors { + + // The following static instances are "almost" thread-safe because they are set at startup and never changed + // The ProviderCollector generic class is fully thread-safe + + /// + /// Contains the file names of the DLLs containing each provider (provider->file). + /// + public static Dictionary FileNames; + + /// + /// The settings storage provider. + /// + public static ISettingsStorageProviderV30 SettingsProvider; + + /// + /// The Users Provider Collector instance. + /// + public static ProviderCollector UsersProviderCollector; + + /// + /// The Pages Provider Collector instance. + /// + public static ProviderCollector PagesProviderCollector; + + /// + /// The Files Provider Collector instance. + /// + public static ProviderCollector FilesProviderCollector; + + /// + /// The Formatter Provider Collector instance. + /// + public static ProviderCollector FormatterProviderCollector; + + /// + /// The Cache Provider Collector instance. + /// + public static ProviderCollector CacheProviderCollector; + + /// + /// The Disabled Users Provider Collector instance. + /// + public static ProviderCollector DisabledUsersProviderCollector; + + /// + /// The Disabled Files Provider Collector instance. + /// + public static ProviderCollector DisabledFilesProviderCollector; + + /// + /// The Disabled Pages Provider Collector instance. + /// + public static ProviderCollector DisabledPagesProviderCollector; + + /// + /// The Disabled Formatter Provider Collector instance. + /// + public static ProviderCollector DisabledFormatterProviderCollector; + + /// + /// The Disabled Cache Provider Collector instance. + /// + public static ProviderCollector DisabledCacheProviderCollector; + + /// + /// Finds a provider. + /// + /// The provider type name. + /// A value indicating whether the provider is enabled. + /// A value indicating whether the provider can be disabled. + /// The provider, or null. + public static IProviderV30 FindProvider(string typeName, out bool enabled, out bool canDisable) { + enabled = true; + canDisable = true; + IProviderV30 prov = null; + + prov = PagesProviderCollector.GetProvider(typeName); + canDisable = typeName != Settings.DefaultPagesProvider; + if(prov == null) { + prov = DisabledPagesProviderCollector.GetProvider(typeName); + if(prov != null) enabled = false; + } + if(prov != null) return prov; + + prov = UsersProviderCollector.GetProvider(typeName); + canDisable = typeName != Settings.DefaultUsersProvider; + if(prov == null) { + prov = DisabledUsersProviderCollector.GetProvider(typeName); + if(prov != null) enabled = false; + } + if(prov != null) return prov; + + prov = FilesProviderCollector.GetProvider(typeName); + canDisable = typeName != Settings.DefaultFilesProvider; + if(prov == null) { + prov = DisabledFilesProviderCollector.GetProvider(typeName); + if(prov != null) enabled = false; + } + if(prov != null) return prov; + + prov = CacheProviderCollector.GetProvider(typeName); + canDisable = typeName != Settings.DefaultCacheProvider; + if(prov == null) { + prov = DisabledCacheProviderCollector.GetProvider(typeName); + if(prov != null) enabled = false; + } + if(prov != null) return prov; + + prov = FormatterProviderCollector.GetProvider(typeName); + if(prov == null) { + prov = DisabledFormatterProviderCollector.GetProvider(typeName); + if(prov != null) enabled = false; + } + if(prov != null) return prov; + + return null; + } + + /// + /// Tries to unload a provider. + /// + /// The provider. + public static void TryUnload(string typeName) { + bool enabled, canDisable; + IProviderV30 prov = FindProvider(typeName, out enabled, out canDisable); + + PagesProviderCollector.RemoveProvider(prov as IPagesStorageProviderV30); + DisabledPagesProviderCollector.RemoveProvider(prov as IPagesStorageProviderV30); + + UsersProviderCollector.RemoveProvider(prov as IUsersStorageProviderV30); + DisabledUsersProviderCollector.RemoveProvider(prov as IUsersStorageProviderV30); + + FilesProviderCollector.RemoveProvider(prov as IFilesStorageProviderV30); + DisabledFilesProviderCollector.RemoveProvider(prov as IFilesStorageProviderV30); + + CacheProviderCollector.RemoveProvider(prov as ICacheProviderV30); + DisabledCacheProviderCollector.RemoveProvider(prov as ICacheProviderV30); + + FormatterProviderCollector.RemoveProvider(prov as IFormatterProviderV30); + DisabledFormatterProviderCollector.RemoveProvider(prov as IFormatterProviderV30); + } + + /// + /// Tries to disable a provider. + /// + /// The provider. + public static void TryDisable(string typeName) { + IProviderV30 prov = null; + + prov = PagesProviderCollector.GetProvider(typeName); + if(prov != null) { + DisabledPagesProviderCollector.AddProvider((IPagesStorageProviderV30)prov); + PagesProviderCollector.RemoveProvider((IPagesStorageProviderV30)prov); + return; + } + + prov = UsersProviderCollector.GetProvider(typeName); + if(prov != null) { + DisabledUsersProviderCollector.AddProvider((IUsersStorageProviderV30)prov); + UsersProviderCollector.RemoveProvider((IUsersStorageProviderV30)prov); + return; + } + + prov = FilesProviderCollector.GetProvider(typeName); + if(prov != null) { + DisabledFilesProviderCollector.AddProvider((IFilesStorageProviderV30)prov); + FilesProviderCollector.RemoveProvider((IFilesStorageProviderV30)prov); + return; + } + + prov = CacheProviderCollector.GetProvider(typeName); + if(prov != null) { + DisabledCacheProviderCollector.AddProvider((ICacheProviderV30)prov); + CacheProviderCollector.RemoveProvider((ICacheProviderV30)prov); + return; + } + + prov = FormatterProviderCollector.GetProvider(typeName); + if(prov != null) { + DisabledFormatterProviderCollector.AddProvider((IFormatterProviderV30)prov); + FormatterProviderCollector.RemoveProvider((IFormatterProviderV30)prov); + return; + } + } + + /// + /// Tries to enable a provider. + /// + /// The provider. + public static void TryEnable(string typeName) { + IProviderV30 prov = null; + + prov = DisabledPagesProviderCollector.GetProvider(typeName); + if(prov != null) { + PagesProviderCollector.AddProvider((IPagesStorageProviderV30)prov); + DisabledPagesProviderCollector.RemoveProvider((IPagesStorageProviderV30)prov); + return; + } + + prov = DisabledUsersProviderCollector.GetProvider(typeName); + if(prov != null) { + UsersProviderCollector.AddProvider((IUsersStorageProviderV30)prov); + DisabledUsersProviderCollector.RemoveProvider((IUsersStorageProviderV30)prov); + return; + } + + prov = DisabledFilesProviderCollector.GetProvider(typeName); + if(prov != null) { + FilesProviderCollector.AddProvider((IFilesStorageProviderV30)prov); + DisabledFilesProviderCollector.RemoveProvider((IFilesStorageProviderV30)prov); + return; + } + + prov = DisabledCacheProviderCollector.GetProvider(typeName); + if(prov != null) { + CacheProviderCollector.AddProvider((ICacheProviderV30)prov); + DisabledCacheProviderCollector.RemoveProvider((ICacheProviderV30)prov); + return; + } + + prov = DisabledFormatterProviderCollector.GetProvider(typeName); + if(prov != null) { + FormatterProviderCollector.AddProvider((IFormatterProviderV30)prov); + DisabledFormatterProviderCollector.RemoveProvider((IFormatterProviderV30)prov); + return; + } + } + + /// + /// Gets the names of all known providers, both enabled and disabled. + /// + /// The names of the known providers. + public static string[] GetAllProviders() { + List result = new List(20); + + foreach(IProviderV30 prov in PagesProviderCollector.AllProviders) result.Add(prov.GetType().FullName); + foreach(IProviderV30 prov in DisabledPagesProviderCollector.AllProviders) result.Add(prov.GetType().FullName); + + foreach(IProviderV30 prov in UsersProviderCollector.AllProviders) result.Add(prov.GetType().FullName); + foreach(IProviderV30 prov in DisabledUsersProviderCollector.AllProviders) result.Add(prov.GetType().FullName); + + foreach(IProviderV30 prov in FilesProviderCollector.AllProviders) result.Add(prov.GetType().FullName); + foreach(IProviderV30 prov in DisabledFilesProviderCollector.AllProviders) result.Add(prov.GetType().FullName); + + foreach(IProviderV30 prov in CacheProviderCollector.AllProviders) result.Add(prov.GetType().FullName); + foreach(IProviderV30 prov in DisabledCacheProviderCollector.AllProviders) result.Add(prov.GetType().FullName); + + foreach(IProviderV30 prov in FormatterProviderCollector.AllProviders) result.Add(prov.GetType().FullName); + foreach(IProviderV30 prov in DisabledFormatterProviderCollector.AllProviders) result.Add(prov.GetType().FullName); + + return result.ToArray(); + } + + } + +} diff --git a/Core/Collisions.cs b/Core/Collisions.cs new file mode 100644 index 0000000..11ac838 --- /dev/null +++ b/Core/Collisions.cs @@ -0,0 +1,57 @@ + +using System; +using System.Collections.Generic; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Manages Page Editing collisions. + /// + public static class Collisions { + + /// + /// The refresh interval used for renewing the sessions. + /// + public const int EditingSessionTimeout = 20; + + /// + /// Adds or updates an editing session. + /// + /// The edited Page. + /// The User who is editing the Page. + public static void RenewEditingSession(PageInfo page, string user) { + Cache.Provider.RenewEditingSession(page.FullName, user); + } + + /// + /// Cancels an editing session. + /// + /// The Page. + /// The User. + public static void CancelEditingSession(PageInfo page, string user) { + Cache.Provider.CancelEditingSession(page.FullName, user); + } + + /// + /// Finds whether a Page is being edited by a different user. + /// + /// The Page. + /// The User who is requesting the status of the Page. + /// True if the Page is being edited by another User. + public static bool IsPageBeingEdited(PageInfo page, string currentUser) { + return Cache.Provider.IsPageBeingEdited(page.FullName, currentUser); + } + + /// + /// Gets the username of the user who's editing a page. + /// + /// The page. + /// The username. + public static string WhosEditing(PageInfo page) { + return Cache.Provider.WhosEditing(page.FullName); + } + + } + +} diff --git a/Core/Content.cs b/Core/Content.cs new file mode 100644 index 0000000..fff0b67 --- /dev/null +++ b/Core/Content.cs @@ -0,0 +1,105 @@ + +using System; +using System.Configuration; +using System.Web; +using System.Collections.Generic; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Contains the Contents. + /// + public static class Content { + + /// + /// Gets a pseudo cache item value. + /// + /// The name of the item to retrieve the value of. + /// The value of the item, or null. + public static string GetPseudoCacheValue(string name) { + return Cache.GetPseudoCacheValue(name); + } + + /// + /// Sets a pseudo cache item value, only if the content cache is enabled. + /// + /// The name of the item to store the value of. + /// The value of the item. + public static void SetPseudoCacheValue(string name, string value) { + if(!Settings.DisableCache) { + Cache.SetPseudoCacheValue(name, value); + } + } + + /// + /// Clears the pseudo cache. + /// + public static void ClearPseudoCache() { + Cache.ClearPseudoCache(); + } + + /// + /// Reads the Content of a Page. + /// + /// The Page. + /// Specifies whether the page has to be cached or not. + /// The Page Content. + public static PageContent GetPageContent(PageInfo pageInfo, bool cached) { + PageContent result = Cache.GetPageContent(pageInfo); + if(result == null) { + result = pageInfo.Provider.GetContent(pageInfo); + if(result!= null && !result.IsEmpty()) { + if(cached && !pageInfo.NonCached && !Settings.DisableCache) { + Cache.SetPageContent(pageInfo, result); + } + } + } + + // result should NEVER be null + if(result == null) { + Log.LogEntry("PageContent could not be retrieved for page " + pageInfo.FullName + " - returning empty", EntryType.Error, Log.SystemUsername); + result = PageContent.GetEmpty(pageInfo); + } + + return result; + } + + /// + /// Gets the formatted Page Content, properly handling content caching and the Formatting Pipeline. + /// + /// The Page to get the formatted Content of. + /// Specifies whether the formatted content has to be cached or not. + /// The formatted content. + public static string GetFormattedPageContent(PageInfo page, bool cached) { + string content = Cache.GetFormattedPageContent(page); + if(content == null) { + PageContent pg = GetPageContent(page, cached); + string[] linkedPages; + content = FormattingPipeline.FormatWithPhase1And2(pg.Content, false, FormattingContext.PageContent, page, out linkedPages); + pg.LinkedPages = linkedPages; + if(!pg.IsEmpty() && cached && !page.NonCached && !Settings.DisableCache) { + Cache.SetFormattedPageContent(page, content); + } + } + return FormattingPipeline.FormatWithPhase3(content, FormattingContext.PageContent, page); + } + + /// + /// Invalidates the cached Content of a Page. + /// + /// The Page to invalidate the cached content of. + public static void InvalidatePage(PageInfo pageInfo) { + Cache.RemovePage(pageInfo); + } + + /// + /// Invalidates all the cache Contents. + /// + public static void InvalidateAllPages() { + Cache.ClearPageCache(); + } + + } + +} diff --git a/Core/Core.csproj b/Core/Core.csproj new file mode 100644 index 0000000..8a52138 --- /dev/null +++ b/Core/Core.csproj @@ -0,0 +1,141 @@ + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {C353A35C-86D0-4154-9500-4F88CAAB29C3} + Library + Properties + ScrewTurn.Wiki + ScrewTurn.Wiki.Core + false + + + + + 2.0 + + + v3.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + 512 + true + bin\Debug\ScrewTurn.Wiki.Core.XML + + + none + true + bin\Release\ + TRACE + prompt + 4 + 512 + bin\Release\ScrewTurn.Wiki.Core.xml + true + + + + + 3.5 + + + + + + + + AssemblyVersion.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + + + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81} + AclEngine + + + {531A83D6-76F9-4014-91C5-295818E2D948} + PluginFramework + + + {2DF980A6-4742-49B1-A090-DE79314644D0} + SearchEngine + + + + + \ No newline at end of file diff --git a/Core/DataMigrator.cs b/Core/DataMigrator.cs new file mode 100644 index 0000000..8823802 --- /dev/null +++ b/Core/DataMigrator.cs @@ -0,0 +1,384 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using ScrewTurn.Wiki.AclEngine; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Provides methods for migrating data from a Provider to another. + /// + public static class DataMigrator { + + /// + /// Migrates all the data from a Pages Provider to another one. + /// + /// The source Provider. + /// The destination Provider. + public static void MigratePagesStorageProviderData(IPagesStorageProviderV30 source, IPagesStorageProviderV30 destination) { + // Move Snippets + Snippet[] snippets = source.GetSnippets(); + for(int i = 0; i < snippets.Length; i++) { + destination.AddSnippet(snippets[i].Name, snippets[i].Content); + source.RemoveSnippet(snippets[i].Name); + } + + // Move Content Templates + ContentTemplate[] templates = source.GetContentTemplates(); + for(int i = 0; i < templates.Length; i++) { + destination.AddContentTemplate(templates[i].Name, templates[i].Content); + source.RemoveContentTemplate(templates[i].Name); + } + + // Create namespaces + NamespaceInfo[] namespaces = source.GetNamespaces(); + NamespaceInfo[] createdNamespaces = new NamespaceInfo[namespaces.Length]; + for(int i = 0; i < namespaces.Length; i++) { + createdNamespaces[i] = destination.AddNamespace(namespaces[i].Name); + } + + List sourceNamespaces = new List(); + sourceNamespaces.Add(null); + sourceNamespaces.AddRange(namespaces); + + int currentNamespaceIndex = 0; + foreach(NamespaceInfo currentNamespace in sourceNamespaces) { + + // Load all nav paths now to avoid problems with missing pages from source provider + // after the pages have been moved already + NavigationPath[] sourceNavPaths = source.GetNavigationPaths(currentNamespace); + + // Copy categories (removed from source later) + CategoryInfo[] sourceCats = source.GetCategories(currentNamespace); + for(int i = 0; i < sourceCats.Length; i++) { + destination.AddCategory(NameTools.GetNamespace(sourceCats[i].FullName), NameTools.GetLocalName(sourceCats[i].FullName)); + } + + // Move Pages + PageInfo[] pages = source.GetPages(currentNamespace); + for(int i = 0; i < pages.Length; i++) { + + // Create Page + PageInfo newPage = destination.AddPage(NameTools.GetNamespace(pages[i].FullName), + NameTools.GetLocalName(pages[i].FullName), pages[i].CreationDateTime); + if(newPage == null) { + Log.LogEntry("Unable to move Page " + pages[i].FullName + " - Skipping", EntryType.Error, Log.SystemUsername); + continue; + } + + // Get content and store it, without backup + PageContent c = source.GetContent(pages[i]); + destination.ModifyPage(newPage, c.Title, c.User, c.LastModified, c.Comment, c.Content, c.Keywords, c.Description, SaveMode.Normal); + + // Move all the backups + int[] revs = source.GetBackups(pages[i]); + for(int k = 0; k < revs.Length; k++) { + c = source.GetBackupContent(pages[i], revs[k]); + destination.SetBackupContent(c, revs[k]); + } + + // Move all messages + Message[] messages = source.GetMessages(pages[i]); + destination.BulkStoreMessages(newPage, messages); + + // Bind current page (find all proper categories, and use them to bind the page) + List pageCats = new List(); + for(int k = 0; k < sourceCats.Length; k++) { + for(int z = 0; z < sourceCats[k].Pages.Length; z++) { + if(sourceCats[k].Pages[z].Equals(newPage.FullName)) { + pageCats.Add(sourceCats[k].FullName); + break; + } + } + } + destination.RebindPage(newPage, pageCats.ToArray()); + + // Copy draft + PageContent draft = source.GetDraft(pages[i]); + if(draft != null) { + destination.ModifyPage(newPage, draft.Title, draft.User, draft.LastModified, + draft.Comment, draft.Content, draft.Keywords, draft.Description, SaveMode.Draft); + } + + // Remove Page from source + source.RemovePage(pages[i]); // Also deletes the Messages + } + + // Remove Categories from source + for(int i = 0; i < sourceCats.Length; i++) { + source.RemoveCategory(sourceCats[i]); + } + + // Move navigation paths + List newPages = new List(destination.GetPages(currentNamespace == null ? null : createdNamespaces[currentNamespaceIndex])); + for(int i = 0; i < sourceNavPaths.Length; i++) { + PageInfo[] tmp = new PageInfo[sourceNavPaths[i].Pages.Length]; + for(int k = 0; k < tmp.Length; k++) { + tmp[k] = newPages.Find(delegate(PageInfo p) { return p.FullName == sourceNavPaths[i].Pages[k]; }); + } + destination.AddNavigationPath(NameTools.GetNamespace(sourceNavPaths[i].FullName), + NameTools.GetLocalName(sourceNavPaths[i].FullName), tmp); + source.RemoveNavigationPath(sourceNavPaths[i]); + } + + if(currentNamespace != null) { + // Set default page + PageInfo defaultPage = currentNamespace.DefaultPage == null ? null : + newPages.Find(delegate(PageInfo p) { return p.FullName == currentNamespace.DefaultPage.FullName; }); + destination.SetNamespaceDefaultPage(createdNamespaces[currentNamespaceIndex], defaultPage); + + // Remove namespace from source + source.RemoveNamespace(currentNamespace); + + currentNamespaceIndex++; + } + } + } + + /// + /// Migrates all the User Accounts from a Provider to another. + /// + /// The source Provider. + /// The destination Provider. + /// A value indicating whether or not to send a notification email message to the moved users. + public static void MigrateUsersStorageProviderData(IUsersStorageProviderV30 source, IUsersStorageProviderV30 destination, bool sendEmailNotification) { + // User groups + UserGroup[] groups = source.GetUserGroups(); + foreach(UserGroup group in groups) { + destination.AddUserGroup(group.Name, group.Description); + } + + // Users + UserInfo[] users = source.GetUsers(); + MovedUser[] movedUsers = new MovedUser[users.Length]; + + for(int i = 0; i < users.Length; i++) { + // Generate new Password, create MovedUser object, add new User, delete old User + string password = Tools.GenerateRandomPassword(); + movedUsers[i] = new MovedUser(users[i].Username, users[i].Email, users[i].DateTime); + UserInfo newUser = destination.AddUser(users[i].Username, users[i].DisplayName, password, users[i].Email, users[i].Active, users[i].DateTime); + + // Membership + destination.SetUserMembership(newUser, users[i].Groups); + + // User data + IDictionary uData = source.RetrieveAllUserData(users[i]); + foreach(KeyValuePair pair in uData) { + destination.StoreUserData(newUser, pair.Key, pair.Value); + } + + source.RemoveUser(users[i]); + } + + // Remove old groups + foreach(UserGroup group in groups) { + source.RemoveUserGroup(group); + } + + if(sendEmailNotification) { + // Send Emails + for(int i = 0; i < movedUsers.Length; i++) { + Users.SendPasswordResetMessage(movedUsers[i].Username, movedUsers[i].Email, movedUsers[i].DateTime); + } + } + } + + /// + /// Migrates all the stored files and page attachments from a Provider to another. + /// + /// The source Provider. + /// The destination Provider. + /// The settings storage provider that handles permissions. + public static void MigrateFilesStorageProviderData(IFilesStorageProviderV30 source, IFilesStorageProviderV30 destination, ISettingsStorageProviderV30 settingsProvider) { + // Directories + MigrateDirectories(source, destination, "/", settingsProvider); + + // Attachments + foreach(string page in source.GetPagesWithAttachments()) { + PageInfo pageInfo = new PageInfo(page, null, DateTime.Now); + + string[] attachments = source.ListPageAttachments(pageInfo); + + foreach(string attachment in attachments) { + // Copy file content + using(MemoryStream ms = new MemoryStream(1048576)) { + source.RetrievePageAttachment(pageInfo, attachment, ms, false); + ms.Seek(0, SeekOrigin.Begin); + destination.StorePageAttachment(pageInfo, attachment, ms, false); + } + + // Copy download count + FileDetails fileDetails = source.GetPageAttachmentDetails(pageInfo, attachment); + destination.SetPageAttachmentRetrievalCount(pageInfo, attachment, fileDetails.RetrievalCount); + + // Delete attachment + source.DeletePageAttachment(pageInfo, attachment); + } + } + } + + private static void MigrateDirectories(IFilesStorageProviderV30 source, IFilesStorageProviderV30 destination, string current, ISettingsStorageProviderV30 settingsProvider) { + // Copy files + string[] files = source.ListFiles(current); + foreach(string file in files) { + // Copy file content + using(MemoryStream ms = new MemoryStream(1048576)) { + source.RetrieveFile(file, ms, false); + ms.Seek(0, SeekOrigin.Begin); + destination.StoreFile(file, ms, false); + } + + // Copy download count + FileDetails fileDetails = source.GetFileDetails(file); + destination.SetFileRetrievalCount(file, fileDetails.RetrievalCount); + + // Delete source file, if root + if(current == "/") { + source.DeleteFile(file); + } + } + + settingsProvider.AclManager.RenameResource( + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(source, current), + Actions.ForDirectories.ResourceMasterPrefix + AuthTools.GetDirectoryName(destination, current)); + + // Copy directories + string[] directories = source.ListDirectories(current); + foreach(string dir in directories) { + destination.CreateDirectory(current, dir.Substring(dir.TrimEnd('/').LastIndexOf("/") + 1).Trim('/')); + MigrateDirectories(source, destination, dir, settingsProvider); + + // Delete directory, if root + if(current == "/") { + source.DeleteDirectory(dir); + } + } + } + + /// + /// Copies the settings from a Provider to another. + /// + /// The source Provider. + /// The destination Provider. + /// The currently known page namespaces. + /// The currently known plugins. + public static void CopySettingsStorageProviderData(ISettingsStorageProviderV30 source, ISettingsStorageProviderV30 destination, + string[] knownNamespaces, string[] knownPlugins) { + + // Settings + destination.BeginBulkUpdate(); + foreach(KeyValuePair pair in source.GetAllSettings()) { + destination.SetSetting(pair.Key, pair.Value); + } + destination.EndBulkUpdate(); + + // Meta-data (global) + destination.SetMetaDataItem(MetaDataItem.AccountActivationMessage, null, + source.GetMetaDataItem(MetaDataItem.AccountActivationMessage, null)); + destination.SetMetaDataItem(MetaDataItem.PasswordResetProcedureMessage, null, + source.GetMetaDataItem(MetaDataItem.PasswordResetProcedureMessage, null)); + destination.SetMetaDataItem(MetaDataItem.LoginNotice, null, + source.GetMetaDataItem(MetaDataItem.LoginNotice, null)); + destination.SetMetaDataItem(MetaDataItem.PageChangeMessage, null, + source.GetMetaDataItem(MetaDataItem.PageChangeMessage, null)); + destination.SetMetaDataItem(MetaDataItem.DiscussionChangeMessage, null, + source.GetMetaDataItem(MetaDataItem.DiscussionChangeMessage, null)); + + // Meta-data (ns-specific) + List namespacesToProcess = new List(); + namespacesToProcess.Add(null); + namespacesToProcess.AddRange(knownNamespaces); + foreach(string nspace in namespacesToProcess) { + destination.SetMetaDataItem(MetaDataItem.EditNotice, nspace, + source.GetMetaDataItem(MetaDataItem.EditNotice, nspace)); + destination.SetMetaDataItem(MetaDataItem.Footer, nspace, + source.GetMetaDataItem(MetaDataItem.Footer, nspace)); + destination.SetMetaDataItem(MetaDataItem.Header, nspace, + source.GetMetaDataItem(MetaDataItem.Header, nspace)); + destination.SetMetaDataItem(MetaDataItem.HtmlHead, nspace, + source.GetMetaDataItem(MetaDataItem.HtmlHead, nspace)); + destination.SetMetaDataItem(MetaDataItem.PageFooter, nspace, + source.GetMetaDataItem(MetaDataItem.PageFooter, nspace)); + destination.SetMetaDataItem(MetaDataItem.PageHeader, nspace, + source.GetMetaDataItem(MetaDataItem.PageHeader, nspace)); + destination.SetMetaDataItem(MetaDataItem.Sidebar, nspace, + source.GetMetaDataItem(MetaDataItem.Sidebar, nspace)); + } + + // Plugin assemblies + string[] assemblies = source.ListPluginAssemblies(); + foreach(string asm in assemblies) { + destination.StorePluginAssembly(asm, + source.RetrievePluginAssembly(asm)); + } + + // Plugin status and config + foreach(string plug in knownPlugins) { + destination.SetPluginStatus(plug, + source.GetPluginStatus(plug)); + + destination.SetPluginConfiguration(plug, + source.GetPluginConfiguration(plug)); + } + + // Outgoing links + IDictionary allLinks = source.GetAllOutgoingLinks(); + foreach(KeyValuePair link in allLinks) { + destination.StoreOutgoingLinks(link.Key, link.Value); + } + + // ACLs + AclEntry[] allEntries = source.AclManager.RetrieveAllEntries(); + foreach(AclEntry entry in allEntries) { + destination.AclManager.StoreEntry(entry.Resource, entry.Action, entry.Subject, entry.Value); + } + } + + } + + /// + /// Contains username, email and registration date/time of a moved User account. + /// + public class MovedUser { + + private string username, email; + private DateTime dateTime; + + /// + /// Initializes a new instance of the MovedUser class. + /// + /// The esername. + /// The email address. + /// The registration date/time. + public MovedUser(string username, string email, DateTime dateTime) { + this.username = username; + this.email = email; + this.dateTime = dateTime; + } + + /// + /// Gets the Username. + /// + public string Username { + get { return username; } + } + + /// + /// Gets the Email. + /// + public string Email { + get { return email; } + } + + /// + /// Gets the registration date/time. + /// + public DateTime DateTime { + get { return dateTime; } + } + + } + +} \ No newline at end of file diff --git a/Core/Defaults.cs b/Core/Defaults.cs new file mode 100644 index 0000000..0a5ed9c --- /dev/null +++ b/Core/Defaults.cs @@ -0,0 +1,178 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki { + + /// + /// Contains default values. + /// + public static class Defaults { + + /// + /// The default content of the main page. + /// + public const string MainPageContent = @"Welcome to '''{WIKITITLE}'''!{BR} +This is the main page of your new ScrewTurn Wiki, created for you by the system. + +You should [Edit.aspx?Page=MainPage|edit this page] and customize the contents. You can also [Edit.aspx|create a new page] from scratch. + +If you need help, try to visit [http://www.screwturn.eu|our website] or [http://www.screwturn.eu/forum|our forum]. + +'''Warning''': remember to setup the ''admin'' account by editing the {{Web.config}} file placed in the root directory of the Wiki. It is ''extremely dangerous'' to keep the default password."; + + /// + /// The default content of the main page of a sub-namespace. + /// + public const string MainPageContentForSubNamespace = @"Welcome to the '''{NAMESPACE}''' namespace of '''{WIKITITLE}'''!{BR} +This is the main page of the namespace, created for you by the system. + +You should [Edit.aspx?Page={NAMESPACE}.MainPage|edit this page] and customize the contents. You can also [Edit.aspx|create a new page] from scratch. + +If you need help, try to visit [http://www.screwturn.eu|our website] or [http://www.screwturn.eu/forum|our forum]."; + + /// + /// The default content of the account activation message. + /// + public const string AccountActivationMessageContent = @"Hi ##USERNAME## and welcome to ##WIKITITLE##! +You must activate your new ##WIKITITLE## Account within 24 hours, following the link below. + +##ACTIVATIONLINK## + +If you have any trouble, please contact us at our Email address, ##EMAILADDRESS## . + +Thank you. + +Best regards, +The ##WIKITITLE## Team."; + + /// + /// The default content of the edit notice. + /// + public const string EditNoticeContent = @"Please '''do not''' include contents covered by copyright without the explicit permission of the Author. Always preview the result before saving.{BR} +If you are having trouble, please visit the [http://www.screwturn.eu/Help.aspx|Help section] at [http://www.screwturn.eu|ScrewTurn Software]."; + + /// + /// The default content of the footer. + /// + public const string FooterContent = @"

[http://www.screwturn.eu|ScrewTurn Wiki] version {WIKIVERSION}. Some of the icons created by [http://www.famfamfam.com|FamFamFam].

"; + + /// + /// The default content of the header. + /// + public const string HeaderContent = @"
Welcome {USERNAME}, you are in: {NAMESPACEDROPDOWN} • {LOGINLOGOUT}

{WIKITITLE}

"; + + /// + /// The default content of the password reset message. + /// + public const string PasswordResetProcedureMessageContent = @"Hi ##USERNAME##! +Your can change your password following the instructions you will see at this link: + ##LINK## + +If you have any trouble, please contact us at our Email address, ##EMAILADDRESS## . + +Thank you. + +Best regards, +The ##WIKITITLE## Team."; + + /// + /// The default content of the sidebar. + /// + public const string SidebarContent = @"
+ +
+====Navigation==== +* '''[MainPage|Main Page]''' + +* [RandPage.aspx|Random Page] +* [Edit.aspx|Create a new Page] +* [AllPages.aspx|All Pages] +* [Category.aspx|Categories] +* [NavPath.aspx|Navigation Paths] + +* [AdminHome.aspx|Administration] +* [Upload.aspx|File Management] + +* [Register.aspx|Create Account] + +'''Search the wiki'''{BR} +{SEARCHBOX} + +[image|PoweredBy|Images/PoweredBy.png|http://www.screwturn.eu]"; + + /// + /// The default content of the sidebar of a sub-namespace. + /// + public const string SidebarContentForSubNamespace = @"
+ +
+====Navigation ({NAMESPACE})==== +* '''[MainPage|Main Page]''' +* [++MainPage|Main Page (root)] + +* [RandPage.aspx|Random Page] +* [Edit.aspx|Create a new Page] +* [AllPages.aspx|All Pages] +* [Category.aspx|Categories] +* [NavPath.aspx|Navigation Paths] + +* [AdminHome.aspx|Administration] +* [Upload.aspx|File Management] + +* [Register.aspx|Create Account] + +'''Search the wiki'''{BR} +{SEARCHBOX} + +[image|PoweredBy|Images/PoweredBy.png|http://www.screwturn.eu]"; + + /// + /// The default content of the page change email message. + /// + public const string PageChangeMessage = @"The page ""##PAGE##"" was modified by ##USER## on ##DATETIME##. +Author's comment: ##COMMENT##. + +The page can be found at the following address: +##LINK## + +Thank you. + +Best regards, +The ##WIKITITLE## Team."; + + /// + /// The default content of the discussion change email message. + /// + public const string DiscussionChangeMessage = @"A new message was posted on the page ""##PAGE##"" by ##USER## on ##DATETIME##. + +The subject of the message is ""##SUBJECT##"" and it can be found at the following address: +##LINK## + +Thank you. + +Best regards, +The ##WIKITITLE## Team."; + + /// + /// The default content of the approve draft email message. + /// + public const string ApproveDraftMessage = @"A draft for the page ""##PAGE##"" was created or modified by ##USER## on ##DATETIME## and is currently held for **approval**. +Author's comment: ##COMMENT##. + +The draft can be found and edited at the following address: +##LINK## +You can directly approve or reject the draft at the following address: +##LINK2## + +Please note that the draft will not be displayed until it is approved. + +Thank you. + +Best regards, +The ##WIKITITLE## Team."; + + } + +} diff --git a/Core/DiffTools.cs b/Core/DiffTools.cs new file mode 100644 index 0000000..2584d7b --- /dev/null +++ b/Core/DiffTools.cs @@ -0,0 +1,477 @@ + +using System; +using System.Collections; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; + +namespace ScrewTurn.Wiki { + + /// + /// Provides methods for diffing text and items. + /// + public static class DiffTools { + + /// + /// Computes the difference between two revisions. + /// + /// The first revision. + /// The second revision. + /// The XHTML-formatted result. + public static string DiffRevisions(string rev1, string rev2) { + string[] aLines = rev1.Split('\n'); + string[] bLines = rev2.Split('\n'); + Difference.Item[] f = Difference.DiffText(rev1, rev2, true, false, false); + + StringBuilder result = new StringBuilder(); + + result.Append(@""); + + int n = 0; + for(int fdx = 0; fdx < f.Length; fdx++) { + Difference.Item aItem = f[fdx]; + // Write unchanged lines + while((n < aItem.StartB) && (n < bLines.Length)) { + result.Append(WriteLine(n, "", bLines[n])); + n++; + } + + // Write deleted lines + for(int m = 0; m < aItem.deletedA; m++) { + result.Append(WriteLine(-1, "d", aLines[aItem.StartA + m])); + } + + // Write inserted lines + while(n < aItem.StartB + aItem.insertedB) { + result.Append(WriteLine(n, "i", bLines[n])); + n++; + } + } + + // Write the rest of unchanged lines + while(n < bLines.Length) { + result.Append(WriteLine(n, "", bLines[n])); + n++; + } + + result.Append("
"); + + return result.ToString(); + } + + private static string WriteLine(int n, string typ, string line) { + StringBuilder sb = new StringBuilder(); + sb.Append(""); + + sb.Append(@""); + if(n >= 0) sb.Append(((int)(n + 1)).ToString()); + else sb.Append(" "); + sb.Append(""); + + sb.Append(@""); + sb.Append(@"
" + HttpContext.Current.Server.HtmlEncode(line) + "
"); + sb.Append(""); + + sb.Append(""); + return sb.ToString(); + } + + } + + /// + /// O(ND) Difference Algorithm for C# + /// Created by Matthias Hertel, see http://www.mathertel.de + /// This work is licensed under a Creative Commons Attribution 2.0 Germany License. + /// see http://creativecommons.org/licenses/by/2.0/de/ + /// + public class Difference { + + /// details of one difference. + public struct Item { + /// Start Line number in Data A. + public int StartA; + /// Start Line number in Data B. + public int StartB; + + /// Number of changes in Data A. + public int deletedA; + /// Number of changes in Data A. + public int insertedB; + } // Item + + /// + /// Shortest Middle Snake Return Data + /// + private struct SMSRD { + internal int x, y; + // internal int u, v; // 2002.09.20: no need for 2 points + } + + /// + /// Find the difference in 2 texts, comparing by textlines. + /// + /// A-version of the text (usualy the old one) + /// B-version of the text (usualy the new one) + /// Returns a array of Items that describe the differences. + public Item[] DiffText(string TextA, string TextB) { + return (DiffText(TextA, TextB, false, false, false)); + } // DiffText + + + /// + /// Find the difference in 2 text documents, comparing by textlines. + /// The algorithm itself is comparing 2 arrays of numbers so when comparing 2 text documents + /// each line is converted into a (hash) number. This hash-value is computed by storing all + /// textlines into a common hashtable so i can find dublicates in there, and generating a + /// new number each time a new textline is inserted. + /// + /// A-version of the text (usualy the old one) + /// B-version of the text (usualy the new one) + /// When set to true, all leading and trailing whitespace characters are stripped out before the comparation is done. + /// When set to true, all whitespace characters are converted to a single space character before the comparation is done. + /// When set to true, all characters are converted to their lowercase equivivalence before the comparation is done. + /// Returns a array of Items that describe the differences. + public static Item[] DiffText(string TextA, string TextB, bool trimSpace, bool ignoreSpace, bool ignoreCase) { + // prepare the input-text and convert to comparable numbers. + Hashtable h = new Hashtable(TextA.Length + TextB.Length); + + // The A-Version of the data (original data) to be compared. + DiffData DataA = new DiffData(DiffCodes(TextA, h, trimSpace, ignoreSpace, ignoreCase)); + + // The B-Version of the data (modified data) to be compared. + DiffData DataB = new DiffData(DiffCodes(TextB, h, trimSpace, ignoreSpace, ignoreCase)); + + h = null; // free up hashtable memory (maybe) + + LCS(DataA, 0, DataA.Length, DataB, 0, DataB.Length); + return CreateDiffs(DataA, DataB); + } // DiffText + + + /// + /// Find the difference in 2 arrays of integers. + /// + /// A-version of the numbers (usualy the old one) + /// B-version of the numbers (usualy the new one) + /// Returns a array of Items that describe the differences. + public static Item[] DiffInt(int[] ArrayA, int[] ArrayB) { + // The A-Version of the data (original data) to be compared. + DiffData DataA = new DiffData(ArrayA); + + // The B-Version of the data (modified data) to be compared. + DiffData DataB = new DiffData(ArrayB); + + LCS(DataA, 0, DataA.Length, DataB, 0, DataB.Length); + return CreateDiffs(DataA, DataB); + } // Diff + + + /// + /// Converts all textlines of the text into unique numbers for every unique textline + /// so further work can work only with simple numbers. + /// + /// The input text + /// This extern initialized hashtable is used for storing all ever used textlines. + /// Ignore leading and trailing space characters + /// Ignore spaces. + /// Ignore case. + /// An array of integers. + private static int[] DiffCodes(string aText, Hashtable h, bool trimSpace, bool ignoreSpace, bool ignoreCase) { + // get all codes of the text + string[] Lines; + int[] Codes; + int lastUsedCode = h.Count; + object aCode; + string s; + + // strip off all cr, only use lf as textline separator. + aText = aText.Replace("\r", ""); + Lines = aText.Split('\n'); + + Codes = new int[Lines.Length]; + + for(int i = 0; i < Lines.Length; ++i) { + s = Lines[i]; + if(trimSpace) + s = s.Trim(); + + if(ignoreSpace) { + s = Regex.Replace(s, "\\s+", " "); // TODO: optimization: faster blank removal. + } + + if(ignoreCase) + s = s.ToLowerInvariant(); + + aCode = h[s]; + if(aCode == null) { + lastUsedCode++; + h[s] = lastUsedCode; + Codes[i] = lastUsedCode; + } + else { + Codes[i] = (int)aCode; + } // if + } // for + return (Codes); + } // DiffCodes + + + /// + /// This is the algorithm to find the Shortest Middle Snake (SMS). + /// + /// sequence A + /// lower bound of the actual range in DataA + /// upper bound of the actual range in DataA (exclusive) + /// sequence B + /// lower bound of the actual range in DataB + /// upper bound of the actual range in DataB (exclusive) + /// a MiddleSnakeData record containing x,y and u,v + private static SMSRD SMS(DiffData DataA, int LowerA, int UpperA, DiffData DataB, int LowerB, int UpperB) { + SMSRD ret; + int MAX = DataA.Length + DataB.Length + 1; + + int DownK = LowerA - LowerB; // the k-line to start the forward search + int UpK = UpperA - UpperB; // the k-line to start the reverse search + + int Delta = (UpperA - LowerA) - (UpperB - LowerB); + bool oddDelta = (Delta & 1) != 0; + + // vector for the (0,0) to (x,y) search + int[] DownVector = new int[2 * MAX + 2]; + + // vector for the (u,v) to (N,M) search + int[] UpVector = new int[2 * MAX + 2]; + + // The vectors in the publication accepts negative indexes. the vectors implemented here are 0-based + // and are access using a specific offset: UpOffset UpVector and DownOffset for DownVektor + int DownOffset = MAX - DownK; + int UpOffset = MAX - UpK; + + int MaxD = ((UpperA - LowerA + UpperB - LowerB) / 2) + 1; + + // Debug.Write(2, "SMS", String.Format("Search the box: A[{0}-{1}] to B[{2}-{3}]", LowerA, UpperA, LowerB, UpperB)); + + // init vectors + DownVector[DownOffset + DownK + 1] = LowerA; + UpVector[UpOffset + UpK - 1] = UpperA; + + for(int D = 0; D <= MaxD; D++) { + + // Extend the forward path. + for(int k = DownK - D; k <= DownK + D; k += 2) { + // Debug.Write(0, "SMS", "extend forward path " + k.ToString()); + + // find the only or better starting point + int x, y; + if(k == DownK - D) { + x = DownVector[DownOffset + k + 1]; // down + } + else { + x = DownVector[DownOffset + k - 1] + 1; // a step to the right + if((k < DownK + D) && (DownVector[DownOffset + k + 1] >= x)) + x = DownVector[DownOffset + k + 1]; // down + } + y = x - k; + + // find the end of the furthest reaching forward D-path in diagonal k. + while((x < UpperA) && (y < UpperB) && (DataA.data[x] == DataB.data[y])) { + x++; y++; + } + DownVector[DownOffset + k] = x; + + // overlap ? + if(oddDelta && (UpK - D < k) && (k < UpK + D)) { + if(UpVector[UpOffset + k] <= DownVector[DownOffset + k]) { + ret.x = DownVector[DownOffset + k]; + ret.y = DownVector[DownOffset + k] - k; + // ret.u = UpVector[UpOffset + k]; // 2002.09.20: no need for 2 points + // ret.v = UpVector[UpOffset + k] - k; + return (ret); + } // if + } // if + + } // for k + + // Extend the reverse path. + for(int k = UpK - D; k <= UpK + D; k += 2) { + // Debug.Write(0, "SMS", "extend reverse path " + k.ToString()); + + // find the only or better starting point + int x, y; + if(k == UpK + D) { + x = UpVector[UpOffset + k - 1]; // up + } + else { + x = UpVector[UpOffset + k + 1] - 1; // left + if((k > UpK - D) && (UpVector[UpOffset + k - 1] < x)) + x = UpVector[UpOffset + k - 1]; // up + } // if + y = x - k; + + while((x > LowerA) && (y > LowerB) && (DataA.data[x - 1] == DataB.data[y - 1])) { + x--; y--; // diagonal + } + UpVector[UpOffset + k] = x; + + // overlap ? + if(!oddDelta && (DownK - D <= k) && (k <= DownK + D)) { + if(UpVector[UpOffset + k] <= DownVector[DownOffset + k]) { + ret.x = DownVector[DownOffset + k]; + ret.y = DownVector[DownOffset + k] - k; + // ret.u = UpVector[UpOffset + k]; // 2002.09.20: no need for 2 points + // ret.v = UpVector[UpOffset + k] - k; + return (ret); + } // if + } // if + + } // for k + + } // for D + + throw new ApplicationException("the algorithm should never come here."); + } // SMS + + + /// + /// This is the divide-and-conquer implementation of the longes common-subsequence (LCS) + /// algorithm. + /// The published algorithm passes recursively parts of the A and B sequences. + /// To avoid copying these arrays the lower and upper bounds are passed while the sequences stay constant. + /// + /// sequence A + /// lower bound of the actual range in DataA + /// upper bound of the actual range in DataA (exclusive) + /// sequence B + /// lower bound of the actual range in DataB + /// upper bound of the actual range in DataB (exclusive) + private static void LCS(DiffData DataA, int LowerA, int UpperA, DiffData DataB, int LowerB, int UpperB) { + // Debug.Write(2, "LCS", String.Format("Analyse the box: A[{0}-{1}] to B[{2}-{3}]", LowerA, UpperA, LowerB, UpperB)); + + // Fast walkthrough equal lines at the start + while(LowerA < UpperA && LowerB < UpperB && DataA.data[LowerA] == DataB.data[LowerB]) { + LowerA++; LowerB++; + } + + // Fast walkthrough equal lines at the end + while(LowerA < UpperA && LowerB < UpperB && DataA.data[UpperA - 1] == DataB.data[UpperB - 1]) { + --UpperA; --UpperB; + } + + if(LowerA == UpperA) { + // mark as inserted lines. + while(LowerB < UpperB) + DataB.modified[LowerB++] = true; + + } + else if(LowerB == UpperB) { + // mark as deleted lines. + while(LowerA < UpperA) + DataA.modified[LowerA++] = true; + + } + else { + // Find the middle snakea and length of an optimal path for A and B + SMSRD smsrd = SMS(DataA, LowerA, UpperA, DataB, LowerB, UpperB); + // Debug.Write(2, "MiddleSnakeData", String.Format("{0},{1}", smsrd.x, smsrd.y)); + + // The path is from LowerX to (x,y) and (x,y) ot UpperX + LCS(DataA, LowerA, smsrd.x, DataB, LowerB, smsrd.y); + LCS(DataA, smsrd.x, UpperA, DataB, smsrd.y, UpperB); // 2002.09.20: no need for 2 points + } + } // LCS() + + + /// Scan the tables of which lines are inserted and deleted, + /// producing an edit script in forward order. + /// + /// dynamic array + private static Item[] CreateDiffs(DiffData DataA, DiffData DataB) { + ArrayList a = new ArrayList(); + Item aItem; + Item[] result; + + int StartA, StartB; + int LineA, LineB; + + LineA = 0; + LineB = 0; + while(LineA < DataA.Length || LineB < DataB.Length) { + if((LineA < DataA.Length) && (!DataA.modified[LineA]) + && (LineB < DataB.Length) && (!DataB.modified[LineB])) { + // equal lines + LineA++; + LineB++; + + } + else { + // maybe deleted and/or inserted lines + StartA = LineA; + StartB = LineB; + + while(LineA < DataA.Length && (LineB >= DataB.Length || DataA.modified[LineA])) + // while (LineA < DataA.Length && DataA.modified[LineA]) + LineA++; + + while(LineB < DataB.Length && (LineA >= DataA.Length || DataB.modified[LineB])) + // while (LineB < DataB.Length && DataB.modified[LineB]) + LineB++; + + if((StartA < LineA) || (StartB < LineB)) { + // store a new difference-item + aItem = new Item(); + aItem.StartA = StartA; + aItem.StartB = StartB; + aItem.deletedA = LineA - StartA; + aItem.insertedB = LineB - StartB; + a.Add(aItem); + } // if + } // if + } // while + + result = new Item[a.Count]; + a.CopyTo(result); + + return (result); + } + + } // class Diff + + /// Data on one input file being compared. + /// + internal class DiffData { + + /// Number of elements (lines). + internal int Length; + + /// Buffer of numbers that will be compared. + internal int[] data; + + /// + /// Array of booleans that flag for modified data. + /// This is the result of the diff. + /// This means deletedA in the first Data or inserted in the second Data. + /// + internal bool[] modified; + + /// + /// Initialize the Diff-Data buffer. + /// + /// Reference to the buffer + internal DiffData(int[] initData) { + data = initData; + Length = initData.Length; + modified = new bool[Length + 2]; + } // DiffData + + } // class DiffData + +} diff --git a/Core/EmailTools.cs b/Core/EmailTools.cs new file mode 100644 index 0000000..9d1b93c --- /dev/null +++ b/Core/EmailTools.cs @@ -0,0 +1,130 @@ + +using System; +using System.Configuration; +using System.Net; +using System.Net.Mail; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Implements email-related tools. + /// + public static class EmailTools { + + /// + /// Sends an email. + /// + /// The recipient. + /// The sender. + /// The subject. + /// The message body. + /// true if the body is HTML. + public static void AsyncSendEmail(string recipient, string sender, string subject, string body, bool html) { + System.Threading.ThreadPool.QueueUserWorkItem(delegate(object state) { + MailMessage message = new MailMessage(sender, recipient, subject, body); + message.IsBodyHtml = html; + TrySendMessage(message); + }); + } + + /// + /// Tries to send a message, swallowing all exceptions. + /// + /// The message to send. + private static void TrySendMessage(MailMessage message) { + try { + GenerateSmtpClient().Send(message); + } + catch(Exception ex) { + if(ex is SmtpException) { + Log.LogEntry("Unable to send Email: " + ex.Message, EntryType.Error, Log.SystemUsername); + } + else Log.LogEntry(ex.ToString(), EntryType.Error, Log.SystemUsername); + } + } + + /// + /// Generates a new SMTP client with the proper settings. + /// + /// The generates SMTP client. + private static SmtpClient GenerateSmtpClient() { + SmtpClient client = new SmtpClient(Settings.SmtpServer); + if(Settings.SmtpUsername.Length > 0) { + client.Credentials = new NetworkCredential(Settings.SmtpUsername, Settings.SmtpPassword); + } + client.EnableSsl = Settings.SmtpSsl; + if(Settings.SmtpPort != -1) client.Port = Settings.SmtpPort; + else if(Settings.SmtpSsl) client.Port = 465; + return client; + } + + /// + /// Gets the email addresses of a set of users. + /// + /// The users. + /// The email addresses. + public static string[] GetRecipients(UserInfo[] users) { + if(users == null) return new string[0]; + + string[] result = new string[users.Length]; + + for(int i = 0; i < result.Length; i++) { + result[i] = users[i].Email; + } + + return result; + } + + /// + /// Asynchronously sends a mass email, using BCC. + /// + /// The recipents. + /// The sender. + /// The subject. + /// The body. + /// true if the body is HTML. + public static void AsyncSendMassEmail(string[] recipients, string sender, string subject, string body, bool html) { + if(recipients.Length == 0) return; + + System.Threading.ThreadPool.QueueUserWorkItem(delegate(object state) { + MailMessage message = new MailMessage(new MailAddress(sender), new MailAddress(sender)); + message.Subject = subject; + message.Body = body; + for(int i = 0; i < recipients.Length; i++) { + message.Bcc.Add(new MailAddress(recipients[i])); + } + message.IsBodyHtml = html; + TrySendMessage(message); + }); + } + + /// + /// Notifies an error to the email addresses set in the configuration, swallowing all exceptions. + /// + /// The exception to notify. + /// The URL that caused the error, if any. + public static void NotifyError(Exception ex, string url) { + try { + string[] recipients = Settings.ErrorsEmails; + + if(recipients.Length > 0) { + AsyncSendMassEmail(recipients, Settings.SenderEmail, "Error Notification", "An error occurred on " + + DateTime.Now.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss") + " (server time) in the wiki hosted at " + + Settings.MainUrl + " - server stack trace follows.\r\n\r\n" + + (!string.IsNullOrEmpty(url) ? url + "\r\n\r\n" : "") + + ex.ToString(), false); + } + } + catch { } + } + + } + +} diff --git a/Core/FileDocument.cs b/Core/FileDocument.cs new file mode 100644 index 0000000..fe33631 --- /dev/null +++ b/Core/FileDocument.cs @@ -0,0 +1,113 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki { + + /// + /// Represents a file document. + /// + public class FileDocument : IDocument { + + /// + /// The type tag for a . + /// + public const string StandardTypeTag = "F"; + + private uint id; + private string name; + private string title; + private string typeTag = StandardTypeTag; + private DateTime dateTime; + private string provider; + + /// + /// Initializes a new instance of the class. + /// + /// The file full name. + /// The file provider. + /// The modification date/time. + public FileDocument(string fullName, string provider, DateTime dateTime) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + if(provider == null) throw new ArgumentNullException("provider"); + if(provider.Length == 0) throw new ArgumentException("Provider cannot be empty", "provider"); + + id = Tools.HashDocumentNameForTemporaryIndex(fullName); + name = provider + "|" + fullName; + title = fullName.Substring(Tools.GetDirectoryName(fullName).Length); + this.dateTime = dateTime; + this.provider = provider; + } + + /// + /// Initializes a new instance of the class. + /// + /// The dumped document. + public FileDocument(DumpedDocument doc) { + string[] fields = doc.Name.Split('|'); + + id = doc.ID; + name = doc.Name; + title = doc.Title; + dateTime = doc.DateTime; + provider = fields[0]; + } + + /// + /// Gets or sets the globally unique ID of the document. + /// + public uint ID { + get { return id; } + set { id = value; } + } + + /// + /// Gets the globally-unique name of the document. + /// + public string Name { + get { return name; } + } + + /// + /// Gets the title of the document, if any. + /// + public string Title { + get { return title; } + } + + /// + /// Gets the tag for the document type. + /// + public string TypeTag { + get { return typeTag; } + } + + /// + /// Gets the document date/time. + /// + public DateTime DateTime { + get { return dateTime; } + } + + /// + /// Performs the tokenization of the document content. + /// + /// The content to tokenize. + /// The extracted words and their positions (always an empty array). + public WordInfo[] Tokenize(string content) { + return ScrewTurn.Wiki.SearchEngine.Tools.Tokenize(content); + } + + /// + /// Gets the provider. + /// + public string Provider { + get { return provider; } + } + + } + +} diff --git a/Core/FilesAndAttachments.cs b/Core/FilesAndAttachments.cs new file mode 100644 index 0000000..358b131 --- /dev/null +++ b/Core/FilesAndAttachments.cs @@ -0,0 +1,294 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using System.IO; + +namespace ScrewTurn.Wiki { + + /// + /// Manages files, directories and attachments. + /// + public static class FilesAndAttachments { + + #region Files + + /// + /// Finds the provider that has a file. + /// + /// The full name of the file. + /// The provider that has the file, or null if the file could not be found. + public static IFilesStorageProviderV30 FindFileProvider(string fullName) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(string.IsNullOrEmpty(fullName)) throw new ArgumentException("Full Name cannot be empty", "fullName"); + + fullName = NormalizeFullName(fullName); + + foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) { + FileDetails details = provider.GetFileDetails(fullName); + if(details != null) return provider; + } + + return null; + } + + /// + /// Gets the details of a file. + /// + /// The full name of the file. + /// The details of the file, or null if no file is found. + public static FileDetails GetFileDetails(string fullName) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(string.IsNullOrEmpty(fullName)) throw new ArgumentException("Full Name cannot be empty", "fullName"); + + fullName = NormalizeFullName(fullName); + + foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) { + FileDetails details = provider.GetFileDetails(fullName); + if(details != null) return details; + } + + return null; + } + + /// + /// Retrieves a File. + /// + /// The full name of the File. + /// The output stream. + /// A value indicating whether or not to count this retrieval in the statistics. + /// true if the file is retrieved, false otherwise. + public static bool RetrieveFile(string fullName, Stream output, bool countHit) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + if(output == null) throw new ArgumentNullException("destinationStream"); + if(!output.CanWrite) throw new ArgumentException("Cannot write into Destination Stream", "destinationStream"); + + fullName = NormalizeFullName(fullName); + + IFilesStorageProviderV30 provider = FindFileProvider(fullName); + + if(provider == null) return false; + else return provider.RetrieveFile(fullName, output, countHit); + } + + #endregion + + #region Directories + + /// + /// Finds the provider that has a directory. + /// + /// The full path of the directory. + /// The provider that has the directory, or null if no directory is found. + public static IFilesStorageProviderV30 FindDirectoryProvider(string fullPath) { + if(fullPath == null) throw new ArgumentNullException("fullPath"); + if(fullPath.Length == 0) throw new ArgumentException("Full Path cannot be empty"); + + fullPath = NormalizeFullPath(fullPath); + + // In order to verify that the full path exists, it is necessary to navigate + // from the root down to the specified directory level + // Example: /my/very/nested/directory/structure/ + // 1. Check that / contains /my/ + // 2. Check that /my/ contains /my/very/ + // 3. ... + + // allLevels contains this: + // /my/very/nested/directory/structure/ + // /my/very/nested/directory/ + // /my/very/nested/ + // /my/very/ + // /my/ + // / + + string oneLevelUp = fullPath; + + List allLevels = new List(10); + allLevels.Add(fullPath.ToLowerInvariant()); + while(oneLevelUp != "/") { + oneLevelUp = UpOneLevel(oneLevelUp); + allLevels.Add(oneLevelUp.ToLowerInvariant()); + } + + foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) { + bool allLevelsFound = true; + + for(int i = allLevels.Count - 1; i >= 1; i--) { + string[] dirs = provider.ListDirectories(allLevels[i]); + + string nextLevel = + (from d in dirs + where d.ToLowerInvariant() == allLevels[i - 1] + select d).FirstOrDefault(); + + if(string.IsNullOrEmpty(nextLevel)) { + allLevelsFound = false; + break; + } + } + + if(allLevelsFound) return provider; + } + + return null; + } + + /// + /// Lists the directories in a directory. + /// + /// The full path. + /// The directories. + /// If the specified directory is the root, then the list is performed on all providers. + public static string[] ListDirectories(string fullPath) { + fullPath = NormalizeFullPath(fullPath); + + if(fullPath == "/") { + List directories = new List(50); + + foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) { + directories.AddRange(provider.ListDirectories(fullPath)); + } + + directories.Sort(); + + return directories.ToArray(); + } + else { + IFilesStorageProviderV30 provider = FindDirectoryProvider(fullPath); + return provider.ListDirectories(fullPath); + } + } + + /// + /// Lists the files in a directory. + /// + /// The full path. + /// The files. + /// If the specified directory is the root, then the list is performed on all providers. + public static string[] ListFiles(string fullPath) { + fullPath = NormalizeFullPath(fullPath); + + if(fullPath == "/") { + List files = new List(50); + + foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) { + files.AddRange(provider.ListFiles(fullPath)); + } + + files.Sort(); + + return files.ToArray(); + } + else { + IFilesStorageProviderV30 provider = FindDirectoryProvider(fullPath); + return provider.ListFiles(fullPath); + } + } + + #endregion + + #region Page Attachments + + /// + /// Finds the provider that has a page attachment. + /// + /// The page. + /// The name of the attachment. + /// The provider that has the attachment, or null if the attachment could not be found. + public static IFilesStorageProviderV30 FindPageAttachmentProvider(PageInfo page, string attachmentName) { + if(page == null) throw new ArgumentNullException("page"); + if(attachmentName == null) throw new ArgumentNullException("attachmentName"); + if(attachmentName.Length == 0) throw new ArgumentException("Attachment Name cannot be empty", "attachmentName"); + + foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) { + FileDetails details = provider.GetPageAttachmentDetails(page, attachmentName); + if(details != null) return provider; + } + + return null; + } + + /// + /// Gets the details of a page attachment. + /// + /// The page. + /// The name of the attachment. + /// The details of the attachment, or null if the attachment could not be found. + public static FileDetails GetPageAttachmentDetails(PageInfo page, string attachmentName) { + if(page == null) throw new ArgumentNullException("page"); + if(attachmentName == null) throw new ArgumentNullException("attachmentName"); + if(attachmentName.Length == 0) throw new ArgumentException("Attachment Name cannot be empty", "attachmentName"); + + foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) { + FileDetails details = provider.GetPageAttachmentDetails(page, attachmentName); + if(details != null) return details; + } + + return null; + } + + /// + /// Retrieves a Page Attachment. + /// + /// The Page Info that owns the Attachment. + /// The name of the Attachment, for example "myfile.jpg". + /// The output stream. + /// A value indicating whether or not to count this retrieval in the statistics. + /// true if the Attachment is retrieved, false otherwise. + public static bool RetrievePageAttachment(PageInfo page, string attachmentName, Stream output, bool countHit) { + if(page == null) throw new ArgumentNullException("pageInfo"); + if(attachmentName == null) throw new ArgumentNullException("name"); + if(attachmentName.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(output == null) throw new ArgumentNullException("destinationStream"); + if(!output.CanWrite) throw new ArgumentException("Cannot write into Destination Stream", "destinationStream"); + + IFilesStorageProviderV30 provider = FindPageAttachmentProvider(page, attachmentName); + + if(provider == null) return false; + else return provider.RetrievePageAttachment(page, attachmentName, output, countHit); + } + + #endregion + + /// + /// Normalizes a full name. + /// + /// The full name. + /// The normalized full name. + private static string NormalizeFullName(string fullName) { + if(!fullName.StartsWith("/")) fullName = "/" + fullName; + return fullName; + } + + /// + /// Normalizes a full path. + /// + /// The full path. + /// The normalized full path. + private static string NormalizeFullPath(string fullPath) { + if(fullPath == null) return "/"; + if(!fullPath.StartsWith("/")) fullPath = "/" + fullPath; + if(!fullPath.EndsWith("/")) fullPath += "/"; + return fullPath; + } + + /// + /// Goes up one level in a directory path. + /// + /// The full path, normalized, different from "/". + /// The directory. + private static string UpOneLevel(string fullPath) { + if(fullPath == "/") throw new ArgumentException("Cannot navigate up from the root"); + + string temp = fullPath.Trim('/'); + int lastIndex = temp.LastIndexOf("/"); + + return "/" + temp.Substring(0, lastIndex + 1); + } + + } + +} diff --git a/Core/FilesStorageProvider.cs b/Core/FilesStorageProvider.cs new file mode 100644 index 0000000..f044f89 --- /dev/null +++ b/Core/FilesStorageProvider.cs @@ -0,0 +1,989 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Implements a Local Files Storage Provider. + /// + public class FilesStorageProvider : IFilesStorageProviderV30 { + + private readonly ComponentInformation info = new ComponentInformation("Local Files Provider", + "ScrewTurn Software", Settings.WikiVersion, "http://www.screwturn.eu", null); + + // The following strings MUST terminate with DirectorySeparatorPath in order to properly work + // in BuildFullPath method + private readonly string UploadDirectory = "Upload" + Path.DirectorySeparatorChar; + private readonly string AttachmentsDirectory = "Attachments" + Path.DirectorySeparatorChar; + + private const string FileDownloadsFile = "FileDownloads.cs"; + private const string AttachmentDownloadsFile = "AttachmentDownloads.cs"; + + // 16 KB buffer used in the StreamCopy method + // 16 KB seems to be the best break-even between performance and memory usage + private const int BufferSize = 16384; + + private IHostV30 host; + + private string GetFullPath(string finalChunk) { + return Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), finalChunk); + } + + /// + /// Initializes the Storage Provider. + /// + /// The Host of the Component. + /// The Configuration data, if any. + /// If or are null. + /// If is not valid or is incorrect. + public void Init(IHostV30 host, string config) { + if(host == null) throw new ArgumentNullException("host"); + if(config == null) throw new ArgumentNullException("config"); + + this.host = host; + + if(!LocalProvidersTools.CheckWritePermissions(host.GetSettingValue(SettingName.PublicDirectory))) { + throw new InvalidConfigurationException("Cannot write into the public directory - check permissions"); + } + + // Create directories, if needed + if(!Directory.Exists(GetFullPath(UploadDirectory))) { + Directory.CreateDirectory(GetFullPath(UploadDirectory)); + } + if(!Directory.Exists(GetFullPath(AttachmentsDirectory))) { + Directory.CreateDirectory(GetFullPath(AttachmentsDirectory)); + } + if(!File.Exists(GetFullPath(FileDownloadsFile))) { + File.Create(GetFullPath(FileDownloadsFile)).Close(); + } + if(!File.Exists(GetFullPath(AttachmentDownloadsFile))) { + File.Create(GetFullPath(AttachmentDownloadsFile)).Close(); + } + } + + /// + /// Method invoked on shutdown. + /// + /// This method might not be invoked in some cases. + public void Shutdown() { + // Nothing to do + } + + /// + /// Gets the Information about the Provider. + /// + public ComponentInformation Information { + get { return info; } + } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + public string ConfigHelpHtml { + get { return null; } + } + + /// + /// Gets a value specifying whether the provider is read-only, i.e. it can only provide data and not store it. + /// + public bool ReadOnly { + get { return false; } + } + + /// + /// Builds a full path from a provider-specific partial path. + /// + /// The partial path. + /// The full path. + /// For example: if partialPath is "/my/directory", the method returns + /// "C:\Inetpub\wwwroot\Wiki\public\Upload\my\directory", assuming the Wiki resides in "C:\Inetpub\wwwroot\Wiki". + private string BuildFullPath(string partialPath) { + if(partialPath == null) partialPath = ""; + partialPath = partialPath.Replace("/", Path.DirectorySeparatorChar.ToString()).TrimStart(Path.DirectorySeparatorChar); + string up = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), UploadDirectory); + return Path.Combine(up, partialPath); // partialPath CANNOT start with "\" -> Path.Combine does not work + } + + /// + /// Builds a full path from a provider-specific partial path. + /// + /// The partial path. + /// The full path. + /// For example: if partialPath is "/my/directory", the method returns + /// "C:\Inetpub\wwwroot\Wiki\public\Attachments\my\directory", assuming the Wiki resides in "C:\Inetpub\wwwroot\Wiki". + private string BuildFullPathForAttachments(string partialPath) { + if(partialPath == null) partialPath = ""; + partialPath = partialPath.Replace("/", Path.DirectorySeparatorChar.ToString()).TrimStart(Path.DirectorySeparatorChar); + string up = Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), AttachmentsDirectory); + return Path.Combine(up, partialPath); // partialPath CANNOT start with "\" -> Path.Combine does not work + } + + /// + /// Lists the Files in the specified Directory. + /// + /// The full directory name, for example "/my/directory". Null, empty or "/" for the root directory. + /// The list of Files in the directory. + /// If does not exist. + public string[] ListFiles(string directory) { + string d = BuildFullPath(directory); + if(!Directory.Exists(d)) throw new ArgumentException("Directory does not exist", "directory"); + + string[] temp = Directory.GetFiles(d); + + // Result must be transformed in the form /my/dir/file.ext + List res = new List(temp.Length); + string root = GetFullPath(UploadDirectory); + foreach(string s in temp) { + // root = C:\blah\ - ends with '\' + res.Add(s.Substring(root.Length - 1).Replace(Path.DirectorySeparatorChar, '/')); + } + + return res.ToArray(); + } + + /// + /// Lists the Directories in the specified directory. + /// + /// The full directory name, for example "/my/directory". Null, empty or "/" for the root directory. + /// The list of Directories in the Directory. + /// If does not exist. + public string[] ListDirectories(string directory) { + string d = BuildFullPath(directory); + if(!Directory.Exists(d)) throw new ArgumentException("Directory does not exist", "directory"); + + string[] temp = Directory.GetDirectories(d); + + // Result must be transformed in the form /my/dir + List res = new List(temp.Length); + string root = GetFullPath(UploadDirectory); + foreach(string s in temp) { + // root = C:\blah\ - ends with '\' + res.Add(s.Substring(root.Length - 1).Replace(Path.DirectorySeparatorChar, '/') + "/"); + } + + return res.ToArray(); + } + + /// + /// Copies data from a Stream to another. + /// + /// The Source stream. + /// The destination Stream. + private static void StreamCopy(Stream source, Stream destination) { + byte[] buff = new byte[BufferSize]; + int copied = 0; + do { + copied = source.Read(buff, 0, buff.Length); + if(copied > 0) { + destination.Write(buff, 0, copied); + } + } while(copied > 0); + } + + /// + /// Stores a file. + /// + /// The full name of the file. + /// A Stream object used as source of a byte stream, + /// i.e. the method reads from the Stream and stores the content properly. + /// true to overwrite an existing file. + /// true if the File is stored, false otherwise. + /// If overwrite is false and File already exists, the method returns false. + /// If os are null. + /// If is empty or does not support reading. + public bool StoreFile(string fullName, Stream sourceStream, bool overwrite) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + if(sourceStream == null) throw new ArgumentNullException("sourceStream"); + if(!sourceStream.CanRead) throw new ArgumentException("Cannot read from Source Stream", "sourceStream"); + + string filename = BuildFullPath(fullName); + + // Abort if the file already exists and overwrite is false + if(File.Exists(filename) && !overwrite) return false; + + FileStream fs = null; + + bool done = false; + + try { + fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); + + // StreamCopy content (throws exception in case of error) + StreamCopy(sourceStream, fs); + + done = true; + } + catch(IOException) { + done = false; + } + finally { + try { + fs.Close(); + } + catch { } + } + + return done; + } + + /// + /// Retrieves a File. + /// + /// The full name of the File. + /// A Stream object used as destination of a byte stream, + /// i.e. the method writes to the Stream the file content. + /// A value indicating whether or not to count this retrieval in the statistics. + /// true if the file is retrieved, false otherwise. + /// If os are null. + /// If is empty or does not support writing, or if does not exist. + public bool RetrieveFile(string fullName, Stream destinationStream, bool countHit) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + if(destinationStream == null) throw new ArgumentNullException("destinationStream"); + if(!destinationStream.CanWrite) throw new ArgumentException("Cannot write into Destination Stream", "destinationStream"); + + string filename = BuildFullPath(fullName); + + if(!File.Exists(filename)) throw new ArgumentException("File does not exist", "fullName"); + + FileStream fs = null; + + bool done = false; + + try { + fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read); + + // StreamCopy content (throws exception in case of error) + StreamCopy(fs, destinationStream); + + done = true; + } + catch(IOException) { + done = false; + } + finally { + try { + fs.Close(); + } + catch { } + } + + if(countHit) { + AddDownloadHit(fullName, GetFullPath(FileDownloadsFile)); + } + + return done; + } + + /// + /// Adds a download hit for the specified item in the specified output file. + /// + /// The item. + /// The full path to the output file. + private void AddDownloadHit(string itemName, string outputFile) { + lock(this) { + string[] lines = File.ReadAllLines(outputFile); + + string lowercaseItemName = itemName.ToLowerInvariant(); + + string[] fields; + bool found = false; + for(int i = 0; i < lines.Length; i++) { + fields = lines[i].Split('|'); + + if(fields[0].ToLowerInvariant() == lowercaseItemName) { + int count = 0; + int.TryParse(fields[1], out count); + count = count + 1; + lines[i] = itemName + "|" + count.ToString(); + found = true; + } + } + + if(!found) { + // Add a new line for the current item + string[] newLines = new string[lines.Length + 1]; + Array.Copy(lines, 0, newLines, 0, lines.Length); + newLines[newLines.Length - 1] = itemName + "|1"; + + lines = newLines; + } + + // Overwrite file with updated data + File.WriteAllLines(outputFile, lines); + } + } + + /// + /// Sets the download hits for the specified item in the specified file. + /// + /// The item. + /// The full path of the output file. + /// The hit count to set. + private void SetDownloadHits(string itemName, string outputFile, int count) { + lock(this) { + string[] lines = File.ReadAllLines(outputFile); + + List outputLines = new List(lines.Length); + + string lowercaseItemName = itemName.ToLowerInvariant(); + + string[] fields; + foreach(string line in lines) { + fields = line.Split('|'); + + if(fields[0].ToLowerInvariant() == lowercaseItemName) { + // Set the new count + outputLines.Add(fields[0] + "|" + count.ToString()); + } + else { + // Copy data with no modification + outputLines.Add(line); + } + } + + File.WriteAllLines(outputFile, outputLines.ToArray()); + } + } + + /// + /// Clears the download hits for the items that match itemName in the specified file. + /// + /// The first part of the item name. + /// The full path of the output file. + private void ClearDownloadHitsPartialMatch(string itemName, string outputFile) { + lock(this) { + string[] lines = File.ReadAllLines(outputFile); + + List newLines = new List(lines.Length); + + string lowercaseItemName = itemName.ToLowerInvariant(); + + string[] fields; + foreach(string line in lines) { + fields = line.Split('|'); + + if(!fields[0].ToLowerInvariant().StartsWith(lowercaseItemName)) { + newLines.Add(line); + } + } + + File.WriteAllLines(outputFile, newLines.ToArray()); + } + } + + /// + /// Renames an item of the download count list in the specified file. + /// + /// The old item name. + /// The new item name. + /// The full path of the output file. + private void RenameDownloadHitsItem(string oldItemName, string newItemName, string outputFile) { + lock(this) { + string[] lines = File.ReadAllLines(outputFile); + + string lowercaseOldItemName = oldItemName.ToLowerInvariant(); + + string[] fields; + bool found = false; + for(int i = 0; i < lines.Length; i++) { + fields = lines[i].Split('|'); + + if(fields[0].ToLowerInvariant() == lowercaseOldItemName) { + lines[i] = newItemName + "|" + fields[1]; + found = true; + break; + } + } + + if(found) { + File.WriteAllLines(outputFile, lines); + } + } + } + + /// + /// Renames an item of the download count list in the specified file. + /// + /// The initial part of the old item name. + /// The corresponding initial part of the new item name. + /// The full path of the output file. + private void RenameDownloadHitsItemPartialMatch(string oldItemName, string newItemName, string outputFile) { + lock(this) { + string[] lines = File.ReadAllLines(outputFile); + + string lowercaseOldItemName = oldItemName.ToLowerInvariant(); + + string[] fields; + bool found = false; + for(int i = 0; i < lines.Length; i++) { + fields = lines[i].Split('|'); + + if(fields[0].ToLowerInvariant().StartsWith(lowercaseOldItemName)) { + lines[i] = newItemName + fields[0].Substring(lowercaseOldItemName.Length) + "|" + fields[1]; + found = true; + } + } + + if(found) { + File.WriteAllLines(outputFile, lines); + } + } + } + + /// + /// Gets the number of times a file was retrieved. + /// + /// The full name of the file. + /// The number of times the file was retrieved. + private int GetFileRetrievalCount(string fullName) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + + lock(this) { + // Format + // /Full/Path/To/File.txt|DownloadCount + + string[] lines = File.ReadAllLines(GetFullPath(FileDownloadsFile)); + + string lowercaseFullName = fullName.ToLowerInvariant(); + + string[] fields; + foreach(string line in lines) { + fields = line.Split('|'); + if(fields[0].ToLowerInvariant() == lowercaseFullName) { + int res = 0; + if(int.TryParse(fields[1], out res)) return res; + else return 0; + } + } + } + + return 0; + } + + /// + /// Clears the number of times a file was retrieved. + /// + /// The full name of the file. + /// The count to set. + /// If is null. + /// If is empty. + /// If is less than zero. + public void SetFileRetrievalCount(string fullName, int count) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + if(count < 0) throw new ArgumentOutOfRangeException("count", "Count must be greater than or equal to zero"); + + SetDownloadHits(fullName, GetFullPath(FileDownloadsFile), 0); + } + + /// + /// Gets the details of a file. + /// + /// The full name of the file. + /// The details, or null if the file does not exist. + /// If is null. + /// If is empty. + public FileDetails GetFileDetails(string fullName) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + + string n = BuildFullPath(fullName); + + if(!File.Exists(n)) return null; + + FileInfo fi = new FileInfo(n); + + return new FileDetails(fi.Length, fi.LastWriteTime, GetFileRetrievalCount(fullName)); + } + + /// + /// Deletes a File. + /// + /// The full name of the File. + /// true if the File is deleted, false otherwise. + /// If is null. + /// If is empty or it does not exist. + public bool DeleteFile(string fullName) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + + string n = BuildFullPath(fullName); + + if(!File.Exists(n)) throw new ArgumentException("File does not exist", "fullName"); + + try { + File.Delete(n); + SetDownloadHits(fullName, GetFullPath(FileDownloadsFile), 0); + return true; + } + catch(IOException) { + return false; + } + } + + /// + /// Renames or moves a File. + /// + /// The old full name of the File. + /// The new full name of the File. + /// true if the File is renamed, false otherwise. + /// If or are null. + /// If or are empty, or if the old file does not exist, or if the new file already exist. + public bool RenameFile(string oldFullName, string newFullName) { + if(oldFullName == null) throw new ArgumentNullException("oldFullName"); + if(oldFullName.Length == 0) throw new ArgumentException("Old Full Name cannot be empty", "oldFullName"); + if(newFullName == null) throw new ArgumentNullException("newFullName"); + if(newFullName.Length == 0) throw new ArgumentException("New Full Name cannot be empty", "newFullName"); + + string oldFilename = BuildFullPath(oldFullName); + string newFilename = BuildFullPath(newFullName); + + if(!File.Exists(oldFilename)) throw new ArgumentException("Old File does not exist", "oldFullName"); + if(File.Exists(newFilename)) throw new ArgumentException("New File already exists", "newFullName"); + + try { + File.Move(oldFilename, newFilename); + RenameDownloadHitsItem(oldFullName, newFullName, GetFullPath(FileDownloadsFile)); + return true; + } + catch(IOException) { + return false; + } + } + + /// + /// Creates a new Directory. + /// + /// The path to create the new Directory in. + /// The name of the new Directory. + /// true if the Directory is created, false otherwise. + /// If path is "/my/directory" and name is "newdir", a new directory named "/my/directory/newdir" is created. + /// If or are null. + /// If is empty or if the directory does not exist, or if the new directory already exists. + public bool CreateDirectory(string path, string name) { + if(path == null) throw new ArgumentNullException("path"); + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + if(!Directory.Exists(BuildFullPath(path))) throw new ArgumentException("Directory does not exist", "path"); + + string partialPath = path + (!path.EndsWith("/") ? "/" : "") + name; + string d = BuildFullPath(partialPath); + + if(Directory.Exists(d)) throw new ArgumentException("Directory already exists", "name"); + + try { + Directory.CreateDirectory(d); + return true; + } + catch(IOException) { + return false; + } + } + + /// + /// Deletes a Directory and all of its content. + /// + /// The full path of the Directory. + /// true if the Directory is delete, false otherwise. + /// If is null. + /// If is empty or if it equals '/' or it does not exist. + public bool DeleteDirectory(string fullPath) { + if(fullPath == null) throw new ArgumentNullException("fullPath"); + if(fullPath.Length == 0) throw new ArgumentException("Full Path cannot be empty", "fullPath"); + if(fullPath == "/") throw new ArgumentException("Cannot delete the root directory", "fullPath"); + + string d = BuildFullPath(fullPath); + + if(!Directory.Exists(d)) throw new ArgumentException("Directory does not exist", "fullPath"); + + try { + Directory.Delete(d, true); + // Make sure tht fullPath ends with "/" so that the method does not clear wrong items + if(!fullPath.EndsWith("/")) fullPath += "/"; + ClearDownloadHitsPartialMatch(fullPath, GetFullPath(FileDownloadsFile)); + return true; + } + catch(IOException) { + return false; + } + } + + /// + /// Renames or moves a Directory. + /// + /// The old full path of the Directory. + /// The new full path of the Directory. + /// true if the Directory is renamed, false otherwise. + /// If or are null. + /// If or are empty or equal to '/', + /// or if the old directory does not exist or the new directory already exists. + public bool RenameDirectory(string oldFullPath, string newFullPath) { + if(oldFullPath == null) throw new ArgumentNullException("oldFullPath"); + if(oldFullPath.Length == 0) throw new ArgumentException("Old Full Path cannot be empty", "oldFullPath"); + if(oldFullPath == "/") throw new ArgumentException("Cannot rename the root directory", "oldFullPath"); + if(newFullPath == null) throw new ArgumentNullException("newFullPath"); + if(newFullPath.Length == 0) throw new ArgumentException("New Full Path cannot be empty", "newFullPath"); + if(newFullPath == "/") throw new ArgumentException("Cannot rename directory to the root directory", "newFullPath"); + + string olddir = BuildFullPath(oldFullPath); + string newdir = BuildFullPath(newFullPath); + + if(!Directory.Exists(olddir)) throw new ArgumentException("Directory does not exist", "oldFullPath"); + if(Directory.Exists(newdir)) throw new ArgumentException("Directory already exists", "newFullPath"); + + try { + Directory.Move(olddir, newdir); + // Make sure that oldFullPath and newFullPath end with "/" so that the method does not rename wrong items + if(!oldFullPath.EndsWith("/")) oldFullPath += "/"; + if(!newFullPath.EndsWith("/")) newFullPath += "/"; + RenameDownloadHitsItemPartialMatch(oldFullPath, newFullPath, GetFullPath(FileDownloadsFile)); + return true; + } + catch(IOException) { + return false; + } + } + + /// + /// Gets the name of the Directory containing the Attachments of a Page. + /// + /// The Page Info. + /// The name of the Directory (not the full path) that contains the Attachments of the specified Page. + private string GetPageAttachmentDirectory(PageInfo pageInfo) { + // Use the Hash to avoid problems with special chars and the like + // Using the hash prevents GetPageWithAttachments to work + //return Hash.Compute(pageInfo.FullName); + + return pageInfo.FullName; + } + + /// + /// The the names of the pages with attachments. + /// + /// The names of the pages with attachments. + public string[] GetPagesWithAttachments() { + string[] directories = Directory.GetDirectories(GetFullPath(AttachmentsDirectory)); + string[] result = new string[directories.Length]; + for(int i = 0; i < result.Length; i++) { + result[i] = Path.GetFileName(directories[i]); + } + return result; + } + + /// + /// Returns the names of the Attachments of a Page. + /// + /// The Page Info object that owns the Attachments. + /// The names, or an empty list. + /// If is null. + public string[] ListPageAttachments(PageInfo pageInfo) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + + string dir = BuildFullPathForAttachments(GetPageAttachmentDirectory(pageInfo)); + + if(!Directory.Exists(dir)) return new string[0]; + + string[] files = Directory.GetFiles(dir); + + // Result must contain only the filename, not the full path + List result = new List(files.Length); + foreach(string f in files) { + result.Add(Path.GetFileName(f)); + } + return result.ToArray(); + } + + /// + /// Stores a Page Attachment. + /// + /// The Page Info that owns the Attachment. + /// The name of the Attachment, for example "myfile.jpg". + /// A Stream object used as source of a byte stream, + /// i.e. the method reads from the Stream and stores the content properly. + /// true to overwrite an existing Attachment. + /// true if the Attachment is stored, false otherwise. + /// If overwrite is false and Attachment already exists, the method returns false. + /// If , or are null. + /// If is empty or if does not support reading. + public bool StorePageAttachment(PageInfo pageInfo, string name, Stream sourceStream, bool overwrite) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(sourceStream == null) throw new ArgumentNullException("sourceStream"); + if(!sourceStream.CanRead) throw new ArgumentException("Cannot read from Source Stream", "sourceStream"); + + string filename = BuildFullPathForAttachments(GetPageAttachmentDirectory(pageInfo) + "/" + name); + + if(!Directory.Exists(Path.GetDirectoryName(filename))) { + try { + Directory.CreateDirectory(Path.GetDirectoryName(filename)); + } + catch(IOException) { + // Cannot create attachments dir + return false; + } + } + + if(File.Exists(filename) && !overwrite) return false; + + FileStream fs = null; + + bool done = false; + + try { + fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); + + // StreamCopy content (throws exception in case of error) + StreamCopy(sourceStream, fs); + + done = true; + } + catch(IOException) { + return false; + } + finally { + try { + fs.Close(); + } + catch { } + } + + return done; + } + + /// + /// Retrieves a Page Attachment. + /// + /// The Page Info that owns the Attachment. + /// The name of the Attachment, for example "myfile.jpg". + /// A Stream object used as destination of a byte stream, + /// i.e. the method writes to the Stream the file content. + /// A value indicating whether or not to count this retrieval in the statistics. + /// true if the Attachment is retrieved, false otherwise. + /// If , or are null. + /// If is empty or if does not support writing, + /// or if the page does not have attachments or if the attachment does not exist. + public bool RetrievePageAttachment(PageInfo pageInfo, string name, Stream destinationStream, bool countHit) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(destinationStream == null) throw new ArgumentNullException("destinationStream"); + if(!destinationStream.CanWrite) throw new ArgumentException("Cannot write into Destination Stream", "destinationStream"); + + string d = GetPageAttachmentDirectory(pageInfo); + if(!Directory.Exists(BuildFullPathForAttachments(d))) throw new ArgumentException("No attachments for Page", "pageInfo"); + + string filename = BuildFullPathForAttachments(d + "/" + name); + if(!File.Exists(filename)) throw new ArgumentException("Attachment does not exist", "name"); + + FileStream fs = null; + + bool done = false; + + try { + fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read); + + // StreamCopy content (throws exception in case of error) + StreamCopy(fs, destinationStream); + + done = true; + } + catch(IOException) { + done = false; + } + finally { + try { + fs.Close(); + } + catch { } + } + + if(countHit) { + AddDownloadHit(pageInfo.FullName + "." + name, GetFullPath(AttachmentDownloadsFile)); + } + + return done; + } + + /// + /// Gets the number of times a page attachment was retrieved. + /// + /// The page. + /// The name of the attachment. + /// The number of times the attachment was retrieved. + private int GetPageAttachmentRetrievalCount(PageInfo pageInfo, string name) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + lock(this) { + // Format + // PageName.File|DownloadCount + + string[] lines = File.ReadAllLines(GetFullPath(AttachmentDownloadsFile)); + + string lowercaseFullName = pageInfo.FullName + "." + name; + lowercaseFullName = lowercaseFullName.ToLowerInvariant(); + + string[] fields; + foreach(string line in lines) { + fields = line.Split('|'); + + if(fields[0].ToLowerInvariant() == lowercaseFullName) { + int count; + if(int.TryParse(fields[1], out count)) return count; + else return 0; + } + } + } + + return 0; + } + + /// + /// Set the number of times a page attachment was retrieved. + /// + /// The page. + /// The name of the attachment. + /// The count to set. + /// If or are null. + /// If is empty. + /// If is less than zero. + public void SetPageAttachmentRetrievalCount(PageInfo pageInfo, string name, int count) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty"); + if(count < 0) throw new ArgumentOutOfRangeException("Count must be greater than or equal to zero", "count"); + + SetDownloadHits(pageInfo.FullName + "." + name, GetFullPath(AttachmentDownloadsFile), count); + } + + /// + /// Gets the details of a page attachment. + /// + /// The page that owns the attachment. + /// The name of the attachment, for example "myfile.jpg". + /// The details of the attachment, or null if the attachment does not exist. + /// If or are null. + /// If is empty. + public FileDetails GetPageAttachmentDetails(PageInfo pageInfo, string name) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty"); + + string d = GetPageAttachmentDirectory(pageInfo); + if(!Directory.Exists(BuildFullPathForAttachments(d))) return null; + + string filename = BuildFullPathForAttachments(d + "/" + name); + if(!File.Exists(filename)) return null; + + FileInfo fi = new FileInfo(filename); + + return new FileDetails(fi.Length, fi.LastWriteTime, GetPageAttachmentRetrievalCount(pageInfo, name)); + } + + /// + /// Deletes a Page Attachment. + /// + /// The Page Info that owns the Attachment. + /// The name of the Attachment, for example "myfile.jpg". + /// true if the Attachment is deleted, false otherwise. + /// If or are null. + /// If is empty or if the page or attachment do not exist. + public bool DeletePageAttachment(PageInfo pageInfo, string name) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty"); + + string d = GetPageAttachmentDirectory(pageInfo); + if(!Directory.Exists(BuildFullPathForAttachments(d))) throw new ArgumentException("Page does not exist", "pageInfo"); + + string filename = BuildFullPathForAttachments(d + "/" + name); + if(!File.Exists(filename)) throw new ArgumentException("Attachment does not exist", "name"); + + try { + File.Delete(filename); + SetDownloadHits(pageInfo.FullName + "." + name, GetFullPath(AttachmentDownloadsFile), 0); + return true; + } + catch(IOException) { + return false; + } + } + + /// + /// Renames a Page Attachment. + /// + /// The Page Info that owns the Attachment. + /// The old name of the Attachment. + /// The new name of the Attachment. + /// true if the Attachment is renamed, false otherwise. + /// If , or are null. + /// If , or are empty, + /// or if the page or old attachment do not exist, or the new attachment name already exists. + public bool RenamePageAttachment(PageInfo pageInfo, string oldName, string newName) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + if(oldName == null) throw new ArgumentNullException("oldName"); + if(oldName.Length == 0) throw new ArgumentException("Old Name cannot be empty", "oldName"); + if(newName == null) throw new ArgumentNullException("newName"); + if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); + + string d = GetPageAttachmentDirectory(pageInfo); + if(!Directory.Exists(BuildFullPathForAttachments(d))) throw new ArgumentException("Page does not exist", "pageInfo"); + + string oldFilename = BuildFullPathForAttachments(d + "/" + oldName); + if(!File.Exists(oldFilename)) throw new ArgumentException("Attachment does not exist", "oldName"); + + string newFilename = BuildFullPathForAttachments(d + "/" + newName); + if(File.Exists(newFilename)) throw new ArgumentException("Attachment already exists", "newName"); + + try { + File.Move(oldFilename, newFilename); + RenameDownloadHitsItem(pageInfo.FullName + "." + oldName, pageInfo.FullName + "." + newName, + GetFullPath(AttachmentDownloadsFile)); + return true; + } + catch(IOException) { + return false; + } + } + + /// + /// Notifies to the Provider that a Page has been renamed. + /// + /// The old Page Info object. + /// The new Page Info object. + /// If or are null + /// If the new page is already in use. + public void NotifyPageRenaming(PageInfo oldPage, PageInfo newPage) { + if(oldPage == null) throw new ArgumentNullException("oldPage"); + if(newPage == null) throw new ArgumentNullException("newPage"); + + string oldName = GetPageAttachmentDirectory(oldPage); + string newName = GetPageAttachmentDirectory(newPage); + + string oldDir = BuildFullPathForAttachments(oldName); + string newDir = BuildFullPathForAttachments(newName); + + if(!Directory.Exists(oldDir)) return; // Nothing to do + if(Directory.Exists(newDir)) throw new ArgumentException("New Page already exists", "newPage"); + + try { + Directory.Move(oldDir, newDir); + RenameDownloadHitsItemPartialMatch(oldPage.FullName + ".", newPage.FullName + ".", + GetFullPath(AttachmentDownloadsFile)); + } + catch(IOException) { } + } + + } + +} diff --git a/Core/Formatter.cs b/Core/Formatter.cs new file mode 100644 index 0000000..f1d5d11 --- /dev/null +++ b/Core/Formatter.cs @@ -0,0 +1,2611 @@ + +using System; +using System.Configuration; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; +using System.Web.SessionState; +using System.Diagnostics; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Performs all the text formatting and parsing operations. + /// + public static class Formatter { + + private static readonly Regex NoWikiRegex = new Regex(@"\(.|\n|\r)+?\<\/nowiki\>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex NoSingleBr = new Regex(@"\(.|\n|\r)+?\<\/nobr\>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex LinkRegex = new Regex(@"(\[\[.+?\]\])|(\[.+?\])", RegexOptions.Compiled); + private static readonly Regex RedirectionRegex = new Regex(@"^\ *\>\>\>\ *.+\ *$", RegexOptions.Compiled | RegexOptions.Multiline); + private static readonly Regex H1Regex = new Regex(@"^==.+?==\n?", RegexOptions.Compiled | RegexOptions.Multiline); + private static readonly Regex H2Regex = new Regex(@"^===.+?===\n?", RegexOptions.Compiled | RegexOptions.Multiline); + private static readonly Regex H3Regex = new Regex(@"^====.+?====\n?", RegexOptions.Compiled | RegexOptions.Multiline); + private static readonly Regex H4Regex = new Regex(@"^=====.+?=====\n?", RegexOptions.Compiled | RegexOptions.Multiline); + private static readonly Regex BoldRegex = new Regex(@"'''.+?'''", RegexOptions.Compiled | RegexOptions.Singleline); + private static readonly Regex ItalicRegex = new Regex(@"''.+?''", RegexOptions.Compiled | RegexOptions.Singleline); + private static readonly Regex BoldItalicRegex = new Regex(@"'''''.+?'''''", RegexOptions.Compiled | RegexOptions.Singleline); + private static readonly Regex UnderlinedRegex = new Regex(@"__.+?__", RegexOptions.Compiled | RegexOptions.Singleline); + private static readonly Regex StrikedRegex = new Regex(@"(?).+?\-\-)(?!(\>|\>))", RegexOptions.Compiled | RegexOptions.Singleline); + private static readonly Regex CodeRegex = new Regex(@"\{\{.+?\}\}", RegexOptions.Compiled | RegexOptions.Singleline); + private static readonly Regex PreRegex = new Regex(@"\{\{\{\{.+?\}\}\}\}", RegexOptions.Compiled | RegexOptions.Singleline); + private static readonly Regex BoxRegex = new Regex(@"\(\(\(.+?\)\)\)", RegexOptions.Compiled | RegexOptions.Singleline); + private static readonly Regex ExtendedUpRegex = new Regex(@"\{up((\:|\().+?)?\}", RegexOptions.Compiled | RegexOptions.IgnoreCase); + //private static readonly Regex UpRegex = new Regex(@"\{up\}", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex SpecialTagRegex = new Regex(@"\{(wikititle|wikiversion|mainurl|rsspage|themepath|clear|br|top|searchbox|pagecount|cloud|orphans|wanted|namespacelist)\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + //private static readonly Regex CloudRegex = new Regex(@"\{cloud\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + //private static readonly Regex NamespaceRegex = new Regex(@"\{namespace\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + //private static readonly Regex NamespaceListRegex = new Regex(@"\{namespacelist\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex Phase3SpecialTagRegex = new Regex(@"\{(username|pagename|loginlogout|namespace|namespacedropdown|incoming|outgoing)\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex RecentChangesRegex = new Regex(@"\{recentchanges(\(\*\))?\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex ListRegex = new Regex(@"(?<=(\n|^))((\*|\#)+(\ )?.+?\n)+((?=\n)|\z)", RegexOptions.Compiled | RegexOptions.Singleline); // Singleline to matche list elements on multiple lines + private static readonly Regex TocRegex = new Regex(@"\{toc\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex TransclusionRegex = new Regex(@"\{T(\:|\|).+}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex HRRegex = new Regex(@"(?<=(\n|^))(\ )*----(\ )*\n", RegexOptions.Compiled); + //private static readonly Regex SnippetRegex = new Regex(@"\{S(\:|\|)(.+?)(\|(.+?))*}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex SnippetRegex = new Regex(@"\{s(\:|\|)(.+?)(\|.*?)*\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); + private static readonly Regex TableRegex = new Regex(@"\{\|(\ [^\n]*)?\n.+?\|\}", RegexOptions.Compiled | RegexOptions.Singleline); + private static readonly Regex IndentRegex = new Regex(@"(?<=(\n|^))\:+(\ )?.+?\n", RegexOptions.Compiled); + private static readonly Regex EscRegex = new Regex(@"\(.|\n|\r)*?\<\/esc\>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); + private static readonly Regex SignRegex = new Regex(@"\(.+?\)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex FullCodeRegex = new Regex(@"@@.+?@@", RegexOptions.Compiled | RegexOptions.Singleline); + //private static readonly Regex UsernameRegex = new Regex(@"\{username\}", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex JavascriptRegex = new Regex(@"\.*?\<\/script\>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant); + private static readonly Regex CommentRegex = new Regex(@"(?[\s\n]*))\<\!\-\-.*?\-\-\>(?!([\s\n]*\<\/script\>))", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant); + + /// + /// The section editing button placeholder. + /// + public const string EditSectionPlaceHolder = "%%%%EditSectionPlaceHolder%%%%"; // This string is also used in History.aspx.cs + private const string TocTitlePlaceHolder = "%%%%TocTitlePlaceHolder%%%%"; + private const string UpReplacement = "GetFile.aspx?File="; + private const string ExtendedUpReplacement = "GetFile.aspx?$File="; + private const string ExtendedUpReplacementForAttachment = "GetFile.aspx?$Page=@&File="; + private const string SingleBrPlaceHolder = "%%%%SingleBrPlaceHolder%%%%"; + + /// + /// Detects the current namespace. + /// + /// The current page, if any. + /// The current namespace (null for the root). + private static NamespaceInfo DetectNamespaceInfo(PageInfo currentPage) { + if(currentPage == null) { + return Tools.DetectCurrentNamespaceInfo(); + } + else { + string ns = NameTools.GetNamespace(currentPage.FullName); + return Pages.FindNamespace(ns); + } + } + + /// + /// Formats WikiMarkup, converting it into XHTML. + /// + /// The raw WikiMarkup text. + /// A value indicating whether the formatting is being done for content indexing. + /// The formatting context. + /// The current Page (can be null). + /// The formatted text. + public static string Format(string raw, bool forIndexing, FormattingContext context, PageInfo current) { + string[] tempLinks; + return Format(raw, forIndexing, context, current, out tempLinks); + } + + /// + /// Formats WikiMarkup, converting it into XHTML. + /// + /// The raw WikiMarkup text. + /// A value indicating whether the formatting is being done for content indexing. + /// The formatting context. + /// The current Page (can be null). + /// The linked pages, both existent and inexistent. + /// The formatted text. + public static string Format(string raw, bool forIndexing, FormattingContext context, PageInfo current, out string[] linkedPages) { + return Format(raw, forIndexing, context, current, out linkedPages, false); + } + + /// + /// Formats WikiMarkup, converting it into XHTML. + /// + /// The raw WikiMarkup text. + /// A value indicating whether the formatting is being done for content indexing. + /// The formatting context. + /// The current Page (can be null). + /// The linked pages, both existent and inexistent. + /// A value indicating whether to format in bare-bones mode (for WYSIWYG editor). + /// The formatted text. + public static string Format(string raw, bool forIndexing, FormattingContext context, PageInfo current, out string[] linkedPages, bool bareBones) { + // Bare Bones: Advanced tags, such as tables, toc, special tags, etc. are not formatted - used for Visual editor display + + linkedPages = new string[0]; + List tempLinkedPages = new List(10); + + StringBuilder sb = new StringBuilder(raw); + Match match; + string tmp, a, n, url, title, bigUrl; + StringBuilder dummy; // Used for temporary string manipulation inside formatting cycles + bool done = false; + List noWikiBegin = new List(), noWikiEnd = new List(); + int end = 0; + List hPos = new List(); + + sb.Replace("\r", ""); + bool addedNewLineAtEnd = false; + if(!sb.ToString().EndsWith("\n")) { + sb.Append("\n"); // Very important to make Regular Expressions work! + addedNewLineAtEnd = true; + } + + // Remove all double- or single-LF in JavaScript tags + bool singleLine = Settings.ProcessSingleLineBreaks; + match = JavascriptRegex.Match(sb.ToString()); + while(match.Success) { + sb.Remove(match.Index, match.Length); + if(singleLine) sb.Insert(match.Index, match.Value.Replace("\n", "")); + else sb.Insert(match.Index, match.Value.Replace("\n\n", "\n")); + match = JavascriptRegex.Match(sb.ToString(), match.Index + 1); + } + + // Strip out all comments + match = CommentRegex.Match(sb.ToString()); + while(match.Success) { + sb.Remove(match.Index, match.Length); + match = CommentRegex.Match(sb.ToString(), match.Index + 1); + } + + // Remove empty NoWiki and NoBr tags + sb.Replace("", ""); + sb.Replace("", ""); + + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + + if(current != null) { + // Check redirection + match = RedirectionRegex.Match(sb.ToString()); + if(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + string destination = match.Value.Trim().Substring(4).Trim(); + while(destination.StartsWith("[") && destination.EndsWith("]")) { + destination = destination.Substring(1, destination.Length - 2); + } + while(sb[match.Index] == '\n' && match.Index < sb.Length - 1) sb.Remove(match.Index, 1); + PageInfo dest = Pages.FindPage(destination); + if(dest != null) { + Redirections.AddRedirection(current, dest); + } + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + } + } + + // Before Producing HTML + match = FullCodeRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + string content = match.Value.Substring(2, match.Length - 4); + dummy = new StringBuilder(); + dummy.Append("
");
+					// IE needs \r\n for line breaks
+					dummy.Append(EscapeWikiMarkup(content).Replace("\n", "\r\n"));
+					dummy.Append("
"); + sb.Insert(match.Index, dummy.ToString()); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = FullCodeRegex.Match(sb.ToString(), end); + } + + // No more needed (Striked Regex modified) + // Temporarily "escape" comments + //sb.Replace("", "(^_$)"); + + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + + // Before Producing HTML + if(!bareBones) { + match = EscRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, match.Value.Substring(5, match.Length - 11).Replace("<", "<").Replace(">", ">")); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = EscRegex.Match(sb.ToString(), end); + } + } + + // Snippets and tables processing was here + + if(!bareBones) { + match = IndentRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, BuildIndent(match.Value) + "\n"); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = IndentRegex.Match(sb.ToString(), end); + } + } + + // Process extended UP before standard UP + match = ExtendedUpRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + string prov = match.Groups[1].Value.StartsWith(":") ? match.Value.Substring(4, match.Value.Length - 5) : match.Value.Substring(3, match.Value.Length - 4); + string page = null; + // prov - Full.Provider.Type.Name(PageName) + // (PageName) is optional, but it can contain brackets, for example (Page(WithBrackets)) + if(prov.EndsWith(")") && prov.Contains("(")) { + page = prov.Substring(prov.IndexOf("(") + 1); + page = page.Substring(0, page.Length - 1); + page = Tools.UrlEncode(page); + prov = prov.Substring(0, prov.IndexOf("(")); + } + if(page == null) { + // Normal file + sb.Insert(match.Index, ExtendedUpReplacement.Replace("$", (prov != "") ? "Provider=" + prov + "&" : "")); + } + else { + // Page attachment + sb.Insert(match.Index, + ExtendedUpReplacementForAttachment.Replace("$", (prov != "")? "Provider=" + prov + "&" : "").Replace("@", page)); + } + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = ExtendedUpRegex.Match(sb.ToString(), end); + } + + /*match = UpRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, UpReplacement); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = UpRegex.Match(sb.ToString(), end); + }*/ + + if(!bareBones) { + match = SpecialTagRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + switch(match.Value.Substring(1, match.Value.Length - 2).ToUpperInvariant()) { + case "WIKITITLE": + sb.Insert(match.Index, Settings.WikiTitle); + break; + case "WIKIVERSION": + sb.Insert(match.Index, Settings.WikiVersion); + break; + case "MAINURL": + sb.Insert(match.Index, Settings.MainUrl); + break; + case "RSSPAGE": + if(current != null) { + sb.Insert(match.Index, @""); + } + break; + case "THEMEPATH": + sb.Insert(match.Index, Settings.GetThemePath(Tools.DetectCurrentNamespace())); + break; + case "CLEAR": + sb.Insert(match.Index, @"
"); + break; + case "BR": + //if(!AreSingleLineBreaksToBeProcessed()) sb.Insert(match.Index, "
"); + sb.Insert(match.Index, "
"); + break; + case "TOP": + sb.Insert(match.Index, @"" + Exchanger.ResourceExchanger.GetResource("Top") + ""); + break; + case "SEARCHBOX": + string textBoxId = "SB" + Guid.NewGuid().ToString("N"); + + NamespaceInfo ns = DetectNamespaceInfo(current); + string nsstring = ns != null ? NameTools.GetFullName(ns.Name, "Search") + ".aspx" : "Search.aspx"; + string doSearchFunction = ""; + sb.Insert(match.Index, doSearchFunction + + @" »"); + break; + case "CLOUD": + string cloud = BuildCloud(DetectNamespaceInfo(current)); + sb.Insert(match.Index, cloud); + break; + case "PAGECOUNT": + sb.Insert(match.Index, Pages.GetPages(DetectNamespaceInfo(current)).Count.ToString()); + break; + case "ORPHANS": + if(!forIndexing) sb.Insert(match.Index, BuildOrphanedPagesList(DetectNamespaceInfo(current), context, current)); + break; + case "WANTED": + sb.Insert(match.Index, BuildWantedPagesList(DetectNamespaceInfo(current))); + break; + case "NAMESPACELIST": + sb.Insert(match.Index, BuildNamespaceList()); + break; + } + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = SpecialTagRegex.Match(sb.ToString(), end); + } + } + + match = ListRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + int d = 0; + try { + string[] lines = match.Value.Split('\n'); + // Inline multi-line list elements + List tempLines = new List(lines); + for(int i = tempLines.Count - 1; i >= 1; i--) { // Skip first line + string trimmedLine = tempLines[i].Trim(); + if(!trimmedLine.StartsWith("*") && !trimmedLine.StartsWith("#")) { + if(i != tempLines.Count - 1) { + trimmedLine = "
" + trimmedLine; + tempLines[i - 1] += trimmedLine; + } + tempLines.RemoveAt(i); + } + } + lines = tempLines.ToArray(); + + sb.Insert(match.Index, GenerateList(lines, 0, 0, ref d) + "\n"); + } + catch { + sb.Insert(match.Index, @"FORMATTER ERROR (Malformed List)"); + } + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = ListRegex.Match(sb.ToString(), end); + } + + match = HRRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, @"

" + "\n"); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = HRRegex.Match(sb.ToString(), end); + } + + // Replace \n with BR was here + + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + + // Transclusion (intra-Wiki) + if(!bareBones) { + match = TransclusionRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + PageInfo info = Pages.FindPage(match.Value.Substring(3, match.Value.Length - 4)); + if(info != null && info != current) { // Avoid circular transclusion! + dummy = new StringBuilder(); + dummy.Append(@"
"); + dummy.Append(FormattingPipeline.FormatWithPhase1And2(Content.GetPageContent(info, true).Content, + forIndexing, FormattingContext.TranscludedPageContent, info)); + dummy.Append("
"); + sb.Insert(match.Index, dummy.ToString()); + } + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = TransclusionRegex.Match(sb.ToString(), end); + } + } + + List attachments = new List(); + + // Links and images + match = LinkRegex.Match(sb.ToString()); + while(match.Success) { + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + match = LinkRegex.Match(sb.ToString(), end); + continue; + } + + // [], [[]] and [[] can occur when empty links are processed + if(match.Value.Equals("[]") || match.Value.Equals("[[]]") || match.Value.Equals("[[]")) { + end += 2; + match = LinkRegex.Match(sb.ToString(), end); + continue; // Prevents formatting emtpy links + } + + done = false; + if(match.Value.StartsWith("[[")) tmp = match.Value.Substring(2, match.Length - 4).Trim(); + else tmp = match.Value.Substring(1, match.Length - 2).Trim(); + sb.Remove(match.Index, match.Length); + a = ""; + n = ""; + if(tmp.IndexOf("|") != -1) { + // There are some fields + string[] fields = tmp.Split('|'); + if(fields.Length == 2) { + // Link with title + a = fields[0]; + n = fields[1]; + } + else { + StringBuilder img = new StringBuilder(); + // Image + if(fields[0].ToLowerInvariant().Equals("imageleft") || fields[0].ToLowerInvariant().Equals("imageright") || fields[0].ToLowerInvariant().Equals("imageauto")) { + string c = ""; + switch(fields[0].ToLowerInvariant()) { + case "imageleft": + c = "imageleft"; + break; + case "imageright": + c = "imageright"; + break; + case "imageauto": + c = "imageauto"; + break; + default: + c = "image"; + break; + } + title = fields[1]; + url = fields[2]; + if(fields.Length == 4) bigUrl = fields[3]; + else bigUrl = ""; + url = EscapeUrl(url); + // bigUrl = EscapeUrl(bigUrl); The url is already escaped by BuildUrl + if(c.Equals("imageauto")) { + img.Append(@"
"); + } + else { + img.Append(@"
"); + } + if(bigUrl.Length > 0) { + dummy = new StringBuilder(200); + dummy.Append(@" 0) dummy.Append(StripWikiMarkup(StripHtml(title.TrimStart('#')))); + else dummy.Append(Exchanger.ResourceExchanger.GetResource("Image")); + dummy.Append(@""" />"); + img.Append(BuildLink(bigUrl, dummy.ToString(), true, title, forIndexing, bareBones, context, null, tempLinkedPages)); + } + else { + img.Append(@" 0) img.Append(StripWikiMarkup(StripHtml(title.TrimStart('#')))); + else img.Append(Exchanger.ResourceExchanger.GetResource("Image")); + img.Append(@""" />"); + } + if(title.Length > 0 && !title.StartsWith("#")) { + img.Append(@"

"); + img.Append(title); + img.Append("

"); + } + if(c.Equals("imageauto")) { + img.Append("
"); + } + else { + img.Append(""); + } + sb.Insert(match.Index, img); + } + else if(fields[0].ToLowerInvariant().Equals("image")) { + title = fields[1]; + url = fields[2]; + if(fields.Length == 4) bigUrl = fields[3]; + else bigUrl = ""; + url = EscapeUrl(url); + // bigUrl = EscapeUrl(bigUrl); The url is already escaped by BuildUrl + if(bigUrl.Length > 0) { + dummy = new StringBuilder(); + dummy.Append(@" 0) dummy.Append(StripWikiMarkup(StripHtml(title.TrimStart('#')))); + else dummy.Append(Exchanger.ResourceExchanger.GetResource("Image")); + dummy.Append(@""" />"); + img.Append(BuildLink(bigUrl, dummy.ToString(), true, title, forIndexing, bareBones, context, null, tempLinkedPages)); + } + else { + img.Append(@" 0) img.Append(StripWikiMarkup(StripHtml(title.TrimStart('#')))); + else img.Append(Exchanger.ResourceExchanger.GetResource("Image")); + img.Append(@""" />"); + } + sb.Insert(match.Index, img.ToString()); + } + else { + sb.Insert(match.Index, @"FORMATTER ERROR (Malformed Image Tag)"); + } + done = true; + } + } + else if(tmp.ToLowerInvariant().StartsWith("attachment:")) { + // This is an attachment + done = true; + string f = tmp.Substring("attachment:".Length); + if(f.StartsWith("{up}")) f = f.Substring(4); + if(f.ToLowerInvariant().StartsWith(UpReplacement.ToLowerInvariant())) f = f.Substring(UpReplacement.Length); + attachments.Add(HttpContext.Current.Server.UrlDecode(f)); + // Remove all trailing \n, so that attachments have no effect on the output in any case + while(sb[match.Index] == '\n' && match.Index < sb.Length - 1) { + sb.Remove(match.Index, 1); + } + } + else { + a = tmp; + n = ""; + } + if(!done) { + sb.Insert(match.Index, BuildLink(a, n, false, "", forIndexing, bareBones, context, current, tempLinkedPages)); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = LinkRegex.Match(sb.ToString(), end); + } + + match = BoldItalicRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + dummy = new StringBuilder(""); + dummy.Append(match.Value.Substring(5, match.Value.Length - 10)); + dummy.Append(""); + sb.Insert(match.Index, dummy.ToString()); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = BoldItalicRegex.Match(sb.ToString(), end); + } + + match = BoldRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + dummy = new StringBuilder(""); + dummy.Append(match.Value.Substring(3, match.Value.Length - 6)); + dummy.Append(""); + sb.Insert(match.Index, dummy.ToString()); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = BoldRegex.Match(sb.ToString(), end); + } + + match = ItalicRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + dummy = new StringBuilder(""); + dummy.Append(match.Value.Substring(2, match.Value.Length - 4)); + dummy.Append(""); + sb.Insert(match.Index, dummy.ToString()); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = ItalicRegex.Match(sb.ToString(), end); + } + + match = UnderlinedRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + dummy = new StringBuilder(""); + dummy.Append(match.Value.Substring(2, match.Value.Length - 4)); + dummy.Append(""); + sb.Insert(match.Index, dummy.ToString()); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = UnderlinedRegex.Match(sb.ToString(), end); + } + + match = StrikedRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + dummy = new StringBuilder(""); + dummy.Append(match.Value.Substring(2, match.Value.Length - 4)); + dummy.Append(""); + sb.Insert(match.Index, dummy.ToString()); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = StrikedRegex.Match(sb.ToString(), end); + } + + match = PreRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + dummy = new StringBuilder("
");
+					// IE needs \r\n for line breaks
+					dummy.Append(match.Value.Substring(4, match.Value.Length - 8).Replace("\n", "\r\n"));
+					dummy.Append("
"); + sb.Insert(match.Index, dummy.ToString()); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = PreRegex.Match(sb.ToString(), end); + } + + match = CodeRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + dummy = new StringBuilder(""); + dummy.Append(match.Value.Substring(2, match.Value.Length - 4)); + dummy.Append(""); + sb.Insert(match.Index, dummy.ToString()); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = CodeRegex.Match(sb.ToString(), end); + } + + string h; + + // Hx: detection pass (used for the TOC generation and section editing) + hPos = DetectHeaders(sb.ToString()); + + // Hx: formatting pass + + int count = 0; + + match = H4Regex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + h = match.Value.Substring(5, match.Value.Length - 10 - (match.Value.EndsWith("\n") ? 1 : 0)); + dummy = new StringBuilder(200); + dummy.Append(@"

"); + dummy.Append(h); + if(!bareBones && !forIndexing) { + string id = BuildHAnchor(h, count.ToString()); + BuildHeaderAnchor(dummy, id); + } + dummy.Append("

"); + sb.Insert(match.Index, dummy.ToString()); + count++; + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = H4Regex.Match(sb.ToString(), end); + } + + match = H3Regex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + h = match.Value.Substring(4, match.Value.Length - 8 - (match.Value.EndsWith("\n") ? 1 : 0)); + dummy = new StringBuilder(200); + if(current != null && !bareBones && !forIndexing) dummy.Append(BuildEditSectionLink(count, current.FullName)); + dummy.Append(@"

"); + dummy.Append(h); + if(!bareBones && !forIndexing) { + string id = BuildHAnchor(h, count.ToString()); + BuildHeaderAnchor(dummy, id); + } + dummy.Append("

"); + sb.Insert(match.Index, dummy.ToString()); + count++; + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = H3Regex.Match(sb.ToString(), end); + } + + match = H2Regex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + h = match.Value.Substring(3, match.Value.Length - 6 - (match.Value.EndsWith("\n") ? 1 : 0)); + dummy = new StringBuilder(200); + if(current != null && !bareBones && !forIndexing) dummy.Append(BuildEditSectionLink(count, current.FullName)); + dummy.Append(@"

"); + dummy.Append(h); + if(!bareBones && !forIndexing) { + string id = BuildHAnchor(h, count.ToString()); + BuildHeaderAnchor(dummy, id); + } + dummy.Append("

"); + sb.Insert(match.Index, dummy.ToString()); + count++; + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = H2Regex.Match(sb.ToString(), end); + } + + match = H1Regex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + h = match.Value.Substring(2, match.Value.Length - 4 - (match.Value.EndsWith("\n") ? 1 : 0)); + dummy = new StringBuilder(200); + if(current != null && !bareBones && !forIndexing) dummy.Append(BuildEditSectionLink(count, current.FullName)); + dummy.Append(@"

"); + dummy.Append(h); + if(!bareBones && !forIndexing) { + string id = BuildHAnchor(h, count.ToString()); + BuildHeaderAnchor(dummy, id); + } + dummy.Append("

"); + sb.Insert(match.Index, dummy.ToString()); + count++; + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = H1Regex.Match(sb.ToString(), end); + } + + match = BoxRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + dummy = new StringBuilder(@"
"); + dummy.Append(match.Value.Substring(3, match.Value.Length - 6)); + dummy.Append("
"); + sb.Insert(match.Index, dummy.ToString()); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = BoxRegex.Match(sb.ToString(), end); + } + + string tocString = BuildToc(hPos); + + if(!bareBones && current != null) { + match = TocRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + if(!forIndexing) sb.Insert(match.Index, tocString); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = TocRegex.Match(sb.ToString(), end); + } + } + + if(!bareBones) { + match = SnippetRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + string balanced = null; + try { + // If the snippet is malformed this can explode + balanced = ExpandToBalanceBrackets(sb, match.Index, match.Value); + } + catch { + balanced = match.Value; + } + sb.Remove(match.Index, balanced.Length); + + if(balanced.IndexOf("}") == balanced.Length - 1) { + // Single-level snippet + string[] temp = null; + sb.Insert(match.Index, + Format(FormatSnippet(balanced, tocString), forIndexing, context, current, out temp, bareBones).Trim('\n')); + if(temp != null) tempLinkedPages.AddRange(temp); + } + else { + // Nested snippet + + int lastOpen = 0; + int firstClosedAfterLastOpen = 0; + + do { + lastOpen = balanced.LastIndexOf("{"); + firstClosedAfterLastOpen = balanced.IndexOf("}", lastOpen + 1); + + if(firstClosedAfterLastOpen <= lastOpen) break; // Give up + + string internalSnippet = balanced.Substring(lastOpen, firstClosedAfterLastOpen - lastOpen + 1); + balanced = balanced.Remove(lastOpen, firstClosedAfterLastOpen - lastOpen + 1); + + string formattedInternalSnippet = FormatSnippet(internalSnippet, tocString); + string[] temp; + formattedInternalSnippet = Format(formattedInternalSnippet, forIndexing, context, current, out temp, bareBones).Trim('\n'); + if(temp != null) tempLinkedPages.AddRange(temp); + + balanced = balanced.Insert(lastOpen, formattedInternalSnippet); + } while(lastOpen != -1); + + sb.Insert(match.Index, balanced); + } + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = SnippetRegex.Match(sb.ToString(), end); + } + } + + if(!bareBones) { + match = TableRegex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, BuildTable(match.Value)); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = TableRegex.Match(sb.ToString(), end); + } + } + + // Remove tags + if(!bareBones) { + sb.Replace("", ""); + sb.Replace("", ""); + } + + ProcessLineBreaks(sb); + + if(addedNewLineAtEnd) { + if(sb.ToString().EndsWith("
")) sb.Remove(sb.Length - 6, 6); + } + + // Append Attachments + if(!bareBones && attachments.Count > 0) { + sb.Append(@"
"); + for(int i = 0; i < attachments.Count; i++) { + sb.Append(@""); + sb.Append(attachments[i]); + sb.Append(""); + if(i != attachments.Count - 1) sb.Append(" - "); + } + sb.Append("
"); + } + + linkedPages = tempLinkedPages.ToArray(); + + return sb.ToString(); + } + + /// + /// Builds the anchor markup for a header. + /// + /// The string builder. + /// The anchor ID. + private static void BuildHeaderAnchor(StringBuilder buffer, string id) { + buffer.Append(@""); + } + + /// + /// Builds the recent changes list. + /// + /// A value indicating whether to build a list for all namespace or for just the current one. + /// The current namespace. + /// The formatting context. + /// The current page, or null. + /// The recent changes list HTML markup. + private static string BuildRecentChanges(NamespaceInfo currentNamespace, bool allNamespaces, FormattingContext context, PageInfo currentPage) { + List allChanges = new List(RecentChanges.GetAllChanges()); + + if(allChanges.Count == 0) return ""; + + // Sort by descending date/time + allChanges.Reverse(); + + // Filter by namespace + if(!allNamespaces) { + allChanges.RemoveAll((c) => { + NamespaceInfo ns = Pages.FindNamespace(NameTools.GetNamespace(c.Page)); + return ns != currentNamespace; + }); + } + + return BuildRecentChangesTable(allChanges, context, currentPage); + } + + /// + /// Builds a table containing recent changes. + /// + /// The changes. + /// The formatting context. + /// The current page, or null. + /// The table HTML. + public static string BuildRecentChangesTable(IList allChanges, FormattingContext context, PageInfo currentPage) { + int maxChanges = Math.Min(Settings.MaxRecentChangesToDisplay, allChanges.Count); + + StringBuilder sb = new StringBuilder(500); + sb.Append(""); + + for(int i = 0; i < maxChanges; i++) { + sb.AppendFormat("", i % 2 == 0 ? "tablerow" : "tablerowalternate"); + sb.Append(""); + sb.Append(""); + } + + sb.Append("
"); + sb.Append(Preferences.AlignWithTimezone(allChanges[i].DateTime).ToString(Settings.DateTimeFormat)); + sb.Append(""); + sb.Append(PrintRecentChange(allChanges[i], context, currentPage)); + sb.Append("
"); + + return sb.ToString(); + } + + /// + /// Prints a recent change. + /// + /// The change. + /// The formatting context. + /// The current page, or null. + /// The proper text to display. + private static string PrintRecentChange(RecentChange change, FormattingContext context, PageInfo currentPage) { + switch(change.Change) { + case Change.PageUpdated: + return Exchanger.ResourceExchanger.GetResource("UserUpdatedPage").Replace("##USER##", Users.UserLink(change.User)).Replace("##PAGE##", PrintPageLink(change, context, currentPage)); + case Change.PageRenamed: + return Exchanger.ResourceExchanger.GetResource("UserRenamedPage").Replace("##USER##", Users.UserLink(change.User)).Replace("##PAGE##", PrintPageLink(change, context, currentPage)); + case Change.PageRolledBack: + return Exchanger.ResourceExchanger.GetResource("UserRolledBackPage").Replace("##USER##", Users.UserLink(change.User)).Replace("##PAGE##", PrintPageLink(change, context, currentPage)); + case Change.PageDeleted: + return Exchanger.ResourceExchanger.GetResource("UserDeletedPage").Replace("##USER##", Users.UserLink(change.User)).Replace("##PAGE##", PrintPageLink(change, context, currentPage)); + case Change.MessagePosted: + return Exchanger.ResourceExchanger.GetResource("UserPostedMessage").Replace("##USER##", Users.UserLink(change.User)).Replace("##PAGE##", PrintMessageLink(change, context, currentPage)); + case Change.MessageEdited: + return Exchanger.ResourceExchanger.GetResource("UserEditedMessage").Replace("##USER##", Users.UserLink(change.User)).Replace("##PAGE##", PrintMessageLink(change, context, currentPage)); + case Change.MessageDeleted: + return Exchanger.ResourceExchanger.GetResource("UserDeletedMessage").Replace("##USER##", Users.UserLink(change.User)).Replace("##PAGE##", PrintMessageLink(change, context, currentPage)); + default: + throw new NotSupportedException(); + } + } + + /// + /// Builds a link to a page, properly handling inexistent pages. + /// + /// The change. + /// The formatting context. + /// The current page, or null. + /// The link HTML markup. + private static string PrintPageLink(RecentChange change, FormattingContext context, PageInfo currentPage) { + PageInfo page = Pages.FindPage(change.Page); + if(page != null) { + return string.Format(@"{2}", + change.Page, Settings.PageExtension, FormattingPipeline.PrepareTitle(change.Title, false, context, currentPage)); + } + else { + return FormattingPipeline.PrepareTitle(change.Title, false, context, currentPage) + " (" + change.Page + ")"; + } + } + + /// + /// Builds a link to a page discussion. + /// + /// The change. + /// The formatting context. + /// The current page, or null. + /// The link HTML markup. + private static string PrintMessageLink(RecentChange change, FormattingContext context, PageInfo currentPage) { + PageInfo page = Pages.FindPage(change.Page); + if(page != null) { + return string.Format(@"{3}", + change.Page, Settings.PageExtension, Tools.GetMessageIdForAnchor(change.DateTime), + FormattingPipeline.PrepareTitle(change.Title, false, context, currentPage) + " (" + + FormattingPipeline.PrepareTitle(change.MessageSubject, false, context, currentPage) + ")"); + } + else { + return FormattingPipeline.PrepareTitle(change.Title, false, context, currentPage) + " (" + change.Page + ")"; + } + } + + /// + /// Builds the orhpaned pages list (valid only in non-indexing mode). + /// + /// The namespace (null for the root). + /// The formatting context. + /// The current page, if any. + /// The list. + private static string BuildOrphanedPagesList(NamespaceInfo nspace, FormattingContext context, PageInfo current) { + PageInfo[] orhpans = Pages.GetOrphanedPages(nspace); + + if(orhpans.Length == 0) return ""; + + StringBuilder sb = new StringBuilder(500); + sb.Append("
    "); + + foreach(PageInfo page in orhpans) { + PageContent content = Content.GetPageContent(page, false); + sb.Append("
  • "); + sb.AppendFormat(@"{2}", Tools.UrlEncode(page.FullName), Settings.PageExtension, + FormattingPipeline.PrepareTitle(content.Title, false, context, current)); + sb.Append("
  • "); + } + + sb.Append("
"); + + return sb.ToString(); + } + + /// + /// Builds the wanted pages list. + /// + /// The namespace (null for the root). + /// The list. + private static string BuildWantedPagesList(NamespaceInfo nspace) { + Dictionary> wanted = Pages.GetWantedPages(nspace != null ? nspace.Name : null); + + if(wanted.Count == 0) return ""; + + StringBuilder sb = new StringBuilder(500); + sb.Append("
    "); + + foreach(string page in wanted.Keys) { + sb.Append("
  • "); + sb.AppendFormat(@"{2}", page, Settings.PageExtension, NameTools.GetLocalName(page)); + sb.Append("
  • "); + } + + sb.Append("
"); + + return sb.ToString(); + } + + /// + /// Builds the incoming links list for a page (valid only in Phase3). + /// + /// The page. + /// The formatting context. + /// The current page, if any. + /// The list. + private static string BuildIncomingLinksList(PageInfo page, FormattingContext context, PageInfo current) { + if(page == null) return ""; + + string[] links = Pages.GetPageIncomingLinks(page); + if(links.Length == 0) return ""; + + StringBuilder sb = new StringBuilder(500); + sb.AppendFormat("
    "); + + foreach(string link in links) { + PageInfo linkedPage = Pages.FindPage(link); + if(linkedPage != null) { + PageContent content = Content.GetPageContent(linkedPage, false); + + sb.Append("
  • "); + sb.AppendFormat(@"{2}", Tools.UrlEncode(link), Settings.PageExtension, + FormattingPipeline.PrepareTitle(content.Title, false, context, current)); + sb.Append("
  • "); + } + } + + sb.AppendFormat("
"); + + return sb.ToString(); + } + + /// + /// Builds the outgoing links list for a page (valid only in Phase3). + /// + /// The page. + /// The formatting context. + /// The current page, if any. + /// The list. + private static string BuildOutgoingLinksList(PageInfo page, FormattingContext context, PageInfo current) { + if(page == null) return ""; + + string[] links = Pages.GetPageOutgoingLinks(page); + if(links.Length == 0) return ""; + + StringBuilder sb = new StringBuilder(500); + sb.Append("
    "); + + foreach(string link in links) { + PageInfo linkedPage = Pages.FindPage(link); + if(linkedPage != null) { + PageContent content = Content.GetPageContent(linkedPage, false); + + sb.Append("
  • "); + sb.AppendFormat(@"{2}", Tools.UrlEncode(link), Settings.PageExtension, + FormattingPipeline.PrepareTitle(content.Title, false, context, current)); + sb.Append("
  • "); + } + } + + sb.Append("
"); + + return sb.ToString(); + } + + /// + /// Builds the link to a namespace. + /// + /// The namespace (null for the root). + /// The link. + private static string BuildNamespaceLink(string nspace) { + return "" + + (string.IsNullOrEmpty(nspace) ? "<root>" : nspace) + ""; + } + + /// + /// Builds the namespace list. + /// + /// The namespace list. + private static string BuildNamespaceList() { + StringBuilder sb = new StringBuilder(100); + + sb.Append("
    "); + sb.Append("
  • "); + sb.Append(BuildNamespaceLink(null)); + sb.Append("
  • "); + foreach(NamespaceInfo ns in Pages.GetNamespaces()) { + sb.Append("
  • "); + sb.Append(BuildNamespaceLink(ns.Name)); + sb.Append("
  • "); + } + + sb.Append("
"); + + return sb.ToString(); + } + + /// + /// Processes line breaks. + /// + /// The containing the text to process. + private static void ProcessLineBreaks(StringBuilder sb) { + if(AreSingleLineBreaksToBeProcessed()) { + // Replace new-lines only when not enclosed in tags + + Match match = NoSingleBr.Match(sb.ToString()); + while(match.Success) { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, match.Value.Replace("\n", SingleBrPlaceHolder)); + match = NoSingleBr.Match(sb.ToString(), match.Index + 1); + } + + sb.Replace("\n", "
"); + + sb.Replace(SingleBrPlaceHolder, "\n"); + } + else { + // Replace new-lines only when not enclosed in
tags + + Match match = NoSingleBr.Match(sb.ToString()); + while(match.Success) { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, match.Value.Replace("\n", SingleBrPlaceHolder)); + match = NoSingleBr.Match(sb.ToString(), match.Index + 1); + } + + sb.Replace("\n\n", "

"); + + sb.Replace(SingleBrPlaceHolder, "\n"); + } + + sb.Replace("
", "
"); + + // BR Hacks + sb.Replace("

", "
"); + sb.Replace("

", "
"); + sb.Replace("

", "
"); + sb.Replace("

", "
"); + if(AreSingleLineBreaksToBeProcessed()) { + sb.Replace("
", ""); + sb.Replace("
", ""); + sb.Replace("
", ""); + sb.Replace("
", ""); + sb.Replace("
", ""); + sb.Replace("
", ""); + sb.Replace("
", ""); + } + else { + sb.Replace("

", "
"); + } + + sb.Replace("
", ""); + sb.Replace("", ""); + } + + /// + /// Gets a value indicating whether or not to process single line breaks. + /// + /// true if SLB are to be processed, false otherwise. + private static bool AreSingleLineBreaksToBeProcessed() { + return Settings.ProcessSingleLineBreaks; + } + + /// + /// Replaces the {TOC} markup in a string. + /// + /// The input string. + /// The TOC replacement. + /// The final string. + private static string ReplaceToc(string input, string cachedToc) { + // HACK: this method is used to trick the formatter so that it works when a {TOC} tag is placed in a trigger + // Basically, the Snippet content is formatted in the same context as the main content, but without + // The headers list available, thus the need for this special treatment + + Match match = TocRegex.Match(input); + while(match.Success) { + input = input.Remove(match.Index, match.Length); + input = input.Insert(match.Index, cachedToc); + match = TocRegex.Match(input, match.Index + 1); + } + + return input; + } + + /// + /// Expands a regex selection to match the number of open curly brackets. + /// + /// The buffer. + /// The match start index. + /// The match value. + /// The balanced string, or null if the brackets could not be balanced. + private static string ExpandToBalanceBrackets(StringBuilder sb, int index, string value) { + int tempIndex = -1; + + int openCount = 0; + do { + tempIndex = value.IndexOf("{", tempIndex + 1); + if(tempIndex >= 0) openCount++; + } while(tempIndex != -1 && tempIndex < value.Length - 1); + + int closeCount = 0; + tempIndex = -1; + do { + tempIndex = value.IndexOf("}", tempIndex + 1); + if(tempIndex >= 0) closeCount++; + } while(tempIndex != -1 && tempIndex < value.Length - 1); + + // Already balanced + if(openCount == closeCount) return value; + + tempIndex = index + value.Length - 1; + + string bigString = sb.ToString(); + + do { + tempIndex = bigString.IndexOf("}", tempIndex + 1); + if(tempIndex != -1) { + closeCount++; + if(closeCount == openCount) { + // Balanced + return bigString.Substring(index, tempIndex - index + 1); + } + } + } while(tempIndex != -1 && tempIndex < bigString.Length - 1); + + return null; + } + + /// + /// Formats a snippet. + /// + /// The captured markup. + /// The TOC content (trick to allow {TOC} to be inserted in snippets). + /// The formatted result. + private static string FormatSnippet(string capturedMarkup, string cachedToc) { + // If the captured markup contains only 1 line, process using "classic" method, assuming there are only numbered parameters + if(capturedMarkup.IndexOf("\n") == -1) { + string tempRes = FormatClassicSnippet(capturedMarkup); + return ReplaceToc(tempRes, cachedToc); + } + + // The format is: + // {s|Name | param = value -- OR + // | param = value -- OR + // | param = value + // + // which continues on next line, preceded by a blank + // } + + // End bracket can be on a line on its own or on the last content line + + // 0. Find snippet object + string snippetName = capturedMarkup.Substring(3, capturedMarkup.IndexOf("\n") - 3).Trim(); + Snippet snippet = Snippets.Find(snippetName); + if(snippet == null) return @"FORMATTER ERROR (Snippet Not Found)"; + + // 1. Strip all useless data at the beginning and end of the text ({s| and }, plus all whitespaces) + + // Strip opening and closing tags + StringBuilder sb = new StringBuilder(capturedMarkup); + sb.Remove(0, 3); + sb.Remove(sb.Length - 1, 1); + + // Strip all whitespaces at the end + while(char.IsWhiteSpace(sb[sb.Length - 1])) sb.Remove(sb.Length - 1, 1); + + // 2. Split into lines, preserving empty lines + string[] lines = sb.ToString().Split('\n'); + + // 3. Find all lines starting with a pipe and containing an equal sign -> those are the ones that define params values + List parametersLines = new List(lines.Length); + for(int i = 0; i < lines.Length; i++) { + if(lines[i].Trim().StartsWith("|") && lines[i].Contains("=")) { + parametersLines.Add(i); + } + } + + // 4. For each parameter line, extract the parameter value, spanning through all subsequent non-parameter lines + // Build a name->value dictionary for parameters + Dictionary values = new Dictionary(parametersLines.Count); + for(int i = 0; i < parametersLines.Count; i++) { + // Extract parameter name + int equalSignIndex = lines[parametersLines[i]].IndexOf("="); + string parameterName = lines[parametersLines[i]].Substring(0, equalSignIndex); + parameterName = parameterName.Trim(' ', '|').ToLowerInvariant(); + + StringBuilder currentValue = new StringBuilder(100); + currentValue.Append(lines[parametersLines[i]].Substring(equalSignIndex + 1).TrimStart(' ')); + + // Span all subsequent lines + if(i < parametersLines.Count - 1) { + for(int span = parametersLines[i] + 1; span < parametersLines[i + 1]; span++) { + currentValue.Append("\n"); + currentValue.Append(lines[span]); + } + } + else if(parametersLines[i] < lines.Length - 1) { + // All remaining lines belong to the last parameter + for(int span = parametersLines[i] + 1; span < lines.Length; span++) { + currentValue.Append("\n"); + currentValue.Append(lines[span]); + } + } + + if(!values.ContainsKey(parameterName)) values.Add(parameterName, currentValue.ToString()); + else values[parameterName] = currentValue.ToString(); + } + + // 5. Prepare the snippet output, replacing parameters placeholders with their values + // Use a lowercase version of the snippet to identify parameters locations + + string lowercaseSnippetContent = snippet.Content.ToLowerInvariant(); + StringBuilder output = new StringBuilder(snippet.Content); + + foreach(KeyValuePair pair in values) { + int index = lowercaseSnippetContent.IndexOf("?" + pair.Key + "?"); + while(index >= 0) { + output.Remove(index, pair.Key.Length + 2); + output.Insert(index, pair.Value); + + // Need to update lowercase representation because the parameters values alter the length + lowercaseSnippetContent = output.ToString().ToLowerInvariant(); + index = lowercaseSnippetContent.IndexOf("?" + pair.Key + "?"); + } + } + + // Remove all remaining parameters and return + string tempResult = Snippets.ParametersRegex.Replace(output.ToString(), ""); + return ReplaceToc(tempResult, cachedToc); + } + + /// + /// Format classic number-parameterized snippets. + /// + /// The captured markup to process. + /// The formatted result. + private static string FormatClassicSnippet(string capturedMarkup) { + int secondPipe = capturedMarkup.Substring(3).IndexOf("|"); + string name = ""; + if(secondPipe == -1) name = capturedMarkup.Substring(3, capturedMarkup.Length - 4); // No parameters + else name = capturedMarkup.Substring(3, secondPipe); + Snippet snippet = Snippets.Find(name); + if(snippet != null) { + string[] parameters = CustomSplit(capturedMarkup.Substring(3 + secondPipe + 1, capturedMarkup.Length - secondPipe - 5)); + string fs = PrepareSnippet(parameters, snippet.Content); + return fs.Trim('\n'); + } + else { + return @"FORMATTER ERROR (Snippet Not Found)"; + } + } + + /// + /// Splits a string at pipe characters, taking into account square brackets for links and images. + /// + /// The input data. + /// The resuling splitted strings. + private static string[] CustomSplit(string data) { + // 1. Find all pipes that are not enclosed in square brackets + List indices = new List(10); + int index = 0; + int openBrackets = 0; // Account for links with two brackets, e.g. [[link]] + while(index < data.Length) { + if(data[index] == '|') { + if(openBrackets == 0) indices.Add(index); + } + else if(data[index] == '[') openBrackets++; + else if(data[index] == ']') openBrackets--; + if(openBrackets < 0) openBrackets = 0; + index++; + } + + // 2. Split string at reported indices + indices.Insert(0, -1); + indices.Add(data.Length); + + List result = new List(indices.Count); + for(int i = 0; i < indices.Count - 1; i++) { + result.Add(data.Substring(indices[i] + 1, indices[i + 1] - indices[i] - 1)); + } + + return result.ToArray(); + } + + /// + /// Prepares the content of a snippet, properly managing parameters. + /// + /// The snippet parameters. + /// The snippet original text. + /// The prepared snippet text. + private static string PrepareSnippet(string[] parameters, string snippet) { + StringBuilder sb = new StringBuilder(snippet); + + for(int i = 0; i < parameters.Length; i++) { + sb.Replace(string.Format("?{0}?", i + 1), parameters[i]); + } + + // Remove all remaining parameters that have no value + return Snippets.ParametersRegex.Replace(sb.ToString(), ""); + } + + /// + /// Escapes all the characters used by the WikiMarkup. + /// + /// The Content. + /// The escaped Content. + private static string EscapeWikiMarkup(string content) { + StringBuilder sb = new StringBuilder(content); + sb.Replace("&", "&"); // Before all other escapes! + sb.Replace("#", "#"); + sb.Replace("*", "*"); + sb.Replace("<", "<"); + sb.Replace(">", ">"); + sb.Replace("[", "["); + sb.Replace("]", "]"); + sb.Replace("{", "{"); + sb.Replace("}", "}"); + sb.Replace("'''", "'''"); + sb.Replace("''", "''"); + sb.Replace("=====", "====="); + sb.Replace("====", "===="); + sb.Replace("===", "==="); + sb.Replace("==", "=="); + sb.Replace("", "§§"); + sb.Replace("__", "__"); + sb.Replace("--", "--"); + sb.Replace("@@", "@@"); + return sb.ToString(); + } + + /// + /// Removes all the characters used by the WikiMarkup. + /// + /// The Content. + /// The stripped Content. + private static string StripWikiMarkup(string content) { + if(string.IsNullOrEmpty(content)) return ""; + + StringBuilder sb = new StringBuilder(content); + sb.Replace("*", ""); + sb.Replace("<", ""); + sb.Replace(">", ""); + sb.Replace("[", ""); + sb.Replace("]", ""); + sb.Replace("{", ""); + sb.Replace("}", ""); + sb.Replace("'''", ""); + sb.Replace("''", ""); + sb.Replace("=====", ""); + sb.Replace("====", ""); + sb.Replace("===", ""); + sb.Replace("==", ""); + sb.Replace("", ""); + sb.Replace("__", ""); + sb.Replace("--", ""); + sb.Replace("@@", ""); + return sb.ToString(); + } + + /// + /// Removes all HTML markup from a string. + /// + /// The string. + /// The result. + public static string StripHtml(string content) { + if(string.IsNullOrEmpty(content)) return ""; + + StringBuilder sb = new StringBuilder(Regex.Replace(content, "<[^>]*>", " ")); + sb.Replace(" ", ""); + sb.Replace(" ", " "); + return sb.ToString(); + } + + /// + /// Builds a Link. + /// + /// The (raw) HREF. + /// The name/title. + /// True if the link contains an Image as "visible content". + /// The title of the image. + /// A value indicating whether the formatting is being done for content indexing. + /// A value indicating whether the formatting is being done in bare-bones mode. + /// The formatting context. + /// The current page, or null. + /// The linked pages list (both existent and inexistent). + /// The formatted Link. + private static string BuildLink(string targetUrl, string title, bool isImage, string imageTitle, + bool forIndexing, bool bareBones, FormattingContext context, PageInfo currentPage, List linkedPages) { + + if(targetUrl == null) targetUrl = ""; + if(title == null) title = ""; + if(imageTitle == null) imageTitle = ""; + + bool blank = false; + if(targetUrl.StartsWith("^")) { + blank = true; + targetUrl = targetUrl.Substring(1); + } + targetUrl = EscapeUrl(targetUrl); + string nstripped = StripWikiMarkup(StripHtml(title)); + string imageTitleStripped = StripWikiMarkup(StripHtml(imageTitle)); + + StringBuilder sb = new StringBuilder(150); + + if(targetUrl.ToLowerInvariant().Equals("anchor") && title.StartsWith("#")) { + sb.Append(@" "); + } + else if(targetUrl.StartsWith("#")) { + sb.Append(@" 0) sb.Append(nstripped); + else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); + else sb.Append(targetUrl.Substring(1)); + sb.Append(@""">"); + if(title.Length > 0) sb.Append(title); + else sb.Append(targetUrl.Substring(1)); + sb.Append(""); + } + else if(targetUrl.StartsWith("http://") || targetUrl.StartsWith("https://") || targetUrl.StartsWith("ftp://") || targetUrl.StartsWith("file://")) { + // The link is complete + sb.Append(@" 0) sb.Append(nstripped); + else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); + else sb.Append(targetUrl); + sb.Append(@""" target=""_blank"">"); + if(title.Length > 0) sb.Append(title); + else sb.Append(targetUrl); + sb.Append(""); + } + else if(targetUrl.StartsWith(@"\\") || targetUrl.StartsWith("//")) { + // The link is a UNC path + sb.Append(@" 0) sb.Append(nstripped); + else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); + else sb.Append(targetUrl); + sb.Append(@""" target=""_blank"">"); + if(title.Length > 0) sb.Append(title); + else sb.Append(targetUrl); + sb.Append(""); + } + else if(targetUrl.IndexOf("@") != -1 && targetUrl.IndexOf(".") != -1) { + // Email + sb.Append(@" 0) sb.Append(nstripped); + else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); + else sb.Append(targetUrl); + sb.Append(@""">"); + if(title.Length > 0) sb.Append(title); + else sb.Append(targetUrl); + sb.Append(""); + } + else if(((targetUrl.IndexOf(".") != -1 && !targetUrl.ToLowerInvariant().EndsWith(".aspx")) || targetUrl.EndsWith("/")) && + !targetUrl.StartsWith("++") && Pages.FindPage(targetUrl) == null && + !targetUrl.StartsWith("c:") && !targetUrl.StartsWith("C:")) { + // Link to an internal file or subdirectory, or link to GetFile.aspx + sb.Append(@" 0) sb.Append(nstripped); + else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); + else sb.Append(targetUrl); + sb.Append(@""">"); + if(title.Length > 0) sb.Append(title); + else sb.Append(targetUrl); + sb.Append(""); + } + else { + if(targetUrl.IndexOf(".aspx") != -1) { + // The link points to a "system" page + sb.Append(@" 0) sb.Append(nstripped); + else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); + else sb.Append(targetUrl); + sb.Append(@""">"); + if(title.Length > 0) sb.Append(title); + else sb.Append(targetUrl); + sb.Append(""); + } + else { + if(targetUrl.StartsWith("c:") || targetUrl.StartsWith("C:")) { + // Category link + //sb.Append(@" 0) sb.Append(nstripped); + else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); + else sb.Append(targetUrl.Substring(2)); + sb.Append(@""">"); + if(title.Length > 0) sb.Append(title); + else sb.Append(targetUrl.Substring(2)); + sb.Append(""); + } + else if(targetUrl.Contains(":") || targetUrl.ToLowerInvariant().Contains("%3a") || targetUrl.Contains("&") || targetUrl.Contains("%26")) { + sb.Append(@"FORMATTER ERROR ("":"" and ""&"" not supported in Page Names)"); + } + else { + // The link points to a wiki page + bool explicitNamespace = false; + string tempLink = targetUrl; + if(tempLink.StartsWith("++")) { + tempLink = tempLink.Substring(2); + targetUrl = targetUrl.Substring(2); + explicitNamespace = true; + } + + if(targetUrl.IndexOf("#") != -1) { + tempLink = targetUrl.Substring(0, targetUrl.IndexOf("#")); + targetUrl = Tools.UrlEncode(targetUrl.Substring(0, targetUrl.IndexOf("#"))) + Settings.PageExtension + targetUrl.Substring(targetUrl.IndexOf("#")); + } + else { + targetUrl += Settings.PageExtension; + targetUrl = Tools.UrlEncode(targetUrl); + } + + string fullName = ""; + if(!explicitNamespace) { + fullName = currentPage != null ? + NameTools.GetFullName(NameTools.GetNamespace(currentPage.FullName), NameTools.GetLocalName(tempLink)) : + tempLink; + } + else fullName = tempLink; + + // Add linked page without repetitions + //linkedPages.Add(fullName); + PageInfo info = Pages.FindPage(fullName); + if(info != null) { + if(!linkedPages.Contains(info.FullName)) linkedPages.Add(info.FullName); + } + else { + string lowercaseFullName = fullName.ToLowerInvariant(); + if(linkedPages.Find(p => { return p.ToLowerInvariant() == lowercaseFullName; }) == null) linkedPages.Add(fullName); + } + + if(info == null) { + sb.Append(@" 0) sb.Append(nstripped); + else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); + else sb.Append(tempLink);*/ + sb.Append(fullName); + sb.Append(@""">"); + if(title.Length > 0) sb.Append(title); + else sb.Append(tempLink); + sb.Append(""); + } + else { + sb.Append(@" 0) sb.Append(nstripped); + else if(isImage && imageTitle.Length > 0) sb.Append(imageTitleStripped); + else sb.Append(FormattingPipeline.PrepareTitle(Content.GetPageContent(info, false).Title, context, currentPage));*/ + + if(forIndexing) { + // When saving a page, the SQL Server provider calls IHost.PrepareContentForIndexing + // If the content contains a reference to the page being saved, the formatter will call GetPageContent on SQL Server, + // resulting in a transaction deadlock (the save transaction waits for the read transaction, while the latter + // waits the the locks on the PageContent table being released) + // See also Content.GetPageContent + + if(currentPage != null && currentPage.FullName == info.FullName) { + // Do not format title + sb.Append(info.FullName); + } + else { + // Try to format title + PageContent retrievedContent = Content.GetPageContent(info, false); + if(retrievedContent != null && !retrievedContent.IsEmpty()) { + sb.Append(FormattingPipeline.PrepareTitle(retrievedContent.Title, forIndexing, context, currentPage)); + } + else sb.Append(info.FullName); + } + } + else sb.Append(FormattingPipeline.PrepareTitle(Content.GetPageContent(info, false).Title, forIndexing, context, currentPage)); + + sb.Append(@""">"); + if(title.Length > 0) sb.Append(title); + else sb.Append(tempLink); + sb.Append(""); + } + } + } + } + return sb.ToString(); + } + + /// + /// Detects all the Headers in a block of text (H1, H2, H3, H4). + /// + /// The text. + /// The List of Header objects, in the same order as they are in the text. + public static List DetectHeaders(string text) { + Match match; + string h; + int end = 0; + List noWikiBegin = new List(), noWikiEnd = new List(); + List hPos = new List(); + StringBuilder sb = new StringBuilder(text); + + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + + int count = 0; + + match = H4Regex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + h = match.Value.Substring(5, match.Value.Length - 10 - (match.Value.EndsWith("\n") ? 1 : 0)); + hPos.Add(new HPosition(match.Index, h, 4, count)); + end = match.Index + match.Length; + count++; + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = H4Regex.Match(sb.ToString(), end); + } + + match = H3Regex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + h = match.Value.Substring(4, match.Value.Length - 8 - (match.Value.EndsWith("\n") ? 1 : 0)); + bool found = false; + for(int i = 0; i < hPos.Count; i++) { + if(match.Index == hPos[i].Index) { + found = true; + break; + } + } + if(!found) { + hPos.Add(new HPosition(match.Index, h, 3, count)); + count++; + } + end = match.Index + match.Length; + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = H3Regex.Match(sb.ToString(), end); + } + + match = H2Regex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + h = match.Value.Substring(3, match.Value.Length - 6 - (match.Value.EndsWith("\n") ? 1 : 0)); + bool found = false; + for(int i = 0; i < hPos.Count; i++) { + if(match.Index == hPos[i].Index) { + found = true; + break; + } + } + if(!found) { + hPos.Add(new HPosition(match.Index, h, 2, count)); + count++; + } + end = match.Index + match.Length; + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = H2Regex.Match(sb.ToString(), end); + } + + match = H1Regex.Match(sb.ToString()); + while(match.Success) { + if(!IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + h = match.Value.Substring(2, match.Value.Length - 4 - (match.Value.EndsWith("\n") ? 1 : 0)); + bool found = false; + for(int i = 0; i < hPos.Count; i++) { + // A special treatment is needed in this case + // because =====xxx===== matches also 2 H1 headers (=='='==) + if(match.Index >= hPos[i].Index && match.Index <= hPos[i].Index + hPos[i].Text.Length + 5) { + found = true; + break; + } + } + if(!found) { + hPos.Add(new HPosition(match.Index, h, 1, count)); + count++; + } + end = match.Index + match.Length; + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = H1Regex.Match(sb.ToString(), end); + } + + return hPos; + } + + /// + /// Builds the "Edit" links for page sections. + /// + /// The section ID. + /// The page name. + /// The link. + private static string BuildEditSectionLink(int id, string page) { + if(!Settings.EnableSectionEditing) return ""; + + StringBuilder sb = new StringBuilder(100); + sb.Append(@""); + sb.Append(EditSectionPlaceHolder); + sb.Append(""); + return sb.ToString(); + } + + /// + /// Generates list HTML markup. + /// + /// The lines in the list WikiMarkup. + /// The current line. + /// The current level. + /// The current line. + /// The correct HTML markup. + private static string GenerateList(string[] lines, int line, int level, ref int currLine) { + StringBuilder sb = new StringBuilder(200); + if(lines[currLine][level] == '*') sb.Append("
    "); + else if(lines[currLine][level] == '#') sb.Append("
      "); + while(currLine <= lines.Length - 1 && CountBullets(lines[currLine]) >= level + 1) { + if(CountBullets(lines[currLine]) == level + 1) { + sb.Append("
    1. "); + sb.Append(lines[currLine].Substring(CountBullets(lines[currLine])).Trim()); + sb.Append("
    2. "); + currLine++; + } + else { + sb.Remove(sb.Length - 5, 5); + sb.Append(GenerateList(lines, currLine, level + 1, ref currLine)); + sb.Append(""); + } + } + if(lines[line][level] == '*') sb.Append("
"); + else if(lines[line][level] == '#') sb.Append(""); + return sb.ToString(); + } + + /// + /// Counts the bullets in a list line. + /// + /// The line. + /// The number of bullets. + private static int CountBullets(string line) { + int res = 0, count = 0; + while(line[count] == '*' || line[count] == '#') { + res++; + count++; + } + return res; + } + + /// + /// Extracts the bullets from a list line. + /// + /// The line. + /// The bullets. + private static string ExtractBullets(string value) { + string res = ""; + for(int i = 0; i < value.Length; i++) { + if(value[i] == '*' || value[i] == '#') res += value[i]; + else break; + } + return res; + } + + /// + /// Builds the TOC of a document. + /// + /// The positions of headers. + /// The TOC HTML markup. + private static string BuildToc(List hPos) { + StringBuilder sb = new StringBuilder(); + + hPos.Sort(new HPositionComparer()); + + // Table only used to workaround IE idiosyncrasies - use TocCointainer for styling + sb.Append(@"
"); + sb.Append(@"
"); + sb.Append(@"

"); + sb.Append(TocTitlePlaceHolder); + sb.Append("

"); + + sb.Append(@"
"); + sb.Append("


"); + for(int i = 0; i < hPos.Count; i++) { + //Debug.WriteLine(i.ToString() + " " + hPos[i].Index.ToString() + ": " + hPos[i].Level); + switch(hPos[i].Level) { + case 1: + break; + case 2: + sb.Append("   "); + break; + case 3: + sb.Append("      "); + break; + case 4: + sb.Append("         "); + break; + } + if(hPos[i].Level == 1) sb.Append(""); + if(hPos[i].Level == 4) sb.Append(""); + sb.Append(@""); + sb.Append(StripWikiMarkup(StripHtml(hPos[i].Text))); + sb.Append(""); + if(hPos[i].Level == 4) sb.Append(""); + if(hPos[i].Level == 1) sb.Append(""); + sb.Append("
"); + } + sb.Append("

"); + sb.Append("
"); + + sb.Append("
"); + sb.Append("
"); + + return sb.ToString(); + } + + /// + /// Builds a valid anchor name from a string. + /// + /// The string, usually a header (Hx). + /// The anchor ID. + public static string BuildHAnchor(string h) { + StringBuilder sb = new StringBuilder(StripWikiMarkup(h)); + sb.Replace(" ", "_"); + sb.Replace(".", ""); + sb.Replace(",", ""); + sb.Replace(";", ""); + sb.Replace("\"", ""); + sb.Replace("/", ""); + sb.Replace("\\", ""); + sb.Replace("'", ""); + sb.Replace("(", ""); + sb.Replace(")", ""); + sb.Replace("[", ""); + sb.Replace("]", ""); + sb.Replace("{", ""); + sb.Replace("}", ""); + sb.Replace("<", ""); + sb.Replace(">", ""); + sb.Replace("#", ""); + sb.Replace("\n", ""); + sb.Replace("?", ""); + sb.Replace("&", ""); + sb.Replace("0", "A"); + sb.Replace("1", "B"); + sb.Replace("2", "C"); + sb.Replace("3", "D"); + sb.Replace("4", "E"); + sb.Replace("5", "F"); + sb.Replace("6", "G"); + sb.Replace("7", "H"); + sb.Replace("8", "I"); + sb.Replace("9", "J"); + return sb.ToString(); + } + + /// + /// Builds a valid and unique anchor name from a string. + /// + /// The string, usually a header (Hx). + /// The unique ID. + /// The anchor ID. + public static string BuildHAnchor(string h, string uid) { + return BuildHAnchor(h) + "_" + uid; + } + + /// + /// Escapes ampersands in a URL. + /// + /// The URL. + /// The escaped URL. + private static string EscapeUrl(string url) { + return url.Replace("&", "&"); + } + + /// + /// Builds a HTML table from WikiMarkup. + /// + /// The WikiMarkup. + /// The HTML. + private static string BuildTable(string table) { + // Proceed line-by-line, ignoring the first and last one + string[] lines = table.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + if(lines.Length < 3) { + return "FORMATTER ERROR (Malformed Table)"; + } + StringBuilder sb = new StringBuilder(); + sb.Append(" 2) { + sb.Append(" "); + sb.Append(lines[0].Substring(3)); + } + sb.Append(">"); + int count = 1; + if(lines[1].Length >= 3 && lines[1].Trim().StartsWith("|+")) { + // Table caption + sb.Append(""); + sb.Append(lines[1].Substring(3)); + sb.Append(""); + count++; + } + + if(!lines[count].StartsWith("|-")) sb.Append(""); + + bool thAdded = false; + + string item; + for(int i = count; i < lines.Length - 1; i++) { + if(lines[i].Trim().StartsWith("|-")) { + // New line + if(i != count) sb.Append(""); + + sb.Append(" 2) { + string style = lines[i].Substring(3); + if(style.Length > 0) { + sb.Append(" "); + sb.Append(style); + } + } + sb.Append(">"); + } + else if(lines[i].Trim().StartsWith("|")) { + // Cell + if(lines[i].Length < 3) continue; + item = lines[i].Substring(2); + if(item.IndexOf(" || ") != -1) { + sb.Append(""); + sb.Append(item.Replace(" || ", "")); + sb.Append(""); + } + else if(item.IndexOf(" | ") != -1) { + sb.Append(""); + sb.Append(item.Substring(item.IndexOf(" | ") + 3)); + sb.Append(""); + } + else { + sb.Append(""); + sb.Append(item); + sb.Append(""); + } + } + else if(lines[i].Trim().StartsWith("!")) { + // Header + if(lines[i].Length < 3) continue; + + // only if ! is found in the first row of the table, it is an header + if(lines[i + 1] == "|-") thAdded = true; + + item = lines[i].Substring(2); + if(item.IndexOf(" !! ") != -1) { + sb.Append(""); + sb.Append(item.Replace(" !! ", "")); + sb.Append(""); + } + else if(item.IndexOf(" ! ") != -1) { + sb.Append(""); + sb.Append(item.Substring(item.IndexOf(" ! ") + 3)); + sb.Append(""); + } + else { + sb.Append(""); + sb.Append(item); + sb.Append(""); + } + } + } + if(sb.ToString().EndsWith("")) { + sb.Remove(sb.Length - 4 - 1, 4); + sb.Append(""); + } + else { + sb.Append(""); + } + sb.Replace("", ""); + + // Add , tags, if table contains header + if(thAdded) { + int thIndex = sb.ToString().IndexOf("= 4) sb.Insert(thIndex - 4, ""); + sb.Insert(thIndex - 4, ""); + + // search for the last tag in the first row of the table + int thCloseIndex = -1; + int thCloseIndex_temp = -1; + do { + thCloseIndex = thCloseIndex_temp; + thCloseIndex_temp = sb.ToString().IndexOf("", thCloseIndex + 1); + } + while (thCloseIndex_temp != -1 && thCloseIndex_temp < sb.ToString().IndexOf("")); + + sb.Insert(thCloseIndex + 10, ""); + sb.Insert(sb.Length - 8, ""); + } + + return sb.ToString(); + } + + /// + /// Builds an indented text block. + /// + /// The input text. + /// The result. + private static string BuildIndent(string indent) { + int colons = 0; + indent = indent.Trim(); + while(colons < indent.Length && indent[colons] == ':') colons++; + indent = indent.Substring(colons).Trim(); + return @"
" + indent + "
"; + } + + /// + /// Builds the tag cloud. + /// + /// The current namespace (null for the root). + /// The tag cloud. + private static string BuildCloud(NamespaceInfo currentNamespace) { + StringBuilder sb = new StringBuilder(); + // Total categorized Pages (uncategorized Pages don't count) + int tot = Pages.GetPages(currentNamespace).Count - Pages.GetUncategorizedPages(currentNamespace).Length; + List categories = Pages.GetCategories(currentNamespace); + for(int i = 0; i < categories.Count; i++) { + if(categories[i].Pages.Length > 0) { + //sb.Append(@""); + sb.Append(categories[i].FullName); + sb.Append(""); + } + if(i != categories.Count - 1) sb.Append(" "); + } + return sb.ToString(); + } + + /// + /// Computes the sixe of a category label in the category cloud. + /// + /// The occurrence percentage of the category. + /// The computes size. + private static int ComputeSize(float percentage) { + // Interpolates min and max size on a line, so that if: + // - percentage = 0 -> size = minSize + // - percentage = 100 -> size = maxSize + // - intermediate values are calculated + float minSize = 8, maxSize = 26; + //return (int)((maxSize - minSize) / 100F * (float)percentage + minSize); // Linear interpolation + return (int)(maxSize - (maxSize - minSize) * Math.Exp(-percentage / 25)); // Exponential interpolation + } + + /// + /// Computes the positions of all NOWIKI tags. + /// + /// The input text. + /// The output list of begin indexes of NOWIKI tags. + /// The output list of end indexes of NOWIKI tags. + private static void ComputeNoWiki(string text, ref List noWikiBegin, ref List noWikiEnd) { + Match match; + noWikiBegin.Clear(); + noWikiEnd.Clear(); + + match = NoWikiRegex.Match(text); + while(match.Success) { + noWikiBegin.Add(match.Index); + noWikiEnd.Add(match.Index + match.Length); + match = NoWikiRegex.Match(text, match.Index + match.Length); + } + } + + /// + /// Determines whether a character is enclosed in a NOWIKI tag. + /// + /// The index of the character. + /// The list of begin indexes of NOWIKI tags. + /// The list of end indexes of NOWIKI tags. + /// The end index of the NOWIKI tag that encloses the specified character, or zero. + /// true if the specified character is enclosed in a NOWIKI tag, false otherwise. + private static bool IsNoWikied(int index, List noWikiBegin, List noWikiEnd, out int end) { + for(int i = 0; i < noWikiBegin.Count; i++) { + if(index > noWikiBegin[i] && index < noWikiEnd[i]) { + end = noWikiEnd[i]; + return true; + } + } + end = 0; + return false; + } + + /// + /// Performs the internal Phase 3 of the Formatting pipeline. + /// + /// The raw data. + /// The formatting context. + /// The current PageInfo, if any. + /// The formatted content. + public static string FormatPhase3(string raw, FormattingContext context, PageInfo current) { + StringBuilder sb = new StringBuilder(raw.Length); + StringBuilder dummy; + sb.Append(raw); + + Match match; + + // Format {CLOUD} tag (it has to be in Phase3 because on page rebind the cache is not cleared) + // --> When rebidning a page, the cache is now cleared + /*match = CloudRegex.Match(sb.ToString()); + string cloudMarkup = BuildCloud(DetectNamespaceInfo(current)); + while(match.Success) { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, cloudMarkup); + match = CloudRegex.Match(sb.ToString(), match.Index + cloudMarkup.Length); + }*/ + + // Format {NAMESPACE} tag + // --> Now processed with {INCOMING/OUTGOING} + /*string ns = Tools.DetectCurrentNamespace(); + if(string.IsNullOrEmpty(ns)) ns = "<root>"; + match = NamespaceRegex.Match(sb.ToString()); + while(match.Success) { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, ns); + match = NamespaceRegex.Match(sb.ToString(), match.Index + 11); + }*/ + + // Format other Phase3 special tags + match = Phase3SpecialTagRegex.Match(sb.ToString()); + while(match.Success) { + sb.Remove(match.Index, match.Length); + switch(match.Value.Substring(1, match.Value.Length - 2).ToUpperInvariant()) { + case "NAMESPACE": + string ns = Tools.DetectCurrentNamespace(); + if(string.IsNullOrEmpty(ns)) ns = "<root>"; + sb.Insert(match.Index, ns); + break; + case "NAMESPACEDROPDOWN": + sb.Insert(match.Index, BuildCurrentNamespaceDropDown()); + break; + case "INCOMING": + sb.Insert(match.Index, BuildIncomingLinksList(current, context, current)); + break; + case "OUTGOING": + sb.Insert(match.Index, BuildOutgoingLinksList(current, context, current)); + break; + case "USERNAME": + if(SessionFacade.LoginKey != null) sb.Insert(match.Index, GetProfileLink(SessionFacade.CurrentUsername)); + else sb.Insert(match.Index, GetLanguageLink(Exchanger.ResourceExchanger.GetResource("Guest"))); + break; + case "PAGENAME": + if(current != null) sb.Insert(match.Index, current.FullName); + break; + case "LOGINLOGOUT": + if(SessionFacade.LoginKey != null) sb.Insert(match.Index, GetLogoutLink()); + else sb.Insert(match.Index, GetLoginLink()); + break; + } + match = Phase3SpecialTagRegex.Match(sb.ToString()); + } + + match = RecentChangesRegex.Match(sb.ToString()); + while(match.Success) { + sb.Remove(match.Index, match.Length); + string trimmedTag = match.Value.Trim('{', '}'); + // If current page is null, assume root namespace + NamespaceInfo currentNamespace = null; + if(current != null) currentNamespace = Pages.FindNamespace(NameTools.GetNamespace(current.FullName)); + sb.Insert(match.Index, BuildRecentChanges(currentNamespace, trimmedTag.EndsWith("(*)"), context, current)); + match = RecentChangesRegex.Match(sb.ToString()); + } + + match = null; + + dummy = new StringBuilder(""); + dummy.Append(Exchanger.ResourceExchanger.GetResource("TableOfContents")); + dummy.Append(@" ["); + dummy.Append(Exchanger.ResourceExchanger.GetResource("HideShow")); + dummy.Append("]"); + sb.Replace(TocTitlePlaceHolder, dummy.ToString()); + + // Display edit links only when formatting page content (and not transcluded page content) + if(current != null && context == FormattingContext.PageContent) { + bool canEdit = false; + bool canEditWithApproval = false; + + Pages.CanEditPage(current, SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames(), + out canEdit, out canEditWithApproval); + + if(canEdit || canEditWithApproval) { + sb.Replace(EditSectionPlaceHolder, Exchanger.ResourceExchanger.GetResource("Edit")); + } + else sb.Replace(EditSectionPlaceHolder, ""); + } + else sb.Replace(EditSectionPlaceHolder, ""); + + match = SignRegex.Match(sb.ToString()); + while(match.Success) { + sb.Remove(match.Index, match.Length); + string txt = match.Value.Substring(3, match.Length - 6); + int idx = txt.LastIndexOf(","); + string[] fields = new string[] { txt.Substring(0, idx), txt.Substring(idx + 1) }; + dummy = new StringBuilder(); + dummy.Append(@""); + dummy.Append(fields[0]); + dummy.Append(", "); + dummy.Append(Preferences.AlignWithTimezone(DateTime.Parse(fields[1])).ToString(Settings.DateTimeFormat)); + dummy.Append(""); + sb.Insert(match.Index, dummy.ToString()); + match = SignRegex.Match(sb.ToString()); + } + + // --> Now processed with other phase3 tags + /*match = UsernameRegex.Match(sb.ToString()); + while(match.Success) { + sb.Remove(match.Index, match.Length); + if(SessionFacade.LoginKey != null) sb.Insert(match.Index, GetProfileLink(SessionFacade.CurrentUser.Username)); + else sb.Insert(match.Index, Exchanger.ResourceExchanger.GetResource("Guest")); + match = UsernameRegex.Match(sb.ToString()); + }*/ + + return sb.ToString(); + } + + /// + /// Builds the current namespace drop-down list. + /// + /// The drop-down list HTML markup. + private static string BuildCurrentNamespaceDropDown() { + string ns = Tools.DetectCurrentNamespace(); + if(ns == null) ns = ""; + + string currentUser = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + + List allNamespaces = Pages.GetNamespaces(); + List allowedNamespaces = new List(allNamespaces.Count); + + if(AuthChecker.CheckActionForNamespace(null, Actions.ForNamespaces.ReadPages, currentUser, currentGroups)) { + allowedNamespaces.Add(""); + } + foreach(NamespaceInfo nspace in allNamespaces) { + if(AuthChecker.CheckActionForNamespace(nspace, Actions.ForNamespaces.ReadPages, currentUser, currentGroups)) { + allowedNamespaces.Add(nspace.Name); + } + } + + StringBuilder sb = new StringBuilder(500); + sb.Append(""); + + return sb.ToString(); + } + + /// + /// Gets the link to the profile page. + /// + /// The username. + /// The link. + private static string GetProfileLink(string username) { + UserInfo user = Users.FindUser(username); + + StringBuilder sb = new StringBuilder(200); + sb.Append(""); + sb.Append(user != null ? Users.GetDisplayName(user) : username); + sb.Append(""); + return sb.ToString(); + } + + /// + /// Gets the link to the language page. + /// + /// The username. + /// The link. + private static string GetLanguageLink(string username) { + UserInfo user = Users.FindUser(username); + + StringBuilder sb = new StringBuilder(200); + sb.Append(""); + sb.Append(user != null ? Users.GetDisplayName(user) : username); + sb.Append(""); + return sb.ToString(); + } + + /// + /// Gets the login link. + /// + /// The login link. + private static string GetLoginLink() { + string login = Exchanger.ResourceExchanger.GetResource("Login"); + StringBuilder sb = new StringBuilder(200); + sb.Append(""); + sb.Append(login); + sb.Append(""); + return sb.ToString(); + } + + /// + /// Gets the logout link. + /// + /// The logout link. + private static string GetLogoutLink() { + string login = Exchanger.ResourceExchanger.GetResource("Logout"); + StringBuilder sb = new StringBuilder(200); + sb.Append(""); + sb.Append(login); + sb.Append(""); + return sb.ToString(); + } + + } + + /// + /// Represents a Header. + /// + public class HPosition { + + private int index; + private string text; + private int level; + private int id; + + /// + /// Initializes a new instance of the HPosition class. + /// + /// The Index. + /// The Text. + /// The Header level. + /// The Unique ID of the Header (0-based counter). + public HPosition(int index, string text, int level, int id) { + this.index = index; + this.text = text; + this.level = level; + this.id = id; + } + + /// + /// Gets or sets the Index. + /// + public int Index { + get { return index; } + set { index = value; } + } + + /// + /// Gets or sets the Text. + /// + public string Text { + get { return text; } + set { text = value; } + } + + /// + /// Gets or sets the Level. + /// + public int Level { + get { return level; } + set { level = value; } + } + + /// + /// Gets or sets the ID (0-based counter). + /// + public int ID { + get { return id; } + set { id = value; } + } + + } + + /// + /// Compares HPosition objects. + /// + public class HPositionComparer : IComparer { + + /// + /// Performs the comparison. + /// + /// The first object. + /// The second object. + /// The comparison result. + public int Compare(HPosition x, HPosition y) { + return x.Index.CompareTo(y.Index); + } + + } + +} diff --git a/Core/FormattingPipeline.cs b/Core/FormattingPipeline.cs new file mode 100644 index 0000000..dd3994d --- /dev/null +++ b/Core/FormattingPipeline.cs @@ -0,0 +1,155 @@ + +using System; +using System.Collections.Generic; +using ScrewTurn.Wiki.PluginFramework; +using System.Web; + +namespace ScrewTurn.Wiki { + + /// + /// Contains methods for formatting content using a pipeline paradigm. + /// + public static class FormattingPipeline { + + /// + /// Gets the formatter providers list sorted by priority. + /// + /// The list. + private static IList GetSortedFormatters() { + List providers = new List(Collectors.FormatterProviderCollector.AllProviders); + + // Sort by priority, then by name + providers.Sort((x, y) => { + int preliminaryResult = x.ExecutionPriority.CompareTo(y.ExecutionPriority); + if(preliminaryResult != 0) return preliminaryResult; + else return x.Information.Name.CompareTo(y.Information.Name); + }); + + return providers; + } + + /// + /// Performs the Phases 1 and 2 of the formatting process. + /// + /// The raw WikiMarkup to format. + /// A value indicating whether the formatting is being done for content indexing. + /// The formatting context. + /// The current Page, if any. + /// The formatted content. + public static string FormatWithPhase1And2(string raw, bool forIndexing, FormattingContext context, PageInfo current) { + string[] tempLinks; + return FormatWithPhase1And2(raw, forIndexing, context, current, out tempLinks); + } + + /// + /// Performs the Phases 1 and 2 of the formatting process. + /// + /// The raw WikiMarkup to format. + /// A value indicating whether the formatting is being done for content indexing. + /// The formatting context. + /// The current Page, if any. + /// The Pages linked by the current Page. + /// The formatted content. + public static string FormatWithPhase1And2(string raw, bool forIndexing, FormattingContext context, PageInfo current, out string[] linkedPages) { + ContextInformation info = null; + string username = SessionFacade.CurrentUsername; + info = new ContextInformation(forIndexing, false, context, current, System.Threading.Thread.CurrentThread.CurrentCulture.Name, HttpContext.Current, + username, SessionFacade.GetCurrentGroupNames()); + + IList providers = GetSortedFormatters(); + + // Phase 1 + foreach(IFormatterProviderV30 provider in providers) { + if(provider.PerformPhase1) { + try { + raw = provider.Format(raw, info, FormattingPhase.Phase1); + } + catch(Exception ex) { + Log.LogEntry("Provider " + provider.Information.Name + " failed to perform Phase1 (silently resuming from next provider): " + ex.ToString(), EntryType.Error, Log.SystemUsername); + } + } + } + + raw = Formatter.Format(raw, forIndexing, context, current, out linkedPages); + + // Phase 2 + foreach(IFormatterProviderV30 provider in providers) { + if(provider.PerformPhase2) { + try { + raw = provider.Format(raw, info, FormattingPhase.Phase2); + } + catch(Exception ex) { + Log.LogEntry("Provider " + provider.Information.Name + " failed to perform Phase2 (silently resuming from next provider): " + ex.ToString(), EntryType.Error, Log.SystemUsername); + } + } + } + + return raw; + } + + /// + /// Performs the Phase 3 of the formatting process. + /// + /// The raw WikiMarkup to format. + /// The formatting context. + /// The current Page, if any. + /// The formatted content. + public static string FormatWithPhase3(string raw, FormattingContext context, PageInfo current) { + raw = Formatter.FormatPhase3(raw, context, current); + + ContextInformation info = null; + string username = SessionFacade.CurrentUsername; + info = new ContextInformation(false, false, context, current, System.Threading.Thread.CurrentThread.CurrentCulture.Name, HttpContext.Current, + username, SessionFacade.GetCurrentGroupNames()); + + // Phase 3 + foreach(IFormatterProviderV30 provider in GetSortedFormatters()) { + if(provider.PerformPhase3) { + try { + raw = provider.Format(raw, info, FormattingPhase.Phase3); + } + catch(Exception ex) { + Log.LogEntry("Provider " + provider.Information.Name + " failed to perform Phase3 (silently resuming from next provider): " + ex.ToString(), EntryType.Error, Log.SystemUsername); + } + } + } + + return raw; + } + + /// + /// Prepares the title of an item for display. + /// + /// The input title. + /// A value indicating whether the formatting is being done for content indexing. + /// The context information. + /// The current page, if any. + /// The prepared title, properly sanitized. + public static string PrepareTitle(string title, bool forIndexing, FormattingContext context, PageInfo current) { + string temp = title; + ContextInformation info = new ContextInformation(forIndexing, false, context, current, System.Threading.Thread.CurrentThread.CurrentCulture.Name, + HttpContext.Current, SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames()); + + foreach(IFormatterProviderV30 prov in GetSortedFormatters()) { + temp = prov.PrepareTitle(temp, info); + } + + return PrepareItemTitle(temp); + } + + /// + /// Prepares the title of an item for safe display. + /// + /// The title. + /// The sanitized title. + private static string PrepareItemTitle(string title) { + return Formatter.StripHtml(title) + .Replace("\"", """) + .Replace("'", "'") + .Replace("<", "<").Replace(">", ">") + .Replace("[", "[").Replace("]", "]"); // This avoid endless loops in Formatter + } + + } + +} diff --git a/Core/Hash.cs b/Core/Hash.cs new file mode 100644 index 0000000..1ed03ee --- /dev/null +++ b/Core/Hash.cs @@ -0,0 +1,55 @@ + +using System; +using System.Configuration; +using System.Security.Cryptography; +using System.Text; +using System.Web; +using System.Web.Security; + +namespace ScrewTurn.Wiki { + + /// + /// Helps computing Hash codes. + /// + public static class Hash { + + /// + /// Computes the Hash code of a string. + /// + /// The string. + /// The Hash code. + public static byte[] ComputeBytes(string input) { + MD5 md5 = MD5CryptoServiceProvider.Create(); + return md5.ComputeHash(Encoding.ASCII.GetBytes(input)); + } + + /// + /// Computes the Hash code of a string and converts it into a Hex string. + /// + /// The string. + /// The Hash code, converted into a Hex string. + public static string Compute(string input) { + byte[] bytes = ComputeBytes(input); + string result = ""; + for(int i = 0; i < bytes.Length; i++) { + result += string.Format("{0:X2}", bytes[i]); + } + return result; + } + + /// + /// Computes the Hash of a Username, mixing it with other data, in order to avoid illegal Account activations. + /// + /// The Username. + /// The email. + /// The date/time. + /// The other data to mix into the input string. + /// The secured Hash of the Username. + public static string ComputeSecurityHash(string username, string email, DateTime dateTime, string otherData) { + // Use a salt (is this actually useful given that STW is opensource and everyone can see the salt?) + return Compute(otherData + username + email + dateTime.ToString("yyyyMMddHHmmss") +"$kbfl?nfc4"); + } + + } + +} diff --git a/Core/Host.cs b/Core/Host.cs new file mode 100644 index 0000000..ed46439 --- /dev/null +++ b/Core/Host.cs @@ -0,0 +1,1005 @@ + +using System; +using System.Collections.Generic; +using ScrewTurn.Wiki.PluginFramework; +using System.Text; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki { + + /// + /// Implements the IHost interface. + /// + public class Host : IHostV30 { + + private static Host instance; + + /// + /// Gets or sets the singleton instance of the Host object. + /// + public static Host Instance { + get { + if(instance == null) throw new InvalidOperationException("Host.Instance is null"); + return instance; + } + set { instance = value; } + } + + private Dictionary customSpecialTags; + + /// + /// Initializes a new instance of the PluginHost class. + /// + public Host() { + customSpecialTags = new Dictionary(5); + } + + /// + /// Gets the Special Tags added by providers. + /// + public Dictionary CustomSpecialTags { + get { + lock(customSpecialTags) { + return customSpecialTags; + } + } + } + + /// + /// Gets the values of the Wiki Settings. + /// + /// The Setting's Name. + /// The Setting's value. + public string GetSettingValue(SettingName name) { + switch(name) { + case SettingName.ContactEmail: + return Settings.ContactEmail; + case SettingName.DateTimeFormat: + return Settings.DateTimeFormat; + case SettingName.DefaultLanguage: + return Settings.DefaultLanguage; + case SettingName.RootNamespaceDefaultPage: + return Settings.DefaultPage; + case SettingName.DefaultTimeZone: + return Settings.DefaultTimezone.ToString(); + case SettingName.MainUrl: + return Settings.MainUrl; + case SettingName.SenderEmail: + return Settings.SenderEmail; + case SettingName.WikiTitle: + return Settings.WikiTitle; + case SettingName.ThemesDirectory: + return Settings.ThemesDirectory; + case SettingName.PublicDirectory: + return Settings.PublicDirectory; + case SettingName.UsersCanRegister: + return Settings.UsersCanRegister.ToString(); + case SettingName.UsernameRegex: + return Settings.UsernameRegex; + case SettingName.PasswordRegex: + return Settings.PasswordRegex; + case SettingName.EmailRegex: + return Settings.EmailRegex; + case SettingName.MainUrlRegex: + return Settings.MainUrlRegex; + case SettingName.EnableDoubleClickEditing: + return Settings.EnableDoubleClickEditing.ToString(); + case SettingName.ProcessSingleLineBreaks: + return Settings.ProcessSingleLineBreaks.ToString(); + case SettingName.AccountActivationMode: + return Settings.AccountActivationMode.ToString(); + case SettingName.AllowedFileTypes: + StringBuilder sb = new StringBuilder(50); + foreach(string s in Settings.AllowedFileTypes) sb.Append(s + ","); + return sb.ToString().TrimEnd(','); + case SettingName.DisableAutomaticVersionCheck: + return Settings.DisableAutomaticVersionCheck.ToString(); + case SettingName.DisableBreadcrumbsTrail: + return Settings.DisableBreadcrumbsTrail.ToString(); + case SettingName.DisableCache: + return Settings.DisableCache.ToString(); + case SettingName.DisableCaptchaControl: + return Settings.DisableCaptchaControl.ToString(); + case SettingName.DisableConcurrentEditing: + return Settings.DisableConcurrentEditing.ToString(); + case SettingName.EnableHttpCompression: + return Settings.EnableHttpCompression.ToString(); + case SettingName.EnableViewStateCompression: + return Settings.EnableViewStateCompression.ToString(); + case SettingName.LoggingLevel: + return Settings.LoggingLevel.ToString(); + case SettingName.MaxFileSize: + return Settings.MaxFileSize.ToString(); + case SettingName.PageExtension: + return Settings.PageExtension; + case SettingName.ScriptTagsAllowed: + return Settings.ScriptTagsAllowed.ToString(); + case SettingName.WikiVersion: + return Settings.WikiVersion; + case SettingName.MaxLogSize: + return Settings.MaxLogSize.ToString(); + case SettingName.MaxRecentChanges: + return Settings.MaxRecentChanges.ToString(); + case SettingName.CacheSize: + return Settings.CacheSize.ToString(); + case SettingName.CacheCutSize: + return Settings.CacheCutSize.ToString(); + case SettingName.EditingSessionTimeout: + return Collisions.EditingSessionTimeout.ToString(); + case SettingName.AdministratorsGroup: + return Settings.AdministratorsGroup; + case SettingName.UsersGroup: + return Settings.UsersGroup; + case SettingName.AnonymousGroup: + return Settings.AnonymousGroup; + case SettingName.ChangeModerationMode: + return Settings.ChangeModerationMode.ToString(); + case SettingName.DefaultPagesStorageProvider: + return Settings.DefaultPagesProvider; + case SettingName.DefaultUsersStorageProvider: + return Settings.DefaultUsersProvider; + case SettingName.DefaultFilesStorageProvider: + return Settings.DefaultFilesProvider; + case SettingName.DefaultCacheProvider: + return Settings.DefaultCacheProvider; + } + return ""; + } + + /// + /// Gets the list of the Users. + /// + /// The users. + public UserInfo[] GetUsers() { + return Users.GetUsers().ToArray(); + } + + /// + /// Finds a user by username, properly handling Users Storage Providers. + /// + /// The username. + /// The , or null if no users are found. + /// If username is null. + /// If username is empty. + public UserInfo FindUser(string username) { + if(username == null) throw new ArgumentNullException("username"); + if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username"); + + return Users.FindUser(username); + } + + /// + /// Gets the authenticated user in the current session, if any. + /// + /// The authenticated user, or null if no user is authenticated. + /// If the built-it admin user is authenticated, the returned user + /// has admin as Username. + public UserInfo GetCurrentUser() { + return SessionFacade.GetCurrentUser(); + } + + /// + /// Gets the list of the user groups. + /// + /// The groups. + public UserGroup[] GetUserGroups() { + return Users.GetUserGroups().ToArray(); + } + + /// + /// Finds a user group by name. + /// + /// The name. + /// The , or null if no groups are found. + /// If name is null. + /// If name is empty. + public UserGroup FindUserGroup(string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + return Users.FindUserGroup(name); + } + + /// + /// Checks whether an action is allowed for a global resource. + /// + /// The action (see class) + /// The user. + /// true if the action is allowed, false otherwise. + /// If action or user are null. + /// If action is empty. + public bool CheckActionForGlobals(string action, UserInfo user) { + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(user == null) throw new ArgumentNullException("user"); + + return AuthChecker.CheckActionForGlobals(action, user.Username, user.Groups); + } + + /// + /// Checks whether an action is allowed for a namespace. + /// + /// The namespace (null for the root). + /// The action (see class) + /// The user. + /// true if the action is allowed, false otherwise. + /// If action or user are null. + /// If action is empty. + public bool CheckActionForNamespace(NamespaceInfo nspace, string action, UserInfo user) { + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(user == null) throw new ArgumentNullException("user"); + + return AuthChecker.CheckActionForNamespace(nspace, action, user.Username, user.Groups); + } + + /// + /// Checks whether an action is allowed for a page. + /// + /// The page. + /// The action (see class) + /// The user. + /// true if the action is allowed, false otherwise. + /// If page, action or user are null. + /// If action is empty. + public bool CheckActionForPage(PageInfo page, string action, UserInfo user) { + if(page == null) throw new ArgumentNullException("page"); + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(user == null) throw new ArgumentNullException("user"); + + return AuthChecker.CheckActionForPage(page, action, user.Username, user.Groups); + } + + /// + /// Checks whether an action is allowed for a directory. + /// + /// The directory. + /// The action (see ). + /// The user. + /// true if the action is allowed, false otherwise. + /// If directory, action or user are null. + /// If action is empty. + public bool CheckActionForDirectory(StDirectoryInfo directory, string action, UserInfo user) { + if(directory == null) throw new ArgumentNullException("directory"); + if(action == null) throw new ArgumentNullException("action"); + if(action.Length == 0) throw new ArgumentException("Action cannot be empty", "action"); + if(user == null) throw new ArgumentNullException("user"); + + return AuthChecker.CheckActionForDirectory(directory.Provider, directory.FullPath, action, + user.Username, user.Groups); + } + + /// + /// Gets the theme in use for a namespace. + /// + /// The namespace (null for the root). + /// The theme. + public string GetTheme(NamespaceInfo nspace) { + return Settings.GetTheme(nspace != null ? nspace.Name : null); + } + + /// + /// Gets the list of the namespaces. + /// + /// The namespaces. + public NamespaceInfo[] GetNamespaces() { + return Pages.GetNamespaces().ToArray(); + } + + /// + /// Finds a namespace by name. + /// + /// The name. + /// The , or null if no namespaces are found. + public NamespaceInfo FindNamespace(string name) { + return Pages.FindNamespace(name); + } + + /// + /// Gets the list of the Wiki Pages in a namespace. + /// + /// The namespace (null for the root). + /// The pages. + public PageInfo[] GetPages(NamespaceInfo nspace) { + return Pages.GetPages(nspace).ToArray(); + } + + /// + /// Gets the List of Categories in a namespace. + /// + /// The namespace (null for the root). + /// The categories. + public CategoryInfo[] GetCategories(NamespaceInfo nspace) { + return Pages.GetCategories(nspace).ToArray(); + } + + /// + /// Gets the list of Snippets. + /// + /// The snippets. + public Snippet[] GetSnippets() { + return Snippets.GetSnippets().ToArray(); + } + + /// + /// Gets the list of Navigation Paths in a namespace. + /// + /// The namespace (null for the root). + /// The navigation paths. + public NavigationPath[] GetNavigationPaths(NamespaceInfo nspace) { + return NavigationPaths.GetNavigationPaths(nspace).ToArray(); + } + + /// + /// Gets the Categories of a Page. + /// + /// The Page. + /// The Categories. + /// If page is null. + public CategoryInfo[] GetCategoriesPerPage(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + return Pages.GetCategoriesForPage(page); + } + + /// + /// Gets a Wiki Page. + /// + /// The full Name of the Page. + /// The Wiki Page or null. + /// If fullName is null. + /// If fullName is empty. + public PageInfo FindPage(string fullName) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty"); + + return Pages.FindPage(fullName); + } + + /// + /// Gets the Content of a Page. + /// + /// The Page. + /// The Page Content. + /// If page is null. + public PageContent GetPageContent(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + return Content.GetPageContent(page, true); + } + + /// + /// Gets the Backup/Revision numbers of a Page. + /// + /// The Page. + /// The Backup/Revision numbers. + /// If page is null. + public int[] GetBackups(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + return Pages.GetBackups(page).ToArray(); + } + + /// + /// Gets the Content of a Page Backup. + /// + /// The Page. + /// The revision. + /// The Backup Content. + /// If page is null. + /// If revision is less than zero. + public PageContent GetBackupContent(PageInfo page, int revision) { + if(page == null) throw new ArgumentNullException("page"); + if(revision < 0) throw new ArgumentOutOfRangeException("revision", "Revision must be greater than or equal to zero"); + + return Pages.GetBackupContent(page, revision); + } + + /// + /// Gets the formatted content of a Wiki Page. + /// + /// The Page. + /// The formatted content. + /// If page is null. + public string GetFormattedContent(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + PageInfo pageInfo = Pages.FindPage(page.FullName); + if(pageInfo == null) return null; + PageContent content = Content.GetPageContent(pageInfo, true); + return Formatter.Format(content.Content, false, FormattingContext.PageContent, page); + } + + /// + /// Formats a block of WikiMarkup, using the built-in formatter only. + /// + /// The block of WikiMarkup. + /// The formatted content. + /// If raw is null. + public string Format(string raw) { + if(raw == null) throw new ArgumentNullException("raw"); + + return Formatter.Format(raw, false, FormattingContext.Unknown, null); + } + + /// + /// Prepares content for indexing in the search engine, performing bare-bones formatting and removing all WikiMarkup and XML-like characters. + /// + /// The page being indexed, if any, null otherwise. + /// The string to prepare. + /// The sanitized string. + /// If content is null. + public string PrepareContentForIndexing(PageInfo page, string content) { + if(content == null) throw new ArgumentNullException("content"); + + // TODO: Improve this method - HTML formatting should not be needed + return Tools.RemoveHtmlMarkup(FormattingPipeline.FormatWithPhase1And2(content, true, + page != null ? FormattingContext.PageContent : FormattingContext.Unknown, page)); + } + + /// + /// Prepares a title for indexing in the search engine, removing all WikiMarkup and XML-like characters. + /// + /// The page being indexed, if any, null otherwise. + /// The title to prepare. + /// The sanitized string. + /// If title is null. + public string PrepareTitleForIndexing(PageInfo page, string title) { + if(title == null) throw new ArgumentNullException("title"); + + return FormattingPipeline.PrepareTitle(title, true, + page != null ? FormattingContext.PageContent : FormattingContext.Unknown, page); + } + + /// + /// Performs a search. + /// + /// The search query. + /// A value indicating whether to perform a full-text search. + /// A value indicating whether to search the names of files and attachments. + /// The search options. + /// The search results. + /// If query is null. + /// If query is empty. + public SearchResultCollection PerformSearch(string query, bool fullText, bool filesAndAttachments, SearchOptions options) { + if(query == null) throw new ArgumentNullException("query"); + if(query.Length == 0) throw new ArgumentException("Query cannot be empty", "query"); + + return SearchTools.Search(query, fullText, filesAndAttachments, options); + } + + /// + /// Lists directories in a directory. + /// + /// The directory (null for the root, first invocation). + /// The directories. + public StDirectoryInfo[] ListDirectories(StDirectoryInfo directory) { + List result = new List(20); + + if(directory == null) { + foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) { + string[] dirs = prov.ListDirectories(null); + + foreach(string dir in dirs) { + result.Add(new StDirectoryInfo(dir, prov)); + } + } + } + else { + string[] dirs = directory.Provider.ListDirectories(null); + + foreach(string dir in dirs) { + result.Add(new StDirectoryInfo(dir, directory.Provider)); + } + } + + return result.ToArray(); + } + + /// + /// Lists files in a directory. + /// + /// The directory (null for the root, first invocation). + /// The files. + public StFileInfo[] ListFiles(StDirectoryInfo directory) { + List result = new List(20); + + if(directory == null) { + foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) { + string[] files = prov.ListFiles(null); + + foreach(string file in files) { + FileDetails details = prov.GetFileDetails(file); + result.Add(new StFileInfo(details.Size, details.LastModified, details.RetrievalCount, file, prov)); + } + } + } + else { + string[] files = directory.Provider.ListFiles(directory.FullPath); + + foreach(string file in files) { + FileDetails details = directory.Provider.GetFileDetails(file); + result.Add(new StFileInfo(details.Size, details.LastModified, details.RetrievalCount, file, directory.Provider)); + } + } + + return result.ToArray(); + } + + /// + /// Lists page attachments. + /// + /// The page. + /// The attachments. + /// If page is null. + public StFileInfo[] ListPageAttachments(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + List result = new List(10); + foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) { + string[] attachments = prov.ListPageAttachments(page); + + foreach(string attn in attachments) { + FileDetails details = prov.GetPageAttachmentDetails(page, attn); + result.Add(new StFileInfo(details.Size, details.LastModified, details.RetrievalCount, attn, prov)); + } + } + + return result.ToArray(); + } + + /// + /// Sends an Email. + /// + /// The Recipient Email address. + /// The Sender's Email address. + /// The Subject. + /// The Body. + /// True if the message is HTML. + /// True if the message has been sent successfully. + /// If recipient, sender, subject or body are null. + /// If recipient, sender, subject or body are empty. + public bool SendEmail(string recipient, string sender, string subject, string body, bool html) { + if(recipient == null) throw new ArgumentNullException("recipient"); + if(recipient.Length == 0) throw new ArgumentException("Recipient cannot be empty"); + if(sender == null) throw new ArgumentNullException("sender"); + if(sender.Length == 0) throw new ArgumentException("Sender cannot be empty"); + if(subject == null) throw new ArgumentNullException("subject"); + if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject"); + if(body == null) throw new ArgumentNullException("body"); + if(body.Length == 0) throw new ArgumentException("Body cannot be empty", "body"); + + try { + EmailTools.AsyncSendEmail(recipient, sender, subject, body, html); + return true; + } + catch { + return false; + } + } + + /// + /// Logs a new message. + /// + /// The Message. + /// The Entry Type. + /// The user, or null. If null, the system will log "PluginName+System". + /// The Component that calls the method. The caller cannot be null. + /// If message or caller are null. + /// If message is empty. + public void LogEntry(string message, LogEntryType entryType, string user, object caller) { + if(message == null) throw new ArgumentNullException("message"); + if(message.Length == 0) throw new ArgumentException("Message cannot be empty"); + if(caller == null) throw new ArgumentNullException("caller"); + + EntryType t = EntryType.General; + switch(entryType) { + case LogEntryType.General: + t = EntryType.General; + break; + case LogEntryType.Warning: + t = EntryType.Warning; + break; + case LogEntryType.Error: + t = EntryType.Error; + break; + } + string name = "?"; + if(user == null) { + if(caller is IUsersStorageProviderV30) name = ((IUsersStorageProviderV30)caller).Information.Name; + else if(caller is IPagesStorageProviderV30) name = ((IPagesStorageProviderV30)caller).Information.Name; + else if(caller is IFormatterProviderV30) name = ((IFormatterProviderV30)caller).Information.Name; + else if(caller is ISettingsStorageProviderV30) name = ((ISettingsStorageProviderV30)caller).Information.Name; + else if(caller is IFilesStorageProviderV30) name = ((IFilesStorageProviderV30)caller).Information.Name; + else if(caller is ICacheProviderV30) name = ((ICacheProviderV30)caller).Information.Name; + name += "+" + Log.SystemUsername; + } + else name = user; + Log.LogEntry(message, t, name); + } + + /// + /// Aligns a Date and Time object to the User's Time Zone preferences. + /// + /// The Date/Time to align. + /// The aligned Date/Time. + /// The method takes care of daylight saving settings. + public DateTime AlignDateTimeWithPreferences(DateTime dt) { + return Preferences.AlignWithTimezone(dt); + } + + /// + /// Clears the cache. + /// + /// The part of the cache to clear. + public void ClearCache(CacheData data) { + switch(data) { + case CacheData.Pages: + Content.InvalidateAllPages(); + break; + case CacheData.MetaFiles: + Content.ClearPseudoCache(); + break; + default: + throw new ArgumentException("Invalid CacheData"); + } + } + + /// + /// Adds an item in the Editing Toolbar. + /// + /// The item to add. + /// The text of the item. + /// The value of the item. + /// If text or value are null. + /// If text or value are empty, or if they contain single or double quotes, + /// or if value does not contain a pipe when item is SpecialTagWrap. + public void AddToolbarItem(ToolbarItem item, string text, string value) { + if(text == null) throw new ArgumentNullException("text"); + if(text.Length == 0) throw new ArgumentException("Text cannot be empty", "text"); + if(text.Contains("\"") || text.Contains("'")) throw new ArgumentException("Text cannot contain single or double quotes", "text"); + if(value == null) throw new ArgumentNullException("value"); + if(value.Length == 0) throw new ArgumentException("Value cannot be empty", "value"); + if(value.Contains("\"") || value.Contains("'")) throw new ArgumentException("Value cannot contain single or double quotes", "value"); + + if(item == ToolbarItem.SpecialTagWrap && !value.Contains("|")) throw new ArgumentException("Invalid value for a SpecialTagWrap (pipe not found)", "value"); + + lock(customSpecialTags) { + if(customSpecialTags.ContainsKey(text)) { + customSpecialTags[text].Value = value; + customSpecialTags[text].Item = item; + } + else customSpecialTags.Add(text, new CustomToolbarItem(item, text, value)); + } + } + + /// + /// Gets the default provider of the specified type. + /// + /// The type of the provider ( + /// , + /// , + /// , + /// ). + /// The Full type name of the default provider of the specified type or null. + public string GetDefaultProvider(Type providerType) { + switch(providerType.FullName) { + case ProviderLoader.UsersProviderInterfaceName: + return Settings.DefaultUsersProvider; + case ProviderLoader.PagesProviderInterfaceName: + return Settings.DefaultPagesProvider; + case ProviderLoader.FilesProviderInterfaceName: + return Settings.DefaultFilesProvider; + case ProviderLoader.CacheProviderInterfaceName: + return Settings.DefaultCacheProvider; + default: + return null; + } + } + + /// + /// Gets the pages storage providers, either enabled or disabled. + /// + /// true to get enabled providers, false to get disabled providers. + /// The providers. + public IPagesStorageProviderV30[] GetPagesStorageProviders(bool enabled) { + if(enabled) return Collectors.PagesProviderCollector.AllProviders; + else return Collectors.DisabledPagesProviderCollector.AllProviders; + } + + /// + /// Gets the users storage providers, either enabled or disabled. + /// + /// true to get enabled providers, false to get disabled providers. + /// The providers. + public IUsersStorageProviderV30[] GetUsersStorageProviders(bool enabled) { + if(enabled) return Collectors.UsersProviderCollector.AllProviders; + else return Collectors.DisabledUsersProviderCollector.AllProviders; + } + + /// + /// Gets the files storage providers, either enabled or disabled. + /// + /// true to get enabled providers, false to get disabled providers. + /// The providers. + public IFilesStorageProviderV30[] GetFilesStorageProviders(bool enabled) { + if(enabled) return Collectors.FilesProviderCollector.AllProviders; + else return Collectors.DisabledFilesProviderCollector.AllProviders; + } + + /// + /// Gets the cache providers, either enabled or disabled. + /// + /// true to get enabled providers, false to get disabled providers. + /// The providers. + public ICacheProviderV30[] GetCacheProviders(bool enabled) { + if(enabled) return Collectors.CacheProviderCollector.AllProviders; + else return Collectors.DisabledCacheProviderCollector.AllProviders; + } + + /// + /// Gets the formatter providers, either enabled or disabled. + /// + /// true to get enabled providers, false to get disabled providers. + /// The providers. + public IFormatterProviderV30[] GetFormatterProviders(bool enabled) { + if(enabled) return Collectors.FormatterProviderCollector.AllProviders; + else return Collectors.DisabledFormatterProviderCollector.AllProviders; + } + + /// + /// Gets the current settings storage provider. + /// + /// The settings storage provider. + public ISettingsStorageProviderV30 GetSettingsStorageProvider() { + return Settings.Provider; + } + + /// + /// Gets the configuration of a provider. + /// + /// The type name of the provider, such as 'Vendor.Namespace.Provider'. + /// The configuration (can be empty or null). + /// If providerTypeName is null. + /// If providerTypeName is empty. + public string GetProviderConfiguration(string providerTypeName) { + if(providerTypeName == null) throw new ArgumentNullException("providerTypeName"); + if(providerTypeName.Length == 0) throw new ArgumentException("Provider Type Name cannot be empty", "providerTypeName"); + + if(providerTypeName == Settings.Provider.GetType().FullName) { + return StartupTools.GetSettingsStorageProviderConfiguration(); + } + else return ProviderLoader.LoadConfiguration(providerTypeName); + } + + /// + /// Sets the configuration of a provider. + /// + /// The provider of which to set the configuration. + /// The configuration to set. + /// true if the configuration is set, false otherwise. + /// If provider is null. + public bool SetProviderConfiguration(IProviderV30 provider, string configuration) { + if(provider == null) throw new ArgumentNullException("provider"); + + if(configuration == null) configuration = ""; + + ProviderLoader.SaveConfiguration(provider.GetType().FullName, configuration); + + return true; + } + + /// + /// Upgrades the old Page Status to use the new ACL facilities. + /// + /// The page of which to upgrade the status. + /// The old status ('L' = Locked, 'P' = Public). + /// true if the operation succeeded, false otherwise. + /// If page is null. + /// If oldStatus is invalid. + public bool UpgradePageStatusToAcl(PageInfo page, char oldStatus) { + if(page == null) throw new ArgumentNullException("page"); + + switch(oldStatus) { + case 'L': + // Locked: only administrators can edit this page + return AuthWriter.SetPermissionForPage(AuthStatus.Deny, page, Actions.ForPages.ModifyPage, + Users.FindUserGroup(Settings.UsersGroup)); + case 'P': + // Public: anonymous users can edit this page + return AuthWriter.SetPermissionForPage(AuthStatus.Grant, page, Actions.ForPages.ModifyPage, + Users.FindUserGroup(Settings.AnonymousGroup)); + default: + throw new ArgumentOutOfRangeException("oldStatus", "Invalid old status code"); + } + } + + /// + /// Upgrades the old security flags to use the new ACL facilities and user groups support. + /// + /// The administrators group. + /// The users group. + /// true if the operation succeeded, false otherwise. + /// If administrators or users are null. + public bool UpgradeSecurityFlagsToGroupsAcl(UserGroup administrators, UserGroup users) { + if(administrators == null) throw new ArgumentNullException("administrators"); + if(users == null) throw new ArgumentNullException("users"); + + bool done = true; + done &= StartupTools.SetAdministratorsGroupDefaultPermissions(administrators); + done &= StartupTools.SetUsersGroupDefaultPermissions(users); + + return done; + } + + /// + /// Event fired whenever an activity is performed on a User Account. + /// + public event EventHandler UserAccountActivity; + + /// + /// Fires the UserAccountActivity event. + /// + /// The user the activity refers to. + /// The activity. + public void OnUserAccountActivity(UserInfo user, UserAccountActivity activity) { + if(UserAccountActivity != null) { + UserAccountActivity(this, new UserAccountActivityEventArgs(user, activity)); + } + } + + /// + /// Event fired whenever an activity is performed on a user group. + /// + public event EventHandler UserGroupActivity; + + /// + /// Fires the UserGroupActivity event. + /// + /// The group the activity refers to. + /// The activity. + public void OnUserGroupActivity(UserGroup group, UserGroupActivity activity) { + if(UserGroupActivity != null) { + UserGroupActivity(this, new UserGroupActivityEventArgs(group, activity)); + } + } + + /// + /// Event fired whenever an activity is performed on a namespace. + /// + public event EventHandler NamespaceActivity; + + /// + /// Fires the NamespaceActivity event. + /// + /// The namespace the activity refers to. + /// The old name of the renamed namespace, or null. + /// The activity. + public void OnNamespaceActivity(NamespaceInfo nspace, string nspaceOldName, NamespaceActivity activity) { + if(NamespaceActivity != null) { + NamespaceActivity(this, new NamespaceActivityEventArgs(nspace, nspaceOldName, activity)); + } + } + + /// + /// Even fired whenever an activity is performed on a Page. + /// + public event EventHandler PageActivity; + + /// + /// Fires the PageActivity event. + /// + /// The page the activity refers to. + /// The old name of the renamed page, or null. + /// The author of the activity. + /// The activity. + public void OnPageActivity(PageInfo page, string pageOldName, string author, PageActivity activity) { + if(PageActivity != null) { + PageActivity(this, new PageActivityEventArgs(page, pageOldName, author, activity)); + } + } + + /// + /// Event fired whenever an activity is performed on a file, directory or attachment. + /// + public event EventHandler FileActivity; + + /// + /// Fires the FileActivity event. + /// + /// The provider that handles the file. + /// The name of the file that changed. + /// The old name of the renamed file, if any. + /// The activity. + public void OnFileActivity(string provider, string file, string oldFileName, FileActivity activity) { + if(FileActivity != null) { + IFilesStorageProviderV30 prov = Collectors.FilesProviderCollector.GetProvider(provider); + + FileActivity(this, new FileActivityEventArgs( + new StFileInfo(prov.GetFileDetails(file), file, prov), oldFileName, null, null, null, activity)); + } + } + + /// + /// Fires the FileActivity event. + /// + /// The provider that handles the attachment. + /// The old name of the renamed attachment, if any. + /// The page that owns the attachment. + /// The old name of the renamed attachment, if any. + /// The activity. + public void OnAttachmentActivity(string provider, string attachment, string page, string oldAttachmentName, FileActivity activity) { + if(FileActivity != null) { + IFilesStorageProviderV30 prov = Collectors.FilesProviderCollector.GetProvider(provider); + PageInfo pageInfo = Pages.FindPage(page); + + FileActivity(this, new FileActivityEventArgs( + new StFileInfo(prov.GetPageAttachmentDetails(pageInfo, attachment), attachment, prov), oldAttachmentName, null, null, pageInfo, activity)); + } + } + + /// + /// Fires the FileActivity event. + /// + /// The provider that handles the directory. + /// The directory that changed. + /// The old name of the renamed directory, if any. + /// The activity. + public void OnDirectoryActivity(string provider, string directory, string oldDirectoryName, FileActivity activity) { + if(FileActivity != null) { + IFilesStorageProviderV30 prov = Collectors.FilesProviderCollector.GetProvider(provider); + + FileActivity(this, new FileActivityEventArgs( + null, null, new StDirectoryInfo(directory, prov), oldDirectoryName, null, activity)); + } + } + + } + + /// + /// Represents a custom toolbar item. + /// + public class CustomToolbarItem { + + private ToolbarItem item; + private string text, value; + + /// + /// Initializes a new instance of the ToolbarItem class. + /// + /// The item. + /// The text. + /// The value. + public CustomToolbarItem(ToolbarItem item, string text, string value) { + this.item = item; + this.text = text; + this.value = value; + } + + /// + /// Gets or sets the item. + /// + public ToolbarItem Item { + get { return item; } + set { item = value; } + } + + /// + /// Gets the text. + /// + public string Text { + get { return text; } + } + + /// + /// Gets or sets the value. + /// + public string Value { + get { return value; } + set { this.value = value; } + } + + } + +} diff --git a/Core/ITranslator.cs b/Core/ITranslator.cs new file mode 100644 index 0000000..0a05b73 --- /dev/null +++ b/Core/ITranslator.cs @@ -0,0 +1,20 @@ + +using System; + +namespace ScrewTurn.Wiki.ImportWiki { + + /// + /// Exposes an interface for building import tools. + /// + public interface ITranslator { + + /// + /// Executes the translation. + /// + /// The input content. + /// The WikiMarkup. + string Translate(string input); + + } + +} diff --git a/Core/IndexStorer.cs b/Core/IndexStorer.cs new file mode 100644 index 0000000..56aed01 --- /dev/null +++ b/Core/IndexStorer.cs @@ -0,0 +1,491 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki { + + /// + /// Stores index data to disk. + /// + /// Instance and static members are thread-safe. + public class IndexStorer : IndexStorerBase { + + private static readonly byte[] ReservedBytes = new byte[] { 2, 0, 0, 0, 0, 0, 0, 0 }; + private static readonly int Zero = 0; + + private string documentsFile, wordsFile, mappingsFile; + + private uint firstFreeDocumentId = 1; + private uint firstFreeWordId = 1; + + // Documents file binary format + // Reserved(8bytes) Count(int) Entries... + // ID(int) Name(string) Title(string) TypeTag(string) DateTime(long) + + // Words file binary format + // Reserved(8bytes) Count(int) Entries... + // ID(int) Text(string) + + // Mappings file binary format + // Reserved(8bytes) Count(int) Entries... + // WordID(int) DocumentID(int) FirstCharIndex(int) WordIndex(int) Location(int) + + /// + /// Initializes a new instance of the class. + /// + /// The file that contains the documents list. + /// The file that contains the words list. + /// The file that contains the index mappings data. + /// The index to manage. + public IndexStorer(string documentsFile, string wordsFile, string mappingsFile, IInMemoryIndex index) + : base(index) { + + if(documentsFile == null) throw new ArgumentNullException("documentsFile"); + if(wordsFile == null) throw new ArgumentNullException("wordsFile"); + if(mappingsFile == null) throw new ArgumentNullException("mappingsFile"); + + if(documentsFile.Length == 0) throw new ArgumentException("Documents File cannot be empty", "documentsFile"); + if(wordsFile.Length == 0) throw new ArgumentException("Words File cannot be emtpy", "wordsFile"); + if(mappingsFile.Length == 0) throw new ArgumentException("Mappings File cannot be empty", "mappingsFile"); + + this.documentsFile = documentsFile; + this.wordsFile = wordsFile; + this.mappingsFile = mappingsFile; + + InitFiles(); + } + + /// + /// Gets the approximate size, in bytes, of the search engine index. + /// + public override long Size { + get { + lock(this) { + long size = 0; + FileInfo fi; + + fi = new FileInfo(documentsFile); + size += fi.Length; + + fi = new FileInfo(wordsFile); + size += fi.Length; + + fi = new FileInfo(mappingsFile); + size += fi.Length; + + return size; + } + } + } + + /// + /// Loads the index from the data store the first time. + /// + /// The dumped documents. + /// The dumped words. + /// The dumped word mappings. + protected override void LoadIndexInternal(out DumpedDocument[] documents, out DumpedWord[] words, out DumpedWordMapping[] mappings) { + uint maxDocumentId = 0; + uint maxWordId = 0; + + // 1. Load Documents + using(FileStream fs = new FileStream(documentsFile, FileMode.Open, FileAccess.Read, FileShare.None)) { + int count = ReadCount(fs); + BinaryReader reader = new BinaryReader(fs, Encoding.UTF8); + documents = new DumpedDocument[count]; + for(int i = 0; i < count; i++) { + documents[i] = ReadDumpedDocument(reader); + if(documents[i].ID > maxDocumentId) maxDocumentId = documents[i].ID; + } + firstFreeDocumentId = maxDocumentId + 1; + } + + // 2. Load Words + using(FileStream fs = new FileStream(wordsFile, FileMode.Open, FileAccess.Read, FileShare.None)) { + int count = ReadCount(fs); + BinaryReader reader = new BinaryReader(fs, Encoding.UTF8); + words = new DumpedWord[count]; + for(int i = 0; i < count; i++) { + words[i] = ReadDumpedWord(reader); + if(words[i].ID > maxWordId) maxWordId = words[i].ID; + } + firstFreeWordId = maxWordId + 1; + } + + // 3. Load Mappings + using(FileStream fs = new FileStream(mappingsFile, FileMode.Open, FileAccess.Read, FileShare.None)) { + int count = ReadCount(fs); + BinaryReader reader = new BinaryReader(fs, Encoding.UTF8); + mappings = new DumpedWordMapping[count]; + for(int i = 0; i < count; i++) { + mappings[i] = ReadDumpedWordMapping(reader); + } + } + } + + /// + /// Reads the reserved bytes. + /// + /// The to read from. + /// true if read bytes are equal to expected bytes, false otherwise. + private static bool ReadReserved(BinaryReader reader) { + bool allEqual = true; + for(int i = 0; i < ReservedBytes.Length; i++) { + int r = reader.ReadByte(); + if(r != ReservedBytes[i]) allEqual = false; + } + return allEqual; + } + + /// + /// Initializes the data files, if needed. + /// + private void InitFiles() { + if(!File.Exists(documentsFile)) { + using(FileStream fs = new FileStream(documentsFile, FileMode.Create, FileAccess.Write, FileShare.None)) { + BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8); + WriteHeader(writer); + } + } + + if(!File.Exists(wordsFile)) { + using(FileStream fs = new FileStream(wordsFile, FileMode.Create, FileAccess.Write, FileShare.None)) { + BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8); + WriteHeader(writer); + } + } + + if(!File.Exists(mappingsFile)) { + using(FileStream fs = new FileStream(mappingsFile, FileMode.Create, FileAccess.Write, FileShare.None)) { + BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8); + WriteHeader(writer); + } + } + } + + /// + /// Initializes the data storage. + /// + /// A state object passed from the index. + protected override void InitDataStore(object state) { + using(FileStream fs = new FileStream(documentsFile, FileMode.Create, FileAccess.Write, FileShare.None)) { + BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8); + WriteHeader(writer); + } + + using(FileStream fs = new FileStream(wordsFile, FileMode.Create, FileAccess.Write, FileShare.None)) { + BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8); + WriteHeader(writer); + } + + using(FileStream fs = new FileStream(mappingsFile, FileMode.Create, FileAccess.Write, FileShare.None)) { + BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8); + WriteHeader(writer); + } + } + + /// + /// Writes the binary file header. + /// + /// The to write into. + private static void WriteHeader(BinaryWriter writer) { + writer.Write(ReservedBytes); + writer.Write(Zero); + } + + /// + /// Reads a from a . + /// + /// The . + /// The . + private static DumpedDocument ReadDumpedDocument(BinaryReader reader) { + uint id; + string name, title, typeTag; + DateTime dateTime; + + id = reader.ReadUInt32(); + name = reader.ReadString(); + title = reader.ReadString(); + typeTag = reader.ReadString(); + dateTime = DateTime.FromBinary(reader.ReadInt64()); + + return new DumpedDocument(id, name, title, typeTag, dateTime); + } + + /// + /// Reads a from a . + /// + /// The . + /// The . + private static DumpedWord ReadDumpedWord(BinaryReader reader) { + uint id; + string text; + + id = reader.ReadUInt32(); + text = reader.ReadString(); + + return new DumpedWord(id, text); + } + + /// + /// Reads a from a . + /// + /// The . + /// The . + private static DumpedWordMapping ReadDumpedWordMapping(BinaryReader reader) { + uint wordId; + uint documentId; + ushort firstCharIndex, wordIndex; + byte location; + + wordId = reader.ReadUInt32(); + documentId = reader.ReadUInt32(); + firstCharIndex = reader.ReadUInt16(); + wordIndex = reader.ReadUInt16(); + location = reader.ReadByte(); + + return new DumpedWordMapping(wordId, documentId, firstCharIndex, wordIndex, location); + } + + /// + /// Reads the count in a . + /// + /// The , at position zero. + /// The count. + /// The caller must properly seek the stream after calling the method. + private static int ReadCount(FileStream fs) { + BinaryReader reader = new BinaryReader(fs, Encoding.UTF8); + if(!ReadReserved(reader)) { + throw new InvalidOperationException("Invalid index file header"); + } + return reader.ReadInt32(); + } + + /// + /// Stores new data into the data storage. + /// + /// The data to store. + /// A state object passed from the index. + /// The storer result, if any. + /// When saving a new document, the document ID in data.Mappings must be + /// replaced with the currect document ID, generated by the concrete implementation of + /// this method. data.Words should have IDs numbered from uint.MaxValue downwards. + /// The method re-numbers the words appropriately. + protected override IndexStorerResult SaveData(DumpedChange data, object state) { + IndexStorerResult result = new IndexStorerResult(null, null); + + // 1. Save Document + using(FileStream fs = new FileStream(documentsFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { + int count = ReadCount(fs); + // Update count and append document + BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8); + fs.Seek(-4, SeekOrigin.Current); + writer.Write(count + 1); + writer.Seek(0, SeekOrigin.End); + data.Document.ID = firstFreeDocumentId; + WriteDumpedDocument(writer, data.Document); + + result.DocumentID = firstFreeDocumentId; + firstFreeDocumentId++; + } + + // 2. Save Words + Dictionary wordIds = null; + using(FileStream fs = new FileStream(wordsFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { + int count = ReadCount(fs); + // Update count and append words + BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8); + fs.Seek(-4, SeekOrigin.Current); + writer.Write(count + data.Words.Count); + fs.Seek(0, SeekOrigin.End); + + wordIds = new Dictionary(data.Words.Count); + foreach(DumpedWord dw in data.Words) { + wordIds.Add(dw.ID, new WordId(dw.Text, firstFreeWordId)); + dw.ID = firstFreeWordId; + WriteDumpedWord(writer, dw); + firstFreeWordId++; + } + result.WordIDs = new List(wordIds.Values); + } + + // 3. Save Mappings + using(FileStream fs = new FileStream(mappingsFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { + int count = ReadCount(fs); + // Update count and append mappings + BinaryWriter writer = new BinaryWriter(fs, Encoding.UTF8); + fs.Seek(-4, SeekOrigin.Current); + writer.Write(count + data.Mappings.Count); + fs.Seek(0, SeekOrigin.End); + foreach(DumpedWordMapping map in data.Mappings) { + // Words are autonumbered from uint.MaxValue downwards by IndexBase so that + // IndexStorer can identify the DumpedWordMappings easily and + // fix the IDs with the ones actually stored + WordId newMappingWordId; + if(wordIds != null && wordIds.TryGetValue(map.WordID, out newMappingWordId)) { + map.WordID = newMappingWordId.ID; + } + WriteDumpedWordMapping(writer, + new DumpedWordMapping(map.WordID, result.DocumentID.Value, + map.FirstCharIndex, map.WordIndex, map.Location)); + } + } + + return result; + } + + /// + /// Gets a tempDumpedWord file name given an original name. + /// + /// The original name. + /// The tempDumpedWord file name. + private static string GetTempFile(string file) { + string folder = Path.GetDirectoryName(file); + string name = Path.GetFileNameWithoutExtension(file) + "_Temp" + Path.GetExtension(file); + return Path.Combine(folder, name); + } + + /// + /// Deletes data from the data storage. + /// + /// The data to delete. + /// A state object passed from the index. + protected override void DeleteData(DumpedChange data, object state) { + // Files are regenerated in a tempDumpedWord location and copied back + string tempDocumentsFile = GetTempFile(documentsFile); + string tempWordsFile = GetTempFile(wordsFile); + string tempMappingsFile = GetTempFile(mappingsFile); + + // 1. Remove Mappings + using(FileStream fsi = new FileStream(mappingsFile, FileMode.Open, FileAccess.Read, FileShare.None)) { + int count = ReadCount(fsi); + int countLocation = (int)fsi.Position - 4; + int writeCount = 0; + BinaryReader reader = new BinaryReader(fsi, Encoding.UTF8); + using(FileStream fso = new FileStream(tempMappingsFile, FileMode.Create, FileAccess.Write, FileShare.None)) { + BinaryWriter writer = new BinaryWriter(fso, Encoding.UTF8); + WriteHeader(writer); + DumpedWordMapping m; + for(int i = 0; i < count; i++) { + m = ReadDumpedWordMapping(reader); + // If m is not contained in data.Mappings, store it in tempDumpedWord file + if(!Find(m, data.Mappings)) { + WriteDumpedWordMapping(writer, m); + writeCount++; + } + } + writer.Seek(countLocation, SeekOrigin.Begin); + writer.Write(writeCount); + } + } + // Replace the file + File.Copy(tempMappingsFile, mappingsFile, true); + File.Delete(tempMappingsFile); + + // 2. Remove Words + using(FileStream fsi = new FileStream(wordsFile, FileMode.Open, FileAccess.Read, FileShare.None)) { + int count = ReadCount(fsi); + int countLocation = (int)fsi.Position - 4; + int writeCount = 0; + BinaryReader reader = new BinaryReader(fsi, Encoding.UTF8); + using(FileStream fso = new FileStream(tempWordsFile, FileMode.Create, FileAccess.Write, FileShare.None)) { + BinaryWriter writer = new BinaryWriter(fso, Encoding.UTF8); + WriteHeader(writer); + DumpedWord w; + for(int i = 0; i < count; i++) { + w = ReadDumpedWord(reader); + // If w is not contained in data.Words, store it in tempDumpedWord file + if(!Find(w, data.Words)) { + WriteDumpedWord(writer, w); + writeCount++; + } + } + writer.Seek(countLocation, SeekOrigin.Begin); + writer.Write(writeCount); + } + } + // Replace the file + File.Copy(tempWordsFile, wordsFile, true); + File.Delete(tempWordsFile); + + // 3. Remove Document + using(FileStream fsi = new FileStream(documentsFile, FileMode.Open, FileAccess.Read, FileShare.None)) { + int count = ReadCount(fsi); + int countLocation = (int)fsi.Position - 4; + BinaryReader reader = new BinaryReader(fsi, Encoding.UTF8); + using(FileStream fso = new FileStream(tempDocumentsFile, FileMode.Create, FileAccess.Write, FileShare.None)) { + BinaryWriter writer = new BinaryWriter(fso, Encoding.UTF8); + WriteHeader(writer); + DumpedDocument d; + for(int i = 0; i < count; i++) { + d = ReadDumpedDocument(reader); + // If d is not equal to data.Document (to be deleted), then copy it to the result file + if(!EqualDumpedDocument(d, data.Document)) { + WriteDumpedDocument(writer, d); + } + } + writer.Seek(countLocation, SeekOrigin.Begin); + writer.Write(count - 1); + } + } + File.Copy(tempDocumentsFile, documentsFile, true); + File.Delete(tempDocumentsFile); + } + + /// + /// Writes a to a . + /// + /// The . + /// The . + private static void WriteDumpedDocument(BinaryWriter writer, DumpedDocument document) { + writer.Write(document.ID); + writer.Write(document.Name); + writer.Write(document.Title); + writer.Write(document.TypeTag); + writer.Write(document.DateTime.ToBinary()); + } + + /// + /// Writes a to a . + /// + /// The . + /// The . + private static void WriteDumpedWord(BinaryWriter writer, DumpedWord word) { + //if(word.Text.Length == 0) throw new InvalidOperationException(); + + writer.Write(word.ID); + writer.Write(word.Text); + } + + /// + /// Writes a to a . + /// + /// The . + /// The . + private static void WriteDumpedWordMapping(BinaryWriter writer, DumpedWordMapping mapping) { + writer.Write(mapping.WordID); + writer.Write(mapping.DocumentID); + writer.Write(mapping.FirstCharIndex); + writer.Write(mapping.WordIndex); + writer.Write(mapping.Location); + } + + /// + /// Determines whether two s are equal. + /// + /// The first document. + /// The second document. + /// true if the documents are equal, false otherwise. + private static bool EqualDumpedDocument(DumpedDocument d1, DumpedDocument d2) { + // Only consider ID, Name and TypeTag + //return d1.ID == d2.ID && d1.Name == d2.Name && d1.Title == d2.Title && + // d1.TypeTag == d2.TypeTag && d1.DateTime == d2.DateTime; + return d1.ID == d2.ID && d1.Name == d2.Name && d1.TypeTag == d2.TypeTag; + } + + } + +} diff --git a/Core/LocalPageInfo.cs b/Core/LocalPageInfo.cs new file mode 100644 index 0000000..7b1c040 --- /dev/null +++ b/Core/LocalPageInfo.cs @@ -0,0 +1,39 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Represents a Local Page. + /// + public class LocalPageInfo : PageInfo { + + private string file; + + /// + /// Initializes a new instance of the PageInfo class. + /// + /// The Full Name of the Page. + /// The Pages Storage Provider that manages this Page. + /// The creation Date/Time. + /// The relative path of the file used for data storage. + public LocalPageInfo(string fullName, IPagesStorageProviderV30 provider, DateTime creationDateTime, string file) + : base(fullName, provider, creationDateTime) { + + this.file = file; + } + + /// + /// Gets or sets the relative path of the File used for data storage. + /// + public string File { + get { return file; } + set { file = value; } + } + + } + +} diff --git a/Core/LocalProvidersTools.cs b/Core/LocalProvidersTools.cs new file mode 100644 index 0000000..bd02fd3 --- /dev/null +++ b/Core/LocalProvidersTools.cs @@ -0,0 +1,48 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Web; +using System.Web.Configuration; + +namespace ScrewTurn.Wiki { + + /// + /// Implements tools for local providers. + /// + public static class LocalProvidersTools { + + /// + /// Checks a directory for write permissions. + /// + /// The directory. + /// true if the directory has write permissions, false otherwise. + public static bool CheckWritePermissions(string dir) { + string file = System.IO.Path.Combine(dir, "__StwTestFile.txt"); + + bool canWrite = true; + + System.IO.FileStream fs = null; + try { + fs = System.IO.File.Create(file); + fs.Write(Encoding.ASCII.GetBytes("Hello"), 0, 5); + } + catch { + canWrite = false; + } + finally { + try { + if(fs != null) fs.Close(); + System.IO.File.Delete(file); + } + catch { + canWrite = false; + } + } + + return canWrite; + } + + } + +} diff --git a/Core/LocalUserInfo.cs b/Core/LocalUserInfo.cs new file mode 100644 index 0000000..3f585f5 --- /dev/null +++ b/Core/LocalUserInfo.cs @@ -0,0 +1,42 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Stores a Local UserInfo object. + /// + public class LocalUserInfo : UserInfo { + + private string passwordHash; + + /// + /// Initializes a new instance of the LocalUserInfo class. + /// + /// The Username. + /// The display name. + /// The Email. + /// Specifies whether the Account is active or not. + /// The creation DateTime. + /// The Users Storage Provider that manages the User. + /// The Password Hash. + public LocalUserInfo(string username, string displayName, string email, bool active, DateTime dateTime, + IUsersStorageProviderV30 provider, string passwordHash) + : base(username, displayName, email, active, dateTime, provider) { + this.passwordHash = passwordHash; + } + + /// + /// Gets or sets the Password Hash. + /// + public string PasswordHash { + get { return passwordHash; } + set { passwordHash = value; } + } + + } + +} diff --git a/Core/Log.cs b/Core/Log.cs new file mode 100644 index 0000000..b02bf39 --- /dev/null +++ b/Core/Log.cs @@ -0,0 +1,57 @@ + +using System; +using System.Configuration; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Web; +using System.Web.Security; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Records and retrieves Log Entries. + /// + public static class Log { + + /// + /// The system username ('SYSTEM'). + /// + public const string SystemUsername = "SYSTEM"; + + /// + /// Writes an Entry in the Log. + /// + /// The Message. + /// The Type of the Entry. + /// The User that generated the Entry. + public static void LogEntry(string message, EntryType type, string user) { + try { + Settings.Provider.LogEntry(message, type, user); + } + catch { } + } + + /// + /// Reads all the Log Entries (newest to oldest). + /// + /// The Entries. + public static List ReadEntries() { + List entries = new List(Settings.Provider.GetLogEntries()); + entries.Reverse(); + return entries; + + } + + /// + /// Clears the Log. + /// + public static void ClearLog() { + Settings.Provider.ClearLog(); + } + + } + +} diff --git a/Core/MimeTypes.cs b/Core/MimeTypes.cs new file mode 100644 index 0000000..ca1fb7a --- /dev/null +++ b/Core/MimeTypes.cs @@ -0,0 +1,90 @@ + +using System; +using System.Collections.Generic; + +namespace ScrewTurn.Wiki { + + /// + /// Contains a list of MIME Types. + /// + public static class MimeTypes { + + private static Dictionary types; + + /// + /// Initializes the list of the MIME Types, with the most common media types. + /// + public static void Init() { + types = new Dictionary(100); + + // Images + types.Add("jpg", "image/jpeg"); + types.Add("jpeg", "image/jpeg"); + types.Add("jpe", "image/jpeg"); + types.Add("gif", "image/gif"); + types.Add("png", "image/png"); + types.Add("bmp", "image/bmp"); + types.Add("tif", "image/tiff"); + types.Add("tiff", "image/tiff"); + types.Add("svg", "image/svg+xml"); + types.Add("ico", "image/x-icon"); + + // Text + types.Add("txt", "text/plain"); + types.Add("htm", "text/html"); + types.Add("html", "text/html"); + types.Add("xhtml", "text/xhtml"); + types.Add("xml", "text/xml"); + types.Add("xsl", "text/xsl"); + types.Add("dtd", "application/xml-dtd"); + types.Add("css", "text/css"); + types.Add("rtf", "text/rtf"); + + // Archives + types.Add("zip", "application/zip"); + types.Add("tar", "application/x-tar"); + + // Multimedia + types.Add("ogg", "application/ogg"); + types.Add("swf", "application/x-shockwave-flash"); + types.Add("mpga", "audio/mpeg"); + types.Add("mp2", "audio/mpeg"); + types.Add("mp3", "audio/mpeg"); + types.Add("m3u", "audio/x-mpegurl"); + types.Add("ram", "audio/x-pn-realaudio"); + types.Add("ra", "audio/x-pn-realaudio"); + types.Add("rm", "application/vnd.rn-realmedia"); + types.Add("wav", "application/x-wav"); + types.Add("mpg", "video/mpeg"); + types.Add("mpeg", "video/mpeg"); + types.Add("mpe", "video/mpeg"); + types.Add("mov", "video/quicktime"); + types.Add("qt", "video/quicktime"); + types.Add("avi", "video/x-msvideo"); + + // Office + types.Add("doc", "application/msword"); + types.Add("xls", "application/vnd.ms-excel"); + types.Add("ppt", "application/vnd.ms-powerpoint"); + // Map Office 2007 formats using the information found here: + // http://www.therightstuff.de/2006/12/16/Office+2007+File+Icons+For+Windows+SharePoint+Services+20+And+SharePoint+Portal+Server+2003.aspx + types.Add("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + types.Add("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + types.Add("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"); + + // Other + types.Add("pdf", "application/pdf"); + types.Add("ai", "application/postscript"); + types.Add("ps", "application/postscript"); + types.Add("eps", "application/postscript"); + } + + /// + /// Gets the list of the MIME Types. + /// + public static Dictionary Types { + get { return types; } + } + + } +} diff --git a/Core/NavigationPaths.cs b/Core/NavigationPaths.cs new file mode 100644 index 0000000..8380e1a --- /dev/null +++ b/Core/NavigationPaths.cs @@ -0,0 +1,148 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Manages navigation paths. + /// + public static class NavigationPaths { + + /// + /// Gets the list of the Navigation Paths. + /// + /// The navigation paths, sorted by name. + public static List GetAllNavigationPaths() { + List allPaths = new List(30); + + // Retrieve paths from every Pages provider + foreach(IPagesStorageProviderV30 provider in Collectors.PagesProviderCollector.AllProviders) { + allPaths.AddRange(provider.GetNavigationPaths(null)); + foreach(NamespaceInfo nspace in provider.GetNamespaces()) { + allPaths.AddRange(provider.GetNavigationPaths(nspace)); + } + } + + allPaths.Sort(new NavigationPathComparer()); + + return allPaths; + } + + /// + /// Gets the list of the Navigation Paths in a namespace. + /// + /// The namespace. + /// The navigation paths, sorted by name. + public static List GetNavigationPaths(NamespaceInfo nspace) { + List allPaths = new List(30); + + // Retrieve paths from every Pages provider + foreach(IPagesStorageProviderV30 provider in Collectors.PagesProviderCollector.AllProviders) { + allPaths.AddRange(provider.GetNavigationPaths(nspace)); + } + + allPaths.Sort(new NavigationPathComparer()); + + return allPaths; + } + + /// + /// Finds a Navigation Path's Name. + /// + /// The Name. + /// True if the Navigation Path exists. + public static bool Exists(string name) { + return Find(name) != null; + } + + /// + /// Finds and returns a Path. + /// + /// The full name. + /// The correct object or null if no path is found. + public static NavigationPath Find(string fullName) { + List allPaths = GetAllNavigationPaths(); + int idx = allPaths.BinarySearch(new NavigationPath(fullName, null), new NavigationPathComparer()); + if(idx >= 0) return allPaths[idx]; + else return null; + } + + /// + /// Adds a new Navigation Path. + /// + /// The target namespace (null for the root). + /// The Name. + /// The Pages. + /// The Provider to use for the new Navigation Path, or null for the default provider. + /// True if the Path is added successfully. + public static bool AddNavigationPath(NamespaceInfo nspace, string name, List pages, IPagesStorageProviderV30 provider) { + string namespaceName = nspace != null ? nspace.Name : null; + string fullName = NameTools.GetFullName(namespaceName, name); + + if(Exists(fullName)) return false; + + if(provider == null) provider = Collectors.PagesProviderCollector.GetProvider(Settings.DefaultPagesProvider); + + NavigationPath newPath = provider.AddNavigationPath(namespaceName, name, pages.ToArray()); + if(newPath != null) Log.LogEntry("Navigation Path " + fullName + " added", EntryType.General, Log.SystemUsername); + else Log.LogEntry("Creation failed for Navigation Path " + fullName, EntryType.Error, Log.SystemUsername); + return newPath != null; + } + + /// + /// Removes a Navigation Path. + /// + /// The full name of the path to remove. + /// true if the path is removed, false otherwise. + public static bool RemoveNavigationPath(string fullName) { + NavigationPath path = Find(fullName); + if(path == null) return false; + + bool done = path.Provider.RemoveNavigationPath(path); + if(done) Log.LogEntry("Navigation Path " + fullName + " removed", EntryType.General, Log.SystemUsername); + else Log.LogEntry("Deletion failed for Navigation Path " + fullName, EntryType.Error, Log.SystemUsername); + return done; + } + + /// + /// Modifies a Navigation Path. + /// + /// The full name of the path to modify. + /// The list of Pages. + /// true if the path is modified, false otherwise. + public static bool ModifyNavigationPath(string fullName, List pages) { + NavigationPath path = Find(fullName); + if(path == null) return false; + + NavigationPath newPath = path.Provider.ModifyNavigationPath(path, pages.ToArray()); + if(newPath != null) Log.LogEntry("Navigation Path " + fullName + " modified", EntryType.General, Log.SystemUsername); + else Log.LogEntry("Modification failed for Navigation Path " + fullName, EntryType.Error, Log.SystemUsername); + return newPath != null; + } + + /// + /// Finds all the Navigation Paths that include a Page. + /// + /// The Page. + /// The list of Navigation Paths. + public static string[] PathsPerPage(PageInfo page) { + NamespaceInfo pageNamespace = Pages.FindNamespace(NameTools.GetNamespace(page.FullName)); + + List result = new List(10); + List allPaths = GetNavigationPaths(pageNamespace); + + for(int i = 0; i < allPaths.Count; i++) { + List pages = new List(allPaths[i].Pages); + if(pages.Contains(page.FullName)) { + result.Add(allPaths[i].FullName); + } + } + return result.ToArray(); + } + + } + +} diff --git a/Core/PageAttachmentDocument.cs b/Core/PageAttachmentDocument.cs new file mode 100644 index 0000000..f4f2b2f --- /dev/null +++ b/Core/PageAttachmentDocument.cs @@ -0,0 +1,126 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.SearchEngine; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Represents a page attachment document. + /// + public class PageAttachmentDocument : IDocument { + + /// + /// The type tag for a . + /// + public const string StandardTypeTag = "A"; + + private uint id; + private string name; + private string title; + private string typeTag = StandardTypeTag; + private DateTime dateTime; + private PageInfo page; + private string provider; + + /// + /// Initializes a new instance of the class. + /// + /// The page. + /// The attachment name. + /// The file provider. + /// The modification date/time. + public PageAttachmentDocument(PageInfo page, string name, string provider, DateTime dateTime) { + if(page == null) throw new ArgumentNullException("page"); + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(provider == null) throw new ArgumentNullException("provider"); + if(provider.Length == 0) throw new ArgumentException("Provider cannot be empty", "provider"); + + this.name = page.FullName + "|" + provider + "|" + name; + id = Tools.HashDocumentNameForTemporaryIndex(this.name); + title = name; + this.dateTime = dateTime; + this.page = page; + this.provider = provider; + } + + /// + /// Initializes a new instance of the class. + /// + /// The dumped document. + public PageAttachmentDocument(DumpedDocument doc) { + string[] fields = doc.Name.Split('|'); + + id = doc.ID; + name = doc.Name; + title = doc.Title; + dateTime = doc.DateTime; + provider = fields[0]; + page = Pages.FindPage(fields[1]); + } + + /// + /// Gets or sets the globally unique ID of the document. + /// + public uint ID { + get { return id; } + set { id = value; } + } + + /// + /// Gets the globally-unique name of the document. + /// + public string Name { + get { return name; } + } + + /// + /// Gets the title of the document, if any. + /// + public string Title { + get { return title; } + } + + /// + /// Gets the tag for the document type. + /// + public string TypeTag { + get { return typeTag; } + } + + /// + /// Gets the document date/time. + /// + public DateTime DateTime { + get { return dateTime; } + } + + /// + /// Performs the tokenization of the document content. + /// + /// The content to tokenize. + /// The extracted words and their positions (always an empty array). + public WordInfo[] Tokenize(string content) { + return ScrewTurn.Wiki.SearchEngine.Tools.Tokenize(content); + } + + /// + /// Gets the page. + /// + public PageInfo Page { + get { return page; } + } + + /// + /// Gets the provider. + /// + public string Provider { + get { return provider; } + } + + } + +} diff --git a/Core/Pages.cs b/Core/Pages.cs new file mode 100644 index 0000000..22616cf --- /dev/null +++ b/Core/Pages.cs @@ -0,0 +1,1523 @@ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using ScrewTurn.Wiki.PluginFramework; +using System.Web; +using System.Globalization; + +namespace ScrewTurn.Wiki { + + /// + /// Allows access to the Pages. + /// + public static class Pages { + + #region Namespaces + + /// + /// Gets all the namespaces, sorted. + /// + /// The namespaces, sorted. + public static List GetNamespaces() { + List result = new List(10); + + int count = 0; + foreach(IPagesStorageProviderV30 provider in Collectors.PagesProviderCollector.AllProviders) { + count++; + result.AddRange(provider.GetNamespaces()); + } + + if(count > 1) { + result.Sort(new NamespaceComparer()); + } + + return result; + } + + /// + /// Finds a namespace. + /// + /// The name of the namespace to find. + /// The namespace, or null if no namespace is found. + public static NamespaceInfo FindNamespace(string name) { + if(string.IsNullOrEmpty(name)) return null; + + IPagesStorageProviderV30 defProv = Collectors.PagesProviderCollector.GetProvider(Settings.DefaultPagesProvider); + NamespaceInfo nspace = defProv.GetNamespace(name); + if(nspace != null) return nspace; + + foreach(IPagesStorageProviderV30 prov in Collectors.PagesProviderCollector.AllProviders) { + if(prov != defProv) { + nspace = prov.GetNamespace(name); + if(nspace != null) return nspace; + } + } + + return null; + } + + /// + /// Finds a namespace. + /// + /// The name of the namespace to find. + /// The provider to look into. + /// The namespace, or null if the namespace is not found. + public static NamespaceInfo FindNamespace(string name, IPagesStorageProviderV30 provider) { + if(string.IsNullOrEmpty(name)) return null; + + return provider.GetNamespace(name); + } + + /// + /// Creates a new namespace in the default pages storage provider. + /// + /// The name of the namespace to add. + /// true if the namespace is created, false otherwise. + public static bool CreateNamespace(string name) { + return CreateNamespace(name, Collectors.PagesProviderCollector.GetProvider(Settings.DefaultPagesProvider)); + } + + /// + /// Creates a new namespace. + /// + /// The name of the namespace to add. + /// The provider to create the namespace into. + /// true if the namespace is created, false otherwise. + public static bool CreateNamespace(string name, IPagesStorageProviderV30 provider) { + if(provider.ReadOnly) return false; + + if(FindNamespace(name) != null) return false; + + NamespaceInfo result = provider.AddNamespace(name); + + if(result != null) { + InitMetaDataItems(name); + + AuthWriter.ClearEntriesForNamespace(name, new List()); + + Cache.ClearPseudoCache(); + Cache.ClearPageCache(); + + Host.Instance.OnNamespaceActivity(result, null, NamespaceActivity.NamespaceAdded); + + Log.LogEntry("Namespace " + name + " created", EntryType.General, Log.SystemUsername); + return true; + } + else { + Log.LogEntry("Namespace creation failed for " + name, EntryType.Error, Log.SystemUsername); + return false; + } + } + + /// + /// Removes a namespace. + /// + /// The namespace to remove. + /// true if the namespace is removed, false otherwise. + public static bool RemoveNamespace(NamespaceInfo nspace) { + if(nspace.Provider.ReadOnly) return false; + + NamespaceInfo realNspace = FindNamespace(nspace.Name); + if(realNspace == null) return false; + + List pages = GetPages(realNspace); + + bool done = realNspace.Provider.RemoveNamespace(realNspace); + if(done) { + DeleteAllAttachments(pages); + + ResetMetaDataItems(nspace.Name); + + AuthWriter.ClearEntriesForNamespace(nspace.Name, pages.ConvertAll((p) => { return NameTools.GetLocalName(p.FullName); })); + + Cache.ClearPseudoCache(); + Cache.ClearPageCache(); + + Host.Instance.OnNamespaceActivity(realNspace, null, NamespaceActivity.NamespaceRemoved); + + Log.LogEntry("Namespace " + realNspace.Name + " removed", EntryType.General, Log.SystemUsername); + return true; + } + else { + Log.LogEntry("Namespace deletion failed for " + realNspace.Name, EntryType.General, Log.SystemUsername); + return false; + } + } + + /// + /// Deletes all page attachments for a whole namespace. + /// + /// The pages in the namespace. + private static void DeleteAllAttachments(List pages) { + foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) { + foreach(PageInfo page in pages) { + string[] attachments = prov.ListPageAttachments(page); + foreach(string attachment in attachments) { + prov.DeletePageAttachment(page, attachment); + } + } + } + } + + /// + /// Renames a namespace. + /// + /// The namespace to rename. + /// The new name. + /// true if the namespace is removed, false otherwise. + public static bool RenameNamespace(NamespaceInfo nspace, string newName) { + if(nspace.Provider.ReadOnly) return false; + + NamespaceInfo realNspace = FindNamespace(nspace.Name); + if(realNspace == null) return false; + if(FindNamespace(newName) != null) return false; + + List pages = GetPages(nspace); + List pageNames = new List(pages.Count); + foreach(PageInfo page in pages) pageNames.Add(NameTools.GetLocalName(page.FullName)); + pages = null; + + string oldName = nspace.Name; + + NamespaceInfo newNspace = realNspace.Provider.RenameNamespace(realNspace, newName); + if(newNspace != null) { + NotifyFilesProvidersForNamespaceRename(pageNames, oldName, newName); + + UpdateMetaDataItems(oldName, newName); + + AuthWriter.ClearEntriesForNamespace(newName, new List()); + AuthWriter.ProcessNamespaceRenaming(oldName, pageNames, newName); + + Cache.ClearPseudoCache(); + Cache.ClearPageCache(); + + Host.Instance.OnNamespaceActivity(newNspace, oldName, NamespaceActivity.NamespaceRenamed); + + Log.LogEntry("Namespace " + nspace.Name + " renamed to " + newName, EntryType.General, Log.SystemUsername); + return true; + } + else { + Log.LogEntry("Namespace rename failed for " + nspace.Name, EntryType.General, Log.SystemUsername); + return false; + } + } + + /// + /// Notifies all files providers that a namespace was renamed. + /// + /// The pages in the renamed namespace. + /// The name of the renamed namespace. + /// The new name of the namespace. + private static void NotifyFilesProvidersForNamespaceRename(List pages, string nspace, string newName) { + foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) { + foreach(string page in pages) { + PageInfo pageToNotify = new PageInfo(NameTools.GetFullName(nspace, page), null, DateTime.Now); + PageInfo newPage = new PageInfo(NameTools.GetFullName(newName, page), null, DateTime.Now); + + prov.NotifyPageRenaming(pageToNotify, newPage); + } + } + } + + /// + /// Initializes the namespace-specific meta-data items for a namespace. + /// + /// The namespace to initialize meta-data items for. + private static void InitMetaDataItems(string nspace) { + // Footer, Header, HtmlHead, PageFooter, PageHeader, Sidebar + + Settings.Provider.SetMetaDataItem(MetaDataItem.EditNotice, nspace, Defaults.EditNoticeContent); + Settings.Provider.SetMetaDataItem(MetaDataItem.Footer, nspace, Defaults.FooterContent); + Settings.Provider.SetMetaDataItem(MetaDataItem.Header, nspace, Defaults.HeaderContent); + Settings.Provider.SetMetaDataItem(MetaDataItem.HtmlHead, nspace, ""); + Settings.Provider.SetMetaDataItem(MetaDataItem.PageFooter, nspace, ""); + Settings.Provider.SetMetaDataItem(MetaDataItem.PageHeader, nspace, ""); + Settings.Provider.SetMetaDataItem(MetaDataItem.Sidebar, nspace, Defaults.SidebarContentForSubNamespace); + } + + /// + /// Resets the namespace-specific meta-data items for a namespace. + /// + /// The namespace to reset meta-data items for. + private static void ResetMetaDataItems(string nspace) { + // Footer, Header, HtmlHead, PageFooter, PageHeader, Sidebar + + Settings.Provider.SetMetaDataItem(MetaDataItem.EditNotice, nspace, ""); + Settings.Provider.SetMetaDataItem(MetaDataItem.Footer, nspace, ""); + Settings.Provider.SetMetaDataItem(MetaDataItem.Header, nspace, ""); + Settings.Provider.SetMetaDataItem(MetaDataItem.HtmlHead, nspace, ""); + Settings.Provider.SetMetaDataItem(MetaDataItem.PageFooter, nspace, ""); + Settings.Provider.SetMetaDataItem(MetaDataItem.PageHeader, nspace, ""); + Settings.Provider.SetMetaDataItem(MetaDataItem.Sidebar, nspace, ""); + } + + /// + /// Updates the namespace-specific meta-data items for a namespace when it is renamed. + /// + /// The renamed namespace to update the meta-data items for. + /// The new name of the namespace. + private static void UpdateMetaDataItems(string nspace, string newName) { + // Footer, Header, HtmlHead, PageFooter, PageHeader, Sidebar + + Settings.Provider.SetMetaDataItem(MetaDataItem.EditNotice, newName, + Settings.Provider.GetMetaDataItem(MetaDataItem.EditNotice, nspace)); + Settings.Provider.SetMetaDataItem(MetaDataItem.Footer, newName, + Settings.Provider.GetMetaDataItem(MetaDataItem.Footer, nspace)); + Settings.Provider.SetMetaDataItem(MetaDataItem.Header, newName, + Settings.Provider.GetMetaDataItem(MetaDataItem.Header, nspace)); + Settings.Provider.SetMetaDataItem(MetaDataItem.HtmlHead, newName, + Settings.Provider.GetMetaDataItem(MetaDataItem.HtmlHead, nspace)); + Settings.Provider.SetMetaDataItem(MetaDataItem.PageFooter, newName, + Settings.Provider.GetMetaDataItem(MetaDataItem.PageFooter, nspace)); + Settings.Provider.SetMetaDataItem(MetaDataItem.PageHeader, newName, + Settings.Provider.GetMetaDataItem(MetaDataItem.PageHeader, nspace)); + Settings.Provider.SetMetaDataItem(MetaDataItem.Sidebar, newName, + Settings.Provider.GetMetaDataItem(MetaDataItem.Sidebar, nspace)); + + ResetMetaDataItems(nspace); + } + + /// + /// Sets the default page of a namespace. + /// + /// The namespace (null for the root). + /// The page. + /// true if the default page is set, false otherwise. + public static bool SetNamespaceDefaultPage(NamespaceInfo nspace, PageInfo page) { + if(nspace == null) { + // Root namespace, default to classic settings storage + Settings.DefaultPage = page.FullName; + return true; + } + + if(nspace.Provider.ReadOnly) return false; + + NamespaceInfo pageNamespace = FindNamespace(NameTools.GetNamespace(page.FullName), page.Provider); + + if(pageNamespace == null) return false; + + NamespaceComparer comp = new NamespaceComparer(); + if(comp.Compare(pageNamespace, nspace) != 0) return false; + + NamespaceInfo result = pageNamespace.Provider.SetNamespaceDefaultPage(nspace, page); + + if(result != null) { + Host.Instance.OnNamespaceActivity(result, null, NamespaceActivity.NamespaceModified); + + Log.LogEntry("Default Page set for " + nspace.Name + " (" + page.FullName + ")", EntryType.General, Log.SystemUsername); + return true; + } + else { + Log.LogEntry("Default Page setting failed for " + nspace.Name + " (" + page.FullName + ")", EntryType.Error, Log.SystemUsername); + return false; + } + } + + #endregion + + #region Pages + + /// + /// Finds a Page. + /// + /// The full name of the page to find (case unsensitive). + /// The correct object, if any, null otherwise. + public static PageInfo FindPage(string fullName) { + if(string.IsNullOrEmpty(fullName)) return null; + + IPagesStorageProviderV30 defProv = Collectors.PagesProviderCollector.GetProvider(Settings.DefaultPagesProvider); + PageInfo page = defProv.GetPage(fullName); + if(page != null) return page; + + foreach(IPagesStorageProviderV30 prov in Collectors.PagesProviderCollector.AllProviders) { + if(prov != defProv) { + page = prov.GetPage(fullName); + if(page != null) return page; + } + } + + return null; + } + + /// + /// Finds a Page in a specific Provider. + /// + /// The full name of the page to find (case unsensitive). + /// The Provider. + /// The correct object, if any, null otherwise. + public static PageInfo FindPage(string fullName, IPagesStorageProviderV30 provider) { + if(string.IsNullOrEmpty(fullName)) return null; + + return provider.GetPage(fullName); + } + + /// + /// Gets a page draft, if any. + /// + /// The draft content, or null if no draft exists. + public static PageContent GetDraft(PageInfo page) { + if(page == null) return null; + + return page.Provider.GetDraft(page); + } + + /// + /// Deletes the draft of a page (if any). + /// + /// The page of which to delete the draft. + public static void DeleteDraft(PageInfo page) { + if(page == null) return; + + if(page.Provider.GetDraft(page) != null) { + page.Provider.DeleteDraft(page); + } + } + + /// + /// Gets the Backups/Revisions of a Page. + /// + /// The Page. + /// The list of available Backups/Revision numbers. + public static List GetBackups(PageInfo page) { + int[] temp = page.Provider.GetBackups(page); + if(temp == null) return null; + else return new List(temp); + } + + /// + /// Gets the Content of a Page Backup. + /// + /// The Page. + /// The Backup/Revision number. + /// The Content of the Backup. + public static PageContent GetBackupContent(PageInfo page, int revision) { + return page.Provider.GetBackupContent(page, revision); + } + + /// + /// Deletes all the backups of a page. + /// + /// The Page. + public static bool DeleteBackups(PageInfo page) { + return DeleteBackups(page, -1); + } + + /// + /// Deletes a subset of the backups of a page. + /// + /// The Page. + /// The first backup to be deleted (this backup and older backups are deleted). + public static bool DeleteBackups(PageInfo page, int firstToDelete) { + if(page.Provider.ReadOnly) return false; + + bool done = page.Provider.DeleteBackups(page, firstToDelete); + if(done) { + Log.LogEntry("Backups (0-" + firstToDelete.ToString() + ") deleted for " + page.FullName, EntryType.General, Log.SystemUsername); + Host.Instance.OnPageActivity(page, null, SessionFacade.GetCurrentUsername(), PageActivity.PageBackupsDeleted); + } + else { + Log.LogEntry("Backups (0-" + firstToDelete.ToString() + ") deletion failed for " + page.FullName, EntryType.Error, Log.SystemUsername); + } + return done; + } + + /// + /// Performs the rollpack of a Page. + /// + /// The Page. + /// The revision to rollback the Page to. + public static bool Rollback(PageInfo page, int version) { + if(page.Provider.ReadOnly) return false; + + bool done = page.Provider.RollbackPage(page, version); + + if(done) { + Content.InvalidatePage(page); + + PageContent pageContent = Content.GetPageContent(page, false); + + // Update page's outgoing links + string[] linkedPages; + Formatter.Format(pageContent.Content, false, FormattingContext.PageContent, page, out linkedPages); + string[] outgoingLinks = new string[linkedPages.Length]; + for(int i = 0; i < outgoingLinks.Length; i++) { + outgoingLinks[i] = linkedPages[i]; + } + + Settings.Provider.StoreOutgoingLinks(page.FullName, outgoingLinks); + + Log.LogEntry("Rollback executed for " + page.FullName + " at revision " + version.ToString(), EntryType.General, Log.SystemUsername); + RecentChanges.AddChange(page.FullName, pageContent.Title, null, DateTime.Now, SessionFacade.GetCurrentUsername(), Change.PageRolledBack, ""); + Host.Instance.OnPageActivity(page, null, SessionFacade.GetCurrentUsername(), PageActivity.PageRolledBack); + return true; + } + else { + Log.LogEntry("Rollback failed for " + page.FullName + " at revision " + version.ToString(), EntryType.Error, Log.SystemUsername); + return false; + } + } + + /// + /// Creates a new Page. + /// + /// The target namespace (null for the root). + /// The Page name. + /// true if the Page is created, false otherwise. + public static bool CreatePage(NamespaceInfo nspace, string name) { + string namespaceName = nspace != null ? nspace.Name : null; + return CreatePage(namespaceName, name, nspace != null ? nspace.Provider : null); + } + + /// + /// Creates a new Page. + /// + /// The target namespace (null for the root). + /// The Page name. + /// true if the Page is created, false otherwise. + public static bool CreatePage(string nspace, string name) { + return CreatePage(nspace, name, Collectors.PagesProviderCollector.GetProvider(Settings.DefaultPagesProvider)); + } + + /// + /// Creates a new Page. + /// + /// The target namespace (null for the root). + /// The Page name. + /// The destination provider. + /// true if the Page is created, false otherwise. + public static bool CreatePage(NamespaceInfo nspace, string name, IPagesStorageProviderV30 provider) { + string namespaceName = nspace != null ? nspace.Name : null; + return CreatePage(namespaceName, name, provider); + } + + /// + /// Creates a new Page. + /// + /// The target namespace (null for the root). + /// The Page name. + /// The destination provider. + /// true if the Page is created, false otherwise. + public static bool CreatePage(string nspace, string name, IPagesStorageProviderV30 provider) { + if(provider.ReadOnly) return false; + + string fullName = NameTools.GetFullName(nspace, name); + + if(FindPage(fullName) != null) return false; + + PageInfo newPage = provider.AddPage(nspace, name, DateTime.Now); + + if(newPage != null) { + AuthWriter.ClearEntriesForPage(fullName); + + Content.InvalidateAllPages(); + Content.ClearPseudoCache(); + Log.LogEntry("Page " + fullName + " created", EntryType.General, Log.SystemUsername); + Host.Instance.OnPageActivity(newPage, null, SessionFacade.GetCurrentUsername(), PageActivity.PageCreated); + return true; + } + else { + Log.LogEntry("Page creation failed for " + fullName, EntryType.Error, Log.SystemUsername); + return false; + } + } + + /// + /// Deletes a Page. + /// + /// The Page to delete. + public static bool DeletePage(PageInfo page) { + if(page.Provider.ReadOnly) return false; + + string title = Content.GetPageContent(page, false).Title; + + bool done = page.Provider.RemovePage(page); + + if(done) { + AuthWriter.ClearEntriesForPage(page.FullName); + + // Remove the deleted page from the Breadcrumbs Trail and Redirections list + SessionFacade.Breadcrumbs.RemovePage(page); + Redirections.WipePageOut(page); + // Cleanup Cache + Content.InvalidatePage(page); + Content.ClearPseudoCache(); + + // Remove outgoing links + Settings.Provider.DeleteOutgoingLinks(page.FullName); + + Log.LogEntry("Page " + page.FullName + " deleted", EntryType.General, Log.SystemUsername); + RecentChanges.AddChange(page.FullName, title, null, DateTime.Now, SessionFacade.GetCurrentUsername(), Change.PageDeleted, ""); + Host.Instance.OnPageActivity(page, null, SessionFacade.GetCurrentUsername(), PageActivity.PageDeleted); + return true; + } + else { + Log.LogEntry("Page deletion failed for " + page.FullName, EntryType.Error, Log.SystemUsername); + return false; + } + } + + /// + /// Renames a Page. + /// + /// The Page to rename. + /// The new name. + public static bool RenamePage(PageInfo page, string name) { + if(page.Provider.ReadOnly) return false; + + string newFullName = NameTools.GetFullName(NameTools.GetNamespace(page.FullName), NameTools.GetLocalName(name)); + + if(FindPage(newFullName) != null) return false; + + string oldName = page.FullName; + + string title = Content.GetPageContent(page, false).Title; + + PageInfo pg = page.Provider.RenamePage(page, name); + if(pg != null) { + AuthWriter.ClearEntriesForPage(newFullName); + AuthWriter.ProcessPageRenaming(oldName, newFullName); + + SessionFacade.Breadcrumbs.RemovePage(page); + Redirections.Clear(); + Content.InvalidateAllPages(); + Content.ClearPseudoCache(); + + // Page redirect is implemented directly in AdminPages.aspx.cs + + Log.LogEntry("Page " + page.FullName + " renamed to " + name, EntryType.General, Log.SystemUsername); + RecentChanges.AddChange(page.FullName, title, null, DateTime.Now, SessionFacade.GetCurrentUsername(), Change.PageRenamed, ""); + Host.Instance.OnPageActivity(page, oldName, SessionFacade.GetCurrentUsername(), PageActivity.PageRenamed); + return true; + } + else { + Log.LogEntry("Page rename failed for " + page.FullName + " (" + name + ")", EntryType.Error, Log.SystemUsername); + return false; + } + } + + /// + /// Migrates a page. + /// + /// The page to migrate. + /// The target namespace. + /// A value indicating whether to copy the page categories to the target namespace. + /// true if the page is migrated, false otherwise. + public static bool MigratePage(PageInfo page, NamespaceInfo targetNamespace, bool copyCategories) { + PageInfo result = page.Provider.MovePage(page, targetNamespace, copyCategories); + if(result != null) { + Settings.Provider.StoreOutgoingLinks(page.FullName, new string[0]); + PageContent content = Content.GetPageContent(result, false); + StorePageOutgoingLinks(result, content.Content); + } + return result != null; + } + + /// + /// Modifies a Page. + /// + /// The Page to modify. + /// The Title of the Page. + /// The Username of the user who modified the Page. + /// The Date/Time of the modification. + /// The comment of the editor, about this revision. + /// The Content. + /// The keywords, usually used for SEO. + /// The description, usually used for SEO. + /// The save mode. + /// True if the Page has been modified successfully. + public static bool ModifyPage(PageInfo page, string title, string username, DateTime dateTime, string comment, string content, + string[] keywords, string description, SaveMode saveMode) { + + if(page.Provider.ReadOnly) return false; + + StringBuilder sb = new StringBuilder(content); + sb.Replace("~~~~", "(" + username + "," + dateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss") + ")"); + content = sb.ToString(); + + bool done = page.Provider.ModifyPage(page, title, username, dateTime, comment, content, keywords, description, saveMode); + + if(done) { + // Because of transclusion and other page-linking features, it is necessary to clear the whole cache + Content.ClearPseudoCache(); + Content.InvalidateAllPages(); + Log.LogEntry("Page Content updated for " + page.FullName, EntryType.General, Log.SystemUsername); + + StorePageOutgoingLinks(page, content); + + if(saveMode != SaveMode.Draft) { + RecentChanges.AddChange(page.FullName, title, null, dateTime, username, Change.PageUpdated, comment); + Host.Instance.OnPageActivity(page, null, username, PageActivity.PageModified); + SendEmailNotificationForPage(page, Users.FindUser(username)); + } + else { + Host.Instance.OnPageActivity(page, null, username, PageActivity.PageDraftSaved); + } + + if(saveMode == SaveMode.Backup) { + // Delete old backups, if needed + DeleteOldBackupsIfNeeded(page); + } + } + else Log.LogEntry("Page Content update failed for " + page.FullName, EntryType.Error, Log.SystemUsername); + return done; + } + + /// + /// Stores outgoing links for a page. + /// + /// The page. + /// The raw content. + public static void StorePageOutgoingLinks(PageInfo page, string content) { + string[] linkedPages; + Formatter.Format(content, false, FormattingContext.PageContent, page, out linkedPages); + + string lowercaseName = page.FullName.ToLowerInvariant(); + + // Avoid self-references + List cleanLinkedPages = new List(linkedPages); + for(int i = cleanLinkedPages.Count - 1; i >= 0; i--) { + if(cleanLinkedPages[i].ToLowerInvariant() == lowercaseName) { + cleanLinkedPages.RemoveAt(i); + } + } + + bool doneLinks = Settings.Provider.StoreOutgoingLinks(page.FullName, cleanLinkedPages.ToArray()); + if(!doneLinks) { + Log.LogEntry("Could not store outgoing links for page " + page.FullName, EntryType.Error, Log.SystemUsername); + } + } + + /// + /// Deletes the old backups if the current number of backups exceeds the limit. + /// + /// The page. + private static void DeleteOldBackupsIfNeeded(PageInfo page) { + int maxBackups = Settings.KeptBackupNumber; + if(maxBackups == -1) return; + + // Oldest to newest: 0, 1, 2, 3 + List backups = GetBackups(page); + if(backups.Count > maxBackups) { + backups.Reverse(); + DeleteBackups(page, backups[maxBackups]); + } + } + + /// + /// Removes a user from an array. + /// + /// The array of users. + /// The user to remove. + /// The resulting array without the specified user. + private static UserInfo[] RemoveUserFromArray(UserInfo[] users, UserInfo userToRemove) { + if(userToRemove == null) return users; + + List temp = new List(users); + UsernameComparer comp = new UsernameComparer(); + temp.RemoveAll(delegate(UserInfo elem) { return comp.Compare(elem, userToRemove) == 0; }); + + return temp.ToArray(); + } + + /// + /// Sends the email notification for a page change. + /// + /// The page that was modified. + /// The author of the modification. + private static void SendEmailNotificationForPage(PageInfo page, UserInfo author) { + if(page == null) return; + + PageContent content = Content.GetPageContent(page, false); + + UserInfo[] usersToNotify = Users.GetUsersToNotifyForPageChange(page); + usersToNotify = RemoveUserFromArray(usersToNotify, author); + string[] recipients = EmailTools.GetRecipients(usersToNotify); + + string body = Settings.Provider.GetMetaDataItem(MetaDataItem.PageChangeMessage, null); + + string title = FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.Other, page); + + EmailTools.AsyncSendMassEmail(recipients, Settings.SenderEmail, + Settings.WikiTitle + " - " + title, + body.Replace("##PAGE##", title).Replace("##USER##", content.User).Replace("##DATETIME##", + Preferences.AlignWithServerTimezone(content.LastModified).ToString(Settings.DateTimeFormat)).Replace("##COMMENT##", + (string.IsNullOrEmpty(content.Comment) ? Exchanger.ResourceExchanger.GetResource("None") : content.Comment)).Replace("##LINK##", + Settings.MainUrl + Tools.UrlEncode(page.FullName) + Settings.PageExtension).Replace("##WIKITITLE##", Settings.WikiTitle), + false); + } + + /// + /// Determines whether a user can edit a page. + /// + /// The page. + /// The username. + /// The groups. + /// A value indicating whether the user can edit the page. + /// A value indicating whether the user can edit the page with subsequent approval. + public static void CanEditPage(PageInfo page, string username, string[] groups, + out bool canEdit, out bool canEditWithApproval) { + + canEdit = false; + canEditWithApproval = false; + switch(Settings.ChangeModerationMode) { + case ChangeModerationMode.RequirePageEditingPermissions: + canEdit = AuthChecker.CheckActionForPage(page, Actions.ForPages.ManagePage, username, groups); + canEditWithApproval = AuthChecker.CheckActionForPage(page, Actions.ForPages.ModifyPage, username, groups); + break; + case ChangeModerationMode.RequirePageViewingPermissions: + canEdit = AuthChecker.CheckActionForPage(page, Actions.ForPages.ModifyPage, username, groups); + canEditWithApproval = AuthChecker.CheckActionForPage(page, Actions.ForPages.ReadPage, username, groups); + break; + case ChangeModerationMode.None: + canEdit = AuthChecker.CheckActionForPage(page, Actions.ForPages.ModifyPage, username, groups); + canEditWithApproval = false; + break; + } + if(canEditWithApproval && canEdit) canEditWithApproval = false; + + if(canEdit && !string.IsNullOrEmpty(Settings.IpHostFilter)) + canEdit = VerifyIpHostFilter(); + } + + /// + /// Verifies whether or not the current user's ip address is in the host filter or not. + /// + /// + private static bool VerifyIpHostFilter() { + const RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace; + var hostAddress = HttpContext.Current.Request.UserHostAddress; + var ips = Settings.IpHostFilter.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + // For each IP in the host filter setting + foreach(var ip in ips) { + + // Split each by the . + var digits = ip.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + var regExpression = string.Empty; + foreach(var digit in digits) { + + // Build a regex to check against the host ip. + if(regExpression != string.Empty) + regExpression += "\\."; + + if(digit == "*") + regExpression += "\\d{1,3}"; + else + regExpression += digit; + } + + // If we match, then the user is in the filter, return false. + var regex = new Regex(regExpression, options); + if(regex.IsMatch(hostAddress)) + return false; + } + return true; // Made it here, then all is well, return true. + } + + /// + /// Determines whether a user can approve/reject a draft of a page. + /// + /// The page. + /// The username. + /// The groups. + /// true if the user can approve/reject a draft of the page, false otherwise. + public static bool CanApproveDraft(PageInfo page, string username, string[] groups) { + string requiredAction = Actions.ForPages.ManagePage; + + // TODO: decide whether it is incorrect to require only ModifyPage permission + /*switch(Settings.ChangeModerationMode) { + case ChangeModerationMode.None: + return; + case ChangeModerationMode.RequirePageViewingPermissions: + requiredAction = Actions.ForPages.ModifyPage; + break; + case ChangeModerationMode.RequirePageEditingPermissions: + requiredAction = Actions.ForPages.ManagePage; + break; + default: + throw new NotSupportedException(); + }*/ + + return AuthChecker.CheckActionForPage(page, requiredAction, username, groups); + } + + /// + /// Sends a draft notification to "administrators". + /// + /// The edited page. + /// The title. + /// The comment. + /// The author. + public static void SendEmailNotificationForDraft(PageInfo currentPage, string title, string comment, string author) { + // Decide the users to notify based on the ChangeModerationMode + // Retrieve the list of matching users + // Asynchronously send the notification + + // Retrieve all the users that have a grant on requiredAction for the current page + // TODO: make this work when Users.GetUsers does not return all existing users but only a sub-set + List usersToNotify = new List(10); + foreach(UserInfo user in Users.GetUsers()) { + if(CanApproveDraft(currentPage, user.Username, user.Groups)) { + usersToNotify.Add(user); + } + } + usersToNotify.Add(new UserInfo("admin", "Administrator", Settings.ContactEmail, + true, DateTime.Now, null)); + + string subject = Settings.WikiTitle + " - " + Exchanger.ResourceExchanger.GetResource("ApproveRejectDraft") + ": " + title; + string body = Settings.Provider.GetMetaDataItem(MetaDataItem.ApproveDraftMessage, null); + body = body.Replace("##PAGE##", title).Replace("##USER##", author).Replace("##DATETIME##", + Preferences.AlignWithServerTimezone(DateTime.Now).ToString(Settings.DateTimeFormat)).Replace("##COMMENT##", + string.IsNullOrEmpty(comment) ? Exchanger.ResourceExchanger.GetResource("None") : comment).Replace("##LINK##", + Settings.MainUrl + UrlTools.BuildUrl("Edit.aspx?Page=", Tools.UrlEncode(currentPage.FullName))).Replace("##LINK2##", + Settings.MainUrl + "AdminPages.aspx?Admin=" + Tools.UrlEncode(currentPage.FullName)).Replace("##WIKITITLE##", + Settings.WikiTitle); + + EmailTools.AsyncSendMassEmail(EmailTools.GetRecipients(usersToNotify.ToArray()), + Settings.SenderEmail, subject, body, false); + } + + /// + /// Gets the list of all the Pages of a namespace. + /// + /// The namespace (null for the root). + /// The pages. + public static List GetPages(NamespaceInfo nspace) { + List allPages = new List(10000); + + // Retrieve all pages from Pages Providers + int count = 0; + foreach(IPagesStorageProviderV30 provider in Collectors.PagesProviderCollector.AllProviders) { + count++; + allPages.AddRange(provider.GetPages(nspace)); + } + + if(count > 1) { + allPages.Sort(new PageNameComparer()); + } + + return allPages; + } + + /// + /// Gets the global number of pages. + /// + /// The number of pages. + public static int GetGlobalPageCount() { + int count = 0; + + foreach(IPagesStorageProviderV30 prov in Collectors.PagesProviderCollector.AllProviders) { + count += prov.GetPages(null).Length; + foreach(NamespaceInfo nspace in prov.GetNamespaces()) { + count += prov.GetPages(nspace).Length; + } + } + + return count; + } + + /// + /// Gets the incoming links for a page. + /// + /// The page. + /// The incoming links. + public static string[] GetPageIncomingLinks(PageInfo page) { + if(page == null) return null; + + IDictionary allLinks = Settings.Provider.GetAllOutgoingLinks(); + string[] knownPages = new string[allLinks.Count]; + allLinks.Keys.CopyTo(knownPages, 0); + + List result = new List(20); + + foreach(string key in knownPages) { + if(Contains(allLinks[key], page.FullName)) { + // result is likely to be very small, so a linear search is fine + if(!result.Contains(key)) result.Add(key); + } + } + + return result.ToArray(); + } + + /// + /// Gets the outgoing links of a page. + /// + /// The page. + /// The outgoing links. + public static string[] GetPageOutgoingLinks(PageInfo page) { + if(page == null) return null; + return Settings.Provider.GetOutgoingLinks(page.FullName); + } + + /// + /// Gets all the pages in a namespace without incoming links. + /// + /// The namespace (null for the root). + /// The orphaned pages. + public static PageInfo[] GetOrphanedPages(NamespaceInfo nspace) { + List pages = GetPages(nspace); + IDictionary allLinks = Settings.Provider.GetAllOutgoingLinks(); + string[] knownPages = new string[allLinks.Count]; + allLinks.Keys.CopyTo(knownPages, 0); + + Dictionary result = new Dictionary(pages.Count); + + foreach(PageInfo page in pages) { + result.Add(page, false); + foreach(string key in knownPages) { + if(Contains(allLinks[key], page.FullName)) { + // page has incoming links + result[page] = true; + } + } + } + + return ExtractNegativeKeys(result); + } + + /// + /// Gets the wanted/inexistent pages in all namespaces. + /// + /// The namespace (null for the root). + /// The wanted/inexistent pages (dictionary wanted_page->linking_pages). + public static Dictionary> GetWantedPages(string nspace) { + if(string.IsNullOrEmpty(nspace)) nspace = null; + + IDictionary allLinks = Settings.Provider.GetAllOutgoingLinks(); + string[] knownPages = new string[allLinks.Count]; + allLinks.Keys.CopyTo(knownPages, 0); + + Dictionary> result = new Dictionary>(100); + + foreach(string key in knownPages) { + foreach(string link in allLinks[key]) { + string linkNamespace = NameTools.GetNamespace(link); + if(linkNamespace == nspace) { + + PageInfo tempPage = FindPage(link); + if(tempPage == null) { + if(!result.ContainsKey(link)) result.Add(link, new List(3)); + result[link].Add(key); + } + } + } + } + + return result; + } + + /// + /// Determines whether an array contains a value. + /// + /// The type of the elements in the array. + /// The array. + /// The value. + /// true if the array contains the value, false otherwise. + private static bool Contains(T[] array, T value) { + return Array.IndexOf(array, value) >= 0; + } + + /// + /// Extracts the negative keys from a dictionary. + /// + /// The type of the key. + /// The dictionary. + /// The negative keys. + private static T[] ExtractNegativeKeys(Dictionary data) { + List result = new List(data.Count); + + foreach(KeyValuePair pair in data) { + if(!pair.Value) result.Add(pair.Key); + } + + return result.ToArray(); + } + + #endregion + + #region Categories + + /// + /// Finds a Category. + /// + /// The full name of the Category to Find (case unsensitive). + /// The correct object or null if no category is found. + public static CategoryInfo FindCategory(string fullName) { + if(string.IsNullOrEmpty(fullName)) return null; + + IPagesStorageProviderV30 defProv = Collectors.PagesProviderCollector.GetProvider(Settings.DefaultPagesProvider); + CategoryInfo category = defProv.GetCategory(fullName); + if(category != null) return category; + + foreach(IPagesStorageProviderV30 prov in Collectors.PagesProviderCollector.AllProviders) { + if(prov != defProv) { + category = prov.GetCategory(fullName); + if(category != null) return category; + } + } + + return null; + } + + /// + /// Creates a new Category. + /// + /// The target namespace (null for the root). + /// The Name of the Category. + /// true if the category is created, false otherwise. + public static bool CreateCategory(NamespaceInfo nspace, string name) { + string namespaceName = nspace != null ? nspace.Name : null; + return CreateCategory(namespaceName, name); + } + + /// + /// Creates a new Category. + /// + /// The target namespace (null for the root). + /// The Name of the Category. + /// true if the category is created, false otherwise. + public static bool CreateCategory(string nspace, string name) { + return CreateCategory(nspace, name, Collectors.PagesProviderCollector.GetProvider(Settings.DefaultPagesProvider)); + } + + /// + /// Creates a new Category in the specifued Provider. + /// + /// The target namespace (null for the root). + /// The Name of the Category. + /// The Provider. + /// true if the category is created, false otherwise. + public static bool CreateCategory(NamespaceInfo nspace, string name, IPagesStorageProviderV30 provider) { + string namespaceName = nspace != null ? nspace.Name : null; + return CreateCategory(namespaceName, name, provider); + } + + /// + /// Creates a new Category in the specifued Provider. + /// + /// The target namespace (null for the root). + /// The Name of the Category. + /// The Provider. + /// true if the category is created, false otherwise. + public static bool CreateCategory(string nspace, string name, IPagesStorageProviderV30 provider) { + if(provider == null) provider = Collectors.PagesProviderCollector.GetProvider(Settings.DefaultPagesProvider); + + if(provider.ReadOnly) return false; + + string fullName = NameTools.GetFullName(nspace, name); + + if(FindCategory(fullName) != null) return false; + + CategoryInfo newCat = provider.AddCategory(nspace, name); + if(newCat != null) { + Log.LogEntry("Category " + fullName + " created", EntryType.General, Log.SystemUsername); + + // Because of transclusion and other page-linking features, it is necessary to clear the whole cache + Content.ClearPseudoCache(); + Content.InvalidateAllPages(); + + return true; + } + else { + Log.LogEntry("Category creation failed for " + fullName, EntryType.Error, Log.SystemUsername); + return false; + } + } + + /// + /// Removes a Category. + /// + /// The Category to remove. + /// True if the Category has been removed successfully. + public static bool RemoveCategory(CategoryInfo category) { + if(category.Provider.ReadOnly) return false; + + bool done = category.Provider.RemoveCategory(category); + if(done) Log.LogEntry("Category " + category.FullName + " removed", EntryType.General, Log.SystemUsername); + else Log.LogEntry("Category deletion failed for " + category.FullName, EntryType.Error, Log.SystemUsername); + + // Because of transclusion and other page-linking features, it is necessary to clear the whole cache + Content.ClearPseudoCache(); + Content.InvalidateAllPages(); + + return done; + } + + /// + /// Renames a Category. + /// + /// The Category to rename. + /// The new Name of the Category. + /// True if the Category has been renamed successfully. + public static bool RenameCategory(CategoryInfo category, string newName) { + if(category.Provider.ReadOnly) return false; + + string newFullName = NameTools.GetFullName(NameTools.GetNamespace(category.FullName), newName); + + if(FindCategory(newFullName) != null) return false; + + string oldName = category.FullName; + + CategoryInfo newCat = category.Provider.RenameCategory(category, newName); + if(newCat != null) Log.LogEntry("Category " + oldName + " renamed to " + newFullName, EntryType.General, Log.SystemUsername); + else Log.LogEntry("Category rename failed for " + oldName + " (" + newFullName + ")", EntryType.Error, Log.SystemUsername); + + // Because of transclusion and other page-linking features, it is necessary to clear the whole cache + Content.ClearPseudoCache(); + Content.InvalidateAllPages(); + + return newCat != null; + } + + /// + /// Gets the Categories of a Page. + /// + /// The Page. + /// The Categories of the Page. + public static CategoryInfo[] GetCategoriesForPage(PageInfo page) { + if(page == null) return new CategoryInfo[0]; + + CategoryInfo[] categories = page.Provider.GetCategoriesForPage(page); + + return categories; + } + + /// + /// Gets all the Uncategorized Pages. + /// + /// The namespace. + /// The Uncategorized Pages. + public static PageInfo[] GetUncategorizedPages(NamespaceInfo nspace) { + if(nspace == null) { + List pages = new List(1000); + + int count = 0; + foreach(IPagesStorageProviderV30 prov in Collectors.PagesProviderCollector.AllProviders) { + count++; + pages.AddRange(prov.GetUncategorizedPages(null)); + } + + if(count > 1) { + pages.Sort(new PageNameComparer()); + } + + return pages.ToArray(); + } + else { + PageInfo[] pages = nspace.Provider.GetUncategorizedPages(nspace); + return pages; + } + } + + /// + /// Gets the valid Categories for a Page, i.e. the Categories managed by the Page's Provider and in the same namespace as the page. + /// + /// The Page, or null to use the default provider. + /// The valid Categories. + public static CategoryInfo[] GetAvailableCategories(PageInfo page) { + NamespaceInfo pageNamespace = FindNamespace(NameTools.GetNamespace(page.FullName)); + + if(page != null) { + return page.Provider.GetCategories(pageNamespace); + } + else { + return Collectors.PagesProviderCollector.GetProvider(Settings.DefaultPagesProvider).GetCategories(pageNamespace); + } + } + + /// + /// Gets the other Categories of the Provider and Namespace of the specified Category. + /// + /// The Category. + /// The matching Categories. + public static CategoryInfo[] GetMatchingCategories(CategoryInfo category) { + NamespaceInfo nspace = FindNamespace(NameTools.GetNamespace(category.FullName)); + + List allCategories = GetCategories(nspace); + List result = new List(10); + + for(int i = 0; i < allCategories.Count; i++) { + if(allCategories[i].Provider == category.Provider && allCategories[i] != category) { + result.Add(allCategories[i]); + } + } + + return result.ToArray(); + } + + /// + /// Binds a Page with some Categories. + /// + /// The Page to rebind. + /// The Categories to bind the Page with. + /// + /// The specified Categories must be managed by the same Provider that manages the Page. + /// The operation removes all the previous bindings. + /// + /// True if the binding succeeded. + public static bool Rebind(PageInfo page, CategoryInfo[] cats) { + if(page.Provider.ReadOnly) return false; + + string[] names = new string[cats.Length]; + for(int i = 0; i < cats.Length; i++) { + if(cats[i].Provider != page.Provider) return false; + names[i] = cats[i].FullName; // Saves one cycle + } + bool done = page.Provider.RebindPage(page, names); + if(done) Log.LogEntry("Page " + page.FullName + " rebound", EntryType.General, Log.SystemUsername); + else Log.LogEntry("Page rebind failed for " + page.FullName, EntryType.Error, Log.SystemUsername); + + // Because of transclusion and other page-linking features, it is necessary to clear the whole cache + Content.ClearPseudoCache(); + Content.InvalidateAllPages(); + + return done; + } + + /// + /// Merges two Categories. + /// + /// The source Category. + /// The destination Category. + /// True if the Categories have been merged successfully. + /// The destination Category remains, while the source Category is deleted, and all its Pages re-binded in the destination Category. + /// The two Categories must have the same provider. + public static bool MergeCategories(CategoryInfo source, CategoryInfo destination) { + if(source.Provider != destination.Provider) return false; + if(source.Provider.ReadOnly) return false; + + CategoryInfo newCat = source.Provider.MergeCategories(source, destination); + + if(newCat != null) Log.LogEntry("Category " + source.FullName + " merged into " + destination.FullName, EntryType.General, Log.SystemUsername); + else Log.LogEntry("Categories merging failed for " + source.FullName + " into " + destination.FullName, EntryType.Error, Log.SystemUsername); + + // Because of transclusion and other page-linking features, it is necessary to clear the whole cache + Content.ClearPseudoCache(); + Content.InvalidateAllPages(); + + return newCat != null; + } + + /// + /// Gets the list of all the Categories of a namespace. The list shouldn't be modified. + /// + /// The namespace (null for the root). + /// The categories, sorted by name. + public static List GetCategories(NamespaceInfo nspace) { + List allCategories = new List(50); + + // Retrieve all the categories from Pages Provider + int count = 0; + foreach(IPagesStorageProviderV30 provider in Collectors.PagesProviderCollector.AllProviders) { + count++; + allCategories.AddRange(provider.GetCategories(nspace)); + } + + if(count > 1) { + allCategories.Sort(new CategoryNameComparer()); + } + + return allCategories; + } + + #endregion + + #region Page Discussion + + /// + /// Gets the Page Messages. + /// + /// The Page. + /// The list of the first-level Messages, containing the replies properly nested. + public static Message[] GetPageMessages(PageInfo page) { + return page.Provider.GetMessages(page); + } + + /// + /// Gets the total number of Messages in a Page Discussion. + /// + /// The Page. + /// The number of messages. + public static int GetMessageCount(PageInfo page) { + return page.Provider.GetMessageCount(page); + } + + /// + /// Finds a Message. + /// + /// The Messages. + /// The Message ID. + /// The Message or null. + public static Message FindMessage(Message[] messages, int id) { + Message result = null; + for(int i = 0; i < messages.Length; i++) { + if(messages[i].ID == id) { + result = messages[i]; + } + if(result == null) { + result = FindMessage(messages[i].Replies, id); + } + if(result != null) break; + } + return result; + } + + /// + /// Adds a new Message to a Page. + /// + /// The Page. + /// The Username. + /// The Subject. + /// The Date/Time. + /// The Body. + /// The Parent Message ID, or -1. + /// True if the Message has been added successfully. + public static bool AddMessage(PageInfo page, string username, string subject, DateTime dateTime, string body, int parent) { + if(page.Provider.ReadOnly) return false; + + bool done = page.Provider.AddMessage(page, username, subject, dateTime, body, parent); + if(done) { + SendEmailNotificationForMessage(page, Users.FindUser(username), + Tools.GetMessageIdForAnchor(dateTime), subject); + + PageContent content = Content.GetPageContent(page, false); + RecentChanges.AddChange(page.FullName, content.Title, subject, dateTime, username, Change.MessagePosted, ""); + Host.Instance.OnPageActivity(page, null, username, PageActivity.MessagePosted); + } + return done; + } + + /// + /// Sends the email notification for a new message. + /// + /// The page the message was posted to. + /// The author of the message. + /// The message ID to be used for anchors. + /// The message subject. + private static void SendEmailNotificationForMessage(PageInfo page, UserInfo author, string id, string subject) { + if(page == null) return; + + PageContent content = Content.GetPageContent(page, false); + + UserInfo[] usersToNotify = Users.GetUsersToNotifyForDiscussionMessages(page); + usersToNotify = RemoveUserFromArray(usersToNotify, author); + string[] recipients = EmailTools.GetRecipients(usersToNotify); + + string body = Settings.Provider.GetMetaDataItem(MetaDataItem.DiscussionChangeMessage, null); + + string title = FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.Other, page); + + EmailTools.AsyncSendMassEmail(recipients, Settings.SenderEmail, + Settings.WikiTitle + " - " + title, + body.Replace("##PAGE##", title).Replace("##USER##", author != null ? Users.GetDisplayName(author) : "anonymous").Replace("##DATETIME##", + Preferences.AlignWithServerTimezone(content.LastModified).ToString(Settings.DateTimeFormat)).Replace("##SUBJECT##", + subject).Replace("##LINK##", Settings.MainUrl + Tools.UrlEncode(page.FullName) + + Settings.PageExtension + "?Discuss=1#" + id).Replace("##WIKITITLE##", Settings.WikiTitle), + false); + } + + /// + /// Removes a Message. + /// + /// The Page. + /// The ID of the Message to remove. + /// A value specifying whether or not to remove the replies. + /// True if the Message has been removed successfully. + public static bool RemoveMessage(PageInfo page, int id, bool removeReplies) { + if(page.Provider.ReadOnly) return false; + + Message[] messages = page.Provider.GetMessages(page); + Message msg = FindMessage(messages, id); + + bool done = page.Provider.RemoveMessage(page, id, removeReplies); + if(done) { + PageContent content = Content.GetPageContent(page, false); + RecentChanges.AddChange(page.FullName, content.Title, msg.Subject, DateTime.Now, msg.Username, Change.MessageDeleted, ""); + Host.Instance.OnPageActivity(page, null, null, PageActivity.MessageDeleted); + } + return done; + } + + /// + /// Removes all messages of a Page. + /// + /// The Page. + /// true if the messages are removed, false otherwise. + public static bool RemoveAllMessages(PageInfo page) { + if(page.Provider.ReadOnly) return false; + + Message[] messages = GetPageMessages(page); + + bool done = true; + foreach(Message msg in messages) { + done &= page.Provider.RemoveMessage(page, msg.ID, true); + } + + return done; + } + + /// + /// Modifies a Message. + /// + /// The Page. + /// The ID of the Message to modify. + /// The Username. + /// The Subject. + /// The Date/Time. + /// The Body. + /// True if the Message has been modified successfully. + public static bool ModifyMessage(PageInfo page, int id, string username, string subject, DateTime dateTime, string body) { + if(page.Provider.ReadOnly) return false; + + bool done = page.Provider.ModifyMessage(page, id, username, subject, dateTime, body); + if(done) { + PageContent content = Content.GetPageContent(page, false); + RecentChanges.AddChange(page.FullName, content.Title, subject, dateTime, username, Change.MessageEdited, ""); + Host.Instance.OnPageActivity(page, null, username, PageActivity.MessageModified); + } + return done; + } + + #endregion + + /// + /// Checks the validity of a Page name. + /// + /// The Page name. + /// True if the name is valid. + public static bool IsValidName(string name) { + if(name == null) return false; + if(name.Replace(" ", "").Length == 0 || name.Length > 100 || + name.Contains("?") || name.Contains("<") || name.Contains(">") || name.Contains("|") || name.Contains(":") || + name.Contains("*") || name.Contains("\"") || name.Contains("/") || name.Contains("\\") || name.Contains("&") || + name.Contains("%") || name.Contains("'") || name.Contains("\"") || name.Contains("+") || name.Contains(".") || + name.Contains("#") || name.Contains("[") || name.Contains("]")) { + return false; + } + else return true; + } + + } + + /// + /// Compares PageContent objects. + /// + public class PageContentDateComparer : IComparer { + + /// + /// Compares two PageContent objects, using the DateTime as parameter. + /// + /// The first object. + /// The second object. + /// The result of the comparison (1, 0 or -1). + public int Compare(PageContent x, PageContent y) { + return x.LastModified.CompareTo(y.LastModified); + } + + } + +} diff --git a/Core/PagesStorageProvider.cs b/Core/PagesStorageProvider.cs new file mode 100644 index 0000000..0fb8134 --- /dev/null +++ b/Core/PagesStorageProvider.cs @@ -0,0 +1,3073 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Globalization; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki { + + /// + /// Implements a Pages Storage Provider. + /// + public class PagesStorageProvider : IPagesStorageProviderV30 { + + private const string NamespacesFile = "Namespaces.cs"; + private const string PagesFile = "Pages.cs"; + private const string CategoriesFile = "Categories.cs"; + private const string NavigationPathsFile = "NavigationPaths.cs"; + private const string DraftsDirectory = "Drafts"; + private const string PagesDirectory = "Pages"; + private const string MessagesDirectory = "Messages"; + private const string SnippetsDirectory = "Snippets"; + private const string ContentTemplatesDirectory = "ContentTemplates"; + private const string IndexDocumentsFile = "IndexDocuments.cs"; + private const string IndexWordsFile = "IndexWords.cs"; + private const string IndexMappingsFile = "IndexMappings.cs"; + + private readonly ComponentInformation info = + new ComponentInformation("Local Pages Provider", "ScrewTurn Software", Settings.WikiVersion, "http://www.screwturn.eu", null); + private IHostV30 host; + + // This cache is needed due to performance problems + private NamespaceInfo[] namespacesCache = null; + private PageInfo[] pagesCache = null; + private CategoryInfo[] categoriesCache = null; + + private IInMemoryIndex index; + private IndexStorerBase indexStorer; + + private string GetFullPath(string filename) { + return Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), filename); + } + + private string GetFullPathForPageContent(string filename) { + return Path.Combine(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), PagesDirectory), filename); + } + + private string GetFullPathForPageDrafts(string filename) { + return Path.Combine(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), DraftsDirectory), filename); + } + + private string GetDraftFullPath(LocalPageInfo page) { + /*return GetFullPathForPageDrafts(GetNamespacePartialPathForPageContent(NameTools.GetNamespace(page.FullName)) + + page.File);*/ + return GetFullPathForPageDrafts(page.File); + } + + private string GetFullPathForMessages(string filename) { + return Path.Combine(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), MessagesDirectory), filename); + } + + private string GetFullPathForSnippets(string filename) { + return Path.Combine(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), SnippetsDirectory), filename); + } + + private string GetFullPathForContentTemplate(string filename) { + return Path.Combine(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), ContentTemplatesDirectory), filename); + } + + /// + /// Gets the partial path of the folder that contains page content files for the specified namespace, followed by a directory separator char if appropriate. + /// + /// The namespace, or null. + /// The correct partial path, such as 'Namespace\' or ''. + private string GetNamespacePartialPathForPageContent(string nspace) { + if(nspace == null || nspace.Length == 0) return ""; + else return nspace + Path.DirectorySeparatorChar; + } + + /// + /// Initializes the Provider. + /// + /// The Host of the Provider. + /// The Configuration data, if any. + /// If host or config are null. + /// If config is not valid or is incorrect. + public void Init(IHostV30 host, string config) { + if(host == null) throw new ArgumentNullException("host"); + if(config == null) throw new ArgumentNullException("config"); + + this.host = host; + + if(!LocalProvidersTools.CheckWritePermissions(host.GetSettingValue(SettingName.PublicDirectory))) { + throw new InvalidConfigurationException("Cannot write into the public directory - check permissions"); + } + + if(!Directory.Exists(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), PagesDirectory))) { + Directory.CreateDirectory(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), PagesDirectory)); + } + if(!Directory.Exists(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), MessagesDirectory))) { + Directory.CreateDirectory(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), MessagesDirectory)); + } + if(!Directory.Exists(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), SnippetsDirectory))) { + Directory.CreateDirectory(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), SnippetsDirectory)); + } + if(!Directory.Exists(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), ContentTemplatesDirectory))) { + Directory.CreateDirectory(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), ContentTemplatesDirectory)); + } + if(!Directory.Exists(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), DraftsDirectory))) { + Directory.CreateDirectory(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), DraftsDirectory)); + } + + bool upgradeNeeded = false; + + if(!File.Exists(GetFullPath(NamespacesFile))) { + File.Create(GetFullPath(NamespacesFile)).Close(); + } + + upgradeNeeded = VerifyIfPagesFileNeedsAnUpgrade(); + + if(!File.Exists(GetFullPath(PagesFile))) { + File.Create(GetFullPath(PagesFile)).Close(); + } + else if(upgradeNeeded) { + VerifyAndPerformUpgradeForPages(); + } + + if(!File.Exists(GetFullPath(CategoriesFile))) { + File.Create(GetFullPath(CategoriesFile)).Close(); + } + else if(upgradeNeeded) { + VerifyAndPerformUpgradeForCategories(); + } + + if(!File.Exists(GetFullPath(NavigationPathsFile))) { + File.Create(GetFullPath(NavigationPathsFile)).Close(); + } + else if(upgradeNeeded) { + VerifyAndPerformUpgradeForNavigationPaths(); + } + + // Prepare search index + index = new StandardIndex(); + index.SetBuildDocumentDelegate(BuildDocumentHandler); + indexStorer = new IndexStorer(GetFullPath(IndexDocumentsFile), + GetFullPath(IndexWordsFile), + GetFullPath(IndexMappingsFile), + index); + indexStorer.LoadIndex(); + + if(indexStorer.DataCorrupted) { + host.LogEntry("Search Engine Index is corrupted and needs to be rebuilt\r\n" + + indexStorer.ReasonForDataCorruption.ToString(), LogEntryType.Warning, null, this); + } + } + + /// + /// Verifies the need for a data upgrade, and performs it when needed. + /// + private void VerifyAndPerformUpgradeForCategories() { + // Load file lines, replacing all dots with underscores in category names + + host.LogEntry("Upgrading categories format from 2.0 to 3.0", LogEntryType.General, null, this); + + string[] lines = File.ReadAllText(GetFullPath(CategoriesFile)).Replace("\r", "").Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + if(lines.Length > 0) { + string[] fields; + for(int i = 0; i < lines.Length; i++) { + fields = lines[i].Split('|'); + // Rename category + if(fields[0].Contains(".")) { + fields[0] = fields[0].Replace(".", "_"); + } + // Rename all pages + for(int f = 1; f < fields.Length; f++) { + if(fields[f].Contains(".")) { + fields[f] = fields[f].Replace(".", "_"); + } + } + lines[i] = string.Join("|", fields); + } + + string backupFile = GetFullPath(Path.GetFileNameWithoutExtension(CategoriesFile) + "_v2" + Path.GetExtension(CategoriesFile)); + File.Copy(GetFullPath(CategoriesFile), backupFile); + + File.WriteAllLines(GetFullPath(CategoriesFile), lines); + } + } + + /// + /// Verifies whether the pages files needs to be upgraded. + /// + /// + private bool VerifyIfPagesFileNeedsAnUpgrade() { + string file = GetFullPath(PagesFile); + if(!File.Exists(file)) return false; + + string[] lines = File.ReadAllText(file).Replace("\r", "").Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + string[] fields; + foreach(string line in lines) { + fields = line.Split('|'); + + // Field count has never been 3 except in version 3.0 + if(fields.Length == 3) return false; + + // Version 1.0 + if(fields.Length == 2) return true; + // Version 2.0 + if(fields.Length == 4) return true; + } + + return false; + } + + /// + /// Verifies the need for a data upgrade, and performs it when needed. + /// + private void VerifyAndPerformUpgradeForPages() { + // Load file lines + // Parse first line (if any) with old (v2) algorithm + // If parsing is successful, then the file must be converted + // Conversion consists in removing the 'Status' field and properly modifying permissions of pages + + host.LogEntry("Upgrading pages format from 2.0 to 3.0", LogEntryType.General, null, this); + + //string[] lines = File.ReadAllLines(GetFullPath(PagesFile)); + string[] lines = File.ReadAllText(GetFullPath(PagesFile)).Replace("\r", "").Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + if(lines.Length > 0) { + LocalPageInfo[] pages = new LocalPageInfo[lines.Length]; + char[] oldStylePermissions = new char[lines.Length]; + + char[] splitter = new char[] { '|' }; + + for(int i = 0; i < lines.Length; i++) { + string[] fields = lines[i].Split(splitter, StringSplitOptions.RemoveEmptyEntries); + + // Structure in version 1.0 + // PageName|PageFile + + // Structure in version 2.0 + // PageName|PageFile|Status|DateTime + + // Use default values (status and date/time were not available in earlier versions) + DateTime creationDateTime = new DateTime(2000, 1, 1); + + // Default to Normal + oldStylePermissions[i] = 'N'; + + if(fields.Length == 2) { + // Version 1.0 + // Use the Date/Time of the file + FileInfo fi = new FileInfo(GetFullPathForPageContent(fields[1])); + creationDateTime = fi.CreationTime; + } + if(fields.Length >= 3) { + // Might be version 2.0 + switch(fields[2].ToLowerInvariant()) { + case "locked": + oldStylePermissions[i] = 'L'; + break; + case "public": + oldStylePermissions[i] = 'P'; + break; + case "normal": + oldStylePermissions[i] = 'N'; + break; + default: + try { + // If this succeeded, then it's Version 3.0, not 2.0 (at least for this line) + creationDateTime = DateTime.Parse(fields[2]); + } + catch { + // Use the Date/Time of the file + FileInfo fi = new FileInfo(GetFullPathForPageContent(fields[1])); + creationDateTime = fi.CreationTime; + } + break; + } + if(fields.Length == 4) { + // Version 2.0 + creationDateTime = DateTime.Parse(fields[3]); + } + } + + pages[i] = new LocalPageInfo(fields[0], this, creationDateTime, fields[1]); + pages[i].FullName = pages[i].FullName.Replace(".", "_"); + // TODO: host.UpdateContentForPageRename(oldName, newName); + } + + // Setup permissions for single pages + for(int i = 0; i < oldStylePermissions.Length; i++) { + if(oldStylePermissions[i] != 'N') { + // Need to set permissions emulating old-style behavior + host.UpgradePageStatusToAcl(pages[i], oldStylePermissions[i]); + } + } + + string backupFile = GetFullPath(Path.GetFileNameWithoutExtension(PagesFile) + "_v2" + Path.GetExtension(PagesFile)); + File.Copy(GetFullPath(PagesFile), backupFile); + + // Re-dump pages so that old format data is discarded + DumpPages(pages); + } + } + + /// + /// Verifies the need for a data upgrade, and performs it when needed. + /// + private void VerifyAndPerformUpgradeForNavigationPaths() { + // Load file lines, replacing all dots with underscores in category names + + host.LogEntry("Upgrading navigation paths format from 2.0 to 3.0", LogEntryType.General, null, this); + + string[] lines = File.ReadAllText(GetFullPath(NavigationPathsFile)).Replace("\r", "").Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + if(lines.Length > 0) { + string[] fields; + for(int i = 0; i < lines.Length; i++) { + fields = lines[i].Split('|'); + // Rename navigation path + if(fields[0].Contains(".")) { + fields[0] = fields[0].Replace(".", "_"); + } + // Rename all pages + for(int f = 1; f < fields.Length; f++) { + if(fields[f].Contains(".")) { + fields[f] = fields[f].Replace(".", "_"); + } + } + lines[i] = string.Join("|", fields); + } + + string backupFile = GetFullPath(Path.GetFileNameWithoutExtension(NavigationPathsFile) + "_v2" + Path.GetExtension(NavigationPathsFile)); + File.Copy(GetFullPath(NavigationPathsFile), backupFile); + + File.WriteAllLines(GetFullPath(NavigationPathsFile), lines); + } + } + + /// + /// Method invoked on shutdown. + /// + /// This method might not be invoked in some cases. + public void Shutdown() { + lock(this) { + indexStorer.Dispose(); + } + } + + /// + /// Gets the Information about the Provider. + /// + public ComponentInformation Information { + get { return info; } + } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + public string ConfigHelpHtml { + get { return null; } + } + + /// + /// Gets a value specifying whether the provider is read-only, i.e. it can only provide data and not store it. + /// + public bool ReadOnly { + get { return false; } + } + + /// + /// Finds a page. + /// + /// The namespace that contains the page. + /// The name of the page to find. + /// The pages array. + /// The found page, or null. + private PageInfo FindPage(string nspace, string name, PageInfo[] pages) { + if(name == null) return null; + + PageNameComparer comp = new PageNameComparer(); + PageInfo target = new PageInfo(NameTools.GetFullName(nspace, name), this, DateTime.Now); + + PageInfo result = Array.Find(pages, delegate(PageInfo p) { return comp.Compare(p, target) == 0; }); + + return result; + } + + /// + /// Gets a namespace. + /// + /// The name of the namespace. + /// The , or null if no namespace is found. + /// If name is null. + /// If name is empty. + public NamespaceInfo GetNamespace(string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + lock(this) { + return FindNamespace(name, GetNamespaces()); + } + } + + /// + /// Gets all the sub-namespaces. + /// + /// The sub-namespaces, sorted by name. + public NamespaceInfo[] GetNamespaces() { + lock(this) { + // Namespaces must be loaded from disk + if(namespacesCache == null) { + + string[] lines = File.ReadAllText(GetFullPath(NamespacesFile)).Replace("\r", "").Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + List result = new List(lines.Length); + + PageInfo[] allPages = GetAllPages(); + Array.Sort(allPages, new PageNameComparer()); + + // Line format + // Name[|Name.DefaultPage] + + string[] fields; + char[] delimiters = new char[] { '|' }; + string name = null, defaultPage = null; + + foreach(string line in lines) { + fields = line.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); + + if(fields.Length >= 1) { + name = fields[0]; + } + else continue; // Skip entry + if(fields.Length == 2) { + defaultPage = fields[1]; + } + + result.Add(new NamespaceInfo(name, this, FindPage(name, NameTools.GetLocalName(defaultPage), allPages))); + } + + result.Sort(new NamespaceComparer()); + + namespacesCache = result.ToArray(); + } + + return namespacesCache; + } + } + + /// + /// Finds a namespace. + /// + /// The name of the namespace to find. + /// The namespaces array. + /// The found namespace, or null. + private NamespaceInfo FindNamespace(string name, NamespaceInfo[] namespaces) { + if(name == null) return null; + + NamespaceInfo target = new NamespaceInfo(name, this, null); + NamespaceComparer comp = new NamespaceComparer(); + + NamespaceInfo result = Array.Find(namespaces, delegate(NamespaceInfo n) { return comp.Compare(n, target) == 0; }); + + return result; + } + + /// + /// Determines whether a namespace exists. + /// + /// The name to check. + /// true if the namespace exists or name is null (indicating the root), false otherwise. + private bool NamespaceExists(string name) { + if(name == null) return true; + NamespaceInfo[] allNamespaces = GetNamespaces(); + Array.Sort(allNamespaces, new NamespaceComparer()); + if(FindNamespace(name, allNamespaces) != null) return true; + else return false; + } + + /// + /// Adds a new namespace. + /// + /// The name of the namespace. + /// The correct object. + /// If name is null. + /// If name is empty. + public NamespaceInfo AddNamespace(string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + lock(this) { + if(NamespaceExists(name)) return null; + + // Append a line to the namespaces file + File.AppendAllText(GetFullPath(NamespacesFile), name + "\r\n"); + + // Create folder for page content files + Directory.CreateDirectory(GetFullPathForPageContent(GetNamespacePartialPathForPageContent(name))); + + // Create folder for messages files + Directory.CreateDirectory(GetFullPathForMessages(GetNamespacePartialPathForPageContent(name))); + + namespacesCache = null; + return new NamespaceInfo(name, this, null); + } + } + + /// + /// Dumps namespaces on disk. + /// + /// The namespaces to dump. + private void DumpNamespaces(NamespaceInfo[] namespaces) { + StringBuilder sb = new StringBuilder(namespaces.Length * 20); + foreach(NamespaceInfo ns in namespaces) { + sb.Append(ns.Name); + sb.Append("|"); + sb.Append(ns.DefaultPage != null ? ns.DefaultPage.FullName : ""); + sb.Append("\r\n"); + } + File.WriteAllText(GetFullPath(NamespacesFile), sb.ToString()); + } + + /// + /// Renames a namespace. + /// + /// The namespace to rename. + /// The new name of the namespace. + /// The correct object. + /// If nspace or newName are null. + /// If newName is empty. + public NamespaceInfo RenameNamespace(NamespaceInfo nspace, string newName) { + if(nspace == null) throw new ArgumentNullException("nspace"); + if(newName == null) throw new ArgumentNullException("newName"); + if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); + + lock(this) { + if(NamespaceExists(newName)) return null; + + string oldName = nspace.Name; + + // Remove all pages and their messages from search engine index + foreach(PageInfo page in GetPages(nspace)) { + PageContent content = GetContent(page); + UnindexPage(content); + + Message[] messages = GetMessages(page); + foreach(Message msg in messages) { + UnindexMessageTree(page, msg); + } + } + + // Load namespace list and change name + NamespaceInfo[] allNamespaces = GetNamespaces(); + NamespaceComparer comp = new NamespaceComparer(); + NamespaceInfo result = FindNamespace(nspace.Name, allNamespaces); + if(result == null) return null; + result.Name = newName; + // Change default page full name + if(result.DefaultPage != null) { + result.DefaultPage = new LocalPageInfo(NameTools.GetFullName(newName, NameTools.GetLocalName(result.DefaultPage.FullName)), + this, result.DefaultPage.CreationDateTime, + GetNamespacePartialPathForPageContent(newName) + Path.GetFileName(((LocalPageInfo)result.DefaultPage).File)); + } + + DumpNamespaces(allNamespaces); + + // Update Category list with new namespace name + CategoryInfo[] allCategories = GetAllCategories(); + for(int k = 0; k < allCategories.Length; k++) { + CategoryInfo category = allCategories[k]; + string catNamespace = NameTools.GetNamespace(category.FullName); + if(catNamespace != null && StringComparer.OrdinalIgnoreCase.Compare(catNamespace, oldName) == 0) { + category.FullName = NameTools.GetFullName(newName, NameTools.GetLocalName(category.FullName)); + for(int i = 0; i < category.Pages.Length; i++) { + category.Pages[i] = NameTools.GetFullName(newName, NameTools.GetLocalName(category.Pages[i])); + } + } + } + DumpCategories(allCategories); + + // Rename namespace folder + Directory.Move(GetFullPathForPageContent(GetNamespacePartialPathForPageContent(oldName)), + GetFullPathForPageContent(GetNamespacePartialPathForPageContent(newName))); + + // Rename drafts folder + string oldDraftsFullPath = GetFullPathForPageDrafts(nspace.Name); + if(Directory.Exists(oldDraftsFullPath)) { + string newDraftsFullPath = GetFullPathForPageDrafts(newName); + + Directory.Move(oldDraftsFullPath, newDraftsFullPath); + } + + // Rename messages folder + Directory.Move(GetFullPathForMessages(GetNamespacePartialPathForPageContent(oldName)), + GetFullPathForMessages(GetNamespacePartialPathForPageContent(newName))); + + // Update Page list with new namespace name and file + PageInfo[] allPages = GetAllPages(); + foreach(PageInfo page in allPages) { + string pageNamespace = NameTools.GetNamespace(page.FullName); + if(pageNamespace != null && StringComparer.OrdinalIgnoreCase.Compare(pageNamespace, oldName) == 0) { + LocalPageInfo local = (LocalPageInfo)page; + local.FullName = NameTools.GetFullName(newName, NameTools.GetLocalName(local.FullName)); + local.File = GetNamespacePartialPathForPageContent(newName) + Path.GetFileName(local.File); + } + } + DumpPages(allPages); + + namespacesCache = null; + pagesCache = null; + categoriesCache = null; + + // Re-add all pages and their messages to the search engine index + foreach(PageInfo page in GetPages(result)) { // result contains the new name + PageContent content = GetContent(page); + IndexPage(content); + + Message[] messages = GetMessages(page); + foreach(Message msg in messages) { + IndexMessageTree(page, msg); + } + } + + return result; + } + } + + /// + /// Sets the default page of a namespace. + /// + /// The namespace of which to set the default page. + /// The page to use as default page, or null. + /// The correct object. + /// If nspace is null. + public NamespaceInfo SetNamespaceDefaultPage(NamespaceInfo nspace, PageInfo page) { + if(nspace == null) throw new ArgumentNullException("nspace"); + + lock(this) { + // Find requested namespace and page: if they don't exist, return null + NamespaceInfo[] allNamespaces = GetNamespaces(); + NamespaceInfo targetNamespace = FindNamespace(nspace.Name, allNamespaces); + if(targetNamespace == null) return null; + + LocalPageInfo localPage = null; + + if(page != null) { + localPage = LoadLocalPageInfo(page); + if(localPage == null) return null; + } + + targetNamespace.DefaultPage = localPage; + DumpNamespaces(allNamespaces); + + return new NamespaceInfo(targetNamespace.Name, this, targetNamespace.DefaultPage); + } + } + + /// + /// Removes a namespace. + /// + /// The namespace to remove. + /// true if the namespace is removed, false otherwise. + /// If nspace is null. + public bool RemoveNamespace(NamespaceInfo nspace) { + if(nspace == null) throw new ArgumentNullException("nspace"); + + lock(this) { + // Load all namespaces and remove the one to remove + List allNamespaces = new List(GetNamespaces()); + NamespaceComparer comp = new NamespaceComparer(); + int index = allNamespaces.FindIndex(delegate(NamespaceInfo x) { return comp.Compare(x, nspace) == 0; }); + + if(index >= 0) { + // Delete all categories + foreach(CategoryInfo cat in GetCategories(nspace)) { + RemoveCategory(cat); + } + + // Delete all pages in the namespace (RemovePage removes the page from the search engine index) + nspace.DefaultPage = null; // TODO: Remove this trick (needed in order to delete the default page) + foreach(PageInfo page in GetPages(nspace)) { + RemovePage(page); + } + + // Update namespaces file + allNamespaces.RemoveAt(index); + DumpNamespaces(allNamespaces.ToArray()); + + // Remove namespace folder + Directory.Delete(GetFullPathForPageContent(GetNamespacePartialPathForPageContent(nspace.Name)), true); + + // Remove drafts folder + string oldDraftsFullPath = GetFullPathForPageDrafts(nspace.Name); + if(Directory.Exists(oldDraftsFullPath)) { + Directory.Delete(oldDraftsFullPath, true); + } + + // Remove messages folder + Directory.Delete(GetFullPathForMessages(GetNamespacePartialPathForPageContent(nspace.Name)), true); + + namespacesCache = null; + pagesCache = null; + categoriesCache = null; + + return true; + } + else return false; + } + } + + /// + /// Moves a page from its namespace into another. + /// + /// The page to move. + /// The destination namespace (null for the root). + /// A value indicating whether to copy the page categories in the destination + /// namespace, if not already available. + /// The correct instance of . + /// If page is null. + public PageInfo MovePage(PageInfo page, NamespaceInfo destination, bool copyCategories) { + if(page == null) throw new ArgumentNullException("page"); + + string destinationName = destination != null ? destination.Name : null; + + NamespaceInfo currentNs = FindNamespace(NameTools.GetNamespace(page.FullName), GetNamespaces()); + NamespaceComparer nsComp = new NamespaceComparer(); + if((currentNs == null && destination == null) || nsComp.Compare(currentNs, destination) == 0) return null; + + if(PageExists(new PageInfo(NameTools.GetFullName(destinationName, NameTools.GetLocalName(page.FullName)), this, page.CreationDateTime))) return null; + if(!NamespaceExists(destinationName)) return null; + + if(currentNs != null && currentNs.DefaultPage != null) { + // Cannot move the default page + if(new PageNameComparer().Compare(currentNs.DefaultPage, page) == 0) return null; + } + + // Store categories for copying them, if needed + CategoryInfo[] pageCategories = GetCategoriesForPage(page); + // Update categories names with new namespace (don't modify the same instance because it's actually the cache!) + for(int i = 0; i < pageCategories.Length; i++) { + string[] pages = pageCategories[i].Pages; + pageCategories[i] = new CategoryInfo(NameTools.GetFullName(destinationName, NameTools.GetLocalName(pageCategories[i].FullName)), this); + pageCategories[i].Pages = new string[pages.Length]; + for(int k = 0; k < pages.Length; k++) { + pageCategories[i].Pages[k] = NameTools.GetFullName(destinationName, NameTools.GetLocalName(pages[k])); + } + } + + // Delete category bindings + RebindPage(page, new string[0]); + + // Change namespace and file + PageInfo[] allPages = GetAllPages(); + PageNameComparer comp = new PageNameComparer(); + PageInfo newPage = null; + foreach(PageInfo current in allPages) { + if(comp.Compare(current, page) == 0) { + // Page found, update data + + // Change namespace and file + LocalPageInfo local = (LocalPageInfo)current; + + // Update search engine index + PageContent oldPageContent = GetContent(local); + UnindexPage(oldPageContent); + foreach(Message msg in GetMessages(local)) { + UnindexMessageTree(local, msg); + } + + // Move backups in new folder + MoveBackups(page, destination); + + string newFile = GetNamespacePartialPathForPageContent(destinationName) + Path.GetFileName(local.File); + + // Move data file + File.Move(GetFullPathForPageContent(local.File), GetFullPathForPageContent(newFile)); + + // Move messages file + string messagesFullPath = GetFullPathForMessages(local.File); + if(File.Exists(messagesFullPath)) { + File.Move(messagesFullPath, GetFullPathForMessages(newFile)); + } + + // Move draft file + string draftFullPath = GetFullPathForPageDrafts(local.File); + if(File.Exists(draftFullPath)) { + string newDraftFullPath = GetFullPathForPageDrafts(newFile); + if(!Directory.Exists(Path.GetDirectoryName(newDraftFullPath))) { + Directory.CreateDirectory(Path.GetDirectoryName(newDraftFullPath)); + } + File.Move(draftFullPath, newDraftFullPath); + } + + //local.Namespace = destinationName; + local.FullName = NameTools.GetFullName(destinationName, NameTools.GetLocalName(local.FullName)); + local.File = newFile; + newPage = local; + + DumpPages(allPages); + + // Update search engine index + IndexPage(new PageContent(newPage, oldPageContent.Title, oldPageContent.User, oldPageContent.LastModified, + oldPageContent.Comment, oldPageContent.Content, oldPageContent.Keywords, oldPageContent.Description)); + foreach(Message msg in GetMessages(local)) { + IndexMessageTree(newPage, msg); + } + + break; + } + } + + // Rebind page, if needed + if(copyCategories) { + // Foreach previously bound category, verify that is present in the destination namespace, if not then create it + List newCategories = new List(pageCategories.Length); + foreach(CategoryInfo oldCategory in pageCategories) { + if(!CategoryExists(new CategoryInfo(oldCategory.FullName, this))) { + AddCategory(destination != null ? destination.Name : null, NameTools.GetLocalName(oldCategory.FullName)); + } + newCategories.Add(oldCategory.FullName); + } + RebindPage(newPage, newCategories.ToArray()); + } + + namespacesCache = null; + pagesCache = null; + categoriesCache = null; + return newPage; + } + + /// + /// Moves the backups of a page into a new namespace. + /// + /// The page that is being moved. + /// The destination namespace (null for the root). + /// This method should be invoked before moving the corresponding page. + private void MoveBackups(PageInfo page, NamespaceInfo destination) { + lock(this) { + int[] backups = GetBackups(page); + if(backups == null) return; // Page does not exist + + LocalPageInfo local = (LocalPageInfo)page; + string extension = Path.GetExtension(local.File); + string currDir = GetNamespacePartialPathForPageContent(NameTools.GetNamespace(page.FullName)); + string newDir = GetNamespacePartialPathForPageContent(destination != null ? destination.Name : null); + string currPartialName = currDir + Path.GetFileNameWithoutExtension(local.File) + "."; + string newPartialName = newDir + NameTools.GetLocalName(page.FullName) + "."; + + for(int i = 0; i < backups.Length; i++) { + File.Move(GetFullPathForPageContent(currPartialName + Tools.GetVersionString(backups[i]) + extension), + GetFullPathForPageContent(newPartialName + Tools.GetVersionString(backups[i]) + extension)); + } + } + } + + /// + /// Extracts an instance of from a line contained in the categories file. + /// + /// The line to process. + /// The instance of . + private CategoryInfo BuildCategoryInfo(string fileLine) { + string[] fields = fileLine.Split('|'); + + // Structure + // Namespace.Cat|Namespace.Page1|Namespace.Page2|... + // First field can be 'Cat' or 'Namespace.Cat' + + string nspace, name; + NameTools.ExpandFullName(fields[0], out nspace, out name); + + CategoryInfo result = new CategoryInfo(fields[0], this); + + List pages = new List(fields.Length); + for(int k = 0; k < fields.Length - 1; k++) { + if(PageExists(new PageInfo(fields[k + 1], this, DateTime.Now))) { + pages.Add(fields[k + 1]); + } + } + result.Pages = pages.ToArray(); + + return result; + } + + /// + /// Gets a category. + /// + /// The full name of the category. + /// The , or null if no category is found. + /// If is null. + /// If is empty. + public CategoryInfo GetCategory(string fullName) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + + lock(this) { + CategoryInfo[] categories = GetAllCategories(); + + StringComparer comp = StringComparer.OrdinalIgnoreCase; + + foreach(CategoryInfo cat in categories) { + if(comp.Compare(cat.FullName, fullName) == 0) return cat; + } + + return null; + } + } + + /// + /// Gets all the Categories in a namespace. + /// + /// The namespace. + /// All the Categories in the namespace, sorted by name. + public CategoryInfo[] GetCategories(NamespaceInfo nspace) { + lock(this) { + CategoryInfo[] allCategories = GetAllCategories(); // Sorted + + // Preallocate assuming that there are 4 namespaces and that they are distributed evenly among them: + // categories might be a few dozens at most, so preallocating a smaller number of items is not a problem + List selectedCategories = new List(allCategories.Length / 4); + + // Select categories that have the same namespace as the requested one, + // either null-null or same name + foreach(CategoryInfo cat in allCategories) { + string catNamespace = NameTools.GetNamespace(cat.FullName); + if(nspace == null && catNamespace == null) selectedCategories.Add(cat); + else if(nspace != null && catNamespace != null && StringComparer.OrdinalIgnoreCase.Compare(nspace.Name, catNamespace) == 0) selectedCategories.Add(cat); + } + + return selectedCategories.ToArray(); + } + } + + /// + /// Gets all the Categories. + /// + /// The Categories. + private CategoryInfo[] GetAllCategories() { + lock(this) { + if(categoriesCache == null) { + string tmp = File.ReadAllText(GetFullPath(CategoriesFile)).Replace("\r", ""); + + string[] lines = tmp.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + CategoryInfo[] result = new CategoryInfo[lines.Length]; + + for(int i = 0; i < lines.Length; i++) { + result[i] = BuildCategoryInfo(lines[i]); + } + + Array.Sort(result, new CategoryNameComparer()); + + categoriesCache = result; + } + + return categoriesCache; + } + } + + /// + /// Gets all the categories of a page. + /// + /// The page. + /// The categories, sorted by name. + /// If is null. + public CategoryInfo[] GetCategoriesForPage(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + string pageNamespace = NameTools.GetNamespace(page.FullName); + CategoryInfo[] categories = GetCategories(FindNamespace(pageNamespace, GetNamespaces())); // Sorted + + List result = new List(10); + + PageNameComparer comp = new PageNameComparer(); + foreach(CategoryInfo cat in categories) { + foreach(string p in cat.Pages) { + if(comp.Compare(page, new PageInfo(p, this, DateTime.Now)) == 0) { + result.Add(cat); + break; + } + } + } + + return result.ToArray(); + } + + /// + /// Determines whether a category exists. + /// + /// The category to check. + /// true if the category exists, false otherwise. + private bool CategoryExists(CategoryInfo category) { + lock(this) { + CategoryInfo[] cats = GetCategories(FindNamespace(NameTools.GetNamespace(category.FullName), GetNamespaces())); + CategoryNameComparer comp = new CategoryNameComparer(); + for(int i = 0; i < cats.Length; i++) { + if(comp.Compare(cats[i], category) == 0) return true; + } + } + return false; + } + + /// + /// Adds a new Category. + /// + /// The target namespace (null for the root). + /// The Category name. + /// The correct CategoryInfo object. + /// If is null. + /// If is empty. + public CategoryInfo AddCategory(string nspace, string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + lock(this) { + CategoryInfo result = new CategoryInfo(NameTools.GetFullName(nspace, name), this); + + if(CategoryExists(result)) return null; + + // Structure + // Namespace.Category|Page1|Page2|... + File.AppendAllText(GetFullPath(CategoriesFile), "\r\n" + result.FullName); + result.Pages = new string[0]; + categoriesCache = null; + return result; + } + } + + /// + /// Renames a Category. + /// + /// The Category to rename. + /// The new Name. + /// The correct CategoryInfo object. + /// If or are null. + /// If is empty. + public CategoryInfo RenameCategory(CategoryInfo category, string newName) { + if(category == null) throw new ArgumentNullException("category"); + if(newName == null) throw new ArgumentNullException("newName"); + if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); + + lock(this) { + CategoryInfo result = new CategoryInfo(NameTools.GetFullName(NameTools.GetNamespace(category.FullName), newName), this); + if(CategoryExists(result)) return null; + + CategoryInfo[] cats = GetAllCategories(); + + CategoryNameComparer comp = new CategoryNameComparer(); + for(int i = 0; i < cats.Length; i++) { + if(comp.Compare(cats[i], category) == 0) { + result.Pages = cats[i].Pages; + cats[i] = result; + DumpCategories(cats); + categoriesCache = null; + return result; + } + } + } + return null; + } + + /// + /// Removes a Category. + /// + /// The Category to remove. + /// True if the Category has been removed successfully. + /// If is null. + public bool RemoveCategory(CategoryInfo category) { + if(category == null) throw new ArgumentNullException("category"); + + lock(this) { + CategoryInfo[] cats = GetAllCategories(); + CategoryNameComparer comp = new CategoryNameComparer(); + for(int i = 0; i < cats.Length; i++) { + if(comp.Compare(cats[i], category) == 0) { + List tmp = new List(cats); + tmp.Remove(tmp[i]); + DumpCategories(tmp.ToArray()); + categoriesCache = null; + return true; + } + } + } + return false; + } + + /// + /// Merges two Categories. + /// + /// The source Category. + /// The destination Category. + /// True if the Categories have been merged successfully. + /// The destination Category remains, while the source Category is deleted, and all its Pages re-bound in the destination Category. + /// If or are null. + public CategoryInfo MergeCategories(CategoryInfo source, CategoryInfo destination) { + if(source == null) throw new ArgumentNullException("source"); + if(destination == null) throw new ArgumentNullException("destination"); + + lock(this) { + NamespaceInfo[] allNamespaces = GetNamespaces(); + NamespaceInfo sourceNs = FindNamespace(NameTools.GetNamespace(source.FullName), allNamespaces); + NamespaceInfo destinationNs = FindNamespace(NameTools.GetNamespace(destination.FullName), allNamespaces); + NamespaceComparer nsComp = new NamespaceComparer(); + if(!(sourceNs == null && destinationNs == null) && nsComp.Compare(sourceNs, destinationNs) != 0) { + // Different namespaces + return null; + } + + CategoryInfo[] cats = GetAllCategories(); + int idxSource = -1, idxDest = -1; + CategoryNameComparer comp = new CategoryNameComparer(); + for(int i = 0; i < cats.Length; i++) { + if(comp.Compare(cats[i], source) == 0) idxSource = i; + if(comp.Compare(cats[i], destination) == 0) idxDest = i; + if(idxSource != -1 && idxDest != -1) break; + } + if(idxSource == -1 || idxDest == -1) return null; + + List tmp = new List(cats); + List newPages = new List(cats[idxDest].Pages); + for(int i = 0; i < cats[idxSource].Pages.Length; i++) { + bool found = false; + for(int k = 0; k < newPages.Count; k++) { + if(StringComparer.OrdinalIgnoreCase.Compare(newPages[k], cats[idxSource].Pages[i]) == 0) { + found = true; + break; + } + } + if(!found) { + newPages.Add(cats[idxSource].Pages[i]); + } + } + tmp[idxDest].Pages = newPages.ToArray(); + tmp.Remove(tmp[idxSource]); + DumpCategories(tmp.ToArray()); + CategoryInfo newCat = new CategoryInfo(destination.FullName, this); + newCat.Pages = newPages.ToArray(); + categoriesCache = null; + return newCat; + } + } + + /// + /// Handles the construction of an for the search engine. + /// + /// The input dumped document. + /// The resulting . + private IDocument BuildDocumentHandler(DumpedDocument dumpedDocument) { + if(dumpedDocument.TypeTag == PageDocument.StandardTypeTag) { + string pageName = PageDocument.GetPageName(dumpedDocument.Name); + + PageInfo page = FindPage(NameTools.GetNamespace(pageName), NameTools.GetLocalName(pageName), + GetAllPages()); + + if(page == null) return null; + else return new PageDocument(page, dumpedDocument, TokenizeContent); + } + else if(dumpedDocument.TypeTag == MessageDocument.StandardTypeTag) { + string pageFullName; + int id; + MessageDocument.GetMessageDetails(dumpedDocument.Name, out pageFullName, out id); + + PageInfo page = FindPage(NameTools.GetNamespace(pageFullName), NameTools.GetLocalName(pageFullName), GetAllPages()); + if(page == null) return null; + else return new MessageDocument(page, id, dumpedDocument, TokenizeContent); + } + else return null; + } + + /// + /// Tokenizes page content. + /// + /// The content to tokenize. + /// The tokenized words. + private static WordInfo[] TokenizeContent(string content) { + WordInfo[] words = SearchEngine.Tools.Tokenize(content); + return words; + } + + /// + /// Indexes a page. + /// + /// The content of the page. + /// The number of indexed words, including duplicates. + private int IndexPage(PageContent content) { + lock(this) { + string documentName = PageDocument.GetDocumentName(content.PageInfo); + + DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(content.PageInfo, content.Title), + PageDocument.StandardTypeTag, content.LastModified); + + // Store the document + // The content should always be prepared using IHost.PrepareForSearchEngineIndexing() + int count = index.StoreDocument(new PageDocument(content.PageInfo, ddoc, TokenizeContent), + content.Keywords, host.PrepareContentForIndexing(content.PageInfo, content.Content), null); + + if(count == 0 && content.Content.Length > 0) { + host.LogEntry("Indexed 0 words for page " + content.PageInfo.FullName + ": possible index corruption. Please report this error to the developers", + LogEntryType.Warning, null, this); + } + + return count; + } + } + + /// + /// Removes a page from the search engine index. + /// + /// The content of the page to remove. + private void UnindexPage(PageContent content) { + lock(this) { + string documentName = PageDocument.GetDocumentName(content.PageInfo); + + DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(content.PageInfo, content.Title), + PageDocument.StandardTypeTag, content.LastModified); + index.RemoveDocument(new PageDocument(content.PageInfo, ddoc, TokenizeContent), null); + } + } + + /// + /// Performs a search in the index. + /// + /// The search parameters. + /// The results. + /// If is null. + public SearchResultCollection PerformSearch(SearchParameters parameters) { + if(parameters == null) throw new ArgumentNullException("parameters"); + + lock(this) { + return index.Search(parameters); + } + } + + /// + /// Rebuilds the search index. + /// + public void RebuildIndex() { + lock(this) { + index.Clear(null); + + foreach(PageInfo page in GetAllPages()) { + IndexPage(GetContent(page)); + + foreach(Message msg in GetMessages(page)) { + IndexMessageTree(page, msg); + } + } + } + } + + /// + /// Gets some statistics about the search engine index. + /// + /// The total number of documents. + /// The total number of unique words. + /// The total number of word-document occurrences. + /// The approximated size, in bytes, of the search engine index. + public void GetIndexStats(out int documentCount, out int wordCount, out int occurrenceCount, out long size) { + lock(this) { + documentCount = index.TotalDocuments; + wordCount = index.TotalWords; + occurrenceCount = index.TotalOccurrences; + size = indexStorer.Size; + } + } + + /// + /// Gets a value indicating whether the search engine index is corrupted and needs to be rebuilt. + /// + public bool IsIndexCorrupted { + get { + lock(this) { + return indexStorer.DataCorrupted; + } + } + } + + /// + /// Extracts an instance of from a line of the pages file. + /// + /// The line to process. + /// The instance of . + private LocalPageInfo BuildLocalPageInfo(string fileLine) { + string[] fields = fileLine.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + // Structure (file format already converted from earlier versions) + // Namespace.PageName|PageFile|DateTime + + if(fields.Length == 3) { + return new LocalPageInfo(fields[0], this, DateTime.Parse(fields[2]), fields[1]); + } + else { + throw new ArgumentException("Unsupported data format", "fileLine"); + } + } + + /// + /// Gets all the Pages. + /// + /// All the Pages. + private PageInfo[] GetAllPages() { + lock(this) { + if(pagesCache == null) { + string tmp = File.ReadAllText(GetFullPath(PagesFile)).Replace("\r", ""); + + string[] lines = tmp.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + PageInfo[] result = new PageInfo[lines.Length]; + + for(int i = 0; i < lines.Length; i++) { + result[i] = BuildLocalPageInfo(lines[i]); + } + + pagesCache = result; + } + + return pagesCache; + } + } + + /// + /// Gets a page. + /// + /// The full name of the page. + /// The , or null if no page is found. + /// If is null. + /// If is empty. + public PageInfo GetPage(string fullName) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + + lock(this) { + string nspace, name; + NameTools.ExpandFullName(fullName, out nspace, out name); + return FindPage(nspace, name, GetAllPages()); + } + } + + /// + /// Gets all the Pages in a namespace. + /// + /// The namespace (null for the root). + /// All the Pages in the namespace. The array is not sorted. + public PageInfo[] GetPages(NamespaceInfo nspace) { + lock(this) { + PageInfo[] allPages = GetAllPages(); + + // Preallocate assuming that there are 2 namespaces and that they are evenly distributed across them: + // pages can be as much as many thousands, so preallocating a smaller number can cause a performance loss + List selectedPages = new List(allPages.Length / 2); + + // Select pages that have the same namespace as the requested one, + // either null-null or same name + foreach(PageInfo page in allPages) { + string pageNamespace = NameTools.GetNamespace(page.FullName); + if(nspace == null && pageNamespace == null) selectedPages.Add(page); + if(nspace != null && pageNamespace != null && StringComparer.OrdinalIgnoreCase.Compare(nspace.Name, pageNamespace) == 0) selectedPages.Add(page); + } + + return selectedPages.ToArray(); + } + } + + /// + /// Gets all the pages in a namespace that are bound to zero categories. + /// + /// The namespace (null for the root). + /// The pages, sorted by name. + public PageInfo[] GetUncategorizedPages(NamespaceInfo nspace) { + lock(this) { + PageInfo[] pages = GetPages(nspace); + CategoryInfo[] categories = GetCategories(nspace); + + List result = new List(pages.Length); + + foreach(PageInfo p in pages) { + bool found = false; + foreach(CategoryInfo c in categories) { + foreach(string name in c.Pages) { + if(StringComparer.OrdinalIgnoreCase.Compare(name, p.FullName) == 0) { + found = true; + break; + } + } + } + if(!found) result.Add(p); + } + + return result.ToArray(); + } + } + + /// + /// Finds a corresponding instance of in the available pages. + /// + /// The instance of to "convert" to . + /// The instance of , or null. + private LocalPageInfo LoadLocalPageInfo(PageInfo page) { + if(page == null) return null; + lock(this) { + PageInfo[] pages = GetAllPages(); + PageNameComparer comp = new PageNameComparer(); + for(int i = 0; i < pages.Length; i++) { + if(comp.Compare(pages[i], page) == 0) return pages[i] as LocalPageInfo; + } + } + return null; + } + + /// + /// Determines whether a page exists. + /// + /// The instance of to look for. + /// true if the page exists, false otherwise. + private bool PageExists(PageInfo page) { + lock(this) { + PageInfo[] pages = GetAllPages(); + PageNameComparer comp = new PageNameComparer(); + for(int i = 0; i < pages.Length; i++) { + if(comp.Compare(pages[i], page) == 0) return true; + } + } + return false; + } + + /// + /// Adds a Page. + /// + /// The target namespace (null for the root). + /// The Page Name. + /// The creation Date/Time. + /// The correct PageInfo object or null. + /// This method should not create the content of the Page. + /// If is null. + /// If is empty. + public PageInfo AddPage(string nspace, string name, DateTime creationDateTime) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + lock(this) { + if(!NamespaceExists(nspace)) return null; + if(PageExists(new PageInfo(NameTools.GetFullName(nspace, name), this, DateTime.Now))) return null; + + LocalPageInfo result = new LocalPageInfo(NameTools.GetFullName(nspace, name), this, creationDateTime, + GetNamespacePartialPathForPageContent(nspace) + name + ".cs"); + + BackupPagesFile(); + + // Structure + // Namespace.Page|File|CreationDateTime + File.AppendAllText(GetFullPath(PagesFile), result.FullName + "|" + result.File + "|" + creationDateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss") + "\r\n"); + //File.Create(GetFullPathForPageContent(result.File)).Close(); // Empty content file might cause problems with backups + File.WriteAllText(GetFullPathForPageContent(result.File), "--\r\n--|1900/01/01 0:00:00|\r\n##PAGE##\r\n--"); + pagesCache = null; + + return result; + } + } + + + /// + /// Gets the Content of a Page. + /// + /// The Page. + /// The Page Content object, null if the page does not exist or is null, + /// or an empty instance if the content could not be retrieved (). + public PageContent GetContent(PageInfo page) { + if(page == null) return null; + + lock(this) { + LocalPageInfo local = LoadLocalPageInfo(page); + if(local == null) return null; + + string text = null; + try { + text = File.ReadAllText(GetFullPathForPageContent(local.File)); + } + catch(Exception ex) { + host.LogEntry("Could not load content file (" + local.File + ") for page " + local.FullName + " - returning empty (" + ex.Message + ")", + LogEntryType.Error, null, this); + return PageContent.GetEmpty(page); + } + + return ExtractContent(text, page); + } + } + + private PageContent ExtractContent(string data, PageInfo pageInfo) { + if(data == null) return null; + // Structure (Keywords and Description are new in v3) + // Page Title + // Username|DateTime[|Comment][|(((Keyword,Keyword,Keyword)))(((Description)))] --- Comment is optional + // ##PAGE## + // Content... + data = data.Replace("\r", ""); + string[] lines = data.Split('\n'); + if(lines.Length < 4) { + host.LogEntry("Corrupted or malformed page data for page " + pageInfo.FullName + " - returning empty", LogEntryType.Error, null, this); + return PageContent.GetEmpty(pageInfo); + } + string[] fields = lines[1].Split('|'); + + string comment = null; + string[] keywords = null; + string description = null; + + if(fields.Length >= 3 && !fields[2].StartsWith("(((")) comment = Tools.UnescapeString(fields[2]); + else comment = ""; + + string lastField = fields[fields.Length - 1]; + if(lastField.StartsWith("(((") && lastField.EndsWith(")))")) { + // Keywords and/or description are specified + int closedBracketsIndex = lastField.IndexOf(")))"); // This identifies the end of keywords block + string keywordsBlock = lastField.Substring(0, closedBracketsIndex).Trim('(', ')'); + keywords = keywordsBlock.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + for(int i = 0; i < keywords.Length; i++) { + keywords[i] = Tools.UnescapeString(keywords[i]); + } + + description = Tools.UnescapeString(lastField.Substring(closedBracketsIndex + 3).Trim('(', ')')); + if(string.IsNullOrEmpty(description)) description = null; + } + + int nlIndex = data.IndexOf("\n"); // Index of first new-line char + // Don't consider page title, since it might contain "##PAGE##" + int idx = data.Substring(nlIndex + 1).IndexOf("##PAGE##") + 8 + 1 + nlIndex + 1; + + return new PageContent(pageInfo, lines[0], fields[0], DateTime.Parse(fields[1]), comment, data.Substring(idx), keywords, description); + } + + /// + /// Backups a Page. + /// + /// The Page to backup. + /// True if the Page has been backupped successfully. + private bool Backup(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + lock(this) { + LocalPageInfo local = LoadLocalPageInfo(page); + if(local == null) return false; + + int[] backups = GetBackups(page); + int rev = (backups.Length > 0 ? backups[backups.Length - 1] + 1 : 0); + File.Copy( + GetFullPathForPageContent(local.File), + GetFullPathForPageContent(GetNamespacePartialPathForPageContent(NameTools.GetNamespace(page.FullName)) + + Path.GetFileNameWithoutExtension(local.File) + "." + Tools.GetVersionString(rev) + Path.GetExtension(local.File))); + } + return true; + } + + /// + /// Gets the Backup/Revision numbers of a Page. + /// + /// The Page to get the Backups of. + /// The list of Backup/Revision numbers. + /// If is null. + public int[] GetBackups(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + lock(this) { + LocalPageInfo local = LoadLocalPageInfo(page); + if(local == null) return null; + + // Files in \Pages\[Namespace\]FileName.NNNNN.cs + string dir = GetFullPath(PagesDirectory); + string nsDir = GetNamespacePartialPathForPageContent(NameTools.GetNamespace(page.FullName)); + if(nsDir.Length > 0) dir = Path.Combine(dir, nsDir); + + string[] files = Directory.GetFiles(dir, Path.GetFileNameWithoutExtension(local.File) + ".*" + Path.GetExtension(local.File)); + + List result = new List(30); + for(int i = 0; i < files.Length; i++) { + string num = Path.GetFileNameWithoutExtension(files[i]).Substring(NameTools.GetLocalName(page.FullName).Length + 1); + int bak = -1; + if(int.TryParse(num, out bak)) result.Add(bak); + } + return result.ToArray(); + } + } + + /// + /// Gets the Content of a Backup. + /// + /// The Page. + /// The revision. + /// The content. + /// If is null. + /// If is less than zero. + public PageContent GetBackupContent(PageInfo page, int revision) { + if(page == null) throw new ArgumentNullException("page"); + if(revision < 0) throw new ArgumentOutOfRangeException("revision", "Invalid Revision"); + + lock(this) { + LocalPageInfo local = LoadLocalPageInfo(page); + if(local == null) return null; + + string filename = Path.GetFileNameWithoutExtension(local.File) + "." + Tools.GetVersionString(revision) + Path.GetExtension(local.File); + string path = GetFullPathForPageContent(GetNamespacePartialPathForPageContent(NameTools.GetNamespace(page.FullName)) + filename); + + if(!File.Exists(path)) return null; + else return ExtractContent(File.ReadAllText(path), page); + } + } + + /// + /// Forces to overwrite or create a Backup. + /// + /// The Backup content. + /// The revision. + /// True if the Backup has been created successfully. + /// If is null. + /// If is less than zero. + public bool SetBackupContent(PageContent content, int revision) { + if(content == null) throw new ArgumentNullException("content"); + if(revision < 0) throw new ArgumentOutOfRangeException("Invalid Revision", "revision"); + + lock(this) { + LocalPageInfo local = LoadLocalPageInfo(content.PageInfo); + if(local == null) return false; + + StringBuilder sb = new StringBuilder(); + sb.Append(content.Title); + sb.Append("\r\n"); + sb.Append(content.User); + sb.Append("|"); + sb.Append(content.LastModified.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss")); + if(!string.IsNullOrEmpty(content.Comment)) { + sb.Append("|"); + sb.Append(Tools.EscapeString(content.Comment)); + } + sb.Append("\r\n##PAGE##\r\n"); + sb.Append(content.Content); + + string filename = Path.GetFileNameWithoutExtension(local.File) + "." + Tools.GetVersionString(revision) + Path.GetExtension(local.File); + File.WriteAllText(GetFullPathForPageContent(GetNamespacePartialPathForPageContent(NameTools.GetNamespace(content.PageInfo.FullName)) + filename), sb.ToString()); + } + return true; + } + + /// + /// Renames a Page. + /// + /// The Page to rename. + /// The new Name. + /// True if the Page has been renamed successfully. + /// If or are null. + /// If is empty. + public PageInfo RenamePage(PageInfo page, string newName) { + if(page == null) throw new ArgumentNullException("page"); + if(newName == null) throw new ArgumentNullException("newName"); + if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); + + lock(this) { + if(PageExists(new PageInfo(NameTools.GetFullName(NameTools.GetNamespace(page.FullName), newName), this, DateTime.Now))) return null; + + NamespaceInfo currentNs = FindNamespace(NameTools.GetNamespace(page.FullName), GetNamespaces()); + if(currentNs != null && currentNs.DefaultPage != null) { + // Cannot rename the default page + if(new PageNameComparer().Compare(currentNs.DefaultPage, page) == 0) return null; + } + + PageInfo[] pgs = GetAllPages(); + PageNameComparer comp = new PageNameComparer(); + + // Store page's categories for rebinding with new page + CategoryInfo[] tmp = GetCategoriesForPage(page); + string[] cats = new string[tmp.Length]; + for(int i = 0; i < tmp.Length; i++) { + cats[i] = tmp[i].FullName; + } + // Remove all bindings for old page + RebindPage(page, new string[0]); + + // Find page and rename files + for(int i = 0; i < pgs.Length; i++) { + if(comp.Compare(pgs[i], page) == 0) { + + LocalPageInfo local = pgs[i] as LocalPageInfo; + + PageContent oldContent = GetContent(page); + + Message[] messages = GetMessages(local); + + // Update search engine index + UnindexPage(oldContent); + foreach(Message msg in messages) { + UnindexMessageTree(local, msg); + } + + local.FullName = NameTools.GetFullName(NameTools.GetNamespace(local.FullName), newName); + + string newFile = GetNamespacePartialPathForPageContent(NameTools.GetNamespace(local.FullName)) + newName + + Path.GetExtension(local.File); + + // Rename content file + string oldLocalName = local.File; + string oldFullPath = GetFullPathForPageContent(local.File); + string newFullPath = GetFullPathForPageContent(newFile); + File.Move(oldFullPath, newFullPath); + + // Rename messages file + if(File.Exists(GetFullPathForMessages(oldLocalName))) { + File.Move(GetFullPathForMessages(oldLocalName), GetFullPathForMessages(newFile)); + } + + // Rename draft file, if any + string oldDraftFullPath = GetDraftFullPath(local); + if(File.Exists(oldDraftFullPath)) { + string newDraftFullPath = GetDraftFullPath(new LocalPageInfo(local.FullName, this, local.CreationDateTime, newFile)); + + File.Move(oldDraftFullPath, newDraftFullPath); + } + + // Set new filename (local references an element in the pgs array) + local.File = newFile; + + // Rename all backups, store new page list on disk + // and rebind new page with old categories + RenameBackups(page, newName); + DumpPages(pgs); + // Clear internal cache + categoriesCache = null; + pagesCache = null; + // Re-bind page with previously saved categories + RebindPage(local, cats); + + // Update search engine index + IndexPage(new PageContent(local, oldContent.Title, oldContent.User, oldContent.LastModified, oldContent.Comment, + oldContent.Content, oldContent.Keywords, oldContent.Description)); + foreach(Message msg in messages) { + IndexMessageTree(local, msg); + } + + return local; + } + } + + // Page not found, return null + return null; + } + } + + /// + /// Renames the backups of a page. + /// + /// The page that is being renamed. + /// The new name of the page. + /// This method should be invoked before renaming the corresponding page. + private void RenameBackups(PageInfo page, string newName) { + lock(this) { + int[] backups = GetBackups(page); + if(backups == null) return; // Page does not exist + + LocalPageInfo local = (LocalPageInfo)page; + string extension = Path.GetExtension(local.File); + string nsDir = GetNamespacePartialPathForPageContent(NameTools.GetNamespace(page.FullName)); + string partialName = nsDir + Path.GetFileNameWithoutExtension(local.File) + "."; + + for(int i = 0; i < backups.Length; i++) { + File.Move(GetFullPathForPageContent(partialName + Tools.GetVersionString(backups[i]) + extension), + GetFullPathForPageContent(nsDir + newName + "." + Tools.GetVersionString(backups[i]) + extension)); + } + } + } + + /// + /// Modifies the Content of a Page. + /// + /// The Page. + /// The Title of the Page. + /// The Username. + /// The Date/Time. + /// The Comment of the editor, about this revision. + /// The Page Content. + /// The keywords, usually used for SEO. + /// The description, usually used for SEO. + /// The save mode for this modification. + /// true if the Page has been modified successfully, false otherwise. + /// If saveMode equals Draft and a draft already exists, it is overwritten. + /// If , or are null. + /// If or are empty. + public bool ModifyPage(PageInfo page, string title, string username, DateTime dateTime, string comment, string content, + string[] keywords, string description, SaveMode saveMode) { + + if(page == null) throw new ArgumentNullException("page"); + if(title == null) throw new ArgumentNullException("title"); + if(title.Length == 0) throw new ArgumentException("Title cannot be empty", "title"); + if(username == null) throw new ArgumentNullException("username"); + if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username"); + if(content == null) throw new ArgumentNullException("content"); // content can be empty + + lock(this) { + LocalPageInfo local = LoadLocalPageInfo(page); + if(local == null) return false; + + if(saveMode == SaveMode.Backup) { + Backup(local); + } + + StringBuilder sb = new StringBuilder(); + sb.Append(title); + sb.Append("\r\n"); + sb.Append(username); + sb.Append("|"); + sb.Append(dateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss")); + if(!string.IsNullOrEmpty(comment)) { + sb.Append("|"); + sb.Append(Tools.EscapeString(comment)); + } + if((keywords != null && keywords.Length > 0) || !string.IsNullOrEmpty(description)) { + sb.Append("|((("); + if(keywords != null) { + for(int i = 0; i < keywords.Length; i++) { + sb.Append(Tools.EscapeString(keywords[i])); + if(i != keywords.Length - 1) sb.Append(","); + } + } + sb.Append(")))((("); + sb.Append(Tools.EscapeString(description)); + sb.Append(")))"); + } + sb.Append("\r\n##PAGE##\r\n"); + sb.Append(content); + + if(saveMode == SaveMode.Draft) { + // Create the namespace directory for the draft, if needed + // Drafts\NS\Page.cs + string targetFileFullPath = GetDraftFullPath(local); + if(!Directory.Exists(Path.GetDirectoryName(targetFileFullPath))) { + Directory.CreateDirectory(Path.GetDirectoryName(targetFileFullPath)); + } + File.WriteAllText(targetFileFullPath, sb.ToString()); + } + else { + File.WriteAllText(GetFullPathForPageContent(local.File), sb.ToString()); + + // Update search engine index + PageContent pageContent = new PageContent(page, title, username, dateTime, comment, content, keywords, description); + IndexPage(pageContent); + } + + } + return true; + } + + /// + /// Gets the content of a draft of a Page. + /// + /// The Page. + /// The draft, or null if no draft exists. + /// If is null. + public PageContent GetDraft(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + lock(this) { + LocalPageInfo local = LoadLocalPageInfo(page); + if(local == null) return null; + + string targetFileFullPath = GetDraftFullPath(local); + + if(!File.Exists(targetFileFullPath)) return null; + else return ExtractContent(File.ReadAllText(targetFileFullPath), local); + } + } + + /// + /// Deletes a draft of a Page. + /// + /// The page. + /// true if the draft is deleted, false otherwise. + /// If is null. + public bool DeleteDraft(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + lock(this) { + LocalPageInfo local = LoadLocalPageInfo(page); + if(local == null) return false; + + string targetFileFullPath = GetDraftFullPath(local); + + if(!File.Exists(targetFileFullPath)) return false; + else { + File.Delete(targetFileFullPath); + // Delete directory if empty + if(Directory.GetFiles(Path.GetDirectoryName(targetFileFullPath)).Length == 0) { + Directory.Delete(Path.GetDirectoryName(targetFileFullPath)); + } + return true; + } + } + } + + /// + /// Performs the rollback of a Page to a specified revision. + /// + /// The Page to rollback. + /// The Revision to rollback the Page to. + /// true if the rollback succeeded, false otherwise. + /// If is null. + /// If is less than zero. + public bool RollbackPage(PageInfo page, int revision) { + if(page == null) throw new ArgumentNullException("page"); + if(revision < 0) throw new ArgumentOutOfRangeException("Invalid Revision", "revision"); + + lock(this) { + if(!PageExists(page)) return false; + + // Operations: + // - Load specific revision's content + // - Modify page with loaded content, performing backup + + PageContent revisionContent = GetBackupContent(page, revision); + if(revisionContent == null) return false; + + bool done = ModifyPage(page, revisionContent.Title, revisionContent.User, revisionContent.LastModified, + revisionContent.Comment, revisionContent.Content, revisionContent.Keywords, revisionContent.Description, + SaveMode.Backup); + + return done; + } + } + + /// + /// Deletes the Backups of a Page, up to a specified revision. + /// + /// The Page to delete the backups of. + /// The newest revision to delete (newer revision are kept) or -1 to delete all the Backups. + /// true if the deletion succeeded, false otherwise. + /// If is null. + /// If is less than -1. + public bool DeleteBackups(PageInfo page, int revision) { + if(page == null) throw new ArgumentNullException("page"); + if(revision < -1) throw new ArgumentOutOfRangeException("Invalid Revision", "revision"); + + lock(this) { + int[] temp = GetBackups(page); + if(temp == null) return false; + if(temp.Length == 0) return true; + + List backups = new List(temp); + + int idx = (revision != -1 ? backups.IndexOf(revision) : backups[backups.Count - 1]); + + // Operations + // - Delete old beckups, from 0 to revision + // - Rename newer backups starting from 0 + + LocalPageInfo local = (LocalPageInfo)page; + string extension = Path.GetExtension(local.File); + string filenameNoExt = Path.GetFileNameWithoutExtension(local.File); + string nsDir = GetNamespacePartialPathForPageContent(NameTools.GetNamespace(page.FullName)); + + for(int i = 0; i <= idx; i++) { + File.Delete(GetFullPathForPageContent(nsDir + filenameNoExt + "." + Tools.GetVersionString(backups[i]) + extension)); + } + + if(revision != -1) { + for(int i = revision + 1; i < backups.Count; i++) { + + File.Move(GetFullPathForPageContent(nsDir + filenameNoExt + "." + Tools.GetVersionString(backups[i]) + extension), + GetFullPathForPageContent(nsDir + filenameNoExt + "." + Tools.GetVersionString(backups[i] - revision - 1) + extension)); + } + } + } + return true; + } + + /// + /// Removes a Page. + /// + /// The Page to remove. + /// true if the Page has been removed successfully, false otherwise. + /// If is null. + public bool RemovePage(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + lock(this) { + NamespaceInfo currentNs = FindNamespace(NameTools.GetNamespace(page.FullName), GetNamespaces()); + if(currentNs != null && currentNs.DefaultPage != null) { + // Cannot remove the default page + if(new PageNameComparer().Compare(currentNs.DefaultPage, page) == 0) return false; + } + + List allPages = new List(GetAllPages()); + PageNameComparer comp = new PageNameComparer(); + for(int i = 0; i < allPages.Count; i++) { + if(comp.Compare(allPages[i], page) == 0) { + PageContent content = GetContent(page); + + LocalPageInfo local = page as LocalPageInfo; + + // Update search engine index + UnindexPage(content); + Message[] messages = GetMessages(local); + foreach(Message msg in messages) { + UnindexMessageTree(local, msg); + } + + allPages.Remove(allPages[i]); + DeleteBackups(page, -1); + DumpPages(allPages.ToArray()); + try { + File.Delete(GetFullPathForPageContent(GetNamespacePartialPathForPageContent(NameTools.GetNamespace(page.FullName)) + ((LocalPageInfo)page).File)); + } + catch { } + try { + File.Delete(GetDraftFullPath(local)); + } + catch { } + try { + File.Delete(GetFullPathForMessages(local.File)); + } + catch { } + pagesCache = null; + categoriesCache = null; + + return true; + } + } + } + return false; + } + + /// + /// Binds a Page with one or more Categories. + /// + /// The Page to bind. + /// The Categories to bind the Page with (full name). + /// True if the binding succeeded. + /// After a successful operation, the Page is bound with all and only the categories passed as argument. + /// If or are null. + public bool RebindPage(PageInfo page, string[] categories) { + if(page == null) throw new ArgumentNullException("page"); + if(categories == null) throw new ArgumentNullException("categories"); + + lock(this) { + if(!PageExists(page)) return false; + + CategoryInfo[] cats = GetAllCategories(); + + // Check all categories (they all must exist and be valid) + foreach(string cat in categories) { + if(cat == null) throw new ArgumentNullException("categories", "A category name cannot be null"); + if(cat.Length == 0) throw new ArgumentException("A category name cannot be empty", "categories"); + + CategoryNameComparer comp = new CategoryNameComparer(); + if(Array.Find(cats, delegate(CategoryInfo x) { + return comp.Compare(x, new CategoryInfo(cat, this)) == 0; + }) == null) return false; + } + + // Operations: + // - Remove the Page from every Category + // - For each specified category, add (if needed) the Page + List pages; + CategoryNameComparer catComp = new CategoryNameComparer(); + for(int i = 0; i < cats.Length; i++) { + pages = new List(cats[i].Pages); + + int idx = GetIndex(pages, page.FullName); + + if(idx != -1) pages.Remove(pages[idx]); + cats[i].Pages = pages.ToArray(); + } + + for(int i = 0; i < cats.Length; i++) { + for(int k = 0; k < categories.Length; k++) { + if(catComp.Compare(cats[i], new CategoryInfo(categories[k], this)) == 0) { + pages = new List(cats[i].Pages); + pages.Add(page.FullName); + cats[i].Pages = pages.ToArray(); + } + } + } + DumpCategories(cats); + pagesCache = null; + categoriesCache = null; + } + return true; + } + + private static int GetIndex(List pages, string page) { + for(int i = 0; i < pages.Count; i++) { + if(StringComparer.OrdinalIgnoreCase.Compare(pages[i], page) == 0) return i; + } + return -1; + } + + /// + /// Makes a backup copy of the pages file. + /// + private void BackupPagesFile() { + lock(this) { + File.Copy(GetFullPath(PagesFile), + GetFullPath(Path.GetFileNameWithoutExtension(PagesFile) + + ".bak" + Path.GetExtension(PagesFile)), true); + } + } + + /// + /// Writes all pages in the storage file. + /// + /// The pages to write. + private void DumpPages(PageInfo[] pages) { + lock(this) { + BackupPagesFile(); + + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < pages.Length; i++) { + sb.Append(pages[i].FullName); + sb.Append("|"); + sb.Append(((LocalPageInfo)pages[i]).File); + sb.Append("|"); + sb.Append(pages[i].CreationDateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss")); + sb.Append("\r\n"); + } + File.WriteAllText(GetFullPath(PagesFile), sb.ToString()); + } + } + + /// + /// Makes a backup copy of the categories file. + /// + private void BackupCategoriesFile() { + lock(this) { + File.Copy(GetFullPath(CategoriesFile), + GetFullPath(Path.GetFileNameWithoutExtension(CategoriesFile) + + ".bak" + Path.GetExtension(CategoriesFile)), true); + } + } + + /// + /// Writes all categories in the storage file. + /// + /// The categories. + private void DumpCategories(CategoryInfo[] categories) { + lock(this) { + BackupCategoriesFile(); + + // Format + // NS.Category|NS.Page1|NS.Page2 + StringBuilder sb = new StringBuilder(10480); + for(int i = 0; i < categories.Length; i++) { + sb.Append(categories[i].FullName); + if(categories[i].Pages.Length > 0) { + for(int k = 0; k < categories[i].Pages.Length; k++) { + sb.Append("|"); + sb.Append(categories[i].Pages[k]); + } + } + sb.Append("\r\n"); + } + File.WriteAllText(GetFullPath(CategoriesFile), sb.ToString()); + } + } + + /// + /// Gets the Page Messages. + /// + /// The Page. + /// The list of the first-level Messages, containing the replies properly nested, sorted by date/time. + /// If is null. + public Message[] GetMessages(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + lock(this) { + LocalPageInfo local = LoadLocalPageInfo(page); + if(local == null) return null; + + // Shortcut + if(!File.Exists(GetFullPathForMessages(local.File))) return new Message[0]; + + string data = File.ReadAllText(GetFullPathForMessages(local.File)).Replace("\r", ""); + + string[] lines = data.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + List result = new List(); + + // Structure + // ID|Username|Subject|DateTime|ParentID|Body + + // This algorithm DOES not handle replies that are stored BEFORE their parent, + // so every reply MUST be stored anywhere but AFTER its parent + // (the message tree should be stored depht-first; new messages can be appended at the end of the file) + + string[] fields; + int id, parent; + string username, subject, body; + DateTime dateTime; + for(int i = 0; i < lines.Length; i++) { + fields = lines[i].Split('|'); + id = int.Parse(fields[0]); + username = fields[1]; + subject = Tools.UnescapeString(fields[2]); + dateTime = DateTime.Parse(fields[3]); + parent = int.Parse(fields[4]); + body = Tools.UnescapeString(fields[5]); + if(parent != -1) { + // Find parent + Message p = FindMessage(result, parent); + if(p == null) { + // Add as top-level message + result.Add(new Message(id, username, subject, dateTime, body)); + } + else { + // Add to parent's replies + Message[] newMessages = new Message[p.Replies.Length + 1]; + Array.Copy(p.Replies, newMessages, p.Replies.Length); + newMessages[newMessages.Length - 1] = new Message(id, username, subject, dateTime, body); + p.Replies = newMessages; + } + } + else { + // Add as top-level message + result.Add(new Message(id, username, subject, dateTime, body)); + } + } + + result.Sort((a, b) => { return a.DateTime.CompareTo(b.DateTime); }); + + return result.ToArray(); + } + } + + /// + /// Gets the total number of Messages in a Page Discussion. + /// + /// The Page. + /// The number of messages. + /// If is null. + public int GetMessageCount(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + lock(this) { + LocalPageInfo local = LoadLocalPageInfo(page); + if(local == null) return -1; + + if(!File.Exists(GetFullPathForMessages(local.File))) return 0; + string data = File.ReadAllText(GetFullPathForMessages(local.File)).Replace("\r", ""); + return data.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).Length; + } + } + + /// + /// Finds a Message in a Message tree. + /// + /// The Message tree. + /// The ID of the Message to find. + /// The Message or null. + /// The method is recursive. + private static Message FindMessage(IEnumerable messages, int id) { + Message result = null; + foreach(Message msg in messages) { + if(msg.ID == id) { + result = msg; + } + if(result == null) { + result = FindMessage(msg.Replies, id); + } + if(result != null) break; + } + return result; + } + + /// + /// Removes all messages for a page and stores the new messages. + /// + /// The page. + /// The new messages to store. + /// true if the messages are stored, false otherwise. + /// If or are null. + public bool BulkStoreMessages(PageInfo page, Message[] messages) { + if(page == null) throw new ArgumentNullException("page"); + if(messages == null) throw new ArgumentNullException("messages"); + + LocalPageInfo local = LoadLocalPageInfo(page); + if(local == null) return false; + + // Validate IDs by using a dictionary as a way of validation + try { + Dictionary ids = new Dictionary(50); + foreach(Message msg in messages) { + AddAllIds(ids, msg); + } + } + catch(ArgumentException) { + return false; + } + + // Be sure to remove all old messages from the search engine index + foreach(Message msg in GetMessages(local)) { + UnindexMessageTree(local, msg); + } + + // Simply overwrite all messages on disk + DumpMessages(local, messages); + + // Add the new messages to the search engine index + foreach(Message msg in messages) { + IndexMessageTree(local, msg); + } + + return true; + } + + private static void AddAllIds(Dictionary dictionary, Message msg) { + dictionary.Add(msg.ID, 0); + foreach(Message m in msg.Replies) { + AddAllIds(dictionary, m); + } + } + + /// + /// Adds a new Message to a Page. + /// + /// The Page. + /// The Username. + /// The Subject. + /// The Date/Time. + /// The Body. + /// The Parent Message ID, or -1. + /// True if the Message has been added successfully. + /// If , , or are null. + /// If or are empty. + /// If is less than -1. + public bool AddMessage(PageInfo page, string username, string subject, DateTime dateTime, string body, int parent) { + if(page == null) throw new ArgumentNullException("page"); + if(username == null) throw new ArgumentNullException("username"); + if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username"); + if(subject == null) throw new ArgumentNullException("subject"); + if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject"); + if(body == null) throw new ArgumentNullException("body"); // body can be empty + if(parent < -1) throw new ArgumentOutOfRangeException("parent", "Invalid Parent Message ID"); + + lock(this) { + LocalPageInfo local = LoadLocalPageInfo(page); + if(local == null) return false; + + if(parent != -1) { + // Check for existence of parent message + Message[] allMessages = GetMessages(page); + if(FindMessage(new List(allMessages), parent) == null) return false; + } + + subject = Tools.EscapeString(subject); + body = Tools.EscapeString(body); + StringBuilder sb = new StringBuilder(); + + // Structure + // ID|Username|Subject|DateTime|ParentID|Body + + int messageID = GetFreeMessageID(local); + + sb.Append(messageID); + sb.Append("|"); + sb.Append(username); + sb.Append("|"); + sb.Append(subject); + sb.Append("|"); + sb.Append(dateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss")); + sb.Append("|"); + sb.Append(parent.ToString()); + sb.Append("|"); + sb.Append(body); + sb.Append("\r\n"); + + File.AppendAllText(GetFullPathForMessages(local.File), sb.ToString()); + + // Update search engine index + IndexMessage(local, messageID, subject, dateTime, body); + } + return true; + } + + /// + /// Indexes a message. + /// + /// The page. + /// The message ID. + /// The subject. + /// The date/time. + /// The body. + /// The number of indexed words, including duplicates. + private int IndexMessage(PageInfo page, int id, string subject, DateTime dateTime, string body) { + lock(this) { + // Trim "RE:" to avoid polluting the search engine index + if(subject.ToLowerInvariant().StartsWith("re:") && subject.Length > 3) subject = subject.Substring(3).Trim(); + + string documentName = MessageDocument.GetDocumentName(page, id); + + DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(null, subject), + MessageDocument.StandardTypeTag, dateTime); + + // Store the document + // The content should always be prepared using IHost.PrepareForSearchEngineIndexing() + int count = index.StoreDocument(new MessageDocument(page, id, ddoc, TokenizeContent), null, + host.PrepareContentForIndexing(null, body), null); + + if(count == 0 && body.Length > 0) { + host.LogEntry("Indexed 0 words for message " + page.FullName + ":" + id.ToString() + ": possible index corruption. Please report this error to the developers", + LogEntryType.Warning, null, this); + } + + return count; + } + } + + /// + /// Indexes a message tree. + /// + /// The page. + /// The tree root. + private void IndexMessageTree(PageInfo page, Message root) { + IndexMessage(page, root.ID, root.Subject, root.DateTime, root.Body); + foreach(Message reply in root.Replies) { + IndexMessageTree(page, reply); + } + } + + /// + /// Removes a message from the search engine index. + /// + /// The page. + /// The message ID. + /// The subject. + /// The date/time. + /// The body. + /// The number of indexed words, including duplicates. + private void UnindexMessage(PageInfo page, int id, string subject, DateTime dateTime, string body) { + lock(this) { + // Trim "RE:" to avoid polluting the search engine index + if(subject.ToLowerInvariant().StartsWith("re:") && subject.Length > 3) subject = subject.Substring(3).Trim(); + + string documentName = MessageDocument.GetDocumentName(page, id); + + DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(null, subject), + MessageDocument.StandardTypeTag, DateTime.Now); + index.RemoveDocument(new MessageDocument(page, id, ddoc, TokenizeContent), null); + } + } + + /// + /// Removes a message tree from the search engine index. + /// + /// The page. + /// The tree root. + private void UnindexMessageTree(PageInfo page, Message root) { + UnindexMessage(page, root.ID, root.Subject, root.DateTime, root.Body); + foreach(Message reply in root.Replies) { + UnindexMessageTree(page, reply); + } + } + + /// + /// Find a free Message ID for a Page. + /// + /// The Page. + /// The Message ID. + private int GetFreeMessageID(LocalPageInfo page) { + lock(this) { + if(!File.Exists(GetFullPathForMessages(page.File))) return 0; + + int result = 0; + + string data = File.ReadAllText(GetFullPathForMessages(page.File)).Replace("\r", ""); + + string[] lines = data.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + int idx, tmp; + for(int i = 0; i < lines.Length; i++) { + idx = lines[i].IndexOf('|'); + tmp = int.Parse(lines[i].Substring(0, idx)); + if(tmp > result) result = tmp; + } + + result++; + + return result; + } + } + + /// + /// Removes a Message. + /// + /// The Page. + /// The ID of the Message to remove. + /// A value specifying whether or not to remove the replies. + /// True if the Message has been removed successfully. + /// If is null. + /// If is less than zero. + public bool RemoveMessage(PageInfo page, int id, bool removeReplies) { + if(page == null) throw new ArgumentNullException("page"); + if(id < 0) throw new ArgumentOutOfRangeException("Invalid ID", "id"); + + lock(this) { + LocalPageInfo local = LoadLocalPageInfo(page); + if(local == null) return false; + + Message[] messages = GetMessages(page); + Message msg = FindMessage(messages, id); + if(msg == null) return false; + + Message[] replies = new Message[0]; + if(!removeReplies) { + replies = msg.Replies; + } + + if(!removeReplies && replies.Length > 0) { + // Find Message's anchestor + Message anchestor = FindAnchestor(messages, msg.ID); + if(anchestor != null) { + Message[] newReplies = new Message[anchestor.Replies.Length + replies.Length]; + Array.Copy(anchestor.Replies, newReplies, anchestor.Replies.Length); + Array.Copy(replies, 0, newReplies, anchestor.Replies.Length, replies.Length); + anchestor.Replies = newReplies; + } + else { + Message[] newMessages = new Message[messages.Length + replies.Length]; + Array.Copy(messages, newMessages, messages.Length); + Array.Copy(replies, 0, newMessages, messages.Length, replies.Length); + messages = newMessages; + } + } + + // Recursively update search engine index + if(removeReplies) { + UnindexMessageTree(page, msg); + } + else UnindexMessage(page, msg.ID, msg.Subject, msg.DateTime, msg.Body); + + List tempList = new List(messages); + RemoveMessage(tempList, msg); + messages = tempList.ToArray(); + tempList = null; + + DumpMessages(page, messages); + } + return true; + } + + /// + /// Finds the anchestor/parent of a Message. + /// + /// The Messages. + /// The Message ID. + /// The anchestor Message or null. + private static Message FindAnchestor(IEnumerable messages, int id) { + Message result = null; + foreach(Message msg in messages) { + for(int k = 0; k < msg.Replies.Length; k++) { + if(msg.Replies[k].ID == id) { + result = msg; + break; + } + if(result == null) { + result = FindAnchestor(msg.Replies, id); + } + } + if(result != null) break; + } + return result; + } + + /// + /// Removes a Message from a Message Tree. + /// + /// The Message Tree. + /// The Message to Remove. + /// True if the Message has been removed. + private static bool RemoveMessage(List messages, Message msg) { + for(int i = 0; i < messages.Count; i++) { + if(messages.Contains(msg)) { + messages.Remove(msg); + return true; + } + List tempList = new List(messages[i].Replies); + bool done = RemoveMessage(tempList, msg); + if(done) { + messages[i].Replies = tempList.ToArray(); + // Message found and removed + return true; + } + } + + // Message not found + return false; + } + + /// + /// Modifies a Message. + /// + /// The Page. + /// The ID of the Message to modify. + /// The Username. + /// The Subject. + /// The Date/Time. + /// The Body. + /// True if the Message has been modified successfully. + /// If , , or are null. + /// If is less than zero. + /// If or are empty. + public bool ModifyMessage(PageInfo page, int id, string username, string subject, DateTime dateTime, string body) { + if(page == null) throw new ArgumentNullException("page"); + if(id < 0) throw new ArgumentOutOfRangeException("Invalid Message ID", "id"); + if(username == null) throw new ArgumentNullException("username"); + if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username"); + if(subject == null) throw new ArgumentNullException("subject"); + if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject"); + if(body == null) throw new ArgumentNullException("body"); // body can be empty + + lock(this) { + if(LoadLocalPageInfo(page) == null) return false; + + List messages = new List(GetMessages(page)); + + Message msg = FindMessage(messages, id); + + if(msg == null) return false; + + // Update search engine index + UnindexMessage(page, id, msg.Subject, msg.DateTime, msg.Body); + + msg.Username = username; + msg.Subject = subject; + msg.DateTime = dateTime; + msg.Body = body; + + DumpMessages(page, messages); + + // Update search engine index + IndexMessage(page, id, subject, dateTime, body); + } + return true; + } + + /// + /// Dumps the Message tree of a Page to disk. + /// + /// The Page. + /// The Message tree. + private void DumpMessages(PageInfo page, IEnumerable messages) { + lock(this) { + StringBuilder sb = new StringBuilder(5000); + AppendMessages(messages, -1, sb); + File.WriteAllText(GetFullPathForMessages(((LocalPageInfo)page).File), sb.ToString()); + } + } + + /// + /// Appends to a StringBuilder object the branches and leaves of a Message tree. + /// + /// The Message tree branch to append. + /// The ID of the parent of the Message tree or -1. + /// The StringBuilder. + /// The methods appends the Messages traversing the tree depht-first, and it is recursive. + private void AppendMessages(IEnumerable messages, int parent, StringBuilder sb) { + // Depht-first + + // Structure + // ID|Username|Subject|DateTime|ParentID|Body + lock(this) { + foreach(Message msg in messages) { + sb.Append(msg.ID.ToString()); + sb.Append("|"); + sb.Append(msg.Username); + sb.Append("|"); + sb.Append(Tools.EscapeString(msg.Subject)); + sb.Append("|"); + sb.Append(msg.DateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss")); + sb.Append("|"); + sb.Append(parent.ToString()); + sb.Append("|"); + sb.Append(Tools.EscapeString(msg.Body)); + sb.Append("\r\n"); + AppendMessages(msg.Replies, msg.ID, sb); + } + } + } + + /// + /// Extracts an instance of from a line in the navigation paths file. + /// + /// The line to process. + /// The instance of + private NavigationPath BuildNavigationPath(string fileLine) { + // Structure + // Namespace.PathName|Page1|Page2|... + // First field can be 'Namespace.PathName' or 'PathName' + + string[] fields = fileLine.Split('|'); + string[] fullName = fields[0].Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + string nspace, name; + + if(fullName.Length == 1) { + nspace = null; + name = fullName[0]; + } + else { + nspace = fullName[0]; + name = fullName[1]; + } + + NavigationPath result = new NavigationPath(NameTools.GetFullName(nspace, name), this); + List tempPages = new List(10); + for(int k = 1; k < fields.Length; k++) { + tempPages.Add(fields[k]); + } + result.Pages = tempPages.ToArray(); + + return result; + } + + /// + /// Gets all the Navigation Paths. + /// + /// The Navigation Paths. + private NavigationPath[] GetAllNavigationPaths() { + lock(this) { + List paths = new List(10); + + string[] lines = File.ReadAllText(GetFullPath(NavigationPathsFile)).Replace("\r", "").Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + // Structure + // Namespace/PathName|Page1|Page2|... + // First field can be 'Namespace/PathName', '/PathName' or 'PathName' + + for(int i = 0; i < lines.Length; i++) { + paths.Add(BuildNavigationPath(lines[i])); + } + + return paths.ToArray(); + } + } + + /// + /// Gets all the Navigation Paths in a Namespace. + /// + /// The Namespace. + /// All the Navigation Paths, sorted by name. + public NavigationPath[] GetNavigationPaths(NamespaceInfo nspace) { + lock(this) { + NavigationPath[] allNavigationPaths = GetAllNavigationPaths(); + + List selectedNavigationPaths = new List(allNavigationPaths.Length / 4); + + foreach(NavigationPath path in allNavigationPaths) { + string pathNamespace = NameTools.GetNamespace(path.FullName); + if(nspace == null && pathNamespace == null) selectedNavigationPaths.Add(path); + if(nspace != null && pathNamespace != null && StringComparer.OrdinalIgnoreCase.Compare(nspace.Name, pathNamespace) == 0) selectedNavigationPaths.Add(path); + } + + selectedNavigationPaths.Sort(new NavigationPathComparer()); + + return selectedNavigationPaths.ToArray(); + } + } + + /// + /// Adds a new Navigation Path. + /// + /// The target namespace (null for the root). + /// The Name of the Path. + /// The Pages array. + /// The correct object. + /// If or are null. + /// If or are empty. + public NavigationPath AddNavigationPath(string nspace, string name, PageInfo[] pages) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(pages == null) throw new ArgumentNullException("pages"); + if(pages.Length == 0) throw new ArgumentException("Pages cannot be empty"); + + lock(this) { + NavigationPathComparer comp = new NavigationPathComparer(); + NavigationPath temp = new NavigationPath(NameTools.GetFullName(nspace, name), this); + if(Array.Find(GetAllNavigationPaths(), delegate(NavigationPath p) { return comp.Compare(p, temp) == 0; }) != null) return null; + temp = null; + + foreach(PageInfo page in pages) { + if(page == null) throw new ArgumentNullException("pages", "A page element cannot be null"); + if(LoadLocalPageInfo(page) == null) throw new ArgumentException("Page not found", "pages"); + } + + NavigationPath result = new NavigationPath(NameTools.GetFullName(nspace, name), this); + List tempPages = new List(pages.Length); + + StringBuilder sb = new StringBuilder(500); + + sb.Append("\r\n"); + sb.Append(result.FullName); + for(int i = 0; i < pages.Length; i++) { + if(pages[i].Provider == this) { + sb.Append("|"); + sb.Append(pages[i].FullName); + tempPages.Add(pages[i].FullName); + } + } + result.Pages = tempPages.ToArray(); + + File.AppendAllText(GetFullPath(NavigationPathsFile), sb.ToString()); + return result; + } + } + + /// + /// Modifies an existing Navigation Path. + /// + /// The Navigation Path to modify. + /// The new Pages array. + /// The correct object. + /// If or are null. + /// If is empty. + public NavigationPath ModifyNavigationPath(NavigationPath path, PageInfo[] pages) { + if(path == null) throw new ArgumentNullException("path"); + if(pages == null) throw new ArgumentNullException("pages"); + if(pages.Length == 0) throw new ArgumentException("Pages cannot be empty"); + + lock(this) { + foreach(PageInfo page in pages) { + if(page == null) throw new ArgumentNullException("pages", "A page element cannot be null"); + if(LoadLocalPageInfo(page) == null) throw new ArgumentException("Page not found", "pages"); + } + + NavigationPath[] paths = GetAllNavigationPaths(); + NavigationPathComparer comp = new NavigationPathComparer(); + for(int i = 0; i < paths.Length; i++) { + if(comp.Compare(path, paths[i]) == 0) { + paths[i].Pages = new string[0]; + + NavigationPath np = new NavigationPath(path.FullName, this); + List tempPages = new List(pages.Length); + + for(int k = 0; k < pages.Length; k++) { + if(pages[i].Provider == this) { + tempPages.Add(pages[k].FullName); + } + } + np.Pages = tempPages.ToArray(); + paths[i] = np; + + DumpNavigationPaths(paths); + return np; + } + } + } + return null; + } + + /// + /// Removes a Navigation Path. + /// + /// The Navigation Path to remove. + /// True if the Path is removed successfully. + /// If is null. + public bool RemoveNavigationPath(NavigationPath path) { + if(path == null) throw new ArgumentNullException("path"); + + lock(this) { + List paths = new List(GetAllNavigationPaths()); + NavigationPathComparer comp = new NavigationPathComparer(); + for(int i = 0; i < paths.Count; i++) { + if(comp.Compare(path, paths[i]) == 0) { + paths.Remove(paths[i]); + DumpNavigationPaths(paths.ToArray()); + return true; + } + } + } + return false; + } + + /// + /// Writes an array of Navigation Paths to disk. + /// + /// The array. + private void DumpNavigationPaths(NavigationPath[] paths) { + lock(this) { + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < paths.Length; i++) { + sb.Append(paths[i].FullName); + for(int k = 0; k < paths[i].Pages.Length; k++) { + sb.Append("|"); + sb.Append(paths[i].Pages[k]); + } + if(i != paths.Length - 1) sb.Append("\r\n"); + } + File.WriteAllText(GetFullPath(NavigationPathsFile), sb.ToString()); + } + } + + /// + /// Gets all the Snippets. + /// + /// All the Snippets, sorted by name. + public Snippet[] GetSnippets() { + lock(this) { + string[] files = Directory.GetFiles(GetFullPath(SnippetsDirectory), "*.cs"); + + Snippet[] snippets = new Snippet[files.Length]; + for(int i = 0; i < files.Length; i++) { + snippets[i] = new Snippet(Path.GetFileNameWithoutExtension(files[i]), File.ReadAllText(files[i]), this); + } + + Array.Sort(snippets, new SnippetNameComparer()); + + return snippets; + } + } + + /// + /// Adds a new Snippet. + /// + /// The Name of the Snippet. + /// The Content of the Snippet. + /// The correct Snippet object. + /// If or are null. + /// If is empty. + public Snippet AddSnippet(string name, string content) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(content == null) throw new ArgumentNullException("content"); // content can be empty + + lock(this) { + SnippetNameComparer comp = new SnippetNameComparer(); + Snippet temp = new Snippet(name, content, this); + if(Array.Find(GetSnippets(), delegate(Snippet s) { return comp.Compare(s, temp) == 0; }) != null) return null; + temp = null; + + File.WriteAllText(GetFullPathForSnippets(name + ".cs"), content); + return new Snippet(name, content, this); + } + } + + /// + /// Modifies a new Snippet. + /// + /// The Name of the Snippet to modify. + /// The Content of the Snippet. + /// The correct Snippet object. + /// If or are null. + /// If is empty. + public Snippet ModifySnippet(string name, string content) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(content == null) throw new ArgumentNullException("content"); // content can be empty + + lock(this) { + SnippetNameComparer comp = new SnippetNameComparer(); + Snippet temp = new Snippet(name, content, this); + if(Array.Find(GetSnippets(), delegate(Snippet s) { return comp.Compare(s, temp) == 0; }) == null) return null; + temp = null; + + File.WriteAllText(GetFullPathForSnippets(name + ".cs"), content); + return new Snippet(name, content, this); + } + } + + /// + /// Removes a new Snippet. + /// + /// The Name of the Snippet to remove. + /// True if the Snippet is removed successfully. + /// If is null. + /// If is empty. + public bool RemoveSnippet(string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + lock(this) { + SnippetNameComparer comp = new SnippetNameComparer(); + Snippet temp = new Snippet(name, "", this); + if(Array.Find(GetSnippets(), delegate(Snippet s) { return comp.Compare(s, temp) == 0; }) == null) return false; + temp = null; + + File.Delete(GetFullPathForSnippets(name + ".cs")); + } + return true; + } + + /// + /// Gets all the content templates. + /// + /// All the content templates, sorted by name. + public ContentTemplate[] GetContentTemplates() { + lock(this) { + string[] files = Directory.GetFiles(GetFullPath(ContentTemplatesDirectory), "*.cs"); + + ContentTemplate[] templates = new ContentTemplate[files.Length]; + for(int i = 0; i < files.Length; i++) { + templates[i] = new ContentTemplate(Path.GetFileNameWithoutExtension(files[i]), File.ReadAllText(files[i]), this); + } + + Array.Sort(templates, new ContentTemplateNameComparer()); + + return templates; + } + } + + /// + /// Adds a new content template. + /// + /// The name of template. + /// The content of the template. + /// The correct object. + /// If or are null. + /// If is empty. + public ContentTemplate AddContentTemplate(string name, string content) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(content == null) throw new ArgumentNullException("content"); + + lock(this) { + string file = GetFullPathForContentTemplate(name + ".cs"); + + if(File.Exists(file)) return null; + + File.WriteAllText(file, content); + + return new ContentTemplate(name, content, this); + } + } + + /// + /// Modifies an existing content template. + /// + /// The name of the template to modify. + /// The content of the template. + /// The correct object. + /// If or are null. + /// If is empty. + public ContentTemplate ModifyContentTemplate(string name, string content) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(content == null) throw new ArgumentNullException("content"); + + lock(this) { + string file = GetFullPathForContentTemplate(name + ".cs"); + + if(!File.Exists(file)) return null; + + File.WriteAllText(file, content); + + return new ContentTemplate(name, content, this); + } + } + + /// + /// Removes a content template. + /// + /// The name of the template to remove. + /// true if the template is removed, false otherwise. + /// If is null. + /// If is empty. + public bool RemoveContentTemplate(string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + lock(this) { + string file = GetFullPathForContentTemplate(name + ".cs"); + + if(!File.Exists(file)) return false; + + File.Delete(file); + + return true; + } + } + + } + +} diff --git a/Core/Preferences.cs b/Core/Preferences.cs new file mode 100644 index 0000000..ed249ce --- /dev/null +++ b/Core/Preferences.cs @@ -0,0 +1,145 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Web; +using ScrewTurn.Wiki.PluginFramework; +using System.Globalization; + + +namespace ScrewTurn.Wiki { + + /// + /// Allows access to current user's preferences. + /// + public static class Preferences { + + /// + /// Loads the language from a cookie. + /// + /// The language, or null. + public static string LoadLanguageFromCookie() { + HttpCookie cookie = HttpContext.Current.Request.Cookies[Settings.CultureCookieName]; + if(cookie != null) { + string culture = cookie["C"]; + return culture; + } + else return null; + } + + /// + /// Loads the language from the current user's data. + /// + /// The language, or null. + public static string LoadLanguageFromUserData() { + UserInfo currentUser = SessionFacade.GetCurrentUser(); + if(currentUser != null) { + string culture = Users.GetUserData(currentUser, "Culture"); + return culture; + } + else return null; + } + + /// + /// Loads the timezone from a cookie. + /// + /// The timezone, or null. + public static int? LoadTimezoneFromCookie() { + HttpCookie cookie = HttpContext.Current.Request.Cookies[Settings.CultureCookieName]; + if(cookie != null) { + string timezone = cookie["T"]; + return int.Parse(timezone, CultureInfo.InvariantCulture); + } + else return null; + } + + /// + /// Loads the timezone from the current user's data. + /// + /// The timezone, or null. + public static int? LoadTimezoneFromUserData() { + UserInfo currentUser = SessionFacade.GetCurrentUser(); + if(currentUser != null) { + string timezone = Users.GetUserData(currentUser, "Timezone"); + if(timezone != null) return int.Parse(timezone, CultureInfo.InvariantCulture); + else return null; + } + else return null; + } + + /// + /// Saves language and timezone preferences into a cookie. + /// + /// The culture. + /// The timezone. + public static void SavePreferencesInCookie(string culture, int timezone) { + HttpCookie cookie = new HttpCookie(Settings.CultureCookieName); + cookie.Expires = DateTime.Now.AddYears(10); + cookie.Path = Settings.CookiePath; + cookie.Values.Add("C", culture); + cookie.Values.Add("T", timezone.ToString(CultureInfo.InvariantCulture)); + HttpContext.Current.Response.Cookies.Add(cookie); + } + + /// + /// Deletes the language and timezone preferences cookie. + /// + public static void DeletePreferencesCookie() { + HttpCookie cookie = new HttpCookie(Settings.CultureCookieName); + cookie.Expires = DateTime.Now.AddYears(-1); + cookie.Path = Settings.CookiePath; + cookie.Values.Add("C", null); + cookie.Values.Add("T", null); + HttpContext.Current.Request.Cookies.Add(cookie); + } + + /// + /// Saves language and timezone preferences into the current user's data. + /// + /// The culture. + /// The timezone. + /// true if the data is stored, false otherwise. + public static bool SavePreferencesInUserData(string culture, int timezone) { + UserInfo user = SessionFacade.GetCurrentUser(); + if(user != null && !user.Provider.UsersDataReadOnly) { + Users.SetUserData(user, "Culture", culture); + Users.SetUserData(user, "Timezone", timezone.ToString(CultureInfo.InvariantCulture)); + + return true; + } + else { + if(user == null) { + Log.LogEntry("Attempt to save user data when no user has logged in", EntryType.Warning, Log.SystemUsername); + } + return false; + } + } + + /// + /// Aligns a date/time with the User's preferences (if any). + /// + /// The date/time to align. + /// The aligned date/time. + public static DateTime AlignWithTimezone(DateTime dateTime) { + // First, look for hard-stored user's preferences + // If they are not available, look at the cookie + + int? tempShift = LoadTimezoneFromUserData(); + if(!tempShift.HasValue) tempShift = LoadTimezoneFromCookie(); + + int shift = tempShift.HasValue ? tempShift.Value : Settings.DefaultTimezone; + return dateTime.ToUniversalTime().AddMinutes(shift + (dateTime.IsDaylightSavingTime() ? 60 : 0)); + } + + /// + /// Aligns a date/time with the default timezone. + /// + /// The date/time to align. + /// The aligned date/time. + public static DateTime AlignWithServerTimezone(DateTime dateTime) { + return dateTime.ToUniversalTime().AddMinutes(Settings.DefaultTimezone + (dateTime.IsDaylightSavingTime() ? 60 : 0)); + } + + } + +} diff --git a/Core/Properties/AssemblyInfo.cs b/Core/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c648754 --- /dev/null +++ b/Core/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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("ScrewTurn Wiki Core")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// 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("b456637d-2218-43af-b6bf-c1b498c91630")] diff --git a/Core/ProviderCollector.cs b/Core/ProviderCollector.cs new file mode 100644 index 0000000..5e6c90c --- /dev/null +++ b/Core/ProviderCollector.cs @@ -0,0 +1,71 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Implements a generic Provider Collector. + /// + /// The type of the Collector. + public class ProviderCollector { + + private List list; + + /// + /// Initializes a new instance of the class. + /// + public ProviderCollector() { + list = new List(3); + } + + /// + /// Adds a Provider to the Collector. + /// + /// The Provider to add. + public void AddProvider(T provider) { + lock(this) { + list.Add(provider); + } + } + + /// + /// Removes a Provider from the Collector. + /// + /// The Provider to remove. + public void RemoveProvider(T provider) { + lock(this) { + list.Remove(provider); + } + } + + /// + /// Gets all the Providers (copied array). + /// + public T[] AllProviders { + get { + lock(this) { + return list.ToArray(); + } + } + } + + /// + /// Gets a Provider, searching for its Type Name. + /// + /// The Type Name. + /// The Provider, or null if the Provider was not found. + public T GetProvider(string typeName) { + lock(this) { + for(int i = 0; i < list.Count; i++) { + if(list[i].GetType().FullName.Equals(typeName)) return list[i]; + } + return default(T); + } + } + + } + +} diff --git a/Core/ProviderLoader.cs b/Core/ProviderLoader.cs new file mode 100644 index 0000000..33d2b10 --- /dev/null +++ b/Core/ProviderLoader.cs @@ -0,0 +1,556 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Reflection; +using ScrewTurn.Wiki.PluginFramework; +using System.Globalization; + +namespace ScrewTurn.Wiki { + + /// + /// Loads providers from assemblies. + /// + public static class ProviderLoader { + + // These must be const because they are used in switch constructs + internal const string UsersProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.IUsersStorageProviderV30"; + internal const string PagesProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.IPagesStorageProviderV30"; + internal const string FilesProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.IFilesStorageProviderV30"; + internal const string FormatterProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.IFormatterProviderV30"; + internal const string CacheProviderInterfaceName = "ScrewTurn.Wiki.PluginFramework.ICacheProviderV30"; + + internal static string SettingsStorageProviderAssemblyName = ""; + + /// + /// Verifies the read-only/read-write constraints of providers. + /// + /// The type of the provider. + /// The provider. + /// Thrown when a constraint is not fulfilled. + private static void VerifyConstraints(T provider) { + if(typeof(T) == typeof(IUsersStorageProviderV30)) { + // If the provider allows to write user accounts data, then group membership must be writeable too + + IUsersStorageProviderV30 actualInstance = (IUsersStorageProviderV30)provider; + if(!actualInstance.UserAccountsReadOnly && actualInstance.GroupMembershipReadOnly) { + throw new ProviderConstraintException("If UserAccountsReadOnly is false, then also GroupMembershipReadOnly must be false"); + } + } + } + + /// + /// Tries to inizialize a provider. + /// + /// The type of the provider, which must implement IProvider. + /// The provider instance to initialize. + /// The collector for enabled providers. + /// The collector for disabled providers. + private static void Initialize(T instance, ProviderCollector collectorEnabled, + ProviderCollector collectorDisabled) where T : class, IProviderV30 { + + if(collectorEnabled.GetProvider(instance.GetType().FullName) != null || + collectorDisabled.GetProvider(instance.GetType().FullName) != null) { + + Log.LogEntry("Provider " + instance.Information.Name + " already in memory", EntryType.Warning, Log.SystemUsername); + return; + } + + bool enabled = !IsDisabled(instance.GetType().FullName); + try { + if(enabled) { + instance.Init(Host.Instance, LoadConfiguration(instance.GetType().FullName)); + } + } + catch(InvalidConfigurationException) { + // Disable Provider + enabled = false; + Log.LogEntry("Unable to load provider " + instance.Information.Name + " (configuration rejected), disabling it", EntryType.Error, Log.SystemUsername); + SaveStatus(instance.GetType().FullName, false); + } + catch { + // Disable Provider + enabled = false; + Log.LogEntry("Unable to load provider " + instance.Information.Name + " (unknown error), disabling it", EntryType.Error, Log.SystemUsername); + SaveStatus(instance.GetType().FullName, false); + throw; // Exception is rethrown because it's not a normal condition + } + if(enabled) collectorEnabled.AddProvider(instance); + else collectorDisabled.AddProvider(instance); + + // Verify constraints + VerifyConstraints(instance); + + Log.LogEntry("Provider " + instance.Information.Name + " loaded (" + (enabled ? "Enabled" : "Disabled") + ")", EntryType.General, Log.SystemUsername); + } + + /// + /// Loads all the Providers and initialises them. + /// + /// A value indicating whether to load users storage providers. + /// A value indicating whether to load pages storage providers. + /// A value indicating whether to load files storage providers. + /// A value indicating whether to load formatter providers. + /// A value indicating whether to load cache providers. + public static void FullLoad(bool loadUsers, bool loadPages, bool loadFiles, bool loadFormatters, bool loadCache) { + string[] pluginAssemblies = Settings.Provider.ListPluginAssemblies(); + + List users = new List(2); + List dUsers = new List(2); + List pages = new List(2); + List dPages = new List(2); + List files = new List(2); + List dFiles = new List(2); + List forms = new List(2); + List dForms = new List(2); + List cache = new List(2); + List dCache = new List(2); + + for(int i = 0; i < pluginAssemblies.Length; i++) { + IFilesStorageProviderV30[] d; + IUsersStorageProviderV30[] u; + IPagesStorageProviderV30[] p; + IFormatterProviderV30[] f; + ICacheProviderV30[] c; + LoadFrom(pluginAssemblies[i], out u, out p, out d, out f, out c); + if(loadFiles) files.AddRange(d); + if(loadUsers) users.AddRange(u); + if(loadPages) pages.AddRange(p); + if(loadFormatters) forms.AddRange(f); + if(loadCache) cache.AddRange(c); + } + + // Init and add to the Collectors, starting from files providers + for(int i = 0; i < files.Count; i++) { + Initialize(files[i], Collectors.FilesProviderCollector, Collectors.DisabledFilesProviderCollector); + } + + for(int i = 0; i < users.Count; i++) { + Initialize(users[i], Collectors.UsersProviderCollector, Collectors.DisabledUsersProviderCollector); + } + + for(int i = 0; i < pages.Count; i++) { + Initialize(pages[i], Collectors.PagesProviderCollector, Collectors.DisabledPagesProviderCollector); + } + + for(int i = 0; i < forms.Count; i++) { + Initialize(forms[i], Collectors.FormatterProviderCollector, Collectors.DisabledFormatterProviderCollector); + } + + for(int i = 0; i < cache.Count; i++) { + Initialize(cache[i], Collectors.CacheProviderCollector, Collectors.DisabledCacheProviderCollector); + } + } + + /// + /// Loads the Configuration data of a Provider. + /// + /// The Type Name of the Provider. + /// The Configuration, if available, otherwise an empty string. + public static string LoadConfiguration(string typeName) { + return Settings.Provider.GetPluginConfiguration(typeName); + } + + /// + /// Saves the Configuration data of a Provider. + /// + /// The Type Name of the Provider. + /// The Configuration data to save. + public static void SaveConfiguration(string typeName, string config) { + Settings.Provider.SetPluginConfiguration(typeName, config); + } + + /// + /// Saves the Status of a Provider. + /// + /// The Type Name of the Provider. + /// A value specifying whether or not the Provider is enabled. + public static void SaveStatus(string typeName, bool enabled) { + Settings.Provider.SetPluginStatus(typeName, enabled); + } + + /// + /// Returns a value specifying whether or not a Provider is disabled. + /// + /// The Type Name of the Provider. + /// True if the Provider is disabled. + public static bool IsDisabled(string typeName) { + return !Settings.Provider.GetPluginStatus(typeName); + } + + /// + /// Loads Providers from an assembly. + /// + /// The path of the Assembly to load the Providers from. + public static int LoadFromAuto(string assembly) { + IUsersStorageProviderV30[] users; + IPagesStorageProviderV30[] pages; + IFilesStorageProviderV30[] files; + IFormatterProviderV30[] forms; + ICacheProviderV30[] cache; + LoadFrom(assembly, out users, out pages, out files, out forms, out cache); + + int count = 0; + + // Init and add to the Collectors, starting from files providers + for(int i = 0; i < files.Length; i++) { + Initialize(files[i], Collectors.FilesProviderCollector, Collectors.DisabledFilesProviderCollector); + count++; + } + + for(int i = 0; i < users.Length; i++) { + Initialize(users[i], Collectors.UsersProviderCollector, Collectors.DisabledUsersProviderCollector); + count++; + } + + for(int i = 0; i < pages.Length; i++) { + Initialize(pages[i], Collectors.PagesProviderCollector, Collectors.DisabledPagesProviderCollector); + count++; + } + + for(int i = 0; i < forms.Length; i++) { + Initialize(forms[i], Collectors.FormatterProviderCollector, Collectors.DisabledFormatterProviderCollector); + count++; + } + + for(int i = 0; i < cache.Length; i++) { + Initialize(cache[i], Collectors.CacheProviderCollector, Collectors.DisabledCacheProviderCollector); + count++; + } + + return count; + } + + /// + /// Loads Providers from an assembly. + /// + /// The path of the Assembly to load the Providers from. + /// The Users Providers. + /// The Files Providers. + /// The Pages Providers. + /// The Formatter Providers. + /// The Cache Providers. + /// The Components returned are not initialized. + public static void LoadFrom(string assembly, out IUsersStorageProviderV30[] users, out IPagesStorageProviderV30[] pages, + out IFilesStorageProviderV30[] files, out IFormatterProviderV30[] formatters, out ICacheProviderV30[] cache) { + + Assembly asm = null; + try { + //asm = Assembly.LoadFile(assembly); + // This way the DLL is not locked and can be deleted at runtime + asm = Assembly.Load(LoadAssemblyFromProvider(Path.GetFileName(assembly))); + } + catch { + files = new IFilesStorageProviderV30[0]; + users = new IUsersStorageProviderV30[0]; + pages = new IPagesStorageProviderV30[0]; + formatters = new IFormatterProviderV30[0]; + cache = new ICacheProviderV30[0]; + + Log.LogEntry("Unable to load assembly " + Path.GetFileNameWithoutExtension(assembly), EntryType.Error, Log.SystemUsername); + return; + } + + Type[] types = null; + + try { + types = asm.GetTypes(); + } + catch(ReflectionTypeLoadException) { + files = new IFilesStorageProviderV30[0]; + users = new IUsersStorageProviderV30[0]; + pages = new IPagesStorageProviderV30[0]; + formatters = new IFormatterProviderV30[0]; + cache = new ICacheProviderV30[0]; + + Log.LogEntry("Unable to load providers from (probably v2) assembly " + Path.GetFileNameWithoutExtension(assembly), EntryType.Error, Log.SystemUsername); + return; + } + + List urs = new List(); + List pgs = new List(); + List fls = new List(); + List frs = new List(); + List che = new List(); + + Type[] interfaces; + for(int i = 0; i < types.Length; i++) { + // Avoid to load abstract classes as they cannot be instantiated + if(types[i].IsAbstract) continue; + + interfaces = types[i].GetInterfaces(); + foreach(Type iface in interfaces) { + if(iface == typeof(IUsersStorageProviderV30)) { + IUsersStorageProviderV30 tmpu = CreateInstance(asm, types[i]); + if(tmpu != null) { + urs.Add(tmpu); + Collectors.FileNames[tmpu.GetType().FullName] = assembly; + } + } + if(iface == typeof(IPagesStorageProviderV30)) { + IPagesStorageProviderV30 tmpp = CreateInstance(asm, types[i]); + if(tmpp != null) { + pgs.Add(tmpp); + Collectors.FileNames[tmpp.GetType().FullName] = assembly; + } + } + if(iface == typeof(IFilesStorageProviderV30)) { + IFilesStorageProviderV30 tmpd = CreateInstance(asm, types[i]); + if(tmpd != null) { + fls.Add(tmpd); + Collectors.FileNames[tmpd.GetType().FullName] = assembly; + } + } + if(iface == typeof(IFormatterProviderV30)) { + IFormatterProviderV30 tmpf = CreateInstance(asm, types[i]); + if(tmpf != null) { + frs.Add(tmpf); + Collectors.FileNames[tmpf.GetType().FullName] = assembly; + } + } + if(iface == typeof(ICacheProviderV30)) { + ICacheProviderV30 tmpc = CreateInstance(asm, types[i]); + if(tmpc != null) { + che.Add(tmpc); + Collectors.FileNames[tmpc.GetType().FullName] = assembly; + } + } + } + } + + users = urs.ToArray(); + pages = pgs.ToArray(); + files = fls.ToArray(); + formatters = frs.ToArray(); + cache = che.ToArray(); + } + + /// + /// Creates an instance of a type implementing a provider interface. + /// + /// The provider interface type. + /// The assembly that contains the type. + /// The type to create an instance of. + /// The instance, or null. + private static T CreateInstance(Assembly asm, Type type) where T : class, IProviderV30 { + T instance; + try { + instance = asm.CreateInstance(type.ToString()) as T; + return instance; + } + catch { + Log.LogEntry("Unable to create instance of " + type.ToString(), EntryType.Error, Log.SystemUsername); + throw; + } + } + + /// + /// Loads the content of an assembly from disk. + /// + /// The assembly file full path. + /// The content of the assembly, in a byte array form. + private static byte[] LoadAssemblyFromDisk(string assembly) { + return File.ReadAllBytes(assembly); + } + + /// + /// Loads the content of an assembly from the settings provider. + /// + /// The name of the assembly, such as "Assembly.dll". + /// The content fo the assembly. + private static byte[] LoadAssemblyFromProvider(string assemblyName) { + return Settings.Provider.RetrievePluginAssembly(assemblyName); + } + + /// + /// Loads the proper Setting Storage Provider, given its name. + /// + /// The fully qualified name (such as "Namespace.ProviderClass, MyAssembly"), or null/String.Empty/"default" for the default provider. + /// The settings storage provider. + public static ISettingsStorageProviderV30 LoadSettingsStorageProvider(string name) { + if(name == null || name.Length == 0 || string.Compare(name, "default", true, CultureInfo.InvariantCulture) == 0) { + return new SettingsStorageProvider(); + } + + ISettingsStorageProviderV30 result = null; + + Exception inner = null; + + if(name.Contains(",")) { + string[] fields = name.Split(','); + if(fields.Length == 2) { + fields[0] = fields[0].Trim(' ', '"'); + fields[1] = fields[1].Trim(' ', '"'); + try { + // assemblyName should be an absolute path or a relative path in bin or public\Plugins + + Assembly asm; + Type t; + string assemblyName = fields[1]; + if(!assemblyName.ToLowerInvariant().EndsWith(".dll")) assemblyName += ".dll"; + + if(File.Exists(assemblyName)) { + asm = Assembly.Load(LoadAssemblyFromDisk(assemblyName)); + t = asm.GetType(fields[0]); + SettingsStorageProviderAssemblyName = Path.GetFileName(assemblyName); + } + else { + string tentativePluginsPath = null; + try { + // Settings.PublicDirectory is only available when running the web app + tentativePluginsPath = Path.Combine(Settings.PublicDirectory, "Plugins"); + tentativePluginsPath = Path.Combine(tentativePluginsPath, assemblyName); + } + catch { } + + if(!string.IsNullOrEmpty(tentativePluginsPath) && File.Exists(tentativePluginsPath)) { + asm = Assembly.Load(LoadAssemblyFromDisk(tentativePluginsPath)); + t = asm.GetType(fields[0]); + SettingsStorageProviderAssemblyName = Path.GetFileName(tentativePluginsPath); + } + else { + // Trim .dll + t = Type.GetType(fields[0] + "," + assemblyName.Substring(0, assemblyName.Length - 4), true, true); + SettingsStorageProviderAssemblyName = assemblyName; + } + } + + result = t.GetConstructor(new Type[0]).Invoke(new object[0]) as ISettingsStorageProviderV30; + } + catch(Exception ex) { + inner = ex; + result = null; + } + } + } + + if(result == null) throw new ArgumentException("Could not load the specified Settings Storage Provider", inner); + else return result; + } + + /// + /// Loads all settings storage providers available in all DLLs stored in a provider. + /// + /// The input provider. + /// The providers found (not initialized). + public static ISettingsStorageProviderV30[] LoadAllSettingsStorageProviders(ISettingsStorageProviderV30 repository) { + // This method is actually a memory leak because it can be executed multimple times + // Every time it loads a set of assemblies which cannot be unloaded (unless a separate AppDomain is used) + + List result = new List(); + + foreach(string dll in repository.ListPluginAssemblies()) { + byte[] asmBin = repository.RetrievePluginAssembly(dll); + Assembly asm = Assembly.Load(asmBin); + + Type[] types = null; + try { + types = asm.GetTypes(); + } + catch(ReflectionTypeLoadException) { + // Skip assembly + Log.LogEntry("Unable to load providers from (probably v2) assembly " + Path.GetFileNameWithoutExtension(dll), EntryType.Error, Log.SystemUsername); + continue; + } + + foreach(Type type in types) { + // Avoid to load abstract classes as they cannot be instantiated + if(type.IsAbstract) continue; + + Type[] interfaces = type.GetInterfaces(); + + foreach(Type iface in interfaces) { + if(iface == typeof(ISettingsStorageProviderV30)) { + try { + ISettingsStorageProviderV30 temp = asm.CreateInstance(type.ToString()) as ISettingsStorageProviderV30; + if(temp != null) result.Add(temp); + } + catch { } + } + } + } + } + + return result.ToArray(); + } + + /// + /// Tries to change a provider's configuration. + /// + /// The provider. + /// The new configuration. + /// The error message, if any. + /// true if the configuration is saved, false if the provider rejected it. + public static bool TryChangeConfiguration(string typeName, string configuration, out string error) { + error = null; + + bool enabled, canDisable; + IProviderV30 provider = Collectors.FindProvider(typeName, out enabled, out canDisable); + + try { + provider.Init(Host.Instance, configuration); + } + catch(InvalidConfigurationException icex) { + error = icex.Message; + return false; + } + + SaveConfiguration(typeName, configuration); + return true; + } + + /// + /// Disables a provider. + /// + /// The provider to disable. + public static void DisableProvider(string typeName) { + bool enabled, canDisable; + IProviderV30 provider = Collectors.FindProvider(typeName, out enabled, out canDisable); + if(enabled && canDisable) { + provider.Shutdown(); + Collectors.TryDisable(typeName); + SaveStatus(typeName, false); + } + } + + /// + /// Enables a provider. + /// + /// The provider to enable. + public static void EnableProvider(string typeName) { + bool enabled, canDisable; + IProviderV30 provider = Collectors.FindProvider(typeName, out enabled, out canDisable); + if(!enabled) { + provider.Init(Host.Instance, LoadConfiguration(typeName)); + Collectors.TryEnable(typeName); + SaveStatus(typeName, true); + } + } + + /// + /// Unloads a provider from memory. + /// + /// The provider to unload. + public static void UnloadProvider(string typeName) { + DisableProvider(typeName); + Collectors.TryUnload(typeName); + } + + } + + /// + /// Defines an exception thrown when a constraint is not fulfilled by a provider. + /// + public class ProviderConstraintException : Exception { + + /// + /// Initializes a new instance of the class. + /// + /// The message. + public ProviderConstraintException(string message) + : base(message) { } + + } + +} diff --git a/Core/ProviderUpdater.cs b/Core/ProviderUpdater.cs new file mode 100644 index 0000000..bac08cf --- /dev/null +++ b/Core/ProviderUpdater.cs @@ -0,0 +1,131 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using System.Net; +using System.IO; + +namespace ScrewTurn.Wiki { + + /// + /// Handles the updating of providers. + /// + public class ProviderUpdater { + + private List visitedUrls; + + private ISettingsStorageProviderV30 settingsProvider; + private List providers; + private Dictionary fileNamesForProviders; + + /// + /// Initializes a new instance of the class. + /// + /// The settings storage provider. + /// A provider->file dictionary. + /// The providers to update. + public ProviderUpdater(ISettingsStorageProviderV30 settingsProvider, + Dictionary fileNamesForProviders, + params IProviderV30[][] providers) { + + if(settingsProvider == null) throw new ArgumentNullException("settingsProvider"); + if(fileNamesForProviders == null) throw new ArgumentNullException("fileNamesForProviders"); + if(providers == null) throw new ArgumentNullException("providers"); + if(providers.Length == 0) throw new ArgumentException("Providers cannot be empty", "providers"); + + this.settingsProvider = settingsProvider; + this.fileNamesForProviders = fileNamesForProviders; + + this.providers = new List(20); + foreach(IProviderV30[] group in providers) { + this.providers.AddRange(group); + } + + visitedUrls = new List(10); + } + + /// + /// Updates all the providers. + /// + /// The number of updated DLLs. + public int UpdateAll() { + Log.LogEntry("Starting automatic providers update", EntryType.General, Log.SystemUsername); + + int updatedDlls = 0; + + foreach(IProviderV30 prov in providers) { + if(string.IsNullOrEmpty(prov.Information.UpdateUrl)) continue; + + string newVersion; + string newDllUrl; + UpdateStatus status = Tools.GetUpdateStatus(prov.Information.UpdateUrl, + prov.Information.Version, out newVersion, out newDllUrl); + + if(status == UpdateStatus.NewVersionFound && !string.IsNullOrEmpty(newDllUrl)) { + // Update is possible + + // Case insensitive check + if(!visitedUrls.Contains(newDllUrl.ToLowerInvariant())) { + string dllName = null; + + if(!fileNamesForProviders.TryGetValue(prov.GetType().FullName, out dllName)) { + Log.LogEntry("Could not determine DLL name for provider " + prov.GetType().FullName, EntryType.Error, Log.SystemUsername); + continue; + } + + // Download DLL and install + if(DownloadAndUpdateDll(prov, newDllUrl, dllName)) { + visitedUrls.Add(newDllUrl.ToLowerInvariant()); + updatedDlls++; + } + } + else { + // Skip DLL (already updated) + Log.LogEntry("Skipping provider " + prov.GetType().FullName + ": DLL already updated", EntryType.General, Log.SystemUsername); + } + } + } + + Log.LogEntry("Automatic providers update completed: updated " + updatedDlls.ToString() + " DLLs", EntryType.General, Log.SystemUsername); + + return updatedDlls; + } + + /// + /// Downloads and updates a DLL. + /// + /// The provider. + /// The URL of the new DLL. + /// The file name of the DLL. + private bool DownloadAndUpdateDll(IProviderV30 provider, string url, string filename) { + try { + HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url); + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + + if(response.StatusCode != HttpStatusCode.OK) { + Log.LogEntry("Update failed for provider " + provider.GetType().FullName + ": Status Code=" + response.StatusCode.ToString(), EntryType.Error, Log.SystemUsername); + response.Close(); + return false; + } + + BinaryReader reader = new BinaryReader(response.GetResponseStream()); + byte[] content = reader.ReadBytes((int)response.ContentLength); + reader.Close(); + + bool done = settingsProvider.StorePluginAssembly(filename, content); + if(done) Log.LogEntry("Provider " + provider.GetType().FullName + " updated", EntryType.General, Log.SystemUsername); + else Log.LogEntry("Update failed for provider " + provider.GetType().FullName + ": could not store assembly", EntryType.Error, Log.SystemUsername); + + return done; + } + catch(Exception ex) { + Log.LogEntry("Update failed for provider " + provider.GetType().FullName + ": " + ex.ToString(), EntryType.Error, Log.SystemUsername); + return false; + } + } + + } + +} diff --git a/Core/RecentChanges.cs b/Core/RecentChanges.cs new file mode 100644 index 0000000..dc4856b --- /dev/null +++ b/Core/RecentChanges.cs @@ -0,0 +1,44 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Manages the Wiki's Recent Changes. + /// + public static class RecentChanges { + + /// + /// Gets all the changes, sorted by date/time ascending. + /// + public static RecentChange[] GetAllChanges() { + RecentChange[] changes = Settings.Provider.GetRecentChanges(); + + RecentChange[] myCopy = new RecentChange[changes.Length]; + Array.Copy(changes, myCopy, changes.Length); + + Array.Sort(myCopy, (x, y) => { return x.DateTime.CompareTo(y.DateTime); }); + + return myCopy; + } + + /// + /// Adds a new change. + /// + /// The page name. + /// The page title. + /// The message subject. + /// The date/time. + /// The user. + /// The change. + /// The description (optional). + public static void AddChange(string page, string title, string messageSubject, DateTime dateTime, string user, Change change, string descr) { + Settings.Provider.AddRecentChange(page, title, messageSubject, dateTime, user, change, descr); + } + + } + +} diff --git a/Core/Redirections.cs b/Core/Redirections.cs new file mode 100644 index 0000000..f468244 --- /dev/null +++ b/Core/Redirections.cs @@ -0,0 +1,61 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Manages information about Page Redirections. + /// + public static class Redirections { + + /// + /// Adds a new Redirection. + /// + /// The source Page. + /// The destination Page. + /// True if the Redirection is added, false otherwise. + /// The method prevents circular and multi-level redirection. + public static void AddRedirection(PageInfo source, PageInfo destination) { + if(source == null) throw new ArgumentNullException("source"); + if(destination == null) throw new ArgumentNullException("destination"); + + Cache.Provider.AddRedirection(source.FullName, destination.FullName); + } + + /// + /// Gets the destination Page. + /// + /// The source Page. + /// The destination Page, or null. + public static PageInfo GetDestination(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + string destination = Cache.Provider.GetRedirectionDestination(page.FullName); + if(string.IsNullOrEmpty(destination)) return null; + else return Pages.FindPage(destination); + } + + /// + /// Removes any occurrence of a Page from the redirection table, both on sources and destinations. + /// + /// The Page to wipe-out. + /// This method is useful when removing a Page. + public static void WipePageOut(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + Cache.Provider.RemovePageFromRedirections(page.FullName); + } + + /// + /// Clears the Redirection table. + /// + public static void Clear() { + Cache.Provider.ClearRedirections(); + } + + } + +} diff --git a/Core/ReverseFormatter.cs b/Core/ReverseFormatter.cs new file mode 100644 index 0000000..23d9ce2 --- /dev/null +++ b/Core/ReverseFormatter.cs @@ -0,0 +1,1127 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; + +namespace ScrewTurn.Wiki { + + /// + /// Implements reverse formatting methods (HTML->WikiMarkup). + /// + public static class ReverseFormatter { + + private static readonly Regex BoldRegex = new Regex(@"()((.|\n|\r)*?)()", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex ItalicRegex = new Regex(@"()((.|\n|\r)*?)()", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex UnderlineRegex = new Regex(@"()((.|\n|\r)*?)()", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex StrikeRegex = new Regex(@"()((.|\n|\r)*?)()", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex H1Regex = new Regex(@"(

)((.|\n|\r)*?)(

)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex H2Regex = new Regex(@"(

)((.|\n|\r)*?)(

)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex H3Regex = new Regex(@"(

)((.|\n|\r)*?)(

)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex H4Regex = new Regex(@"(

)((.|\n|\r)*?)(

)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex PageLinkRegex = new Regex(@"()((.|\n|\r)*?)()", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex UnknownLinkRegex = new Regex(@"()((.|\n|\r)*?)()", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex FileLinkRegex = new Regex(@"()((.|\n|\r)+?)()", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex AttachmentLinkRegex = new Regex(@"()((.|\n|\r)+?)()", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex SystemLinkRegex = new Regex(@"((.|\n|\r)*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex ExternalLinkRegex = new Regex(@"()((.|\n|\r)*?)()", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex InternalLinkRegex = new Regex(@"((.|\n|\r)*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex AnchorLinkRegex = new Regex(@"((.|\n|\r)*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex EmailLinkRegex = new Regex(@"()((.|\n|\r)*?)()", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex AnchorRegex = new Regex(@"(.*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex ImageLeftRightRegex = new Regex(@"(
|
)()?()?(

((.)*?)

)?(|
)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex ImageInlineRegex = new Regex(@"()?()?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex HRRegex = new Regex(@"

\s*

", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex BoxRegex = new Regex(@"
((.|\n|\r)*?)
", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex CodeRegex = new Regex(@"((.|\n|\r)*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex PreRegex = new Regex(@"
((.|\n|\r)*?)
", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex SingleBR = new Regex(@"(?)
(?!
)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly Regex SingleNewLine = new Regex(@"(?(.*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + // Title=1 - Href=2 - Target=3 - Content=4 --- Href=http://www.server.com/Spaced%20Page.ashx + private static readonly Regex UnknownLinkRegexIE = new Regex(@"(.*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + // Title=1 - ProviderGlobal=3 - Provider=4 - Page=6 - File=7 - Target=8 - Content=9 + private static readonly Regex FileOrAttachmentLinkRegexIE = new Regex(@"(.*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + // Title=1 - Href=2 - Target=3 - Content=4 --- Href=http://www.server.com/Register.aspx + private static readonly Regex SystemLinkRegexIE = new Regex(@"(.*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + // Title=1 - Href=2 - Target=3 - Content=4 + private static readonly Regex ExternalLinkRegexIE = new Regex(@"(.*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + // Title=1 - Href=2 - Target=3 - Content=4 + private static readonly Regex InternalLinkRegexIE = new Regex(@"(.*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + // AnchorLinkRegexIE would be equal to InternalLinkRegex - no need for it + + // Title=1 - Href=2 - Target=3 - Content=4 + private static readonly Regex EmailLinkRegexIE = new Regex(@"", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + // DivClass=1 - A=2 - ATitle=3 - AHref=4 - ATarget=5 - ImageAlt=6 - ImageSrc=7 - P=9 - PContent=10 --- Href/Src=http://www.server.com/Blah.ashx/GetFile.aspx... + private static readonly Regex ImageLeftRightRegexIE = new Regex(@"
()?\""?(.*?)\""?()?(\r\n

(.*?)

)?
", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + // A=1 - ATitle=2 - AHref=3 - ATarget=4 - ImageAlt=5 - ImageSrc=6 - P=8 - PContent=9 --- Href/Src=http://www.server.com/Blah.ashx/GetFile.aspx... + private static readonly Regex ImageAutoRegexIE = new Regex(@"\r\n\r\n\r\n
()?\""?(.*?)\""?()?(\r\n

(.*?)

)?
", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + // A=1 - ATitle=2 - AHref=3 - ATarget=4 - ImageAlt=5 - ImageSrc=6 --- Href/Src=http://www.server.com/Blah.ashx/GetFile.aspx... + private static readonly Regex ImageInlineRegexIE = new Regex(@"()?\""?(.*?)\""?()?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + /// + /// Reverse formats HTML content into WikiMarkup. + /// + /// The input HTML. + /// The corresponding WikiMarkup. + public static string ReverseFormat(string html) { + Match match = null; + + StringBuilder buffer = new StringBuilder(html); + + buffer.Replace("
", "
"); + buffer.Replace("
", "
"); + + buffer.Replace("", ""); + buffer.Replace("", ""); + buffer.Replace("", ""); + buffer.Replace("", ""); + buffer.Replace("", ""); + buffer.Replace("", ""); + buffer.Replace("", ""); + buffer.Replace("", ""); + + buffer.Replace("&amp;", "&"); + + // Escape square brackets, otherwise they're interpreted as links + buffer.Replace("[", "["); + buffer.Replace("]", "]"); + + // Temporarily replace
in
 tags
+			match = PreRegex.Match(buffer.ToString());
+			while(match.Success) {
+				Match subMatch = SingleBR.Match(match.Value);
+				while(subMatch.Success) {
+					buffer.Remove(match.Index + subMatch.Index, subMatch.Length);
+					buffer.Insert(match.Index + subMatch.Index, "");
+					subMatch = SingleBR.Match(match.Value, subMatch.Index + 1);
+				}
+				match = PreRegex.Match(buffer.ToString(), match.Index + 1);
+			}
+			buffer.Replace("", "\r\n");
+
+			// Code
+			match = CodeRegex.Match(buffer.ToString());
+			while(match.Success) {
+				buffer.Remove(match.Index, match.Length);
+				buffer.Insert(match.Index, "{{" + match.Value.Substring(6, match.Length - 13) + "}}");
+				match = CodeRegex.Match(buffer.ToString(), match.Index + 1);
+			}
+
+			// Pre
+			// Unescape square brackets
+			match = PreRegex.Match(buffer.ToString());
+			while(match.Success) {
+				buffer.Remove(match.Index, match.Length);
+				buffer.Insert(match.Index, "@@" +
+					match.Value.Substring(5, match.Length - 11).Replace("&", "&").Replace("[", "[").Replace("]", "]") +
+					"@@");
+				match = PreRegex.Match(buffer.ToString(), match.Index + 1);
+			}
+
+			// Bold
+			match = BoldRegex.Match(buffer.ToString());
+			while(match.Success) {
+				buffer.Remove(match.Index, match.Length);
+				buffer.Insert(match.Index, "'''" + match.Groups[2].Value + "'''");
+				match = BoldRegex.Match(buffer.ToString(), match.Index + 1);
+			}
+
+			// Italic
+			match = ItalicRegex.Match(buffer.ToString());
+			while(match.Success) {
+				buffer.Remove(match.Index, match.Length);
+				buffer.Insert(match.Index, "''" + match.Groups[2].Value + "''");
+				match = ItalicRegex.Match(buffer.ToString(), match.Index + 1);
+			}
+
+			// Underline
+			match = UnderlineRegex.Match(buffer.ToString());
+			while(match.Success) {
+				buffer.Remove(match.Index, match.Length);
+				buffer.Insert(match.Index, "__" + match.Groups[2].Value + "__");
+				match = UnderlineRegex.Match(buffer.ToString(), match.Index + 1);
+			}
+
+			// Strike
+			match = StrikeRegex.Match(buffer.ToString());
+			while(match.Success) {
+				buffer.Remove(match.Index, match.Length);
+				buffer.Insert(match.Index, "--" + match.Groups[2].Value + "--");
+				match = StrikeRegex.Match(buffer.ToString(), match.Index + 1);
+			}
+
+			// Horizontal Ruler
+			match = HRRegex.Match(buffer.ToString());
+			while(match.Success) {
+				buffer.Remove(match.Index, match.Length);
+				buffer.Insert(match.Index, "----");
+				match = HRRegex.Match(buffer.ToString(), match.Index + 1);
+			}
+
+			// H1
+			match = H1Regex.Match(buffer.ToString());
+			while(match.Success) {
+				char c = buffer[match.Index + match.Length];
+				bool addNewLine = false;
+				if(buffer[match.Index + match.Length] != '\n') addNewLine = true;
+				buffer.Remove(match.Index, match.Length);
+				if(addNewLine) buffer.Insert(match.Index, "==" + match.Groups[2].Value + "==\n");
+				else buffer.Insert(match.Index, "==" + match.Groups[2].Value + "==");
+				match = H1Regex.Match(buffer.ToString(), match.Index + 1);
+			}
+
+			// H2
+			match = H2Regex.Match(buffer.ToString());
+			while(match.Success) {
+				bool addNewLine = false;
+				if(buffer[match.Index + match.Length] != '\n') addNewLine = true;
+				buffer.Remove(match.Index, match.Length);
+				if(addNewLine) buffer.Insert(match.Index, "===" + match.Groups[2].Value + "===\n");
+				else buffer.Insert(match.Index, "===" + match.Groups[2].Value + "===");
+				match = H2Regex.Match(buffer.ToString(), match.Index + 1);
+			}
+
+			// H3
+			match = H3Regex.Match(buffer.ToString());
+			while(match.Success) {
+				bool addNewLine = false;
+				if(buffer[match.Index + match.Length] != '\n') addNewLine = true;
+				buffer.Remove(match.Index, match.Length);
+				if(addNewLine) buffer.Insert(match.Index, "====" + match.Groups[2].Value + "====\n");
+				else buffer.Insert(match.Index, "====" + match.Groups[2].Value + "====");
+				match = H3Regex.Match(buffer.ToString(), match.Index + 1);
+			}
+
+			// H4
+			match = H4Regex.Match(buffer.ToString());
+			while(match.Success) {
+				bool addNewLine = false;
+				if(buffer[match.Index + match.Length] != '\n') addNewLine = true;
+				buffer.Remove(match.Index, match.Length);
+				if(addNewLine) buffer.Insert(match.Index, "=====" + match.Groups[2].Value + "=====\n");
+				else buffer.Insert(match.Index, "=====" + match.Groups[2].Value + "=====");
+				match = H4Regex.Match(buffer.ToString(), match.Index + 1);
+			}
+
+			// Lists
+			buffer.Replace("
    ", "
      "); + buffer.Replace("
    ", "
"); + buffer.Replace("
    ", "
      "); + buffer.Replace("
    ", "
"); + buffer.Replace("
  • ", "
  • "); + buffer.Replace("
  • ", ""); + ProcessLists(buffer); + + // Page Link + match = PageLinkRegex.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + if(match.Groups[2].Value == @"target=""_blank"" ") insertion += "^"; + string decoded = Tools.UrlDecode(match.Groups[3].Value); + insertion += decoded; + if(match.Groups[6].Value != decoded) insertion += "|" + match.Groups[6].Value; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = PageLinkRegex.Match(buffer.ToString(), match.Index + 1); + } + + // Page Link IE + match = PageLinkRegexIE.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + if(match.Groups[3].Value == " target=_blank") insertion += "^"; + string page = match.Groups[2].Value.Substring(match.Groups[2].Value.LastIndexOf("/") + 1); + page = page.Substring(0, page.Length - 5); // Remove .ashx + page = Tools.UrlDecode(page); + insertion += page; + if(match.Groups[4].Value != page) insertion += "|" + match.Groups[4].Value; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = PageLinkRegexIE.Match(buffer.ToString(), match.Index + 1); + } + + // Unknown Link + match = UnknownLinkRegex.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + if(match.Groups[2].Value == @"target=""_blank"" ") insertion += "^"; + string decoded = Tools.UrlDecode(match.Groups[3].Value); + insertion += decoded; + if(match.Groups[6].Value != decoded) insertion += "|" + match.Groups[6].Value; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = UnknownLinkRegex.Match(buffer.ToString(), match.Index + 1); + } + + // Unknown Link IE + match = UnknownLinkRegexIE.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + if(match.Groups[3].Value == " target=_blank") insertion += "^"; + string page = match.Groups[2].Value.Substring(match.Groups[2].Value.LastIndexOf("/") + 1); + page = page.Substring(0, page.Length - 5); // Remove .ashx + page = Tools.UrlDecode(page); + insertion += page; + if(match.Groups[4].Value != page) insertion += "|" + match.Groups[4].Value; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = UnknownLinkRegexIE.Match(buffer.ToString(), match.Index + 1); + } + + // File Link + match = FileLinkRegex.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + if(match.Groups[2].Value == @"target=""_blank"" ") insertion += "^"; + if(match.Groups[3].Value != "") insertion += "{UP:" + match.Groups[4].Value + "}" + match.Groups[6].Value; + else insertion += "{UP}" + match.Groups[6].Value; + if(!match.Groups[10].Value.StartsWith("GetFile.aspx") && !match.Groups[10].Value.StartsWith("{UP")) insertion += "|" + match.Groups[10]; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = FileLinkRegex.Match(buffer.ToString(), match.Index + 1); + } + + // File Link IE + match = FileOrAttachmentLinkRegexIE.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + if(match.Groups[8].Value == " target=_blank") insertion += "^"; + if(match.Groups[3].Value != "") insertion += "{UP:" + match.Groups[4].Value; + else insertion += "{UP"; + if(match.Groups[6].Value != "") insertion += "(" + match.Groups[6].Value + ")"; + insertion += "}"; + insertion += match.Groups[7].Value; + if(!match.Groups[9].Value.StartsWith("GetFile.aspx") && !match.Groups[9].Value.StartsWith("{UP")) insertion += "|" + match.Groups[9].Value; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = FileOrAttachmentLinkRegexIE.Match(buffer.ToString(), match.Index + 1); + } + + // Attachment Link + match = AttachmentLinkRegex.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + if(match.Groups[2].Value == @"target=""_blank"" ") insertion += "^"; + // if the provider is not present "{UP" is added without ":providername" + insertion += match.Groups[4].Value == "" ? "{UP" : "{UP:" + match.Groups[4].Value; + insertion += "(" + Tools.UrlDecode(match.Groups[6].Value) + ")}" + Tools.UrlDecode(match.Groups[8].Value); + if(!match.Groups[12].Value.StartsWith("GetFile.aspx") && !match.Groups[12].Value.StartsWith("{UP")) insertion += "|" + match.Groups[12]; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = AttachmentLinkRegex.Match(buffer.ToString(), match.Index + 1); + } + + // External Link + match = ExternalLinkRegex.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + //if(match.Groups[6].Value == @"target=""_blank""") insertion += "^"; + string url = match.Groups[2].Value; + if(url.StartsWith(Settings.MainUrl)) url = url.Substring(Settings.MainUrl.Length); + insertion += url; + if(match.Groups[7].Value != match.Groups[2].Value && match.Groups[7].Value + "/" != match.Groups[2].Value) insertion += "|" + match.Groups[7].Value; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = ExternalLinkRegex.Match(buffer.ToString(), match.Index + 1); + } + + // External Link IE + match = ExternalLinkRegexIE.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + string url = match.Groups[2].Value; + if(url.StartsWith(Settings.MainUrl)) url = url.Substring(Settings.MainUrl.Length); + insertion += url; + if(match.Groups[4].Value != match.Groups[2].Value.TrimEnd('/')) insertion += "|" + match.Groups[4].Value; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = ExternalLinkRegexIE.Match(buffer.ToString(), match.Index + 1); + } + + // Internal Link + match = InternalLinkRegex.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + if(match.Groups[1].Value == @"target=""_blank""") insertion += "^"; + string url = match.Groups[2].Value; + if(url.StartsWith(Settings.MainUrl)) url = url.Substring(Settings.MainUrl.Length); + insertion += url; + if(match.Groups[2].Value != match.Groups[6].Value) insertion += "|" + match.Groups[6].Value; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = InternalLinkRegex.Match(buffer.ToString(), match.Index + 1); + } + + // Internal Link IE + match = InternalLinkRegexIE.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + if(match.Groups[3].Value == " target=_blank") insertion += "^"; + string url = match.Groups[2].Value; + if(url.StartsWith(Settings.MainUrl)) url = url.Substring(Settings.MainUrl.Length); + insertion += url; + if(match.Groups[4].Value != match.Groups[2].Value) insertion += "|" + match.Groups[4].Value; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = InternalLinkRegexIE.Match(buffer.ToString(), match.Index + 1); + } + + // Anchor Link + match = AnchorLinkRegex.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + if(match.Groups[3].Value != "") insertion += "^"; + insertion += "#"; + insertion += match.Groups[1].Value; + string val = match.Groups[6].Value.ToLowerInvariant().Replace(" ", ""); + if(val != "") insertion += "|" + val; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = AnchorLinkRegex.Match(buffer.ToString(), match.Index + 1); + } + + // System Link (.aspx) + match = SystemLinkRegex.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + if(match.Groups[1].Value == @"target=""_blank""") insertion += "^"; + insertion += match.Groups[2].Value; + if(match.Groups[2].Value != match.Groups[6].Value) insertion += "|" + match.Groups[6].Value; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = SystemLinkRegex.Match(buffer.ToString(), match.Index + 1); + } + + // System Link IE + match = SystemLinkRegexIE.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + if(match.Groups[3].Value == " target=_blank") insertion += "^"; + string url = match.Groups[2].Value.Substring(match.Groups[2].Value.LastIndexOf("/") + 1); + insertion += url; + if(match.Groups[4].Value != url) insertion += "|" + match.Groups[4].Value; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = SystemLinkRegexIE.Match(buffer.ToString(), match.Index + 1); + } + + // Email Link + match = EmailLinkRegex.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + if(match.Groups[2].Value == @"target=""_blank"" ") insertion += "^"; + insertion += match.Groups[3].Value; + if(match.Groups[6].Value != match.Groups[3].Value) insertion += "|" + match.Groups[6].Value; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = EmailLinkRegex.Match(buffer.ToString(), match.Index + 1); + } + + // Email Link IE + match = EmailLinkRegexIE.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + insertion += match.Groups[2].Value.Substring(7); // Remove mailto: + if(match.Groups[4].Value != match.Groups[2].Value.Substring(7)) insertion += "|" + match.Groups[4].Value; + insertion += "]"; + buffer.Insert(match.Index, insertion); + match = EmailLinkRegexIE.Match(buffer.ToString(), match.Index + 1); + } + + // Anchor + match = AnchorRegex.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + buffer.Insert(match.Index, "[anchor|#" + match.Groups[1].Value + "]"); + match = AnchorRegex.Match(buffer.ToString(), match.Index + 1); + } + + // Image Left/Right/Auto + match = ImageLeftRightRegex.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + string insertion = "["; + if(match.Groups[1].Value.StartsWith("", ""); + buffer.Replace("

    ", "
    "); + buffer.Replace("

    ", ""); + buffer.Replace("

    ", "
    "); + + //
    + //sb.Replace("

    ", "\r\n\r\n"); + //sb.Replace("\r\n
    ", "\r\n\r\n"); + //sb.Replace("
    ", "{BR}\r\n"); + buffer.Replace("
    ", "\r\n"); + match = SingleNewLine.Match(buffer.ToString()); + while(match.Success) { + buffer.Remove(match.Index, match.Length); + buffer.Insert(match.Index, "{BR}"); + match = SingleNewLine.Match(buffer.ToString(), match.Index); + } + + // Fix line breaks in IE + //sb.Replace("\r\n\r\n\r\n=====", "\r\n\r\n====="); + //sb.Replace("\r\n\r\n\r\n====", "\r\n\r\n===="); + //sb.Replace("\r\n\r\n\r\n===", "\r\n\r\n==="); + //sb.Replace("\r\n\r\n\r\n==", "\r\n\r\n=="); + //sb.Replace("\r\n\r\n\r\n----", "\r\n\r\n----"); + + buffer.Replace("<", "<"); + buffer.Replace(">", ">"); + + string result = buffer.ToString(); + + return result.TrimEnd('\r', '\n'); + } + + /// + /// Processes unordered and ordered lists. + /// + /// The string builder buffer. + private static void ProcessLists(StringBuilder sb) { + string temp = null; + + int ulIndex = -1; + int olIndex = -1; + + int lastIndex = 0; + + do { + temp = sb.ToString().ToLowerInvariant(); + + ulIndex = temp.IndexOf("
      ", lastIndex); + olIndex = temp.IndexOf("
        ", lastIndex); + + if(ulIndex != -1 || olIndex != -1) { + // 1. Find tag pairs + // 2. Extract block and remove it from SB + // 3. Process block and generate WikiMarkup output + // 4. Insert new markup in SB at original position + + if(ulIndex != -1 && (ulIndex < olIndex || olIndex == -1)) { + // Find a UL block + int openIndex, closeIndex; + + if(FindTagsPair(sb, "
          ", "
        ", lastIndex, out openIndex, out closeIndex)) { + string section = sb.ToString().Substring(openIndex, closeIndex - openIndex + 5); + sb.Remove(openIndex, closeIndex - openIndex + 5); + + string result = ProcessList(false, section); + + sb.Insert(openIndex, result); + + // Skip processed data + lastIndex = openIndex + result.Length; + } + else lastIndex += 4; + + continue; + } + + if(olIndex != -1 && (olIndex < ulIndex || ulIndex == -1)) { + // Find a OL block + int openIndex, closeIndex; + + if(FindTagsPair(sb, "
          ", "
        ", lastIndex, out openIndex, out closeIndex)) { + string section = sb.ToString().Substring(openIndex, closeIndex - openIndex + 5); + sb.Remove(openIndex, closeIndex - openIndex + 5); + + string result = ProcessList(true, section); + + sb.Insert(openIndex, result); + + // Skip processed data + lastIndex = openIndex + result.Length; + } + else lastIndex += 4; + + continue; + } + } + + } while(ulIndex != -1 || olIndex != -1); + } + + /// + /// Processes an unordered or ordered list. + /// + /// true for an ordered list, false for an unordered list. + /// The input HTML. + /// The output WikiMarkup. + private static string ProcessList(bool ordered, string html) { + HtmlList list = BuildListTree(ordered, html); + + string wikiMarkup = BuildListWikiMarkup(list, ""); + + return wikiMarkup; + } + + /// + /// Builds the WikiMarkup for a list. + /// + /// The root list. + /// The previous bullets, used at upper levels. + /// The WikiMarkup. + private static string BuildListWikiMarkup(HtmlList list, string previousBullets) { + previousBullets = previousBullets + (list.Type == HtmlListType.Ordered ? "#" : "*"); + + StringBuilder sb = new StringBuilder(500); + + foreach(HtmlListElement elem in list.Elements) { + sb.Append(previousBullets); + sb.Append(" "); + sb.Append(elem.Text); + sb.Append("\r\n"); + + if(elem.SubList != null) { + sb.Append(BuildListWikiMarkup(elem.SubList, previousBullets)); + } + } + + // Remove empty lines in the middle of the list + string raw = sb.ToString().Replace("\r", ""); + string[] lines = raw.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + return + string.Join("\r\n", lines) + + (raw.EndsWith("\r\n") || raw.EndsWith("\n") ? "\r\n" : ""); + } + + /// + /// Builds a list tree. + /// + /// true for an ordered list. + /// The input HTML. + /// The list tree. + private static HtmlList BuildListTree(bool ordered, string html) { + string[] tags = new string[] { "
          ", "
            ", "
          • ", "
          • ", "
          ", "
        " }; + + // IE seems to add new-lines after some elements + // \r\n are never added by the Formatter, so it is safe to remove all them + html = html.Replace("\r", ""); + html = html.Replace("\n", ""); + + int index = 0; + int lastOpenListItemIndex = 0; + int stringFound; + + HtmlList root = new HtmlList(ordered ? HtmlListType.Ordered : HtmlListType.Unordered); + HtmlList currentList = root; + + do { + index = FirstIndexOfAny(html, index, out stringFound, tags); + + if(index != -1) { + switch(stringFound) { + case 0: //
          + // Unless at the beginning, start a new sub-list + if(index != 0) { + // Set text of current element (sub-lists are added into the previous item) + if(lastOpenListItemIndex != -1) { + string text = html.Substring(lastOpenListItemIndex + 4, index - (lastOpenListItemIndex + 4)); + currentList.Elements[currentList.Elements.Count - 1].Text = text; + } + currentList.Elements[currentList.Elements.Count - 1].SubList = new HtmlList(HtmlListType.Ordered); + currentList = currentList.Elements[currentList.Elements.Count - 1].SubList; + } + break; + case 1: //
            + // Unless at the beginning, start a new sub-list + if(index != 0) { + // Set text of current element (sub-lists are added into the previous item) + if(lastOpenListItemIndex != -1) { + string text = html.Substring(lastOpenListItemIndex + 4, index - (lastOpenListItemIndex + 4)); + currentList.Elements[currentList.Elements.Count - 1].Text = text; + } + currentList.Elements[currentList.Elements.Count - 1].SubList = new HtmlList(HtmlListType.Unordered); + currentList = currentList.Elements[currentList.Elements.Count - 1].SubList; + } + break; + case 2: //
          • + lastOpenListItemIndex = index; + currentList.Elements.Add(new HtmlListElement()); + break; + case 3: //
          • + // If lastOpenListItemIndex != -1 (i.e. there are no sub-lists) extract item text and set it to the last list element + // Otherwise, navigate upwards to parent list (if any) + if(lastOpenListItemIndex != -1) { + string text = html.Substring(lastOpenListItemIndex + 4, index - (lastOpenListItemIndex + 4)); + currentList.Elements[currentList.Elements.Count - 1].Text = text; + } + else { + currentList = FindAnchestor(root, currentList); + } + break; + case 4: //
          + // Close last open list (nothing to do) + lastOpenListItemIndex = -1; + break; + case 5: //
        + // Close last open list (nothing to do) + lastOpenListItemIndex = -1; + break; + default: + throw new NotSupportedException(); + } + + index++; + } + } while(index != -1); + + return root; + } + + /// + /// Finds the anchestor of a list in a tree. + /// + /// The root of the tree. + /// The current element. + /// The anchestor of current. + private static HtmlList FindAnchestor(HtmlList root, HtmlList current) { + foreach(HtmlListElement elem in root.Elements) { + if(elem.SubList == current) return root; + else if(elem.SubList != null) { + HtmlList temp = FindAnchestor(elem.SubList, current); + if(temp != null) return temp; + } + } + //return root; + return null; + } + + /// + /// Finds the index of the first string. + /// + /// The input string. + /// The start index. + /// The index (in strings) of the string found. + /// The strings to search for. + /// The index of the string found in input. + private static int FirstIndexOfAny(string input, int startIndex, out int stringFound, params string[] strings) { + if(startIndex > input.Length) { + stringFound = -1; + return -1; + } + + int[] indices = new int[strings.Length]; + + for(int i = 0; i < strings.Length; i++) { + indices[i] = input.IndexOf(strings[i], startIndex); + } + + bool nothingFound = true; + int min = int.MaxValue; + stringFound = -1; + for(int i = 0; i < indices.Length; i++) { + if(indices[i] != -1 && indices[i] < min) { + nothingFound = false; + min = indices[i]; + stringFound = i; + } + } + + if(nothingFound) return -1; + else return min; + } + + /// + /// Finds the position of a matched tag pair. + /// + /// The string builder buffer. + /// The open tag. + /// The close tag. + /// The start index. + /// The open index. + /// The (matched/balanced) close index. + /// true if a tag pair is found, false otherwise. + private static bool FindTagsPair(StringBuilder sb, string openTag, string closeTag, int startIndex, out int openIndex, out int closeIndex) { + // Find indexes for all open and close tags + // Identify the smallest tag tree + + string text = sb.ToString(); + + List openIndexes = new List(10); + List closeIndexes = new List(10); + + if(startIndex >= sb.Length) { + openIndex = -1; + closeIndex = -1; + return false; + } + + int currentOpenIndex = startIndex - 1; + int currentCloseIndex = startIndex - 1; + + do { + currentOpenIndex = text.IndexOf(openTag, currentOpenIndex + 1); + if(currentOpenIndex != -1) openIndexes.Add(currentOpenIndex); + } while(currentOpenIndex != -1); + + // Optimization + if(openIndexes.Count == 0) { + openIndex = -1; + closeIndex = -1; + return false; + } + + do { + currentCloseIndex = text.IndexOf(closeTag, currentCloseIndex + 1); + if(currentCloseIndex != -1) closeIndexes.Add(currentCloseIndex); + } while(currentCloseIndex != -1); + + // Optimization + if(closeIndexes.Count == 0) { + openIndex = -1; + closeIndex = -1; + return false; + } + + // Condition needed for further processing + if(openIndexes.Count != closeIndexes.Count) { + openIndex = -1; + closeIndex = -1; + return false; + } + + // Build a sorted list of tags + List tags = new List(openIndexes.Count * 2); + foreach(int index in openIndexes) { + tags.Add(new Tag() { Type = TagType.Open, Index = index }); + } + foreach(int index in closeIndexes) { + tags.Add(new Tag() { Type = TagType.Close, Index = index }); + } + tags.Sort((x, y) => { return x.Index.CompareTo(y.Index); }); + + // Find shortest closed tree + int openCount = 0; + int firstOpenIndex = -1; + foreach(Tag tag in tags) { + if(tag.Type == TagType.Open) { + openCount++; + if(firstOpenIndex == -1) firstOpenIndex = tag.Index; + } + else openCount--; + + if(openCount == 0) { + openIndex = firstOpenIndex; + closeIndex = tag.Index; + return true; + } + } + + openIndex = -1; + closeIndex = -1; + return false; + } + + /// + /// Prepares a link URL. + /// + /// The raw URL, as generated by the formatter. + /// The prepared link URL, suitable for formatting. + private static string PrepareLink(string rawUrl) { + string mainUrl = GetCurrentRequestMainUrl().ToLowerInvariant(); + if(rawUrl.ToLowerInvariant().StartsWith(mainUrl)) rawUrl = rawUrl.Substring(mainUrl.Length); + + if(rawUrl.ToLowerInvariant().EndsWith(".ashx")) return rawUrl.Substring(0, rawUrl.Length - 5); + + int extensionIndex = rawUrl.ToLowerInvariant().IndexOf(".ashx#"); + if(extensionIndex != -1) { + return rawUrl.Remove(extensionIndex, 5); + } + + if(rawUrl.StartsWith("GetFile.aspx")) { + // Look for File and Provider parameter (v2 and v3) + + string provider, page, file; + GetProviderAndFileAndPage(rawUrl, out provider, out page, out file); + + if(provider == null && page == null) return "{UP}" + file; + else if(page != null) { + return "{UP" + (provider != null ? ":" + provider : "") + "(" + page + ")}" + file; + } + else { + return "{UP" + (provider != null ? ":" + provider : "") + "}" + file; + } + } + + return rawUrl; + } + + /// + /// Prepares an image URL. + /// + /// The raw URL, as generated by the formatter. + /// The prepared image URL, suitable for formatting. + private static string PrepareImageUrl(string rawUrl) { + string mainUrl = GetCurrentRequestMainUrl().ToLowerInvariant(); + if(rawUrl.ToLowerInvariant().StartsWith(mainUrl)) rawUrl = rawUrl.Substring(mainUrl.Length); + + if(rawUrl.StartsWith("GetFile.aspx")) { + // Look for File and Provider parameter (v2 and v3) + + string provider, page, file; + GetProviderAndFileAndPage(rawUrl, out provider, out page, out file); + + if (provider == null) return "{UP" + (page != null ? "(" + page + ")" : "") + "}" + file; + else return "{UP:" + provider + (page != null ? "(" + page + ")" : "") + "}" + file; + } + else return rawUrl; + } + + /// + /// Gets the current request main URL, such as http://www.server.com/Wiki/. + /// + /// The URL. + private static string GetCurrentRequestMainUrl() { + string url = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path); + if(!url.EndsWith("/")) { + int index = url.LastIndexOf("/"); + if(index != -1) url = url.Substring(0, index + 1); + } + return url; + } + + /// + /// Gets the provider and file of a link or URL. + /// + /// The raw URL, in the format ...?Provider=PROVIDER[&IsPageAttachment=1&Page=PAGE]&File=FILE. + /// The provider, or null. + /// The page (for attachments), or null. + /// The file. + private static void GetProviderAndFileAndPage(string rawUrl, out string provider, out string page, out string file) { + rawUrl = rawUrl.Substring(rawUrl.IndexOf("?") + 1).Replace("&", "&"); + + string[] chunks = rawUrl.Split('&'); + + provider = null; + page = null; + file = null; + + foreach(string chunk in chunks) { + if(chunk.StartsWith("Provider=")) { + provider = chunk.Substring(9); + } + if(chunk.StartsWith("File=")) { + file = chunk.Substring(5); + } + if(chunk.StartsWith("Page=")) { + page = chunk.Substring(5); + } + } + } + + } + + /// + /// Represents an open or close tag. + /// + public class Tag { + + /// + /// Gets or sets the tag type. + /// + public TagType Type { get; set; } + + /// + /// Gets or sets the tag index. + /// + public int Index { get; set; } + + } + + /// + /// Lists tag types. + /// + public enum TagType { + /// + /// An open tag. + /// + Open, + /// + /// A close tag. + /// + Close + } + + /// + /// Represents a HTML list. + /// + public class HtmlList { + + /// + /// Initializes a new instance of the class. + /// + /// The list type. + public HtmlList(HtmlListType type) { + Type = type; + Elements = new List(10); + } + + /// + /// Gets or sets the list type. + /// + public HtmlListType Type { get; set; } + + /// + /// Gets or sets the list elements. + /// + public List Elements { get; set; } + + } + + /// + /// Represents a HTML list element. + /// + public class HtmlListElement { + + /// + /// Gets or sets the text. + /// + public string Text { get; set; } + + /// + /// Gets or sets the sub-list. + /// + public HtmlList SubList { get; set; } + + } + + /// + /// Lists HTML list types. + /// + public enum HtmlListType { + /// + /// An ordered list. + /// + Ordered, + /// + /// An unordered list. + /// + Unordered + } + +} diff --git a/Core/SearchTools.cs b/Core/SearchTools.cs new file mode 100644 index 0000000..6bdc4b7 --- /dev/null +++ b/Core/SearchTools.cs @@ -0,0 +1,210 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki { + + /// + /// Implements tools for searching through the wiki. + /// + public static class SearchTools { + + /// + /// Searches for pages with name or title similar to a specified value. + /// + /// The name to look for (null for the root). + /// The namespace to search into. + /// The similar pages, if any. + public static PageInfo[] SearchSimilarPages(string name, string nspace) { + if(string.IsNullOrEmpty(nspace)) nspace = null; + + SearchResultCollection searchResults = Search(name, false, false, SearchOptions.AtLeastOneWord); + + List result = new List(20); + + foreach(SearchResult res in searchResults) { + PageDocument pageDoc = res.Document as PageDocument; + if(pageDoc != null) { + string pageNamespace = NameTools.GetNamespace(pageDoc.PageInfo.FullName); + if(string.IsNullOrEmpty(pageNamespace)) pageNamespace = null; + + if(pageNamespace == nspace) { + result.Add(pageDoc.PageInfo); + } + } + } + + // Search page names for matches + List allPages = Pages.GetPages(Pages.FindNamespace(nspace)); + PageNameComparer comp = new PageNameComparer(); + string currentName = name.ToLowerInvariant(); + foreach(PageInfo page in allPages) { + if(NameTools.GetLocalName(page.FullName).ToLowerInvariant().Contains(currentName)) { + if(result.Find(delegate(PageInfo p) { return comp.Compare(p, page) == 0; }) == null) { + result.Add(page); + } + } + } + + return result.ToArray(); + } + + /// + /// Performs a search in the wiki. + /// + /// The search query. + /// A value indicating whether to perform a full-text search. + /// A value indicating whether to search through files and attachments. + /// The search options. + /// The results collection. + public static SearchResultCollection Search(string query, bool fullText, bool searchFilesAndAttachments, SearchOptions options) { + + // First, search regular page content... + List allCollections = new List(3); + + foreach(IPagesStorageProviderV30 prov in Collectors.PagesProviderCollector.AllProviders) { + SearchResultCollection currentResults = prov.PerformSearch(new SearchParameters(query, options)); + + if(!fullText) { + // All non title-related matches must be removed + SearchResultCollection filteredResults = new SearchResultCollection(10); + + foreach(SearchResult res in currentResults) { + foreach(WordInfo word in res.Matches) { + if(word.Location == WordLocation.Title) { + filteredResults.Add(res); + break; + } + } + } + + allCollections.Add(filteredResults); + } + else allCollections.Add(currentResults); + } + + // ... normalize relevance based on the number of providers + float providerNormalizationFactor = 1F / (float)Collectors.PagesProviderCollector.AllProviders.Length; + foreach(SearchResultCollection coll in allCollections) { + foreach(SearchResult result in coll) { + result.Relevance.NormalizeAfterFinalization(providerNormalizationFactor); + } + } + + if(searchFilesAndAttachments) { + // ... then build a temporary index for files and attachments... + StandardIndex temporaryIndex = new StandardIndex(); + uint tempDocumentId = 1; + uint tempWordId = 1; + temporaryIndex.IndexChanged += delegate(object sender, IndexChangedEventArgs e) { + if(e.Change == IndexChangeType.DocumentAdded) { + List ids = null; + if(e.ChangeData.Words != null) { + ids = new List(20); + foreach(DumpedWord d in e.ChangeData.Words) { + ids.Add(new WordId(d.Text, tempWordId)); + tempWordId++; + } + } + e.Result = new IndexStorerResult(tempDocumentId, ids); + tempDocumentId++; + } + }; + temporaryIndex.SetBuildDocumentDelegate(DetectFileOrAttachment); + + foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) { + TraverseDirectories(temporaryIndex, prov, null); + + string[] pagesWithAttachments = prov.GetPagesWithAttachments(); + foreach(string page in pagesWithAttachments) { + // Store attachments for the current page in the index + PageInfo pageInfo = Pages.FindPage(page); + + // pageInfo can be null if the index is corrupted + if(pageInfo != null) { + foreach(string attachment in prov.ListPageAttachments(pageInfo)) { + FileDetails details = prov.GetPageAttachmentDetails(pageInfo, attachment); + temporaryIndex.StoreDocument(new PageAttachmentDocument(pageInfo, + attachment, prov.GetType().FullName, details.LastModified), + new string[0], "", null); + } + } + } + } + + // ... then search in the temporary index and normalize relevance + SearchResultCollection filesAndAttachments = temporaryIndex.Search(new SearchParameters(query, options)); + providerNormalizationFactor = 1F / (float)Collectors.FilesProviderCollector.AllProviders.Length; + foreach(SearchResult result in filesAndAttachments) { + result.Relevance.NormalizeAfterFinalization(providerNormalizationFactor); + } + + allCollections.Add(filesAndAttachments); + } + + return CombineCollections(allCollections); + } + + /// + /// Detects the document in a dumped instance for files and attachments. + /// + /// The dumped document instance. + /// The proper document instance. + private static IDocument DetectFileOrAttachment(DumpedDocument doc) { + if(doc.TypeTag == FileDocument.StandardTypeTag) { + return new FileDocument(doc); + } + else if(doc.TypeTag == PageAttachmentDocument.StandardTypeTag) { + return new PageAttachmentDocument(doc); + } + else throw new NotSupportedException(); + } + + /// + /// Traverses a directory tree, indexing all files. + /// + /// The output index. + /// The provider. + /// The current directory. + private static void TraverseDirectories(InMemoryIndexBase index, IFilesStorageProviderV30 provider, string currentDir) { + // Store files in the index + foreach(string file in provider.ListFiles(currentDir)) { + FileDetails details = provider.GetFileDetails(file); + index.StoreDocument(new FileDocument(file, provider.GetType().FullName, details.LastModified), + new string[0], "", null); + } + + // Recursively process all sub-directories + foreach(string directory in provider.ListDirectories(currentDir)) { + TraverseDirectories(index, provider, directory); + } + } + + /// + /// Combines a set of s into a single object. + /// + /// The collections. + /// The resulting . + private static SearchResultCollection CombineCollections(List collections) { + List tempResults = new List(100); + + foreach(SearchResultCollection coll in collections) { + tempResults.AddRange(coll); + } + + tempResults.Sort(delegate(SearchResult x, SearchResult y) { return y.Relevance.Value.CompareTo(x.Relevance.Value); }); + + SearchResultCollection resultCollection = new SearchResultCollection(50); + foreach(SearchResult singleResult in tempResults) { + resultCollection.Add(singleResult); + } + + return resultCollection; + } + + } + +} diff --git a/Core/SessionFacade.cs b/Core/SessionFacade.cs new file mode 100644 index 0000000..7d35b2b --- /dev/null +++ b/Core/SessionFacade.cs @@ -0,0 +1,194 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Web.SessionState; +using System.Web; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Exposes in a strongly-typed fashion the Session variables. + /// + public static class SessionFacade { + + /// + /// Gets the current Session object. + /// + private static HttpSessionState Session { + get { return HttpContext.Current.Session; } + } + + /// + /// Gets or sets the Login Key. + /// + public static string LoginKey { + get { return Session != null ? (string)Session["LoginKey"] : null; } + set { if(Session != null) Session["LoginKey"] = value; } + } + + /// + /// Gets or sets the current username, or null. + /// + public static string CurrentUsername { + get { return Session != null ? Session["Username"] as string : null; } + set { if(Session != null) Session["Username"] = value; } + } + + /// + /// Gets the current user, if any. + /// + /// The current user, or null. + public static UserInfo GetCurrentUser() { + if(Session != null) { + string sessionId = Session.SessionID; + + UserInfo current = SessionCache.GetCurrentUser(sessionId); + if(current != null) return current; + else { + string un = CurrentUsername; + if(string.IsNullOrEmpty(un)) return null; + else if(object.ReferenceEquals(un, AnonymousUsername) || un == AnonymousUsername) return Users.GetAdministratorAccount(); + else { + current = Users.FindUser(un); + SessionCache.SetCurrentUser(sessionId, current); + return current; + } + } + } + else return null; + } + + /// + /// The username for anonymous users. + /// + public const string AnonymousUsername = "|"; + + /// + /// Gets the current username for security checks purposes only. + /// + /// The current username, or AnonymousUsername if anonymous. + public static string GetCurrentUsername() { + string un = CurrentUsername; + if(string.IsNullOrEmpty(un)) return AnonymousUsername; + else return un; + } + + /// + /// Gets the current user groups. + /// + public static UserGroup[] GetCurrentGroups() { + if(Session != null) { + string sessionId = Session.SessionID; + UserGroup[] groups = SessionCache.GetCurrentGroups(sessionId); + + if(groups == null || groups.Length == 0) { + UserInfo current = GetCurrentUser(); + if(current != null) { + groups = new UserGroup[current.Groups.Length]; + for(int i = 0; i < groups.Length; i++) { + groups[i] = Users.FindUserGroup(current.Groups[i]); + } + } + else { + groups = new UserGroup[] { Users.FindUserGroup(Settings.AnonymousGroup) }; + } + + SessionCache.SetCurrentGroups(sessionId, groups); + } + + return groups; + } + else return new UserGroup[0]; + } + + /// + /// Gets the current group names. + /// + /// The group names. + public static string[] GetCurrentGroupNames() { + return Array.ConvertAll(GetCurrentGroups(), delegate(UserGroup g) { return g.Name; }); + } + + /// + /// Gets the Breadcrumbs Manager. + /// + public static BreadcrumbsManager Breadcrumbs { + get { return new BreadcrumbsManager(); } + } + + } + + /// + /// Implements a session data cache whose lifetime is only limited to one request. + /// + public static class SessionCache { + + private static Dictionary currentUsers = new Dictionary(100); + private static Dictionary currentGroups = new Dictionary(100); + + /// + /// Gets the current user, if any, of a session. + /// + /// The session ID. + /// The current user, or null. + public static UserInfo GetCurrentUser(string sessionId) { + lock(currentUsers) { + UserInfo result = null; + currentUsers.TryGetValue(sessionId, out result); + return result; + } + } + + /// + /// Sets the current user of a session. + /// + /// The session ID. + /// The user. + public static void SetCurrentUser(string sessionId, UserInfo user) { + lock(currentUsers) { + currentUsers[sessionId] = user; + } + } + + /// + /// Gets the current groups, if any, of a session. + /// + /// The session ID. + /// The groups, or null. + public static UserGroup[] GetCurrentGroups(string sessionId) { + lock(currentGroups) { + UserGroup[] result = null; + currentGroups.TryGetValue(sessionId, out result); + return result; + } + } + + /// + /// Sets the current groups of a session. + /// + /// The session ID. + /// The groups. + public static void SetCurrentGroups(string sessionId, UserGroup[] groups) { + lock(currentGroups) { + currentGroups[sessionId] = groups; + } + } + + /// + /// Clears all cached data of a session. + /// + /// The session ID. + public static void ClearData(string sessionId) { + lock(currentUsers) { + currentUsers.Remove(sessionId); + } + lock(currentGroups) { + currentGroups.Remove(sessionId); + } + } + + } + +} diff --git a/Core/Settings.cs b/Core/Settings.cs new file mode 100644 index 0000000..659621d --- /dev/null +++ b/Core/Settings.cs @@ -0,0 +1,1233 @@ + +using System; +using System.Configuration; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Web; +using System.Web.Security; +using System.Web.Configuration; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Allows access to all the ScrewTurn Wiki settings and configuration options. + /// + [System.Security.Permissions.FileIOPermission(System.Security.Permissions.SecurityAction.Assert, + AllLocalFiles = System.Security.Permissions.FileIOPermissionAccess.PathDiscovery)] + public static class Settings { + + private static string version = null; + + /// + /// Gets the settings storage provider. + /// + public static ISettingsStorageProviderV30 Provider { + get { return Collectors.SettingsProvider; } + } + + /// + /// Gets the version of the Wiki. + /// + public static string WikiVersion { + get { + if(version == null) { + version = typeof(Settings).Assembly.GetName().Version.ToString(); + } + + return version; + } + } + + /// + /// Gets the name of the Login Cookie. + /// + public static string LoginCookieName { + get { return "ScrewTurnWikiLogin3"; } + } + + /// + /// Gets the name of the Culture Cookie. + /// + public static string CultureCookieName { + get { return "ScrewTurnWikiCulture3"; } + } + + /// + /// Gets the Master Password, used to encrypt the Users data. + /// + public static string MasterPassword { + get { + string pass = WebConfigurationManager.AppSettings["MasterPassword"]; + if(pass == null || pass.Length == 0) throw new Exception("Configuration: MasterPassword cannot be null."); + return pass; + } + } + + /// + /// Gets the bytes of the MasterPassword. + /// + public static byte[] MasterPasswordBytes { + get { + MD5 md5 = MD5CryptoServiceProvider.Create(); + return md5.ComputeHash(System.Text.UTF8Encoding.UTF8.GetBytes(MasterPassword)); + } + } + + /// + /// Gets the extension used for Pages, including the dot. + /// + public static string PageExtension { + get { return ".ashx"; } + } + + /// + /// Gets the display name validation regex. + /// + public static string DisplayNameRegex { + get { return "^[^\\|\\r\\n]*$"; } + } + + /// + /// Gets the Email validation Regex. + /// + public static string EmailRegex { + get { return @"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})$"; } + } + + /// + /// Gets the WikiTitle validation Regex. + /// + public static string WikiTitleRegex { + get { return ".+"; } + } + + /// + /// Gets the MainUrl validation Regex. + /// + public static string MainUrlRegex { + get { return @"^https?\://{1}\S+/$"; } + } + + /// + /// Gets the SMTP Server validation Regex. + /// + public static string SmtpServerRegex { + //get { return @"^(?:[a-zA-Z0-9_\-]{1,63}\.?)+(?:[a-zA-Z]{2,})|(?=\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})[0-2]*[0-9]*[0-9]+\.[0-2]*[0-9]*[0-9]+\.[0-2]*[0-9]*[0-9]+\.[0-2]*[0-9]*[0-9]+$"; } + get { return @"^[A-Za-z0-9\.\-_]+$"; } + } + + /// + /// Begins a bulk update session. + /// + public static void BeginBulkUpdate() { + Provider.BeginBulkUpdate(); + } + + /// + /// Ends a bulk update session. + /// + public static void EndBulkUpdate() { + Provider.EndBulkUpdate(); + } + + #region Directories and Files + + /// + /// Gets the Root Directory of the Wiki. + /// + public static string RootDirectory { + get { return System.Web.HttpRuntime.AppDomainAppPath; } + } + + /// + /// Gets the Public Directory of the Wiki. + /// + public static string PublicDirectory { + get { return RootDirectory + PublicDirectoryName + Path.DirectorySeparatorChar; } + } + + /// + /// Gets the Public Directory Name (without the full Path) of the Wiki. + /// + public static string PublicDirectoryName { + get { + string dir = WebConfigurationManager.AppSettings["PublicDirectory"]; + dir = dir.Trim('\\', '/'); // Remove '/' and '\' from head and tail + if(dir == null || dir.Length == 0) throw new Exception("PublicDirectory cannot be null."); + else return dir; + } + } + + /// + /// Gets the Name of the Themes directory. + /// + public static string ThemesDirectoryName { + get { return "Themes"; } + } + + /// + /// Gets the Themes directory. + /// + public static string ThemesDirectory { + get { return RootDirectory + ThemesDirectoryName + Path.DirectorySeparatorChar; } + } + + /// + /// Gets the Name of the JavaScript Directory. + /// + public static string JsDirectoryName { + get { return "JS"; } + } + + /// + /// Gets the JavaScript Directory. + /// + public static string JsDirectory { + get { return RootDirectory + JsDirectoryName + Path.DirectorySeparatorChar; } + } + + #endregion + + #region Basic Settings and Associated Data + + /// + /// Gets an integer. + /// + /// The string value. + /// The default value, returned when string parsing fails. + /// The result. + private static int GetInt(string value, int def) { + if(value == null) return def; + int i = def; + int.TryParse(value, out i); + return i; + } + + /// + /// Gets a boolean. + /// + /// The string value. + /// The default value, returned when parsing fails. + /// The result. + private static bool GetBool(string value, bool def) { + if(value == null) return def; + else { + if(value.ToLowerInvariant() == "yes") return true; + bool b = def; + bool.TryParse(value, out b); + return b; + } + } + + /// + /// Prints a boolean. + /// + /// The value. + /// The string result. + private static string PrintBool(bool value) { + return value ? "yes" : "no"; + } + + /// + /// Gets a string. + /// + /// The raw string. + /// The default value, returned when the raw string is null. + /// The result. + private static string GetString(string value, string def) { + if(string.IsNullOrEmpty(value)) return def; + else return value; + } + + /// + /// Gets or sets the Title of the Wiki. + /// + public static string WikiTitle { + get { + return GetString(Provider.GetSetting("WikiTitle"), "ScrewTurn Wiki"); + } + set { + Provider.SetSetting("WikiTitle", value); + } + } + + /// + /// Gets or sets the SMTP Server. + /// + public static string SmtpServer { + get { + return GetString(Provider.GetSetting("SmtpServer"), "smtp.server.com"); + } + set { + Provider.SetSetting("SmtpServer", value); + } + } + + /// + /// Gets or sets the SMTP Server Username. + /// + public static string SmtpUsername { + get { + return GetString(Provider.GetSetting("SmtpUsername"), ""); + } + set { + Provider.SetSetting("SmtpUsername", value); + } + } + + /// + /// Gets or sets the SMTP Server Password. + /// + public static string SmtpPassword { + get { + return GetString(Provider.GetSetting("SmtpPassword"), ""); + } + set { + Provider.SetSetting("SmtpPassword", value); + } + } + + /// + /// Gets or sets the SMTP Server Port. + /// + public static int SmtpPort { + get { + return GetInt(Provider.GetSetting("SmtpPort"), -1); + } + set { + Provider.SetSetting("SmtpPort", value.ToString()); + } + } + + /// + /// Gets or sets a value specifying whether to enable SSL in SMTP. + /// + public static bool SmtpSsl { + get { + return GetBool(Provider.GetSetting("SmtpSsl"), false); + } + set { + Provider.SetSetting("SmtpSsl", PrintBool(value)); + } + } + + /// + /// Gets or sets a value specifying whether the access to the Wiki is public or not (in this case users won't need to login in order to edit pagesCache). + /// + /// Deprecated in version 3.0. + public static bool PublicAccess { + get { + return GetBool(Provider.GetSetting("PublicAccess"), false); + } + set { + Provider.SetSetting("PublicAccess", PrintBool(value)); + } + } + + /// + /// Gets or sets a value specifying whether the access to the Wiki is private or not (in this case users won't be able to view pagesCache unless they are logged in). + /// + /// Deprecated in version 3.0. + public static bool PrivateAccess { + get { + return GetBool(Provider.GetSetting("PrivateAccess"), false); + } + set { + Provider.SetSetting("PrivateAccess", PrintBool(value)); + } + } + + /// + /// Gets or sets a value specifying whether, in Public Access mode, anonymous file management is allowed. + /// + /// Deprecated in version 3.0. + public static bool FileManagementInPublicAccessAllowed { + get { + return GetBool(Provider.GetSetting("FileManagementInPublicAccessAllowed"), false); + } + set { + Provider.SetSetting("FileManagementInPublicAccessAllowed", PrintBool(value)); + } + } + + /// + /// Gets or sets a value specifying whether Users can create new accounts or not (in this case Register.aspx won't be available). + /// + public static bool UsersCanRegister { + get { + return GetBool(Provider.GetSetting("UsersCanRegister"), true); + } + set { + Provider.SetSetting("UsersCanRegister", PrintBool(value)); + } + } + + /// + /// Gets or sets a value specifying whether to disable the Captcha control in the Registration Page. + /// + public static bool DisableCaptchaControl { + get { + return GetBool(Provider.GetSetting("DisableCaptchaControl"), false); + } + set { + Provider.SetSetting("DisableCaptchaControl", PrintBool(value)); + } + } + + /// + /// Gets or sets a value indicating whether to enable the "View Page Code" feature. + /// + public static bool EnableViewPageCodeFeature { + get { + return GetBool(Provider.GetSetting("EnableViewPageCode"), true); + } + set { + Provider.SetSetting("EnableViewPageCode", PrintBool(value)); + } + } + + /// + /// Gets or sets a value indicating whether to enable the Page Information DIV. + /// + public static bool EnablePageInfoDiv { + get { + return GetBool(Provider.GetSetting("EnablePageInfoDiv"), true); + } + set { + Provider.SetSetting("EnablePageInfoDiv", PrintBool(value)); + } + } + + /// + /// Gets or sets a value indicating whether to enable the Page Toolbar. + /// + public static bool EnablePageToolbar { + get { + return GetBool(Provider.GetSetting("EnablePageToolbar"), true); + } + set { + Provider.SetSetting("EnablePageToolbar", PrintBool(value)); + } + } + + /// + /// Gets or sets the Account Activation Mode. + /// + public static AccountActivationMode AccountActivationMode { + get { + string value = GetString(Provider.GetSetting("AccountActivationMode"), "EMAIL"); + switch(value.ToLowerInvariant()) { + case "email": + return AccountActivationMode.Email; + case "admin": + return AccountActivationMode.Administrator; + case "auto": + return AccountActivationMode.Auto; + default: + return AccountActivationMode.Email; + } + } + set { + string aa = ""; + switch(value) { + case AccountActivationMode.Email: + aa = "EMAIL"; + break; + case AccountActivationMode.Administrator: + aa = "ADMIN"; + break; + case AccountActivationMode.Auto: + aa = "AUTO"; + break; + default: + throw new ArgumentException("Invalid Account Activation Mode."); + } + + Provider.SetSetting("AccountActivationMode", aa); + } + } + + /// + /// Gets or sets the page change moderation mode. + /// + public static ChangeModerationMode ChangeModerationMode { + get { + string value = GetString(Provider.GetSetting("ChangeModerationMode"), + ChangeModerationMode.None.ToString()); + + return (ChangeModerationMode)Enum.Parse(typeof(ChangeModerationMode), value, true); + } + set { + Provider.SetSetting("ChangeModerationMode", value.ToString()); + } + } + + /// + /// Gets or sets a value specifying whether or not Users can create new Page. + /// + /// Deprecated in version 3.0. + public static bool UsersCanCreateNewPages { + get { + return GetBool(Provider.GetSetting("UsersCanCreateNewPages"), true); + } + set { + Provider.SetSetting("UsersCanCreateNewPages", PrintBool(value)); + } + } + + /// + /// Gets or sets a value specifying whether or not users can view uploaded Files. + /// + /// Deprecated in version 3.0. + public static bool UsersCanViewFiles { + get { + return GetBool(Provider.GetSetting("UsersCanViewFiles"), true); + } + set { + Provider.SetSetting("UsersCanViewFiles", PrintBool(value)); + } + } + + /// + /// Gets or sets a value specifying whether or not users can upload Files. + /// + /// Deprecated in version 3.0. + public static bool UsersCanUploadFiles { + get { + return GetBool(Provider.GetSetting("UsersCanUploadFiles"), false); + } + set { + Provider.SetSetting("UsersCanUploadFiles", PrintBool(value)); + } + } + + /// + /// Gets or sets a value specifying whether or not users can delete Files. + /// + /// Deprecated in version 3.0. + public static bool UsersCanDeleteFiles { + get { + return GetBool(Provider.GetSetting("UsersCanDeleteFiles"), false); + } + set { + Provider.SetSetting("UsersCanDeleteFiles", PrintBool(value)); + } + } + + /// + /// Gets or sets a value specifying whether or not users can create new Categories. + /// + /// Deprecated in version 3.0. + public static bool UsersCanCreateNewCategories { + get { + return GetBool(Provider.GetSetting("UsersCanCreateNewCategories"), false); + } + set { + Provider.SetSetting("UsersCanCreateNewCategories", PrintBool(value)); + } + } + + /// + /// Gets or sets a value specifying whether or not users can manage Page Categories. + /// + /// Deprecated in version 3.0. + public static bool UsersCanManagePageCategories { + get { + return GetBool(Provider.GetSetting("UsersCanManagePageCategories"), false); + } + set { + Provider.SetSetting("UsersCanManagePageCategories", PrintBool(value)); + } + } + + /// + /// Gets or sets the Default Language. + /// + public static string DefaultLanguage { + get { + return GetString(Provider.GetSetting("DefaultLanguage"), "en-US"); + } + set { + Provider.SetSetting("DefaultLanguage", value); + } + } + + /// + /// Gets or sets the Default Timezone (time delta in minutes). + /// + public static int DefaultTimezone { + get { + string value = GetString(Provider.GetSetting("DefaultTimezone"), "0"); + return int.Parse(value); + } + set { + Provider.SetSetting("DefaultTimezone", value.ToString()); + } + } + + /// + /// Gets or sets the DateTime format. + /// + public static string DateTimeFormat { + get { + return GetString(Provider.GetSetting("DateTimeFormat"), "yyyy'/'MM'/'dd' 'HH':'mm"); + } + set { + Provider.SetSetting("DateTimeFormat", value); + } + } + + /// + /// Gets or sets the main URL of the Wiki. + /// + public static string MainUrl { + get { + string s = GetString(Provider.GetSetting("MainUrl"), "http://www.server.com/"); + if(!s.EndsWith("/")) s += "/"; + return s; + } + set { + Provider.SetSetting("MainUrl", value); + } + } + + /// + /// Gets the correct path to use with Cookies. + /// + public static string CookiePath { + get { + return HttpContext.Current.Request.ApplicationPath; + } + } + + /// + /// Gets or sets the Contact Email. + /// + public static string ContactEmail { + get { + return GetString(Provider.GetSetting("ContactEmail"), "info@server.com"); + } + set { + Provider.SetSetting("ContactEmail", value); + } + } + + /// + /// Gets or sets the Sender Email. + /// + public static string SenderEmail { + get { + return GetString(Provider.GetSetting("SenderEmail"), "no-reply@server.com"); + } + set { + Provider.SetSetting("SenderEmail", value); + } + } + + /// + /// Gets or sets the email addresses to send a message to when an error occurs. + /// + public static string[] ErrorsEmails { + get { + return GetString(Provider.GetSetting("ErrorsEmails"), "").Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + } + set { + Provider.SetSetting("ErrorsEmails", string.Join("|", value)); + } + } + + /// + /// Gets or sets the Defaul Page of the Wiki. + /// + public static string DefaultPage { + get { + return GetString(Provider.GetSetting("DefaultPage"), "MainPage"); + } + set { + Provider.SetSetting("DefaultPage", value); + } + } + + /// + /// Gets or sets the maximum number of recent changes to display with {RecentChanges} special tag. + /// + public static int MaxRecentChangesToDisplay { + get { + return GetInt(Provider.GetSetting("MaxRecentChangesToDisplay"), 10); + } + set { + Provider.SetSetting("MaxRecentChangesToDisplay", value.ToString()); + } + } + + /// + /// Gets or sets a value specifying whether to enable double-click Page editing. + /// + public static bool EnableDoubleClickEditing { + get { + return GetBool(Provider.GetSetting("EnableDoubleClickEditing"), false); + } + set { + Provider.SetSetting("EnableDoubleClickEditing", PrintBool(value)); + } + } + + /// + /// Gets or sets a value indicating whether to enable section editing. + /// + public static bool EnableSectionEditing { + get { + return GetBool(Provider.GetSetting("EnableSectionEditing"), true); + } + set { + Provider.SetSetting("EnableSectionEditing", PrintBool(value)); + } + } + + /// + /// Gets or sets a value indicating whether to disable the Breadcrumbs Trail. + /// + public static bool DisableBreadcrumbsTrail { + get { + return GetBool(Provider.GetSetting("DisableBreadcrumbsTrail"), false); + } + set { + Provider.SetSetting("DisableBreadcrumbsTrail", PrintBool(value)); + } + } + + /// + /// Gets or sets a value indicating whether the editor should auto-generate page names from the title. + /// + public static bool AutoGeneratePageNames { + get { + return GetBool(Provider.GetSetting("AutoGeneratePageNames"), true); + } + set { + Provider.SetSetting("AutoGeneratePageNames", PrintBool(value)); + } + } + + /// + /// Gets or sets a value indicating whether or not to process single line breaks in WikiMarkup. + /// + public static bool ProcessSingleLineBreaks { + get { + return GetBool(Provider.GetSetting("ProcessSingleLineBreaks"), false); + } + set { + Provider.SetSetting("ProcessSingleLineBreaks", PrintBool(value)); + } + } + + /// + /// Gets or sets the # of Backups that are kept. Older backups are deleted after Page editing. + /// + /// -1 indicates that no backups are deleted. + public static int KeptBackupNumber { + get { + return GetInt(Provider.GetSetting("KeptBackupNumber"), -1); + } + set { + Provider.SetSetting("KeptBackupNumber", value.ToString()); + } + } + + /// + /// Gets the theme name for a namespace. + /// + /// The namespace (null for the root). + /// The theme name. + public static string GetTheme(string nspace) { + string propertyName = "Theme" + (!string.IsNullOrEmpty(nspace) ? "-" + nspace : ""); + return GetString(Provider.GetSetting(propertyName), "Default"); + } + + /// + /// Sets the theme for a namespace. + /// + /// The namespace (null for the root). + /// The theme name. + public static void SetTheme(string nspace, string theme) { + string propertyName = "Theme" + (!string.IsNullOrEmpty(nspace) ? "-" + nspace : ""); + Provider.SetSetting(propertyName, theme); + } + + /// + /// Gets the Theme Path for a namespace. + /// + /// The namespace (null for the root). + /// The path of the theme. + public static string GetThemePath(string nspace) { + return ThemesDirectoryName + "/" + GetTheme(nspace) + "/"; + } + + /// + /// Gets or sets the list of allowed file types. + /// + public static string[] AllowedFileTypes { + get { + string raw = GetString(Provider.GetSetting("AllowedFileTypes"), "jpg|jpeg|gif|png|tif|tiff|bmp|svg|htm|html|zip|rar|pdf|txt|doc|xls|ppt|docx|xlsx|pptx"); + return raw.ToLowerInvariant().Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + } + set { + string res = string.Join("|", value); + Provider.SetSetting("AllowedFileTypes", res); + } + } + + /// + /// Gets or sets the file download count filter mode. + /// + public static FileDownloadCountFilterMode FileDownloadCountFilterMode { + get { + string raw = GetString(Provider.GetSetting("FileDownloadCountFilterMode"), FileDownloadCountFilterMode.CountAll.ToString()); + return (FileDownloadCountFilterMode)Enum.Parse(typeof(FileDownloadCountFilterMode), raw); + } + set { + Provider.SetSetting("FileDownloadCountFilterMode", value.ToString()); + } + } + + /// + /// Gets or sets the file download count extension filter. + /// + public static string[] FileDownloadCountFilter { + get { + string raw = GetString(Provider.GetSetting("FileDownloadCountFilter"), ""); + return raw.ToLowerInvariant().Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + } + set { + string res = string.Join("|", value); + Provider.SetSetting("FileDownloadCountFilter", res); + } + } + + /// + /// Gets or sets a value specifying whether script tags are allowed. + /// + public static bool ScriptTagsAllowed { + get { + return GetBool(Provider.GetSetting("ScriptTagsAllowed"), false); + } + set { + Provider.SetSetting("ScriptTagsAllowed", PrintBool(value)); + } + } + + /// + /// Gets or sets the Logging Level. + /// + public static LoggingLevel LoggingLevel { + get { + int value = GetInt(Provider.GetSetting("LoggingLevel"), 3); + return (LoggingLevel)value; + } + set { + Provider.SetSetting("LoggingLevel", ((int)value).ToString()); + } + } + + /// + /// Gets or sets the Max size of the Log file (KB). + /// + public static int MaxLogSize { + get { + return GetInt(Provider.GetSetting("MaxLogSize"), 256); + } + set { + Provider.SetSetting("MaxLogSize", value.ToString()); + } + } + + /// + /// Gets or sets the IP/Host filter for page editing. + /// + public static string IpHostFilter + { + get{ + + return Provider.GetSetting("IpHostFilter"); + } + set + { + Provider.SetSetting("IpHostFilter", value); + } + } + + /// + /// Gets or sets the max number of recent changes to log. + /// + public static int MaxRecentChanges { + get { + return GetInt(Provider.GetSetting("MaxRecentChanges"), 100); + } + set { + Provider.SetSetting("MaxRecentChanges", value.ToString()); + } + } + + /// + /// Gets or sets a valus specifying whether or not Administrators cannot access the Configuration section in the Administration page. + /// + /// Deprecated in version 3.0. + [Obsolete] + public static bool ConfigVisibleToAdmins { + get { + return GetBool(Provider.GetSetting("ConfigVisibleToAdmins"), false); + } + set { + Provider.SetSetting("ConfigVisibleToAdmins", PrintBool(value)); + } + } + + /// + /// Gets or sets the Type name of the Default Users Provider. + /// + public static string DefaultUsersProvider { + get { + return GetString(Provider.GetSetting("DefaultUsersProvider"), typeof(UsersStorageProvider).ToString()); + } + set { + Provider.SetSetting("DefaultUsersProvider", value); + } + } + + /// + /// Gets or sets the Type name of the Default Pages Provider. + /// + public static string DefaultPagesProvider { + get { + return GetString(Provider.GetSetting("DefaultPagesProvider"), typeof(PagesStorageProvider).ToString()); + } + set { + Provider.SetSetting("DefaultPagesProvider", value); + } + } + + /// + /// Gets or sets the Type name of the Default Files Provider. + /// + public static string DefaultFilesProvider { + get { + return GetString(Provider.GetSetting("DefaultFilesProvider"), typeof(FilesStorageProvider).ToString()); + } + set { + Provider.SetSetting("DefaultFilesProvider", value); + } + } + + /// + /// Gets or sets the Default Cache Provider. + /// + public static string DefaultCacheProvider { + get { + return GetString(Provider.GetSetting("DefaultCacheProvider"), typeof(CacheProvider).ToString()); + } + set { + Provider.SetSetting("DefaultCacheProvider", value); + } + } + + /// + /// Gets or sets the Discussion Permissions. + /// + public static string DiscussionPermissions { + get { + return GetString(Provider.GetSetting("DiscussionPermissions"), "PAGE"); + } + set { + Provider.SetSetting("DiscussionPermissions", value); + } + } + + /// + /// Gets or sets a value specifying whether or not to disable concurrent editing of Pages. + /// + public static bool DisableConcurrentEditing { + get { + return GetBool(Provider.GetSetting("DisableConcurrentEditing"), false); + } + set { + Provider.SetSetting("DisableConcurrentEditing", PrintBool(value)); + } + } + + /// + /// Gets or sets a value indicating whether to use the Visual editor as default. + /// + public static bool UseVisualEditorAsDefault { + get { + return GetBool(Provider.GetSetting("UseVisualEditorAsDefault"), false); + } + set { + Provider.SetSetting("UseVisualEditorAsDefault", PrintBool(value)); + } + } + + /// + /// Gets or sets the name of the default Administrators group. + /// + public static string AdministratorsGroup { + get { + return GetString(Provider.GetSetting("AdministratorsGroup"), "Administrators"); + } + set { + Provider.SetSetting("AdministratorsGroup", value); + } + } + + /// + /// Gets or sets the name of the default Users group. + /// + public static string UsersGroup { + get { + return GetString(Provider.GetSetting("UsersGroup"), "Users"); + } + set { + Provider.SetSetting("UsersGroup", value); + } + } + + /// + /// Gets or sets the name of the default Anonymous users group. + /// + public static string AnonymousGroup { + get { + return GetString(Provider.GetSetting("AnonymousGroup"), "Anonymous"); + } + set { + Provider.SetSetting("AnonymousGroup", value); + } + } + + /// + /// Gets or sets a value indicating whether to display gravatars. + /// + public static bool DisplayGravatars { + get { + return GetBool(Provider.GetSetting("DisplayGravatars"), true); + } + set { + Provider.SetSetting("DisplayGravatars", PrintBool(value)); + } + } + + /// + /// Gets or sets the functioning mode of RSS feeds. + /// + public static RssFeedsMode RssFeedsMode { + get { + string value = GetString(Provider.GetSetting("RssFeedsMode"), RssFeedsMode.Summary.ToString()); + return (RssFeedsMode)Enum.Parse(typeof(RssFeedsMode), value); + } + set { + Provider.SetSetting("RssFeedsMode", value.ToString()); + } + } + + #endregion + + #region Advanced Settings and Associated Data + + /// + /// Gets or sets a value indicating whether to disable the Automatic Version Check. + /// + public static bool DisableAutomaticVersionCheck { + get { + return GetBool(Provider.GetSetting("DisableAutomaticVersionCheck"), false); + } + set { + Provider.SetSetting("DisableAutomaticVersionCheck", PrintBool(value)); + } + } + + /// + /// Gets or sets the Max file size for upload (in KB). + /// + public static int MaxFileSize { + get { + return GetInt(Provider.GetSetting("MaxFileSize"), 10240); + } + set { + Provider.SetSetting("MaxFileSize", value.ToString()); + } + } + + /// + /// Gets or sets a value specifying whether to disable the cache. + /// + public static bool DisableCache { + get { + return GetBool(Provider.GetSetting("DisableCache"), false); + } + set { + Provider.SetSetting("DisableCache", PrintBool(value)); + } + } + + /// + /// Gets or sets the Cache Size. + /// + public static int CacheSize { + get { + return GetInt(Provider.GetSetting("CacheSize"), 100); + } + set { + Provider.SetSetting("CacheSize", value.ToString()); + } + } + + /// + /// Gets or sets the Cache Cut Size. + /// + public static int CacheCutSize { + get { + return GetInt(Provider.GetSetting("CacheCutSize"), 20); + } + set { + Provider.SetSetting("CacheCutSize", value.ToString()); + } + } + + /// + /// Gets or sets a value specifying whether ViewState compression is enabled or not. + /// + public static bool EnableViewStateCompression { + get { + return GetBool(Provider.GetSetting("EnableViewStateCompression"), false); + } + set { + Provider.SetSetting("EnableViewStateCompression", PrintBool(value)); + } + } + + /// + /// Gets or sets a value specifying whether HTTP compression is enabled or not. + /// + public static bool EnableHttpCompression { + get { + return GetBool(Provider.GetSetting("EnableHttpCompression"), false); + } + set { + Provider.SetSetting("EnableHttpCompression", PrintBool(value)); + } + } + + /// + /// Gets or sets the Username validation Regex. + /// + public static string UsernameRegex { + get { + return GetString(Provider.GetSetting("UsernameRegex"), @"^\w[\w\ !$@%^\.\(\)]{3,25}$"); + } + set { + Provider.SetSetting("UsernameRegex", value); + } + } + + /// + /// Gets or sets the Password validation Regex. + /// + public static string PasswordRegex { + get { + return GetString(Provider.GetSetting("PasswordRegex"), @"^\w[\w~!@#$%^\(\)\[\]\{\}\.,=\-_\ ]{6,25}$"); + } + set { + Provider.SetSetting("PasswordRegex", value); + } + } + + #endregion + + /// + /// Determines whether a meta-data item is global or namespace-specific. + /// + /// The item to test. + /// true if the meta-data item is global, false otherwise. + public static bool IsMetaDataItemGlobal(MetaDataItem item) { + int value = (int)item; + return value < 100; // See MetaDataItem + } + + } + + /// + /// Lists legal RSS feeds function modes. + /// + public enum RssFeedsMode { + /// + /// RSS feeds serve full-text content. + /// + FullText, + /// + /// RSS feeds serve summary content. + /// + Summary, + /// + /// RSS feeds are disabled. + /// + Disabled + } + + /// + /// Lists legal file download filter modes. + /// + public enum FileDownloadCountFilterMode { + /// + /// Counts all downloads. + /// + CountAll, + /// + /// Counts only the specified extensions. + /// + CountSpecifiedExtensions, + /// + /// Excludes the specified extensions. + /// + ExcludeSpecifiedExtensions + } + + /// + /// Lists legal page change moderation mode values. + /// + public enum ChangeModerationMode { + /// + /// Page change moderation is disabled. + /// + None, + /// + /// Anyone who has page editing permissoins but not page management permissions + /// can edit pages, but the changes are held in moderation. + /// + RequirePageEditingPermissions, + /// + /// Anyone who has page viewing permissions can edit pages, but the changes are + /// held in moderation. + /// + RequirePageViewingPermissions + } + + /// + /// Lists legal account activation mode values. + /// + public enum AccountActivationMode { + /// + /// Users must activate their account via email. + /// + Email, + /// + /// Accounts must be activated by administrators. + /// + Administrator, + /// + /// Accounts are active by default. + /// + Auto + } + +} diff --git a/Core/SettingsStorageProvider.cs b/Core/SettingsStorageProvider.cs new file mode 100644 index 0000000..b0470b5 --- /dev/null +++ b/Core/SettingsStorageProvider.cs @@ -0,0 +1,1239 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.AclEngine; + +namespace ScrewTurn.Wiki { + + /// + /// Implements a Settings Storage Provider against local text pluginAssemblies. + /// + public class SettingsStorageProvider : ISettingsStorageProviderV30 { + + // Filenames: Settings, Log, RecentChanges, MetaData + private const string ConfigFile = "Config.cs"; + private const string LogFile = "Log.cs"; + private const string RecentChangesFile = "RecentChanges3.cs"; // Old v2 format no more supported + private const string AccountActivationMessageFile = "AccountActivationMessage.cs"; + private const string EditNoticeFile = "EditNotice.cs"; + private const string FooterFile = "Footer.cs"; + private const string HeaderFile = "Header.cs"; + private const string HtmlHeadFile = "HtmlHead.cs"; + private const string LoginNoticeFile = "LoginNotice.cs"; + private const string AccessDeniedNoticeFile = "AccessDeniedNotice.cs"; + private const string RegisterNoticeFile = "RegisterNotice.cs"; + private const string PageFooterFile = "PageFooter.cs"; + private const string PageHeaderFile = "PageHeader.cs"; + private const string PasswordResetProcedureMessageFile = "PasswordResetProcedureMessage.cs"; + private const string SidebarFile = "Sidebar.cs"; + private const string PageChangeMessageFile = "PageChangeMessage.cs"; + private const string DiscussionChangeMessageFile = "DiscussionChangeMessage.cs"; + private const string ApproveDraftMessageFile = "ApproveDraftMessage.cs"; + + private const string PluginsDirectory = "Plugins"; + private const string PluginsStatusFile = "Status.cs"; + private const string PluginsConfigDirectory = "Config"; + + private const string AclFile = "ACL.cs"; + + private const string LinksFile = "Links.cs"; + + private const int EstimatedLogEntrySize = 60; // bytes + + /// + /// The name of the provider. + /// + public static readonly string ProviderName = "Local Settings Provider"; + + private readonly ComponentInformation info = + new ComponentInformation(ProviderName, "ScrewTurn Software", Settings.WikiVersion, "http://www.screwturn.eu", null); + + private IHostV30 host; + + private IAclManager aclManager; + private AclStorerBase aclStorer; + + private bool bulkUpdating = false; + private Dictionary configData = null; + + private string GetFullPath(string name) { + return Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), name); + } + + private string GetFullPathForPlugin(string name) { + return Path.Combine(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), PluginsDirectory), name); + } + + private string GetFullPathForPluginConfig(string name) { + return Path.Combine(Path.Combine(Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), PluginsDirectory), PluginsConfigDirectory), name); + } + + /// + /// Initializes the Storage Provider. + /// + /// The Host of the Component. + /// The Configuration data, if any. + /// If host or config are null. + /// If config is not valid or is incorrect. + public void Init(IHostV30 host, string config) { + if(host == null) throw new ArgumentNullException("host"); + if(config == null) throw new ArgumentNullException("config"); + + this.host = host; + + if(!LocalProvidersTools.CheckWritePermissions(host.GetSettingValue(SettingName.PublicDirectory))) { + throw new InvalidConfigurationException("Cannot write into the public directory - check permissions"); + } + + // Create all needed pluginAssemblies + if(!File.Exists(GetFullPath(LogFile))) { + File.Create(GetFullPath(LogFile)).Close(); + } + + if(!File.Exists(GetFullPath(ConfigFile))) { + File.Create(GetFullPath(ConfigFile)).Close(); + } + + if(!File.Exists(GetFullPath(RecentChangesFile))) { + File.Create(GetFullPath(RecentChangesFile)).Close(); + } + + if(!File.Exists(GetFullPath(HtmlHeadFile))) { + File.Create(GetFullPath(HtmlHeadFile)).Close(); + } + + if(!File.Exists(GetFullPath(HeaderFile))) { + File.Create(GetFullPath(HeaderFile)).Close(); + } + + if(!File.Exists(GetFullPath(SidebarFile))) { + File.Create(GetFullPath(SidebarFile)).Close(); + } + + if(!File.Exists(GetFullPath(FooterFile))) { + File.Create(GetFullPath(FooterFile)).Close(); + } + + if(!File.Exists(GetFullPath(PageHeaderFile))) { + File.Create(GetFullPath(PageHeaderFile)).Close(); + } + + if(!File.Exists(GetFullPath(PageFooterFile))) { + File.Create(GetFullPath(PageFooterFile)).Close(); + } + + if(!File.Exists(GetFullPath(AccountActivationMessageFile))) { + File.Create(GetFullPath(AccountActivationMessageFile)).Close(); + } + + if(!File.Exists(GetFullPath(PasswordResetProcedureMessageFile))) { + File.Create(GetFullPath(PasswordResetProcedureMessageFile)).Close(); + } + + if(!File.Exists(GetFullPath(EditNoticeFile))) { + File.Create(GetFullPath(EditNoticeFile)).Close(); + } + + if(!File.Exists(GetFullPath(LoginNoticeFile))) { + File.Create(GetFullPath(LoginNoticeFile)).Close(); + } + + if(!File.Exists(GetFullPath(AccessDeniedNoticeFile))) { + File.Create(GetFullPath(AccessDeniedNoticeFile)).Close(); + } + + if(!File.Exists(GetFullPath(RegisterNoticeFile))) { + File.Create(GetFullPath(RegisterNoticeFile)).Close(); + } + + if(!File.Exists(GetFullPath(PageChangeMessageFile))) { + File.Create(GetFullPath(PageChangeMessageFile)).Close(); + } + + if(!File.Exists(GetFullPath(DiscussionChangeMessageFile))) { + File.Create(GetFullPath(DiscussionChangeMessageFile)).Close(); + } + + if(!File.Exists(GetFullPath(ApproveDraftMessageFile))) { + File.Create(GetFullPath(ApproveDraftMessageFile)).Close(); + } + + if(!Directory.Exists(GetFullPath(PluginsDirectory))) { + Directory.CreateDirectory(GetFullPath(PluginsDirectory)); + } + + if(!Directory.Exists(GetFullPathForPlugin(PluginsConfigDirectory))) { + Directory.CreateDirectory(GetFullPathForPlugin(PluginsConfigDirectory)); + } + + if(!File.Exists(GetFullPathForPlugin(PluginsStatusFile))) { + File.Create(GetFullPathForPlugin(PluginsStatusFile)).Close(); + } + + if(!File.Exists(GetFullPath(LinksFile))) { + File.Create(GetFullPath(LinksFile)).Close(); + } + + LoadConfig(); + + // Initialize ACL Manager and Storer + aclManager = new StandardAclManager(); + aclStorer = new AclStorer(aclManager, GetFullPath(AclFile)); + aclStorer.LoadData(); + } + + /// + /// Method invoked on shutdown. + /// + /// This method might not be invoked in some cases. + public void Shutdown() { + lock(this) { + aclStorer.Dispose(); + } + } + + /// + /// Gets the Information about the Provider. + /// + public ComponentInformation Information { + get { return info; } + } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + public string ConfigHelpHtml { + get { return null; } + } + + /// + /// Retrieves the value of a Setting. + /// + /// The name of the Setting. + /// The value of the Setting, or null. + /// If name is null. + /// If name is empty. + public string GetSetting(string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + lock(this) { + string val = null; + if(configData.TryGetValue(name, out val)) return val; + else return null; + } + } + + /// + /// Stores the value of a Setting. + /// + /// The name of the Setting. + /// The value of the Setting. Value cannot contain CR and LF characters, which will be removed. + /// True if the Setting is stored, false otherwise. + /// This method stores the Value immediately. + /// If name is null. + /// If name is empty. + public bool SetSetting(string name, string value) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + // Nulls are converted to empty strings + if(value == null) value = ""; + + // Store, then if not bulkUpdating, dump + lock(this) { + configData[name] = value; + if(!bulkUpdating) DumpConfig(); + + return true; + } + } + + /// + /// Gets the all the setting values. + /// + /// All the settings. + public IDictionary GetAllSettings() { + lock(this) { + Dictionary result = new Dictionary(configData.Count); + foreach(KeyValuePair pair in configData) { + result.Add(pair.Key, pair.Value); + } + return result; + } + } + + /// + /// Loads configuration settings from disk. + /// + private void LoadConfig() { + // This method should not call Log.*(...) + lock(this) { + configData = new Dictionary(30); + string data = File.ReadAllText(GetFullPath(ConfigFile), System.Text.UTF8Encoding.UTF8); + + data = data.Replace("\r", ""); + + string[] lines = data.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + string[] fields; + for(int i = 0; i < lines.Length; i++) { + lines[i] = lines[i].Trim(); + + // Skip comments + if(lines[i].StartsWith("#")) continue; + + fields = new string[2]; + int idx = lines[i].IndexOf("="); + if(idx < 0) continue; + + try { + // Extract key + fields[0] = lines[i].Substring(0, idx).Trim(); + } + catch { + // Unexpected string format + continue; + } + + try { + // Extract value + fields[1] = lines[i].Substring(idx + 1).Trim(); + } + catch { + // Blank/invalid value? + fields[1] = ""; + } + + configData.Add(fields[0], fields[1]); + } + } + } + + /// + /// Dumps settings on disk. + /// + private void DumpConfig() { + lock(this) { + StringBuilder buffer = new StringBuilder(4096); + + string[] keys = new string[configData.Keys.Count]; + configData.Keys.CopyTo(keys, 0); + for(int i = 0; i < keys.Length; i++) { + buffer.AppendFormat("{0} = {1}\r\n", keys[i], configData[keys[i]]); + } + + File.WriteAllText(GetFullPath(ConfigFile), buffer.ToString()); + } + } + + /// + /// Starts a Bulk update of the Settings so that a bulk of settings can be set before storing them. + /// + public void BeginBulkUpdate() { + lock(this) { + bulkUpdating = true; + } + } + + /// + /// Ends a Bulk update of the Settings and stores the settings. + /// + public void EndBulkUpdate() { + lock(this) { + bulkUpdating = false; + DumpConfig(); + } + } + + /// + /// Sanitizes a stiring from all unfriendly characters. + /// + /// The input string. + /// The sanitized result. + private static string Sanitize(string input) { + StringBuilder sb = new StringBuilder(input); + sb.Replace("|", "{PIPE}"); + sb.Replace("\r", ""); + sb.Replace("\n", "{BR}"); + sb.Replace("<", "<"); + sb.Replace(">", ">"); + return sb.ToString(); + } + + /// + /// Re-sanitizes a string from all unfriendly characters. + /// + /// The input string. + /// The sanitized result. + private static string Resanitize(string input) { + StringBuilder sb = new StringBuilder(input); + sb.Replace("<", "<"); + sb.Replace(">", ">"); + sb.Replace("{BR}", "\n"); + sb.Replace("{PIPE}", "|"); + return sb.ToString(); + } + + /// + /// Converts an to a string. + /// + /// The entry type. + /// The corresponding string. + private static string EntryTypeToString(EntryType type) { + switch(type) { + case EntryType.General: + return "G"; + case EntryType.Warning: + return "W"; + case EntryType.Error: + return "E"; + default: + return "G"; + } + } + + /// + /// Converts an entry type string to an . + /// + /// The string. + /// The . + private static EntryType EntryTypeParse(string value) { + switch(value) { + case "G": + return EntryType.General; + case "W": + return EntryType.Warning; + case "E": + return EntryType.Error; + default: + return EntryType.General; + } + } + + /// + /// Records a message to the System Log. + /// + /// The Log Message. + /// The Type of the Entry. + /// The User. + /// This method should not write messages to the Log using the method IHost.LogEntry. + /// This method should also never throw exceptions (except for parameter validation). + /// If message or user are null. + /// If message or user are empty. + public void LogEntry(string message, EntryType entryType, string user) { + if(message == null) throw new ArgumentNullException("message"); + if(message.Length == 0) throw new ArgumentException("Message cannot be empty", "message"); + if(user == null) throw new ArgumentNullException("user"); + if(user.Length == 0) throw new ArgumentException("User cannot be empty", "user"); + + lock(this) { + message = Sanitize(message); + user = Sanitize(user); + LoggingLevel level = (LoggingLevel)Enum.Parse(typeof(LoggingLevel), host.GetSettingValue(SettingName.LoggingLevel)); + switch(level) { + case LoggingLevel.AllMessages: + break; + case LoggingLevel.WarningsAndErrors: + if(entryType != EntryType.Error && entryType != EntryType.Warning) return; + break; + case LoggingLevel.ErrorsOnly: + if(entryType != EntryType.Error) return; + break; + case LoggingLevel.DisableLog: + return; + default: + break; + } + + FileStream fs = null; + try { + fs = new FileStream(GetFullPath(LogFile), FileMode.Append, FileAccess.Write, FileShare.None); + } + catch { + return; + } + + StreamWriter sw = new StreamWriter(fs, System.Text.UTF8Encoding.UTF8); + // Type | DateTime | Message | User + try { + sw.Write(EntryTypeToString(entryType) + "|" + string.Format("{0:yyyy'/'MM'/'dd' 'HH':'mm':'ss}", DateTime.Now) + "|" + message + "|" + user + "\r\n"); + } + catch { } + finally { + try { + sw.Close(); + } + catch { } + } + + try { + FileInfo fi = new FileInfo(GetFullPath(LogFile)); + if(fi.Length > (long)(int.Parse(host.GetSettingValue(SettingName.MaxLogSize)) * 1024)) { + CutLog((int)(fi.Length * 0.75)); + } + } + catch { } + } + } + + /// + /// Reduces the size of the Log to the specified size (or less). + /// + /// The size to shrink the log to (in bytes). + private void CutLog(int size) { + lock(this) { + // Contains the log messages from the newest to the oldest + List entries = new List(GetLogEntries()); + + FileInfo fi = new FileInfo(GetFullPath(LogFile)); + int difference = (int)(fi.Length - size); + int removeEntries = difference / EstimatedLogEntrySize * 2; // Double the number of removed entries in order to reduce the # of times Cut is needed + int preserve = entries.Count - removeEntries; // The number of entries to be preserved + + // Copy the entries to preserve in a temp list + List toStore = new List(); + for(int i = 0; i < preserve; i++) { + toStore.Add(entries[i]); + } + + // Reverse the temp list because entries contains the log messages + // starting from the newest to the oldest + toStore.Reverse(); + + StringBuilder sb = new StringBuilder(); + // Type | DateTime | Message | User + foreach(LogEntry e in toStore) { + sb.Append(EntryTypeToString(e.EntryType)); + sb.Append("|"); + sb.Append(e.DateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss")); + sb.Append("|"); + sb.Append(e.Message); + sb.Append("|"); + sb.Append(e.User); + sb.Append("\r\n"); + } + + FileStream fs = null; + try { + fs = new FileStream(GetFullPath(LogFile), FileMode.Create, FileAccess.Write, FileShare.None); + } + catch(Exception ex) { + throw new IOException("Unable to open the file: " + LogFile, ex); + } + + StreamWriter sw = new StreamWriter(fs, System.Text.UTF8Encoding.UTF8); + // Type | DateTime | Message | User + try { + sw.Write(sb.ToString()); + } + catch { } + sw.Close(); + } + } + + /// + /// Gets all the Log Entries, sorted by date/time (oldest to newest). + /// + /// The Log Entries. + public LogEntry[] GetLogEntries() { + lock(this) { + string content = File.ReadAllText(GetFullPath(LogFile)).Replace("\r", ""); + List result = new List(50); + string[] lines = content.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + string[] fields; + for(int i = 0; i < lines.Length; i++) { + fields = lines[i].Split('|'); + try { + // Try/catch to avoid problems with corrupted file (raw method) + result.Add(new LogEntry(EntryTypeParse(fields[0]), DateTime.Parse(fields[1]), Resanitize(fields[2]), Resanitize(fields[3]))); + } + catch { } + } + return result.ToArray(); + } + } + + /// + /// Clear the Log. + /// + public void ClearLog() { + lock(this) { + FileStream fs = null; + try { + fs = new FileStream(GetFullPath(LogFile), FileMode.Create, FileAccess.Write, FileShare.None); + } + catch(Exception ex) { + throw new IOException("Unable to access the file: " + LogFile, ex); + } + fs.Close(); + } + } + + /// + /// Gets the current size of the Log, in KB. + /// + public int LogSize { + get { + lock(this) { + FileInfo fi = new FileInfo(GetFullPath(LogFile)); + return (int)(fi.Length / 1024); + } + } + } + + /// + /// Builds the full path for a meta-data item file. + /// + /// The tag that specifies the context. + /// The file. + /// The full path. + private string GetFullPathForMetaDataItem(string tag, string file) { + string targetFile = (!string.IsNullOrEmpty(tag) ? tag + "." : "") + file; + return GetFullPath(targetFile); + } + + private static readonly Dictionary MetaDataItemFiles = new Dictionary() { + { MetaDataItem.AccountActivationMessage, AccountActivationMessageFile }, + { MetaDataItem.EditNotice, EditNoticeFile }, + { MetaDataItem.Footer, FooterFile }, + { MetaDataItem.Header, HeaderFile }, + { MetaDataItem.HtmlHead, HtmlHeadFile }, + { MetaDataItem.LoginNotice, LoginNoticeFile }, + { MetaDataItem.AccessDeniedNotice, AccessDeniedNoticeFile }, + { MetaDataItem.RegisterNotice, RegisterNoticeFile }, + { MetaDataItem.PageFooter, PageFooterFile }, + { MetaDataItem.PageHeader, PageHeaderFile }, + { MetaDataItem.PasswordResetProcedureMessage, PasswordResetProcedureMessageFile }, + { MetaDataItem.Sidebar, SidebarFile }, + { MetaDataItem.PageChangeMessage, PageChangeMessageFile }, + { MetaDataItem.DiscussionChangeMessage, DiscussionChangeMessageFile }, + { MetaDataItem.ApproveDraftMessage, ApproveDraftMessageFile } + }; + + /// + /// Gets a meta-data item's content. + /// + /// The item. + /// The tag that specifies the context (usually the namespace). + /// The content. + public string GetMetaDataItem(MetaDataItem item, string tag) { + lock(this) { + string fullFile = GetFullPathForMetaDataItem(tag, MetaDataItemFiles[item]); + if(!File.Exists(fullFile)) return ""; + else return File.ReadAllText(fullFile); + } + } + + /// + /// Sets a meta-data items' content. + /// + /// The item. + /// The tag that specifies the context (usually the namespace). + /// The content. + /// true if the content is set, false otherwise. + public bool SetMetaDataItem(MetaDataItem item, string tag, string content) { + if(content == null) content = ""; + + lock(this) { + File.WriteAllText(GetFullPathForMetaDataItem(tag, MetaDataItemFiles[item]), content); + return true; + } + } + + /// + /// Gets the change from a string. + /// + /// The string. + /// The change. + private static ScrewTurn.Wiki.PluginFramework.Change GetChange(string change) { + switch(change.ToUpperInvariant()) { + case "U": + return ScrewTurn.Wiki.PluginFramework.Change.PageUpdated; + case "D": + return ScrewTurn.Wiki.PluginFramework.Change.PageDeleted; + case "R": + return ScrewTurn.Wiki.PluginFramework.Change.PageRolledBack; + case "N": + return ScrewTurn.Wiki.PluginFramework.Change.PageRenamed; + case "MP": + return ScrewTurn.Wiki.PluginFramework.Change.MessagePosted; + case "ME": + return ScrewTurn.Wiki.PluginFramework.Change.MessageEdited; + case "MD": + return ScrewTurn.Wiki.PluginFramework.Change.MessageDeleted; + default: + throw new NotSupportedException(); + } + } + + /// + /// Gets the change string for a change. + /// + /// The change. + /// The change string. + private static string GetChangeString(ScrewTurn.Wiki.PluginFramework.Change change) { + switch(change) { + case ScrewTurn.Wiki.PluginFramework.Change.PageUpdated: + return "U"; + case ScrewTurn.Wiki.PluginFramework.Change.PageDeleted: + return "D"; + case ScrewTurn.Wiki.PluginFramework.Change.PageRolledBack: + return "R"; + case ScrewTurn.Wiki.PluginFramework.Change.PageRenamed: + return "N"; + case ScrewTurn.Wiki.PluginFramework.Change.MessagePosted: + return "MP"; + case ScrewTurn.Wiki.PluginFramework.Change.MessageEdited: + return "ME"; + case ScrewTurn.Wiki.PluginFramework.Change.MessageDeleted: + return "MD"; + default: + throw new NotSupportedException(); + } + } + + /// + /// Gets the recent changes of the Wiki. + /// + /// The recent Changes (oldest to newest). + public RecentChange[] GetRecentChanges() { + lock(this) { + // Load from file + string data = File.ReadAllText(GetFullPath(RecentChangesFile)).Replace("\r", ""); + string[] lines = data.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + List changes = new List(lines.Length); + + string[] fields; + for(int i = 0; i < lines.Length; i++) { + try { + // Try/catch to avoid problems for corrupted file (raw method) + fields = lines[i].Split('|'); + ScrewTurn.Wiki.PluginFramework.Change c = GetChange(fields[5]); + changes.Add(new RecentChange(Tools.UnescapeString(fields[0]), Tools.UnescapeString(fields[1]), Tools.UnescapeString(fields[2]), + DateTime.Parse(fields[3]), Tools.UnescapeString(fields[4]), c, Tools.UnescapeString(fields[6]))); + } + catch { } + } + + changes.Sort((x, y) => { return x.DateTime.CompareTo(y.DateTime); }); + + return changes.ToArray(); + } + } + + /// + /// Adds a new change. + /// + /// The page name. + /// The page title. + /// The message subject (or null). + /// The date/time. + /// The user. + /// The change. + /// The description (optional). + /// true if the change is saved, false otherwise. + /// If page, title or user are null. + /// If page, title or user are empty. + public bool AddRecentChange(string page, string title, string messageSubject, DateTime dateTime, string user, + ScrewTurn.Wiki.PluginFramework.Change change, string descr) { + + if(page == null) throw new ArgumentNullException("page"); + if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); + if(title == null) throw new ArgumentNullException("title"); + if(title.Length == 0) throw new ArgumentException("Title cannot be empty", "title"); + if(user == null) throw new ArgumentNullException("user"); + if(user.Length == 0) throw new ArgumentException("User cannot be empty", "user"); + + if(messageSubject == null) messageSubject = ""; + if(descr == null) descr = ""; + + lock(this) { + StringBuilder sb = new StringBuilder(100); + sb.Append(Tools.EscapeString(page)); + sb.Append("|"); + sb.Append(Tools.EscapeString(title)); + sb.Append("|"); + sb.Append(Tools.EscapeString(messageSubject)); + sb.Append("|"); + sb.Append(dateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss")); + sb.Append("|"); + sb.Append(Tools.EscapeString(user)); + sb.Append("|"); + sb.Append(GetChangeString(change)); + sb.Append("|"); + sb.Append(Tools.EscapeString(descr)); + sb.Append("\r\n"); + File.AppendAllText(GetFullPath(RecentChangesFile), sb.ToString()); + + // Delete old changes, if needed + int max = int.Parse(host.GetSettingValue(SettingName.MaxRecentChanges)); + if(GetRecentChanges().Length > max) CutRecentChanges((int)(max * 0.90)); + + return true; + } + } + + /// + /// Reduces the size of the recent changes file to the specified size, deleting old entries. + /// + /// The new Size. + private void CutRecentChanges(int size) { + lock(this) { + List changes = new List(GetRecentChanges()); + if(size >= changes.Count) return; + + int idx = changes.Count - size + 1; + + StringBuilder sb = new StringBuilder(); + + for(int i = idx; i < changes.Count; i++) { + sb.Append(Tools.EscapeString(changes[i].Page)); + sb.Append("|"); + sb.Append(Tools.EscapeString(changes[i].Title)); + sb.Append("|"); + sb.Append(Tools.EscapeString(changes[i].MessageSubject)); + sb.Append("|"); + sb.Append(changes[i].DateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss")); + sb.Append("|"); + sb.Append(Tools.EscapeString(changes[i].User)); + sb.Append("|"); + sb.Append(GetChangeString(changes[i].Change)); + sb.Append("|"); + sb.Append(Tools.EscapeString(changes[i].Description)); + sb.Append("\r\n"); + } + File.WriteAllText(GetFullPath(RecentChangesFile), sb.ToString()); + } + } + + /// + /// Lists the stored plugin assemblies. + /// + /// + public string[] ListPluginAssemblies() { + lock(this) { + string[] files = Directory.GetFiles(GetFullPath(PluginsDirectory), "*.dll"); + string[] result = new string[files.Length]; + for(int i = 0; i < files.Length; i++) result[i] = Path.GetFileName(files[i]); + return result; + } + } + + /// + /// Stores a plugin's assembly, overwriting existing ones if present. + /// + /// The file name of the assembly, such as "Assembly.dll". + /// The assembly content. + /// true if the assembly is stored, false otherwise. + /// If filename or assembly are null. + /// If filename or assembly are empty. + public bool StorePluginAssembly(string filename, byte[] assembly) { + if(filename == null) throw new ArgumentNullException("filename"); + if(filename.Length == 0) throw new ArgumentException("Filename cannot be empty", "filename"); + if(assembly == null) throw new ArgumentNullException("assembly"); + if(assembly.Length == 0) throw new ArgumentException("Assembly cannot be empty", "assembly"); + + lock(this) { + try { + File.WriteAllBytes(GetFullPathForPlugin(filename), assembly); + } + catch(IOException) { + return false; + } + return true; + } + } + + /// + /// Retrieves a plugin's assembly. + /// + /// The file name of the assembly. + /// The assembly content, or null. + /// If filename is null. + /// If filename is empty. + public byte[] RetrievePluginAssembly(string filename) { + if(filename == null) throw new ArgumentNullException("filename"); + if(filename.Length == 0) throw new ArgumentException("Filename cannot be empty", "filename"); + + if(!File.Exists(GetFullPathForPlugin(filename))) return null; + + lock(this) { + try { + return File.ReadAllBytes(GetFullPathForPlugin(filename)); + } + catch(IOException) { + return null; + } + } + } + + /// + /// Removes a plugin's assembly. + /// + /// The file name of the assembly to remove, such as "Assembly.dll". + /// true if the assembly is removed, false otherwise. + /// If filename is null. + /// If filename is empty. + public bool DeletePluginAssembly(string filename) { + if(filename == null) throw new ArgumentNullException(filename); + if(filename.Length == 0) throw new ArgumentException("Filename cannot be empty", "filename"); + + lock(this) { + string fullName = GetFullPathForPlugin(filename); + if(!File.Exists(fullName)) return false; + try { + File.Delete(fullName); + return true; + } + catch(IOException) { + return false; + } + } + } + + /// + /// Sets the status of a plugin. + /// + /// The Type name of the plugin. + /// The plugin status. + /// true if the status is stored, false otherwise. + /// If typeName is null. + /// If typeName is empty. + public bool SetPluginStatus(string typeName, bool enabled) { + if(typeName == null) throw new ArgumentNullException("typeName"); + if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName"); + + lock(this) { + string data = File.ReadAllText(GetFullPathForPlugin(PluginsStatusFile)).Replace("\r", ""); + string[] lines = data.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + int idx = -1; + for(int i = 0; i < lines.Length; i++) { + if(lines[i].Equals(typeName)) { + idx = i; + break; + } + } + if(enabled) { + if(idx >= 0) { + StringBuilder sb = new StringBuilder(200); + for(int i = 0; i < lines.Length; i++) { + if(i != idx) sb.Append(lines[i] + "\r\n"); + } + File.WriteAllText(GetFullPathForPlugin(PluginsStatusFile), sb.ToString()); + } + // Else nothing to do + } + else { + if(idx == -1) { + StringBuilder sb = new StringBuilder(200); + for(int i = 0; i < lines.Length; i++) { + if(i != idx) sb.Append(lines[i] + "\r\n"); + } + sb.Append(typeName + "\r\n"); + File.WriteAllText(GetFullPathForPlugin(PluginsStatusFile), sb.ToString()); + } + // Else nothing to do + } + } + return true; + } + + /// + /// Gets the status of a plugin. + /// + /// The Type name of the plugin. + /// The status (true for enabled, false for disabled), or true if no status is found. + /// If typeName is null. + /// If typeName is empty. + public bool GetPluginStatus(string typeName) { + if(typeName == null) throw new ArgumentNullException("typeName"); + if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName"); + + lock(this) { + string data = File.ReadAllText(GetFullPathForPlugin(PluginsStatusFile)).Replace("\r", ""); + string[] lines = data.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + for(int i = 0; i < lines.Length; i++) { + if(lines[i].Equals(typeName)) return false; + } + return true; + } + } + + /// + /// Sets the configuration of a plugin. + /// + /// The Type name of the plugin. + /// The configuration. + /// true if the configuration is stored, false otherwise. + /// If typeName is null. + /// If typeName is empty. + public bool SetPluginConfiguration(string typeName, string config) { + if(typeName == null) throw new ArgumentNullException("typeName"); + if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName"); + + lock(this) { + try { + File.WriteAllText(GetFullPathForPluginConfig(typeName + ".cs"), config != null ? config : ""); + return true; + } + catch(IOException) { + return false; + } + } + } + + /// + /// Gets the configuration of a plugin. + /// + /// The Type name of the plugin. + /// The plugin configuration, or String.Empty. + /// If typeName is null. + /// If typeName is empty. + public string GetPluginConfiguration(string typeName) { + if(typeName == null) throw new ArgumentNullException("typeName"); + if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName"); + + lock(this) { + string file = GetFullPathForPluginConfig(typeName + ".cs"); + if(!File.Exists(file)) return ""; + else { + try { + return File.ReadAllText(file); + } + catch(IOException) { + return ""; + } + } + } + } + + /// + /// Gets the ACL Manager instance. + /// + public IAclManager AclManager { + get { + lock(this) { + return aclManager; + } + } + } + + /// + /// Stores the outgoing links of a page, overwriting existing data. + /// + /// The full name of the page. + /// The full names of the pages that page links to. + /// true if the outgoing links are stored, false otherwise. + /// If page or outgoingLinks are null. + /// If page or outgoingLinks are empty. + public bool StoreOutgoingLinks(string page, string[] outgoingLinks) { + if(page == null) throw new ArgumentNullException("page"); + if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); + if(outgoingLinks == null) throw new ArgumentNullException("outgoingLinks"); + + lock(this) { + // Step 1: remove old values + string[] lines = File.ReadAllLines(GetFullPath(LinksFile)); + + StringBuilder sb = new StringBuilder(lines.Length * 100); + + string testString = page + "|"; + + foreach(string line in lines) { + if(!line.StartsWith(testString)) { + sb.Append(line); + sb.Append("\r\n"); + } + } + + lines = null; + + // Step 2: add new values + sb.Append(page); + sb.Append("|"); + + for(int i = 0; i < outgoingLinks.Length; i++) { + if(outgoingLinks[i] == null) throw new ArgumentNullException("outgoingLinks", "Null element in outgoing links array"); + if(outgoingLinks[i].Length == 0) throw new ArgumentException("Elements in outgoing links cannot be empty", "outgoingLinks"); + + sb.Append(outgoingLinks[i]); + if(i != outgoingLinks.Length - 1) sb.Append("|"); + } + sb.Append("\r\n"); + + File.WriteAllText(GetFullPath(LinksFile), sb.ToString()); + } + + return true; + } + + /// + /// Gets the outgoing links of a page. + /// + /// The full name of the page. + /// The outgoing links. + /// If page is null. + /// If page is empty. + public string[] GetOutgoingLinks(string page) { + if(page == null) throw new ArgumentNullException("page"); + if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); + + lock(this) { + string[] lines = File.ReadAllLines(GetFullPath(LinksFile)); + + string testString = page + "|"; + + foreach(string line in lines) { + if(line.StartsWith(testString)) { + string[] fields = line.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + + string[] result = new string[fields.Length - 1]; + Array.Copy(fields, 1, result, 0, result.Length); + + return result; + } + } + } + + // Nothing found, return empty array + return new string[0]; + } + + /// + /// Gets all the outgoing links stored. + /// + /// The outgoing links, in a dictionary in the form page->outgoing_links. + public IDictionary GetAllOutgoingLinks() { + lock(this) { + string[] lines = File.ReadAllLines(GetFullPath(LinksFile)); + + Dictionary result = new Dictionary(lines.Length); + + string[] fields; + string[] links; + foreach(string line in lines) { + fields = line.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + + links = new string[fields.Length - 1]; + Array.Copy(fields, 1, links, 0, links.Length); + + result.Add(fields[0], links); + } + + return result; + } + } + + /// + /// Deletes the outgoing links of a page and all the target links that include the page. + /// + /// The full name of the page. + /// true if the links are deleted, false otherwise. + /// If page is null. + /// If page is empty. + public bool DeleteOutgoingLinks(string page) { + if(page == null) throw new ArgumentNullException("page"); + if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); + + lock(this) { + bool removedSomething = false; + + IDictionary links = GetAllOutgoingLinks(); + + // Step 1: rename source page, if any + removedSomething = links.Remove(page); + + // Step 2: rename all target pages, for all source pages + string[] keys = new string[links.Count]; + links.Keys.CopyTo(keys, 0); + foreach(string key in keys) { + List currentLinks = new List(links[key]); + removedSomething |= currentLinks.Remove(page); + links[key] = currentLinks.ToArray(); + } + + // Step 3: save on disk, if data changed + if(removedSomething) { + StringBuilder sb = new StringBuilder(links.Count * 100); + foreach(string key in links.Keys) { + sb.Append(key); + sb.Append("|"); + for(int i = 0; i < links[key].Length; i++) { + sb.Append(links[key][i]); + if(i != links[key][i].Length - 1) sb.Append("|"); + } + sb.Append("\r\n"); + } + + File.WriteAllText(GetFullPath(LinksFile), sb.ToString()); + } + + return removedSomething; + } + } + + /// + /// Updates all outgoing links data for a page rename. + /// + /// The old page name. + /// The new page name. + /// true if the data is updated, false otherwise. + /// If oldName or newName are null. + /// If oldName or newName are empty. + public bool UpdateOutgoingLinksForRename(string oldName, string newName) { + if(oldName == null) throw new ArgumentNullException("oldName"); + if(oldName.Length == 0) throw new ArgumentException("Old Name cannot be empty", "oldName"); + if(newName == null) throw new ArgumentNullException("newName"); + if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); + + lock(this) { + bool replacedSomething = false; + + IDictionary links = GetAllOutgoingLinks(); + + // Step 1: rename source page, if any + string[] tempLinks = null; + if(links.TryGetValue(oldName, out tempLinks)) { + links.Remove(oldName); + links.Add(newName, tempLinks); + replacedSomething = true; + } + + // Step 2: rename all target pages, for all source pages + foreach(string key in links.Keys) { + for(int i = 0; i < links[key].Length; i++) { + if(links[key][i] == oldName) { + links[key][i] = newName; + replacedSomething = true; + } + } + } + + // Step 3: save on disk, if data changed + if(replacedSomething) { + StringBuilder sb = new StringBuilder(links.Count * 100); + foreach(string key in links.Keys) { + sb.Append(key); + sb.Append("|"); + for(int i = 0; i < links[key].Length; i++) { + sb.Append(links[key][i]); + if(i != links[key][i].Length - 1) sb.Append("|"); + } + sb.Append("\r\n"); + } + + File.WriteAllText(GetFullPath(LinksFile), sb.ToString()); + } + + return replacedSomething; + } + } + + } + +} diff --git a/Core/Snippets.cs b/Core/Snippets.cs new file mode 100644 index 0000000..11cd749 --- /dev/null +++ b/Core/Snippets.cs @@ -0,0 +1,137 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using ScrewTurn.Wiki.PluginFramework; +using System.Text.RegularExpressions; + +namespace ScrewTurn.Wiki { + + /// + /// Manages snippets. + /// + public static class Snippets { + + /// + /// Gets the complete list of the Snippets. + /// + /// The snippets, sorted by name. + public static List GetSnippets() { + List allSnippets = new List(50); + + // Retrieve all snippets from Pages Provider + foreach(IPagesStorageProviderV30 provider in Collectors.PagesProviderCollector.AllProviders) { + allSnippets.AddRange(provider.GetSnippets()); + } + + allSnippets.Sort(new SnippetNameComparer()); + + return allSnippets; + } + + /// + /// Finds a Snippet. + /// + /// The Name of the Snippet to find. + /// The Snippet or null if it is not found. + public static Snippet Find(string name) { + List allSnippets = GetSnippets(); + + int result = allSnippets.BinarySearch(new Snippet(name, "", null), new SnippetNameComparer()); + + if(allSnippets.Count > 0 && result >= 0) return allSnippets[result]; + else return null; + } + + /// + /// Creates a new Snippet. + /// + /// The name of the Snippet. + /// The content of the Snippet. + /// The Provider to use to store the Snippet (null for the default provider). + /// True if the Snippets has been addedd successfully. + public static bool AddSnippet(string name, string content, IPagesStorageProviderV30 provider) { + if(Find(name) != null) return false; + + if(provider == null) provider = Collectors.PagesProviderCollector.GetProvider(Settings.DefaultPagesProvider); + + Snippet newSnippet = provider.AddSnippet(name, content); + + if(newSnippet != null) { + Log.LogEntry("Snippet " + name + " created", EntryType.General, Log.SystemUsername); + Content.ClearPseudoCache(); + Content.InvalidateAllPages(); + } + else Log.LogEntry("Creation failed for Snippet " + name, EntryType.Error, Log.SystemUsername); + + return newSnippet != null; + } + + /// + /// Removes a Snippet. + /// + /// The Snippet to remove. + /// True if the Snippet has been removed successfully. + public static bool RemoveSnippet(Snippet snippet) { + bool done = snippet.Provider.RemoveSnippet(snippet.Name); + + if(done) { + Log.LogEntry("Snippet " + snippet.Name + " deleted", EntryType.General, Log.SystemUsername); + Content.ClearPseudoCache(); + Content.InvalidateAllPages(); + } + else Log.LogEntry("Deletion failed for Snippet " + snippet.Name, EntryType.Error, Log.SystemUsername); + + return done; + } + + /// + /// Modifies the Content of a Snippet. + /// + /// The Snippet to update. + /// The new Content. + /// True if the Snippet has been updated successfully. + public static bool ModifySnippet(Snippet snippet, string content) { + Snippet newSnippet = snippet.Provider.ModifySnippet(snippet.Name, content); + + if(newSnippet != null) { + Log.LogEntry("Snippet " + snippet.Name + " updated", EntryType.General, Log.SystemUsername); + Content.ClearPseudoCache(); + Content.InvalidateAllPages(); + } + else Log.LogEntry("Modification failed for Snippet " + snippet.Name, EntryType.Error, Log.SystemUsername); + + return newSnippet != null; + } + + /// + /// The regular expression to use for extracting parameters. + /// + public static readonly Regex ParametersRegex = new Regex("\\?[a-zA-Z0-9_-]+\\?", RegexOptions.Compiled | RegexOptions.CultureInvariant); + + /// + /// Counts the parameters in a snippet. + /// + /// The snippet. + /// The number of parameters. + public static int CountParameters(Snippet snippet) { + return ExtractParameterNames(snippet).Length; + } + + /// + /// Finds the parameters in a snippet. + /// + /// The snippet. + /// The parameter names. + public static string[] ExtractParameterNames(Snippet snippet) { + List parms = new List(); + foreach(Match m in ParametersRegex.Matches(snippet.Content)) { + string value = m.Value.Substring(1, m.Value.Length - 2); + if(m.Success && !parms.Contains(value)) parms.Add(value); + } + return parms.ToArray(); + } + + } + +} diff --git a/Core/StartupTools.cs b/Core/StartupTools.cs new file mode 100644 index 0000000..69f5201 --- /dev/null +++ b/Core/StartupTools.cs @@ -0,0 +1,436 @@ + +using System; +using System.IO; +using System.Resources; +using System.Web.Configuration; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Provides tools for starting and shutting down the wiki engine. + /// + public static class StartupTools { + + /// + /// Gets the Settings Storage Provider configuration string from web.config. + /// + /// The configuration string. + public static string GetSettingsStorageProviderConfiguration() { + string config = WebConfigurationManager.AppSettings["SettingsStorageProviderConfig"]; + if(config != null) return config; + else return ""; + } + + /// + /// Updates the DLLs into the settings storage provider, if appropriate. + /// + /// The provider. + /// The file name of the assembly that contains the current Settings Storage Provider. + private static void UpdateDllsIntoSettingsProvider(ISettingsStorageProviderV30 provider, string settingsProviderAsmName) { + // Look into public\Plugins (hardcoded) + string fullPath = Path.Combine(Settings.PublicDirectory, "Plugins"); + + if(!Directory.Exists(fullPath)) return; + + string[] dlls = Directory.GetFiles(fullPath, "*.dll"); + string[] installedDlls = provider.ListPluginAssemblies(); + + foreach(string dll in dlls) { + bool found = false; + string filename = Path.GetFileName(dll); + foreach(string instDll in installedDlls) { + if(instDll.ToLowerInvariant() == filename.ToLowerInvariant()) { + found = true; + break; + } + } + + if(!found && filename.ToLowerInvariant() == settingsProviderAsmName.ToLowerInvariant()) { + found = true; + } + + if(found) { + // Update DLL + provider.StorePluginAssembly(filename, File.ReadAllBytes(dll)); + } + } + } + + /// + /// Performs all needed startup operations. + /// + public static void Startup() { + // Load Host + Host.Instance = new Host(); + + // Load config + ISettingsStorageProviderV30 ssp = ProviderLoader.LoadSettingsStorageProvider(WebConfigurationManager.AppSettings["SettingsStorageProvider"]); + ssp.Init(Host.Instance, GetSettingsStorageProviderConfiguration()); + Collectors.SettingsProvider = ssp; + //Settings.Instance = new Settings(ssp); + + if(!(ssp is SettingsStorageProvider)) { + // Update DLLs from public\Plugins + UpdateDllsIntoSettingsProvider(ssp, ProviderLoader.SettingsStorageProviderAssemblyName); + } + + // Initialize authorization managers + //AuthReader.Instance = new AuthReader(Settings.Provider); + //AuthWriter.Instance = new AuthWriter(Settings.Provider); + //AuthChecker.Instance = new AuthChecker(Settings.Provider); + + if(ssp.GetMetaDataItem(MetaDataItem.AccountActivationMessage, null) == "") + ssp.SetMetaDataItem(MetaDataItem.AccountActivationMessage, null, Defaults.AccountActivationMessageContent); + if(ssp.GetMetaDataItem(MetaDataItem.EditNotice, null) == "") + ssp.SetMetaDataItem(MetaDataItem.EditNotice, null, Defaults.EditNoticeContent); + if(ssp.GetMetaDataItem(MetaDataItem.Footer, null) == "") + ssp.SetMetaDataItem(MetaDataItem.Footer, null, Defaults.FooterContent); + if(ssp.GetMetaDataItem(MetaDataItem.Header, null) == "") + ssp.SetMetaDataItem(MetaDataItem.Header, null, Defaults.HeaderContent); + if(ssp.GetMetaDataItem(MetaDataItem.PasswordResetProcedureMessage, null) == "") + ssp.SetMetaDataItem(MetaDataItem.PasswordResetProcedureMessage, null, Defaults.PasswordResetProcedureMessageContent); + if(ssp.GetMetaDataItem(MetaDataItem.Sidebar, null) == "") + ssp.SetMetaDataItem(MetaDataItem.Sidebar, null, Defaults.SidebarContent); + if(ssp.GetMetaDataItem(MetaDataItem.PageChangeMessage, null) == "") + ssp.SetMetaDataItem(MetaDataItem.PageChangeMessage, null, Defaults.PageChangeMessage); + if(ssp.GetMetaDataItem(MetaDataItem.DiscussionChangeMessage, null) == "") + ssp.SetMetaDataItem(MetaDataItem.DiscussionChangeMessage, null, Defaults.DiscussionChangeMessage); + if(ssp.GetMetaDataItem(MetaDataItem.ApproveDraftMessage, null) == "") { + ssp.SetMetaDataItem(MetaDataItem.ApproveDraftMessage, null, Defaults.ApproveDraftMessage); + } + + // Load MIME Types + MimeTypes.Init(); + + // Load Providers + Collectors.FileNames = new System.Collections.Generic.Dictionary(10); + Collectors.UsersProviderCollector = new ProviderCollector(); + Collectors.PagesProviderCollector = new ProviderCollector(); + Collectors.FilesProviderCollector = new ProviderCollector(); + Collectors.FormatterProviderCollector = new ProviderCollector(); + Collectors.CacheProviderCollector = new ProviderCollector(); + Collectors.DisabledUsersProviderCollector = new ProviderCollector(); + Collectors.DisabledPagesProviderCollector = new ProviderCollector(); + Collectors.DisabledFilesProviderCollector = new ProviderCollector(); + Collectors.DisabledFormatterProviderCollector = new ProviderCollector(); + Collectors.DisabledCacheProviderCollector = new ProviderCollector(); + + // Load built-in providers + + // Files storage providers have to be loaded BEFORE users storage providers in order to properly set permissions + FilesStorageProvider f = new FilesStorageProvider(); + if(!ProviderLoader.IsDisabled(f.GetType().FullName)) { + f.Init(Host.Instance, ""); + Collectors.FilesProviderCollector.AddProvider(f); + Log.LogEntry("Provider " + f.Information.Name + " loaded (Enabled)", EntryType.General, Log.SystemUsername); + } + else { + Collectors.DisabledFilesProviderCollector.AddProvider(f); + Log.LogEntry("Provider " + f.Information.Name + " loaded (Disabled)", EntryType.General, Log.SystemUsername); + } + + UsersStorageProvider u = new UsersStorageProvider(); + if(!ProviderLoader.IsDisabled(u.GetType().FullName)) { + u.Init(Host.Instance, ""); + Collectors.UsersProviderCollector.AddProvider(u); + Log.LogEntry("Provider " + u.Information.Name + " loaded (Enabled)", EntryType.General, Log.SystemUsername); + } + else { + Collectors.DisabledUsersProviderCollector.AddProvider(u); + Log.LogEntry("Provider " + u.Information.Name + " loaded (Disabled)", EntryType.General, Log.SystemUsername); + } + + // Load Users (pages storage providers might need access to users/groups data for upgrading from 2.0 to 3.0) + ProviderLoader.FullLoad(true, false, false, false, false); + //Users.Instance = new Users(); + bool groupsCreated = VerifyAndCreateDefaultGroups(); + + PagesStorageProvider p = new PagesStorageProvider(); + if(!ProviderLoader.IsDisabled(p.GetType().FullName)) { + p.Init(Host.Instance, ""); + Collectors.PagesProviderCollector.AddProvider(p); + Log.LogEntry("Provider " + p.Information.Name + " loaded (Enabled)", EntryType.General, Log.SystemUsername); + } + else { + Collectors.DisabledPagesProviderCollector.AddProvider(p); + Log.LogEntry("Provider " + p.Information.Name + " loaded (Disabled)", EntryType.General, Log.SystemUsername); + } + + CacheProvider c = new CacheProvider(); + if(!ProviderLoader.IsDisabled(c.GetType().FullName)) { + c.Init(Host.Instance, ""); + Collectors.CacheProviderCollector.AddProvider(c); + Log.LogEntry("Provider " + c.Information.Name + " loaded (Enabled)", EntryType.General, Log.SystemUsername); + } + else { + Collectors.DisabledCacheProviderCollector.AddProvider(c); + Log.LogEntry("Provider " + c.Information.Name + " loaded (Disabled)", EntryType.General, Log.SystemUsername); + } + + // Load all other providers + ProviderLoader.FullLoad(false, true, true, true, true); + + if(groupsCreated) { + // It is necessary to set default permissions for file management + UserGroup administratorsGroup = Users.FindUserGroup(Settings.AdministratorsGroup); + UserGroup anonymousGroup = Users.FindUserGroup(Settings.AnonymousGroup); + UserGroup usersGroup = Users.FindUserGroup(Settings.UsersGroup); + + SetAdministratorsGroupDefaultPermissions(administratorsGroup); + SetUsersGroupDefaultPermissions(usersGroup); + SetAnonymousGroupDefaultPermissions(anonymousGroup); + } + + // Init cache + //Cache.Instance = new Cache(Collectors.CacheProviderCollector.GetProvider(Settings.DefaultCacheProvider)); + if(Collectors.CacheProviderCollector.GetProvider(Settings.DefaultCacheProvider) == null) { + Log.LogEntry("Default Cache Provider was not loaded, backing to integrated provider", EntryType.Error, Log.SystemUsername); + Settings.DefaultCacheProvider = typeof(CacheProvider).FullName; + Collectors.TryEnable(Settings.DefaultCacheProvider); + } + + // Load Snippets and templates + //Snippets.Instance = new Snippets(); + //Templates.Instance = new Templates(); + + // Load Pages + //Pages.Instance = new Pages(); + + // Load Nav. Paths + //NavigationPaths.Instance = new NavigationPaths(); + + // Create Collisions class + //Collisions.Instance = new Collisions(); + + // Create Redirections class + //Redirections.Instance = new Redirections(); + + // Create the Main Page, if needed + if(Pages.FindPage(Settings.DefaultPage) == null) CreateMainPage(); + + Log.LogEntry("ScrewTurn Wiki is ready", EntryType.General, Log.SystemUsername); + } + + /// + /// Verifies the existence of the default user groups and creates them if necessary. + /// + /// true if the groups were created, false otherwise. + private static bool VerifyAndCreateDefaultGroups() { + UserGroup administratorsGroup = Users.FindUserGroup(Settings.AdministratorsGroup); + UserGroup anonymousGroup = Users.FindUserGroup(Settings.AnonymousGroup); + UserGroup usersGroup = Users.FindUserGroup(Settings.UsersGroup); + + // Create default groups if they don't exist already, initializing permissions + + bool aGroupWasCreated = false; + + if(administratorsGroup == null) { + Users.AddUserGroup(Settings.AdministratorsGroup, "Built-in Administrators"); + administratorsGroup = Users.FindUserGroup(Settings.AdministratorsGroup); + + aGroupWasCreated = true; + } + + if(usersGroup == null) { + Users.AddUserGroup(Settings.UsersGroup, "Built-in Users"); + usersGroup = Users.FindUserGroup(Settings.UsersGroup); + + aGroupWasCreated = true; + } + + if(anonymousGroup == null) { + Users.AddUserGroup(Settings.AnonymousGroup, "Built-in Anonymous Users"); + anonymousGroup = Users.FindUserGroup(Settings.AnonymousGroup); + + aGroupWasCreated = true; + } + + if(aGroupWasCreated) { + ImportPageDiscussionPermissions(); + } + + return aGroupWasCreated; + } + + /// + /// Creates the main page. + /// + private static void CreateMainPage() { + Pages.CreatePage(null as string, Settings.DefaultPage); + Pages.ModifyPage(Pages.FindPage(Settings.DefaultPage), "Main Page", Log.SystemUsername, + DateTime.Now, "", Defaults.MainPageContent, null, null, SaveMode.Normal); + } + + /// + /// Performs shutdown operations, such as shutting-down Providers. + /// + public static void Shutdown() { + foreach(IFormatterProviderV30 provider in Collectors.FormatterProviderCollector.AllProviders) { + provider.Shutdown(); + } + foreach(IPagesStorageProviderV30 provider in Collectors.PagesProviderCollector.AllProviders) { + provider.Shutdown(); + } + foreach(IUsersStorageProviderV30 provider in Collectors.UsersProviderCollector.AllProviders) { + provider.Shutdown(); + } + foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) { + provider.Shutdown(); + } + foreach(ICacheProviderV30 provider in Collectors.CacheProviderCollector.AllProviders) { + provider.Shutdown(); + } + Settings.Provider.Shutdown(); + } + + /// + /// Sets the default permissions for the administrators group, properly importing version 2.0 values. + /// + /// The administrators group. + /// true if the operation succeeded, false otherwise. + public static bool SetAdministratorsGroupDefaultPermissions(UserGroup administrators) { + // Administrators can do any operation + return AuthWriter.SetPermissionForGlobals(AuthStatus.Grant, Actions.FullControl, administrators); + + // Settings.ConfigVisibleToAdmins is not imported on purpose + } + + /// + /// Sets the default permissions for the users group, properly importing version 2.0 values. + /// + /// The users group. + /// true if the operation succeeded, false otherwise. + public static bool SetUsersGroupDefaultPermissions(UserGroup users) { + bool done = true; + + // Set namespace-related permissions + if(Settings.UsersCanCreateNewPages) { + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.CreatePages, users); + } + else done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.ModifyPages, users); + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.PostDiscussion, users); + if(Settings.UsersCanCreateNewCategories || Settings.UsersCanManagePageCategories) { + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.ManageCategories, users); + } + + done &= SetupFileManagementPermissions(users); + + return done; + } + + /// + /// Sets the default permissions for the anonymous users group, properly importing version 2.0 values. + /// + /// The anonymous users group. + /// true if the operation succeeded, false otherwise. + public static bool SetAnonymousGroupDefaultPermissions(UserGroup anonymous) { + bool done = true; + + // Properly import Private/Public Mode wiki + if(Settings.PrivateAccess) { + // Nothing to do, because without any explicit grant, Anonymous users cannot do anything + } + else if(Settings.PublicAccess) { + // Public access, allow modification and propagate file management permissions if they were allowed for anonymous users + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.ModifyPages, anonymous); + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.DownloadAttachments, anonymous); + if(Settings.UsersCanCreateNewPages) { + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.CreatePages, anonymous); + } + if(Settings.UsersCanCreateNewCategories || Settings.UsersCanManagePageCategories) { + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.ManageCategories, anonymous); + } + if(Settings.FileManagementInPublicAccessAllowed) { + SetupFileManagementPermissions(anonymous); + } + } + else { + // Standard configuration, only allow read permissions + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.ReadPages, anonymous); + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.ReadDiscussion, anonymous); + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.DownloadAttachments, anonymous); + + foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) { + done &= AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, prov, "/", Actions.ForDirectories.DownloadFiles, anonymous); + } + } + + return done; + } + + /// + /// Sets file management permissions for the users or anonymous users group, importing version 2.0 values. + /// + /// The group. + /// true if the operation succeeded, false otherwise. + private static bool SetupFileManagementPermissions(UserGroup group) { + bool done = true; + + if(Settings.UsersCanViewFiles) { + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.DownloadAttachments, group); + foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) { + done &= AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, prov, "/", Actions.ForDirectories.DownloadFiles, group); + } + } + if(Settings.UsersCanUploadFiles) { + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.UploadAttachments, group); + foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) { + done &= AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, prov, "/", Actions.ForDirectories.UploadFiles, group); + done &= AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, prov, "/", Actions.ForDirectories.CreateDirectories, group); + } + } + if(Settings.UsersCanDeleteFiles) { + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.DeleteAttachments, group); + foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) { + done &= AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, prov, "/", Actions.ForDirectories.DeleteFiles, group); + done &= AuthWriter.SetPermissionForDirectory(AuthStatus.Grant, prov, "/", Actions.ForDirectories.DeleteDirectories, group); + } + } + + return done; + } + + /// + /// Imports version 2.0 page discussion settings and properly propagates them to user groups and single pages, when needed. + /// + /// true if the operation succeeded, false otherwise. + private static bool ImportPageDiscussionPermissions() { + // Notes + // Who can read pages, can read discussions + // Who can modify pages, can post messages and read discussions + // Who can manage pages, can manage discussions and post messages + + // Possible values: page|normal|locked|public + string value = Settings.DiscussionPermissions.ToLowerInvariant(); + + UserGroup usersGroup = Users.FindUserGroup(Settings.UsersGroup); + UserGroup anonymousGroup = Users.FindUserGroup(Settings.AnonymousGroup); + + bool done = true; + + switch(value) { + case "page": + // Nothing to do + break; + case "normal": + // Allow Users to post messages + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.PostDiscussion, usersGroup); + break; + case "locked": + // Deny Users to post messages + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Deny, null, Actions.ForNamespaces.PostDiscussion, usersGroup); + break; + case "public": + // Allow Users and Anonymous Users to post messages + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.PostDiscussion, usersGroup); + done &= AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, null, Actions.ForNamespaces.PostDiscussion, anonymousGroup); + break; + } + + return true; + } + + } + +} diff --git a/Core/SubjectInfo.cs b/Core/SubjectInfo.cs new file mode 100644 index 0000000..4b21103 --- /dev/null +++ b/Core/SubjectInfo.cs @@ -0,0 +1,56 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki { + + /// + /// Describes the subject of an ACL entry. + /// + public class SubjectInfo { + + private string name; + private SubjectType type; + + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The type. + public SubjectInfo(string name, SubjectType type) { + this.name = name; + this.type = type; + } + + /// + /// Gets the name. + /// + public string Name { + get { return name; } + } + + /// + /// Gets the type. + /// + public SubjectType Type { + get { return type; } + } + + } + + /// + /// Lists legal values for the type of a subject. + /// + public enum SubjectType { + /// + /// A user. + /// + User, + /// + /// A group. + /// + Group + } + +} diff --git a/Core/Templates.cs b/Core/Templates.cs new file mode 100644 index 0000000..859ce47 --- /dev/null +++ b/Core/Templates.cs @@ -0,0 +1,96 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Manages content templates. + /// + public static class Templates { + + /// + /// Gets all the content templates. + /// + /// The content templates, sorted by name. + public static List GetTemplates() { + List result = new List(20); + + // Retrieve templates from all providers + foreach(IPagesStorageProviderV30 prov in Collectors.PagesProviderCollector.AllProviders) { + result.AddRange(prov.GetContentTemplates()); + } + + result.Sort(new ContentTemplateNameComparer()); + + return result; + } + + /// + /// Finds a content template. + /// + /// The name of the template to find. + /// The content template, or null. + public static ContentTemplate Find(string name) { + List templates = GetTemplates(); + + int index = templates.BinarySearch(new ContentTemplate(name, "", null), new ContentTemplateNameComparer()); + + if(templates.Count > 0 && index >= 0) return templates[index]; + else return null; + } + + /// + /// Adds a new content template. + /// + /// The name of the template. + /// The content of the template. + /// The target provider (null for the default provider). + /// true if the template is added, false otherwise. + public static bool AddTemplate(string name, string content, IPagesStorageProviderV30 provider) { + if(Find(name) != null) return false; + + if(provider == null) provider = Collectors.PagesProviderCollector.GetProvider(Settings.DefaultPagesProvider); + + ContentTemplate result = provider.AddContentTemplate(name, content); + + if(result != null) Log.LogEntry("Content Template " + name + " created", EntryType.General, Log.SystemUsername); + else Log.LogEntry("Creation failed for Content Template " + name, EntryType.Error, Log.SystemUsername); + + return result != null; + } + + /// + /// Removes a content template. + /// + /// The template to remove. + /// true if the template is removed, false otherwise. + public static bool RemoveTemplate(ContentTemplate template) { + bool done = template.Provider.RemoveContentTemplate(template.Name); + + if(done) Log.LogEntry("Content Template " + template.Name + " deleted", EntryType.General, Log.SystemUsername); + else Log.LogEntry("Deletion failed for Content Template " + template.Name, EntryType.Error, Log.SystemUsername); + + return done; + } + + /// + /// Modifies a content template. + /// + /// The template to modify. + /// The new content of the template. + /// true if the template is modified, false otherwise. + public static bool ModifyTemplate(ContentTemplate template, string content) { + ContentTemplate result = template.Provider.ModifyContentTemplate(template.Name, content); + + if(result != null) Log.LogEntry("Content Template " + template.Name + " updated", EntryType.General, Log.SystemUsername); + else Log.LogEntry("Update failed for Content Template " + template.Name, EntryType.Error, Log.SystemUsername); + + return result != null; + } + + } + +} diff --git a/Core/Tools.cs b/Core/Tools.cs new file mode 100644 index 0000000..dbd9c9b --- /dev/null +++ b/Core/Tools.cs @@ -0,0 +1,573 @@ + +using System; +using System.Configuration; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Web; +using System.Web.Security; +using System.Globalization; +using ScrewTurn.Wiki.PluginFramework; +using System.Reflection; +using System.Net; + +namespace ScrewTurn.Wiki { + + /// + /// Contains useful Tools. + /// + public static class Tools { + + /// + /// Gets all the included files for the HTML Head, such as CSS, JavaScript and Icon pluginAssemblies, for a namespace. + /// + /// The namespace (null for the root). + /// The includes. + public static string GetIncludes(string nspace) { + string theme = Settings.GetTheme(nspace); + string themePath = Settings.GetThemePath(nspace); + + StringBuilder result = new StringBuilder(300); + + string[] css = Directory.GetFiles(Settings.ThemesDirectory + theme, "*.css"); + string firstChunk; + for(int i = 0; i < css.Length; i++) { + if(Path.GetFileName(css[i]).IndexOf("_") != -1) { + firstChunk = Path.GetFileName(css[i]).Substring(0, Path.GetFileName(css[i]).IndexOf("_")).ToLower(CultureInfo.CurrentCulture); + if(firstChunk.Equals("screen") || firstChunk.Equals("print") || firstChunk.Equals("all") || + firstChunk.Equals("aural") || firstChunk.Equals("braille") || firstChunk.Equals("embossed") || + firstChunk.Equals("handheld") || firstChunk.Equals("projection") || firstChunk.Equals("tty") || firstChunk.Equals("tv")) { + result.Append(@"" + "\n"); + } + else { + result.Append(@"" + "\n"); + } + } + else { + result.Append(@"" + "\n"); + } + } + + string customEditorCss = Path.Combine(Settings.ThemesDirectory, theme); + customEditorCss = Path.Combine(customEditorCss, "Editor.css"); + if(File.Exists(customEditorCss)) result.AppendFormat(@"" + "\n", theme); + else result.Append(@"" + "\n"); + + // OpenSearch + result.AppendFormat(@"", + Settings.MainUrl, Settings.WikiTitle + " - Search"); + + string[] js = Directory.GetFiles(Settings.ThemesDirectory + theme, "*.js"); + for(int i = 0; i < js.Length; i++) { + result.Append(@"" + "\n"); + } + + string[] icons = Directory.GetFiles(Settings.ThemesDirectory + theme, "Icon.*"); + if(icons.Length > 0) { + result.Append(@"" + "\n"); + } + + result.Append(GetJavaScriptIncludes()); + + // Include HTML Head + result.Append(Settings.Provider.GetMetaDataItem(MetaDataItem.HtmlHead, null)); + + return result.ToString(); + } + + /// + /// Gets all the JavaScript files to include. + /// + /// The JS files. + public static string GetJavaScriptIncludes() { + StringBuilder buffer = new StringBuilder(100); + + foreach(string js in Directory.GetFiles(Settings.JsDirectory, "*.js")) { + buffer.Append(@"" + "\n"); + } + + return buffer.ToString(); + } + + /// + /// Converts a byte number into a string, formatted using KB, MB or GB. + /// + /// The # of bytes. + /// The formatted string. + public static string BytesToString(long bytes) { + if(bytes < 1024) return bytes.ToString() + " B"; + else if(bytes < 1048576) return string.Format("{0:N2} KB", (float)bytes / 1024F); + else if(bytes < 1073741824) return string.Format("{0:N2} MB", (float)bytes / 1048576F); + else return string.Format("{0:N2} GB", (float)bytes / 1073741824F); + } + + /// + /// Computes the Disk Space Usage of a directory. + /// + /// The directory. + /// The used Disk Space, in bytes. + public static long DiskUsage(string dir) { + string[] files = Directory.GetFiles(dir); + string[] directories = Directory.GetDirectories(dir); + long result = 0; + + FileInfo file; + for(int i = 0; i < files.Length; i++) { + file = new FileInfo(files[i]); + result += file.Length; + } + for(int i = 0; i < directories.Length; i++) { + result += DiskUsage(directories[i]); + } + return result; + } + + /// + /// Generates the standard 5-digit Page Version string. + /// + /// The Page version. + /// The 5-digit Version string. + public static string GetVersionString(int version) { + string result = version.ToString(); + int len = result.Length; + for(int i = 0; i < 5 - len; i++) { + result = "0" + result; + } + return result; + } + + /// + /// Gets the available Themes. + /// + public static string[] AvailableThemes { + get { + string[] dirs = Directory.GetDirectories(Settings.ThemesDirectory); + string[] res = new string[dirs.Length]; + for(int i = 0; i < dirs.Length; i++) { + //if(dirs[i].EndsWith("\\")) dirs[i] = dirs[i].Substring(0, dirs[i].Length - 1); + dirs[i] = dirs[i].TrimEnd(Path.DirectorySeparatorChar); + res[i] = dirs[i].Substring(dirs[i].LastIndexOf(Path.DirectorySeparatorChar) + 1); + } + return res; + } + } + + /// + /// Gets the available Cultures. + /// + public static string[] AvailableCultures { + get { + // It seems, at least in VS 2008, that for Precompiled Web Sites, the GlobalResources pluginAssemblies that are not the + // default resource (Culture=neutral), get sorted into subdirectories named by the Culture Info name. Every + // assembly in these directories is called "ScrewTurn.Wiki.resources.dll" + + // I'm sure it's possible to just use the subdirectory names in the bin directory to get the culture info names, + // however, I'm not sure what other things might get tossed in there by the compiler now or in the future. + // That's why I'm specifically going for the App_GlobalResources.resources.dlls. + + // So, get all of the App_GlobalResources.resources.dll pluginAssemblies from bin and recurse subdirectories + string[] dllFiles = Directory.GetFiles(Path.Combine(Settings.RootDirectory, "bin"), "ScrewTurn.Wiki.resources.dll", SearchOption.AllDirectories); + // List to collect constructed culture names + List cultureNames = new List(); + + // Manually add en-US culture + CultureInfo enCI = new CultureInfo("en-US"); + cultureNames.Add(enCI.Name + "|" + UppercaseInitial(enCI.NativeName) + " - " + enCI.EnglishName); + + // For every file we find + // List format: xx-ZZ|Native name (English name) + foreach(string s in dllFiles) { + try { + // Load a reflection only assembly from the filename + Assembly asm = Assembly.ReflectionOnlyLoadFrom(s); + // string for destructive parsing of the assembly's full name + // Which, btw, looks something like this + // App_GlobalResources.resources, Version=0.0.0.0, Culture=zh-cn, PublicKeyToken=null + string fullName = asm.FullName; + // Find the Culture= attribute + int find = fullName.IndexOf("Culture="); + // Remove it and everything prior + fullName = fullName.Substring(find + 8); + // Find the trailing comma + find = fullName.IndexOf(','); + // Remove it and everything after + fullName = fullName.Substring(0, find); + // Fullname should now be the culture info name and we can instantiate the CultureInfo class from it + CultureInfo ci = new CultureInfo(fullName); + // StringBuilders + StringBuilder sb = new StringBuilder(); + sb.Append(ci.Name); + sb.Append("|"); + sb.Append(UppercaseInitial(ci.NativeName)); + sb.Append(" - "); + sb.Append(ci.EnglishName); + // Add the newly constructed Culture string + cultureNames.Add(sb.ToString()); + } + catch(Exception ex) { + Log.LogEntry("Error parsing culture info from " + s + Environment.NewLine + ex.Message, EntryType.Error, Log.SystemUsername); + } + } + + // If for whatever reason every one fails, this will return a 1 element array with the en-US info. + cultureNames.Sort(); + return cultureNames.ToArray(); + } + } + + private static string UppercaseInitial(string value) { + if(value.Length > 0) { + return value[0].ToString().ToUpper(CultureInfo.CurrentCulture) + value.Substring(1); + } + else return ""; + } + + /// + /// Computes the Hash of a Username, mixing it with other data, in order to avoid illegal Account activations. + /// + /// The Username. + /// The email. + /// The date/time. + /// The secured Hash of the Username. + public static string ComputeSecurityHash(string username, string email, DateTime dateTime) { + return Hash.ComputeSecurityHash(username, email, dateTime, Settings.MasterPassword); + } + + /// + /// Escapes bad characters in a string (pipes and \n). + /// + /// The input string. + /// The escaped string. + public static string EscapeString(string input) { + StringBuilder sb = new StringBuilder(input); + sb.Replace("\r", ""); + sb.Replace("\n", "%0A"); + sb.Replace("|", "%7C"); + return sb.ToString(); + } + + /// + /// Unescapes bad characters in a string (pipes and \n). + /// + /// The input string. + /// The unescaped string. + public static string UnescapeString(string input) { + StringBuilder sb = new StringBuilder(input); + sb.Replace("%7C", "|"); + sb.Replace("%0A", "\n"); + return sb.ToString(); + } + + /// + /// Generates a random 10-char Password. + /// + /// The Password. + public static string GenerateRandomPassword() { + Random r = new Random(); + string password = ""; + for(int i = 0; i < 10; i++) { + if(i % 2 == 0) + password += ((char)r.Next(65, 91)).ToString(); // Uppercase letter + else password += ((char)r.Next(97, 123)).ToString(); // Lowercase letter + } + return password; + } + + /// + /// Gets the approximate System Uptime. + /// + public static TimeSpan SystemUptime { + get { + int t = Environment.TickCount; + if(t < 0) t = t + int.MaxValue; + t = t / 1000; + return TimeSpan.FromSeconds(t); + } + } + + /// + /// Converts a Time Span to string. + /// + /// The Time Span. + /// The string. + public static string TimeSpanToString(TimeSpan span) { + string result = span.Days.ToString() + "d "; + result += span.Hours.ToString() + "h "; + result += span.Minutes.ToString() + "m "; + result += span.Seconds.ToString() + "s"; + return result; + } + + /// + /// Executes URL-encoding, avoiding to use '+' for spaces. + /// + /// The input string. + /// The encoded string. + public static string UrlEncode(string input) { + return HttpContext.Current.Server.UrlEncode(input).Replace("+", "%20"); + } + + /// + /// Executes URL-decoding, replacing spaces as processed by UrlEncode. + /// + /// The input string. + /// The decoded string. + public static string UrlDecode(string input) { + return HttpContext.Current.Server.UrlDecode(input.Replace("%20", " ")); + } + + /// + /// Removes all HTML tags from a text. + /// + /// The input HTML. + /// The extracted plain text. + public static string RemoveHtmlMarkup(string html) { + StringBuilder sb = new StringBuilder(System.Text.RegularExpressions.Regex.Replace(html, "<[^>]*>", " ")); + sb.Replace(" ", " "); + sb.Replace(" ", " "); + return sb.ToString(); + } + + /// + /// Extracts the directory name from a path used in the Files Storage Providers. + /// + /// The path, for example '/folder/blah/'. + /// The directory name, for example 'blah'. + public static string ExtractDirectoryName(string path) { + path = path.Trim('/'); + + int idx = path.LastIndexOf("/"); + return idx != -1 ? path.Substring(idx + 1) : path; + } + + /// + /// Detects the correct object associated to the current page using the Page and NS parameters in the query string. + /// + /// true to load the default page of the specified namespace when Page is not specified, false otherwise. + /// If Page is specified and exists, the correct , otherwise null if loadDefault is false, + /// or the object representing the default page of the specified namespace if loadDefault is true. + public static PageInfo DetectCurrentPageInfo(bool loadDefault) { + string nspace = HttpContext.Current.Request["NS"]; + NamespaceInfo nsinfo = nspace != null ? Pages.FindNamespace(nspace) : null; + + string page = HttpContext.Current.Request["Page"]; + if(string.IsNullOrEmpty(page)) { + if(loadDefault) { + if(nsinfo == null) page = Settings.DefaultPage; + else page = nsinfo.DefaultPage != null ? nsinfo.DefaultPage.FullName : ""; + } + else return null; + } + + string fullName = null; + if(!page.StartsWith(nspace + ".")) fullName = nspace + "." + page; + else fullName = page; + + fullName = fullName.Trim('.'); + + return Pages.FindPage(fullName); + } + + /// + /// Detects the full name of the current page using the Page and NS parameters in the query string. + /// + /// The full name of the page, regardless of the existence of the page. + public static string DetectCurrentFullName() { + string nspace = HttpContext.Current.Request["NS"] != null ? HttpContext.Current.Request["NS"] : ""; + string page = HttpContext.Current.Request["Page"] != null ? HttpContext.Current.Request["Page"] : ""; + + string fullName = null; + if(!page.StartsWith(nspace + ".")) fullName = nspace + "." + page; + else fullName = page; + + return fullName.Trim('.'); + } + + /// + /// Detects the correct object associated to the current namespace using the NS parameter in the query string. + /// + /// The correct object, or null. + public static NamespaceInfo DetectCurrentNamespaceInfo() { + string nspace = HttpContext.Current.Request["NS"]; + NamespaceInfo nsinfo = nspace != null ? Pages.FindNamespace(nspace) : null; + return nsinfo; + } + + /// + /// Detects the name of the current namespace using the NS parameter in the query string. + /// + /// The name of the namespace, or an empty string. + public static string DetectCurrentNamespace() { + return HttpContext.Current.Request["NS"] != null ? HttpContext.Current.Request["NS"] : ""; + } + + /// + /// Gets the message ID for HTML anchors. + /// + /// The message date/time. + /// The ID. + public static string GetMessageIdForAnchor(DateTime messageDateTime) { + return "MSG_" + messageDateTime.ToString("yyyyMMddHHmmss"); + } + + /// + /// Gets the name of a file's directory. + /// + /// The filename. + /// The name of the item. + public static string GetDirectoryName(string filename) { + if(filename != null) { + int index = filename.LastIndexOf("/"); + if(index > 0) { + string directoryName = filename.Substring(0, index + 1); + if(!directoryName.StartsWith("/")) directoryName = "/" + directoryName; + return directoryName; + } + } + + // Assume to navigate in the root directory + return "/"; + } + + /// + /// Gets the update status of a component. + /// + /// The version file URL. + /// The current version. + /// The new version, if any. + /// The URL of the new assembly, if applicable and available. + /// The update status. + /// This method only works in Full Trust. + public static UpdateStatus GetUpdateStatus(string url, string currentVersion, out string newVersion, out string newAssemblyUrl) { + // TODO: Verify usage of WebPermission class + // http://msdn.microsoft.com/en-us/library/system.net.webpermission.aspx + + string urlHash = "UpdUrlCache-" + url.GetHashCode().ToString(); + + try { + string ver = null; + + if(HttpContext.Current != null) { + ver = HttpContext.Current.Cache[urlHash] as string; + } + + if(ver == null) { + HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url); + req.AllowAutoRedirect = true; + HttpWebResponse res = (HttpWebResponse)req.GetResponse(); + + if(res.StatusCode != HttpStatusCode.OK) { + newVersion = null; + newAssemblyUrl = null; + return UpdateStatus.Error; + } + + StreamReader sr = new StreamReader(res.GetResponseStream()); + ver = sr.ReadToEnd(); + sr.Close(); + + if(HttpContext.Current != null) { + HttpContext.Current.Cache.Add(urlHash, ver, null, DateTime.Now.AddMinutes(5), + System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null); + } + } + + string[] lines = ver.Replace("\r", "").Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + if(lines.Length == 0) { + newVersion = null; + newAssemblyUrl = null; + return UpdateStatus.Error; + } + + string[] versions = lines[0].Split('|'); + bool upToDate = false; + for(int i = 0; i < versions.Length; i++) { + ver = versions[i]; + if(versions[i].Equals(currentVersion)) { + if(i == versions.Length - 1) upToDate = true; + else upToDate = false; + ver = versions[versions.Length - 1]; + break; + } + } + + if(upToDate) { + newVersion = null; + newAssemblyUrl = null; + return UpdateStatus.UpToDate; + } + else { + newVersion = ver; + + if(lines.Length == 2) newAssemblyUrl = lines[1]; + else newAssemblyUrl = null; + + return UpdateStatus.NewVersionFound; + } + } + catch(Exception) { + if(HttpContext.Current != null) { + HttpContext.Current.Cache.Add(urlHash, "", null, DateTime.Now.AddMinutes(5), + System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null); + } + + newVersion = null; + newAssemblyUrl = null; + return UpdateStatus.Error; + } + } + + /// + /// Computes the hash value of a string that is value across application instances and versions. + /// + /// The string to compute the hash of. + /// The hash value. + public static uint HashDocumentNameForTemporaryIndex(string value) { + if(value == null) throw new ArgumentNullException("value"); + + // sdbm algorithm, borrowed from http://www.cse.yorku.ca/~oz/hash.html + uint hash = 0; + + foreach(char c in value) { + // hash(i) = hash(i - 1) * 65599 + str[i] + hash = c + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + } + + /// + /// Lists legal update statuses. + /// + public enum UpdateStatus { + /// + /// Error while retrieving version information. + /// + Error, + /// + /// The component is up-to-date. + /// + UpToDate, + /// + /// A new version was found. + /// + NewVersionFound + } + +} diff --git a/Core/Translator.cs b/Core/Translator.cs new file mode 100644 index 0000000..bd05035 --- /dev/null +++ b/Core/Translator.cs @@ -0,0 +1,308 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace ScrewTurn.Wiki.ImportWiki { + + /// + /// Implements a translator tool for importing MediaWiki data. + /// + public class Translator : ScrewTurn.Wiki.ImportWiki.ITranslator { + + private Regex noWiki = new Regex(@"(.|\n|\r)*?", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + /// + /// Executes the translation. + /// + /// The input content. + /// The WikiMarkup. + public string Translate(string input) { + StringBuilder sb = new StringBuilder(); + sb.Append(input); + + Regex firsttitle = new Regex(@"(\={2,5}).+?\1"); + Regex doubleSquare = new Regex(@"\[{2}.+?\]{2}"); + Regex exlink = new Regex(@"(\[(\w+):\/\/([^\]]+)\])|(?\w+):\/\/(?[\w.]+\/?)\S*)(?!\"")"); + Regex mailto = new Regex(@"(\[mailto\:([\w\d\.\@]+)\])|(?((.|\n|\r)*?
    )?"); + Regex newlinespace = new Regex(@"^\ .+", RegexOptions.Multiline); + Regex transclusion = new Regex(@"(\{{2})(.|\n)+?(\}{2})"); + Regex math = new Regex(@"(.|\n|\r)*?"); + Regex references = new Regex(@"(.|\n|\r)*?"); + Regex references1 = new Regex(@"\"); + Regex redirect = new Regex(@"\#REDIRECT\ \[{2}.+\]{2}"); + Match match; + + List noWikiBegin = new List(), noWikiEnd = new List(); + + /*ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + + if (sb.ToString().Contains("__NOTOC__")) + { + sb.Remove(sb.ToString().IndexOf("__NOTOC__"), 9); + if (sb.ToString().Contains("{{compactTOC}}")) + { + sb.Remove(sb.ToString().IndexOf("{{compactTOC}}"),14); + sb.Insert(sb.ToString().IndexOf("{{compactTOC}}"), "\n{TOC}\n"); + } + } + else + { + match = firsttitle.Match(sb.ToString()); + if(match.Success) + sb.Insert(match.Index, "\n{TOC}\n"); + }*/ + + sb.Replace("\r", ""); + + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + + /////////////////////////////////////////////// + //////BEGIN of unsupported formatting-tag////// + /////////////////////////////////////////////// + match = math.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Replace(match.Value, @"<math>" + match.Value.Substring(6, match.Length - 13) + @"</math> "); + match = math.Match(sb.ToString()); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + } + else + match = math.Match(sb.ToString(), end); + } + + match = references.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Replace(match.Value, @"<ref>" + match.Value.Substring(5, match.Length - 11) + @"</ref> "); + match = references.Match(sb.ToString()); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + } + else + match = references.Match(sb.ToString(), end); + } + + match = references1.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Replace(match.Value, @"<references/> "); + match = references1.Match(sb.ToString()); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + } + else + match = references1.Match(sb.ToString(), end); + } + + match = redirect.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + sb.Replace(match.Value, @"" + match.Value + @" "); + match = redirect.Match(sb.ToString()); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + } + else + match = redirect.Match(sb.ToString(), end); + } + + match = transclusion.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + string s = @"" + match.Value + @" "; + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, s); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = transclusion.Match(sb.ToString(), match.Index + s.Length); + } + match = transclusion.Match(sb.ToString(), end); + } + ////////////////////////////////////////// + /////END of unsupported formetter-tag///// + ////////////////////////////////////////// + + match = pre.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + string s = "{{{{" + match.Value.Replace("
    ", "").Replace("
    ", "") + @"
    }}}}"; + sb.Replace(match.Value, s); + match = pre.Match(sb.ToString(), match.Index + s.Length); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + } + else + match = pre.Match(sb.ToString(), end); + } + + match = exlink.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + if(match.Value[0] == '[') { + string[] split = match.Value.Split(new char[] { ' ' }, 2); + if(split.Length == 2) { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, split[0] + "|" + split[1]); + match = exlink.Match(sb.ToString(), match.Index + match.Length); + } + else + match = exlink.Match(sb.ToString(), match.Index + match.Length); + } + else { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, "[" + match.Value + "]"); + match = exlink.Match(sb.ToString(), match.Index + match.Length + 1); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + } + } + else + match = exlink.Match(sb.ToString(), end); + } + + match = mailto.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + string str = match.Value.Replace(@"mailto:", ""); + if(str[0] == '[') { + string[] split = str.Split(new char[] { ' ' }, 2); + if(split.Length == 2) { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Length, split[0] + "|" + split[1]); + } + /*else + { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, split[0]); + }*/ + match = mailto.Match(sb.ToString(), match.Index + str.Length); + } + else { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, "[" + str.Substring(0, str.Length) + "]"); + match = mailto.Match(sb.ToString(), match.Index + str.Length + 1); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + } + } + else + match = mailto.Match(sb.ToString(), end); + } + + match = image.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + string str = match.Value.Remove(0, 7); + str = str.Remove(str.Length - 1); + + string name = ""; + string location = ""; + string caption = ""; + string[] splits = str.Split(new char[] { '|' }); + name = splits[0]; + for(int i = 0; i < splits.Length; i++) { + if(splits[i] == "right" || splits[i] == "left" || splits[i] == "center" || splits[i] == "none") + location = splits[i]; + else if(splits[i].Contains("px")) { } + else if(splits[i] == "thumb" || splits[i] == "thumbnail" || splits[i] == "frame") { } + else + caption = splits[i] + "|"; + } + if(location == "right" || location == "left") { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, "[image" + location + "|" + caption + "{UP}" + name + "]"); + } + else { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, "[imageauto|" + caption + "{UP}" + name + "]"); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + } + match = image.Match(sb.ToString(), end); + } + + match = doubleSquare.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + if(match.Value.Contains(":")) { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, match.Value.Replace(':', '_').Replace("/", "_").Replace(@"\", "_").Replace('?', '_')); + } + else { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, match.Value.Substring(1, match.Length - 2).Replace("/", "_").Replace(@"\", "_").Replace('?', '_')); + } + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + } + match = doubleSquare.Match(sb.ToString(), end); + } + + bool first = true; + match = newlinespace.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) { + string s = ""; + if(first) + s += "{{{{" + match.Value.Substring(1, match.Value.Length - 1); + else + s += match.Value.Substring(1, match.Value.Length - 1); + if(sb.Length > match.Index + match.Length + 1) { + if(sb[match.Index + match.Length] == '\n' && sb[match.Index + match.Length + 1] == ' ') { + first = false; + } + else { + s += "}}}}"; + first = true; + } + } + else { + s += "}}}}"; + first = true; + } + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, s); + match = newlinespace.Match(sb.ToString(), match.Index + s.Length); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + } + else + match = newlinespace.Match(sb.ToString(), end); + + } + + return sb.ToString(); + } + + private void ComputeNoWiki(string text, ref List noWikiBegin, ref List noWikiEnd) { + Match match; + noWikiBegin.Clear(); + noWikiEnd.Clear(); + + match = noWiki.Match(text); + while(match.Success) { + noWikiBegin.Add(match.Index); + noWikiEnd.Add(match.Index + match.Length - 1); + match = noWiki.Match(text, match.Index + match.Length); + } + } + + private bool IsNoWikied(int index, List noWikiBegin, List noWikiEnd, out int end) { + for(int i = 0; i < noWikiBegin.Count; i++) { + if(index >= noWikiBegin[i] && index <= noWikiEnd[i]) { + end = noWikiEnd[i]; + return false; + } + } + end = 0; + return true; + } + } + +} diff --git a/Core/TranslatorFlex.cs b/Core/TranslatorFlex.cs new file mode 100644 index 0000000..340738d --- /dev/null +++ b/Core/TranslatorFlex.cs @@ -0,0 +1,419 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace ScrewTurn.Wiki.ImportWiki { + + /// + /// Implements a translator tool for importing FlexWiki data. + /// + public class TranslatorFlex : ScrewTurn.Wiki.ImportWiki.ITranslator { + + private Regex noWiki = new Regex(@"\(.|\s)+?\<\/nowiki\>", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private Regex noFlex = new Regex(@"\(.|\s)+?\<\/noflex\>", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + /// + /// Executes the translation. + /// + /// The input content. + /// The WikiMarkup. + public string Translate(string input) { + StringBuilder sb = new StringBuilder(); + sb.Append(input); + List noWikiBegin = new List(), noWikiEnd = new List(), noFlexBegin = new List(), noFlexEnd = new List(); + + Match match; + Regex doubleDoubleBrackets = new Regex(@"\""{2}.+?\""{2}"); + Regex italic = new Regex(@"_.+?_"); + Regex bold = new Regex(@"\*.+?\*"); + Regex underline = new Regex(@"\+.+?\+"); + Regex strikethrough = new Regex(@"\-.*?\-"); + Regex head = new Regex(@"^!{1,6}.+", RegexOptions.Multiline); + Regex wikiTalk = new Regex(@"@@.+?@@"); + Regex code = new Regex(@"@.+?@"); + Regex pascalCase = new Regex(@"(\""[^""]+?\""\:)?([A-Z][a-z]+){2,}(\.([A-Z][a-z]+){2,})?"); + Regex camelCase = new Regex(@"(\""[^""]+?\""\:)?(([A-Z][a-z]+){2,}\.)?\[.+?\]"); + Regex exlink = new Regex(@"(\""[^""]+?\""\:)?(?\w+):\/\/(?[\w.]+\/?)\S*"); + Regex email = new Regex(@"(\""[^""]+?\""\:)?mailto\:.+"); + Regex lists = new Regex(@"^(\t|\s{8})+(\*|(1\.))", RegexOptions.Multiline); + Regex table = new Regex(@"^\|{2}", RegexOptions.Multiline); + Regex newlinespace = new Regex(@"^\ .+", RegexOptions.Multiline); + + sb.Replace("\r", ""); + + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + + match = doubleDoubleBrackets.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) + match = doubleDoubleBrackets.Match(sb.ToString(), end); + else { + string s = "" + match.Value.Substring(2, match.Length - 4) + ""; + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, s); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = doubleDoubleBrackets.Match(sb.ToString(), match.Index + s.Length); + } + } + + match = exlink.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) + match = exlink.Match(sb.ToString(), end); + else { + string s; + string[] split = match.Value.Split(new char[] { '"' }); + if(split.Length == 1) { + if(match.Value.EndsWith(".jpeg", StringComparison.CurrentCultureIgnoreCase) | match.Value.EndsWith(".gif", StringComparison.CurrentCultureIgnoreCase) | match.Value.EndsWith(".jpg", StringComparison.CurrentCultureIgnoreCase)) { + string imgName = match.Value.Substring(match.Value.LastIndexOf('/') + 1, match.Length - match.Value.LastIndexOf('/') - 1); + s = "[image|" + imgName + "|{UP}" + imgName + "]"; + } + else s = "[" + match.Value + "]"; + } + else { + if(split[1].EndsWith(".jpeg", StringComparison.CurrentCultureIgnoreCase) | split[1].EndsWith(".gif", StringComparison.CurrentCultureIgnoreCase) | split[1].EndsWith(".jpg", StringComparison.CurrentCultureIgnoreCase)) { + string imgName = split[1].Substring(split[1].LastIndexOf('/') + 1, split[1].Length - split[1].LastIndexOf('/') - 1); + s = "[image|" + imgName + "|{UP}" + imgName + "|" + split[2].Substring(1, split[2].Length - 1) + "]"; + } + else s = "[" + split[2].Substring(1, split[2].Length - 1) + "|" + split[1] + "]"; + } + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, s); + ComputeNoFlex(sb.ToString(), ref noFlexBegin, ref noFlexEnd); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = exlink.Match(sb.ToString(), match.Index + s.Length); + } + } + + match = email.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) + match = email.Match(sb.ToString(), end); + else { + string mailLink = ""; + if(match.Value.StartsWith("mailto:")) mailLink = "[" + match.Value.Replace("mailto:", "") + "]"; + else mailLink = "[" + match.Value.Split(new char[] { ':' }, 2)[1].Replace("mailto:", "") + "|" + match.Value.Split(new char[] { ':' }, 2)[0].Substring(1, match.Value.Split(new char[] { ':' }, 2)[0].Length - 2) + "]"; + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, mailLink); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = email.Match(sb.ToString(), match.Index + mailLink.Length); + } + } + + match = bold.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) + match = bold.Match(sb.ToString(), end); + else { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, "'''" + match.Value.Substring(1, match.Length - 2) + @"'''"); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = bold.Match(sb.ToString()); + } + } + + match = underline.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) + match = underline.Match(sb.ToString(), end); + else { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, "__" + match.Value.Substring(1, match.Length - 2) + @"__"); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = underline.Match(sb.ToString()); + } + } + + match = strikethrough.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) + match = strikethrough.Match(sb.ToString(), end); + else { + string s = ""; + if(match.Value == "---") s = "---"; + else if(match.Value == "--") s = "-"; + else s = "--" + match.Value.Substring(1, match.Length - 2) + @"--"; + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, s); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = strikethrough.Match(sb.ToString(), match.Index + s.Length); + } + } + + match = head.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) + match = head.Match(sb.ToString(), end); + else { + //Count the number of ! in order to put a corresponding + //number of = + int count = 1; + for(int i = 0; i < match.Length; i++) { + if(match.Value[i] == '!') + count++; + } + if(match.Value.EndsWith("!")) count--; + string s = ""; + for(int i = 0; i < count; i++) + s += "="; + s += match.Value.Substring(count - 1, match.Length - (count - 1) - 1); + for(int i = 0; i < count; i++) + s += "="; + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, s); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = head.Match(sb.ToString()); + } + } + + match = wikiTalk.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noFlexBegin, noFlexEnd, out end)) + match = wikiTalk.Match(sb.ToString(), end); + else { + string s = @"" + match.Value + @""; + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, s); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = wikiTalk.Match(sb.ToString(), match.Index + s.Length); + } + } + + match = code.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) + match = code.Match(sb.ToString(), end); + else { + string s = "{{" + match.Value.Substring(1, match.Length - 2) + @"}}"; + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, s); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = code.Match(sb.ToString()); + } + } + + match = camelCase.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) + match = camelCase.Match(sb.ToString(), end); + else { + string s; + string[] split = match.Value.Split(new char[] { ':' }, 2); + if(split.Length == 1) { + string[] split1 = match.Value.Split(new char[] { '.' }, 2); + if(split1.Length == 1) s = match.Value; + else s = split1[1]; + } + else { + string[] split1 = split[1].Split(new char[] { '.' }, 2); + if(split1.Length == 1) s = "[" + split[1].Substring(1, split[1].Length - 2) + "|" + split[0].Substring(1, split[0].Length - 2) + "]"; + else s = "[" + split1[1].Substring(0, split1[1].Length - 1) + "|" + split[0].Substring(1, split[0].Length - 2) + "]"; + } + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, s); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = camelCase.Match(sb.ToString(), match.Index + s.Length); + } + } + + match = pascalCase.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) + match = pascalCase.Match(sb.ToString(), end); + else { + string s; + string[] split = match.Value.Split(new char[] { ':' }, 2); + if(split.Length == 1) { + string[] split1 = match.Value.Split(new char[] { '.' }, 2); + if(split1.Length == 1) s = "[" + match.Value + "]"; + else s = "[" + split1[1] + "]"; + } + else { + string[] split1 = split[1].Split(new char[] { '.' }); + if(split1.Length == 1) s = "[" + split[1] + "|" + split[0].Substring(1, split[0].Length - 2) + "]"; + else s = "[" + split1[1] + "|" + split[0].Substring(1, split[0].Length - 2) + "]"; + } + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, s); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = pascalCase.Match(sb.ToString(), match.Index + s.Length); + } + } + + match = lists.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) + match = lists.Match(sb.ToString(), end); + else { + string s = ""; + char kindOfList; + int i = 0; + char[] ca = match.Value.ToCharArray(); + while(ca[i] == ' ') + i++; + if(i > 0) { + kindOfList = ca[i]; + i = i / 8; + } + else { + while(ca[i] == '\t') + i++; + kindOfList = ca[i]; + } + for(int k = 1; k <= i; k++) { + if(kindOfList == '*') s += "*"; + if(kindOfList == '1') s += "#"; + } + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, s); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = lists.Match(sb.ToString(), match.Index + s.Length); + } + } + + bool tableBegin = true; + match = table.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) + match = table.Match(sb.ToString(), end); + else { + string s = ""; + int indexEndOfLine = sb.ToString().IndexOf("\r\n", match.Index); + if(indexEndOfLine < 0) indexEndOfLine = sb.Length; + string[] split = sb.ToString().Substring(match.Index + 2, indexEndOfLine - match.Index - 4).Split(new string[] { "||" }, StringSplitOptions.None); + if(tableBegin) s = "{|\r\n| " + split[0]; + else s = "|-\r\n| " + split[0]; + for(int i = 1; i < split.Length; i++) + s += " || " + split[i]; + if(indexEndOfLine != sb.Length) { + if(sb.ToString().Substring(indexEndOfLine + 2, 2) == "||") tableBegin = false; + else { + tableBegin = true; + s += "\r\n|}"; + } + } + else { + tableBegin = true; + s += "\r\n|}"; + } + sb.Remove(match.Index, indexEndOfLine - match.Index); + sb.Insert(match.Index, s); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = table.Match(sb.ToString(), match.Index + s.Length); + } + } + + match = italic.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) + match = italic.Match(sb.ToString(), end); + else { + if(IsNoFlexed(match.Index, noFlexBegin, noFlexEnd, out end)) + match = italic.Match(sb.ToString(), end); + else { + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, "''" + match.Value.Substring(1, match.Length - 2) + @"''"); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + match = italic.Match(sb.ToString()); + } + } + } + + bool first = true; + match = newlinespace.Match(sb.ToString()); + while(match.Success) { + int end; + if(IsNoWikied(match.Index, noWikiBegin, noWikiEnd, out end)) + match = newlinespace.Match(sb.ToString(), end); + else { + string s = ""; + if(first) + s += "{{{{" + match.Value.Substring(1, match.Value.Length - 1); + else + s += match.Value.Substring(1, match.Value.Length - 1); + if(sb.Length > match.Index + match.Length + 1) { + if(sb[match.Index + match.Length] == '\n' && sb[match.Index + match.Length + 1] == ' ') { + first = false; + } + else { + s += "}}}}"; + first = true; + } + } + sb.Remove(match.Index, match.Length); + sb.Insert(match.Index, s); + match = newlinespace.Match(sb.ToString(), match.Index + s.Length); + ComputeNoWiki(sb.ToString(), ref noWikiBegin, ref noWikiEnd); + } + + } + + return sb.ToString().Replace("", "").Replace("", ""); + } + + private void ComputeNoWiki(string text, ref List noWikiBegin, ref List noWikiEnd) { + Match match; + noWikiBegin.Clear(); + noWikiEnd.Clear(); + + match = noWiki.Match(text); + while(match.Success) { + noWikiBegin.Add(match.Index); + noWikiEnd.Add(match.Index + match.Length - 1); + match = noWiki.Match(text, match.Index + match.Length); + } + } + + private bool IsNoWikied(int index, List noWikiBegin, List noWikiEnd, out int end) { + for(int i = 0; i < noWikiBegin.Count; i++) { + if(index >= noWikiBegin[i] && index <= noWikiEnd[i]) { + end = noWikiEnd[i]; + return true; + } + } + end = 0; + return false; + } + + private void ComputeNoFlex(string text, ref List noFlexBegin, ref List noFlexEnd) { + Match match; + noFlexBegin.Clear(); + noFlexEnd.Clear(); + + match = noFlex.Match(text); + while(match.Success) { + noFlexBegin.Add(match.Index); + noFlexEnd.Add(match.Index + match.Length - 1); + match = noFlex.Match(text, match.Index + match.Length); + } + } + + private bool IsNoFlexed(int index, List noFlexBegin, List noFlexEnd, out int end) { + for(int i = 0; i < noFlexBegin.Count; i++) { + if(index >= noFlexBegin[i] && index <= noFlexEnd[i]) { + end = noFlexEnd[i]; + return true; + } + } + end = 0; + return false; + } + } + +} diff --git a/Core/UrlTools.cs b/Core/UrlTools.cs new file mode 100644 index 0000000..d3200dd --- /dev/null +++ b/Core/UrlTools.cs @@ -0,0 +1,145 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Web; +using System.Text; + +namespace ScrewTurn.Wiki { + + /// + /// Implements useful URL-handling tools. + /// + public static class UrlTools { + + /// + /// Properly routes the current virtual request to a physical ASP.NET page. + /// + public static void RouteCurrentRequest() { + // Extract the physical page name, e.g. MainPage, Edit or Category + string pageName = Path.GetFileNameWithoutExtension(HttpContext.Current.Request.PhysicalPath); + // Exctract the extension, e.g. .ashx or .aspx + string ext = Path.GetExtension(HttpContext.Current.Request.PhysicalPath).ToLowerInvariant(); + // Remove trailing dot, .ashx -> ashx + if(ext.Length > 0) ext = ext.Substring(1); + + // IIS7+Integrated Pipeline handles all requests through the ASP.NET engine + // All non-interesting files are not processed, such as GIF, CSS, etc. + if(ext != "ashx" && ext != "aspx") return; + + // Extract the current namespace, if any + string nspace = GetCurrentNamespace(); + if(!string.IsNullOrEmpty(nspace)) pageName = pageName.Substring(nspace.Length + 1); // Trim Namespace. from pageName + + //HttpContext.Current.Response.Filter = + // new ScrewTurn.Wiki.RelativeUrlFilterStream(HttpContext.Current.Response.Filter, nspace); + + string queryString = ""; // Empty or begins with ampersand, not question mark + try { + // This might throw exceptions if 3rd-party modules interfer with the request pipeline + queryString = HttpContext.Current.Request.Url.Query.Replace("?", "&"); + } + catch { } + + if(ext.Equals("ashx")) { + // Content page requested, process it via Default.aspx + if(!queryString.Contains("NS=")) { + HttpContext.Current.RewritePath("~/Default.aspx?Page=" + Tools.UrlEncode(pageName) + "&NS=" + Tools.UrlEncode(nspace) + queryString); + } + else { + HttpContext.Current.RewritePath("~/Default.aspx?Page=" + Tools.UrlEncode(pageName) + queryString); + } + } + else if(ext.Equals("aspx")) { + // System page requested, redirect to the root of the application + // For example: http://www.server.com/Namespace.Edit.aspx?Page=MainPage -> http://www.server.com/Edit.aspx?Page=MainPage&NS=Namespace + if(!string.IsNullOrEmpty(nspace)) { // Needed to avoid infinite loops + if(!queryString.Contains("NS=")) { + HttpContext.Current.RewritePath("~/" + Tools.UrlEncode(pageName) + "." + ext + "?NS=" + Tools.UrlEncode(nspace) + queryString); + } + else { + if(queryString.Length > 1) queryString = "?" + queryString.Substring(1); + HttpContext.Current.RewritePath("~/" + Tools.UrlEncode(pageName) + "." + ext + queryString); + } + } + } + // else nothing to do + } + + /// + /// Extracts the current namespace from the URL, such as /App/Namespace.Edit.aspx. + /// + /// The current namespace, or an empty string. null if the URL format is not specified. + public static string GetCurrentNamespace() { + string filename = Path.GetFileNameWithoutExtension(HttpContext.Current.Request.Path); // e.g. MainPage or Edit or Namespace.MainPage or Namespace.Edit + + // Use dot to split the filename + string[] fields = filename.Split('.'); + + if(fields.Length != 1 && fields.Length != 2) return null; // Unrecognized format + if(fields.Length == 1) return ""; // Just page name + else return fields[0]; // Namespace.Page + } + + /// + /// Redirects the current response to the specified URL, properly appending the current namespace if any. + /// + /// The target URL. + public static void Redirect(string target) { + string nspace = HttpContext.Current.Request["NS"]; + if(nspace == null || nspace.Length == 0) HttpContext.Current.Response.Redirect(target); + else HttpContext.Current.Response.Redirect(target + (target.Contains("?") ? "&" : "?") + "NS=" + Tools.UrlEncode(nspace)); + } + + /// + /// Builds a URL properly prepending the namespace to the URL. + /// + /// The chunks used to build the URL. + /// The complete URL. + public static string BuildUrl(params string[] chunks) { + if(chunks == null) throw new ArgumentNullException("chunks"); + if(chunks.Length == 0) return ""; // Shortcut + + StringBuilder temp = new StringBuilder(chunks.Length * 10); + foreach(string chunk in chunks) { + temp.Append(chunk); + } + + string tempString = temp.ToString(); + + if(tempString.StartsWith("++")) return tempString.Substring(2); + + string nspace = HttpContext.Current.Request["NS"]; + if(string.IsNullOrEmpty(nspace)) nspace = null; + if(nspace == null) nspace = GetCurrentNamespace(); + if(string.IsNullOrEmpty(nspace)) nspace = null; + + if(nspace != null) { + string tempStringLower = tempString.ToLowerInvariant(); + if((tempStringLower.Contains(".ashx") || tempStringLower.Contains(".aspx")) && !tempString.StartsWith(Tools.UrlEncode(nspace) + ".")) temp.Insert(0, nspace + "."); + } + + return temp.ToString(); + } + + /// + /// Builds a URL properly appendind the NS parameter if appropriate. + /// + /// The destination . + /// The chunks to append. + public static void BuildUrl(StringBuilder destination, params string[] chunks) { + if(destination == null) throw new ArgumentNullException("destination"); + + destination.Append(BuildUrl(chunks)); + } + + /// + /// Redirects to the default page of the current namespace. + /// + public static void RedirectHome() { + Redirect(BuildUrl(Settings.DefaultPage, Settings.PageExtension)); + } + + } + +} diff --git a/Core/Users.cs b/Core/Users.cs new file mode 100644 index 0000000..1ad14d7 --- /dev/null +++ b/Core/Users.cs @@ -0,0 +1,921 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using System.Web; + +namespace ScrewTurn.Wiki { + + /// + /// Manages all the User Accounts data. + /// + public static class Users { + + private static UserInfo adminAccount = null; + + /// + /// Gets the built-in administrator account. + /// + /// The account. + public static UserInfo GetAdministratorAccount() { + if(adminAccount == null) { + adminAccount = new UserInfo("admin", "Administrator", Settings.ContactEmail, true, DateTime.MinValue, null); + adminAccount.Groups = new string[] { Settings.AdministratorsGroup }; + } + + return adminAccount; + } + + /// + /// The user data key pointing to page changes notification entries. + /// + private const string PageChangesKey = "PageChanges"; + + /// + /// The user data key pointing to discussion messages notification entries. + /// + private const string DiscussionMessagesKey = "DiscussionMessages"; + + /// + /// The user data key pointing to page changes notification entries for whole namespace. + /// + private const string NamespacePageChangesKey = "NamespacePageChanges"; + + /// + /// The user data key pointing to discussion messages notification entries for whole namespaces. + /// + private const string NamespaceDiscussionMessagesKey = "NamespaceDiscussionMessages"; + + /// + /// Gets all the Users that the providers declare to manage. + /// + /// The users, sorted by username. + public static List GetUsers() { + List allUsers = new List(1000); + + // Retrieve all the users from the Users Providers + int count = 0; + foreach(IUsersStorageProviderV30 provider in Collectors.UsersProviderCollector.AllProviders) { + count++; + allUsers.AddRange(provider.GetUsers()); + } + + if(count > 1) { + allUsers.Sort(new UsernameComparer()); + } + + return allUsers; + } + + /// + /// Finds a user. + /// + /// The username. + /// The user, or null. + public static UserInfo FindUser(string username) { + if(username == "admin") return GetAdministratorAccount(); + + // Try default provider first + IUsersStorageProviderV30 defaultProvider = Collectors.UsersProviderCollector.GetProvider(Settings.DefaultUsersProvider); + UserInfo temp = defaultProvider.GetUser(username); + if(temp != null) return temp; + + // The try other providers + temp = null; + IUsersStorageProviderV30[] providers = Collectors.UsersProviderCollector.AllProviders; + foreach(IUsersStorageProviderV30 p in providers) { + IUsersStorageProviderV30 extProv = p as IUsersStorageProviderV30; + if(extProv != null && extProv != defaultProvider) { + temp = extProv.GetUser(username); + if(temp != null) return temp; + } + } + return null; + } + + /// + /// Finds a user by email. + /// + /// The email address. + /// The user, or null. + public static UserInfo FindUserByEmail(string email) { + // Try default provider first + IUsersStorageProviderV30 defaultProvider = Collectors.UsersProviderCollector.GetProvider(Settings.DefaultUsersProvider); + UserInfo temp = defaultProvider.GetUserByEmail(email); + if(temp != null) return temp; + + // The try other providers + temp = null; + IUsersStorageProviderV30[] providers = Collectors.UsersProviderCollector.AllProviders; + foreach(IUsersStorageProviderV30 p in providers) { + IUsersStorageProviderV30 extProv = p as IUsersStorageProviderV30; + if(extProv != null && extProv != defaultProvider) { + temp = extProv.GetUserByEmail(email); + if(temp != null) return temp; + } + } + return null; + } + + /// + /// Gets user data. + /// + /// The user. + /// The data key. + /// The data, or null if either the user or the key is not found. + public static string GetUserData(UserInfo user, string key) { + if(user == null) return null; + if(string.IsNullOrEmpty(key)) return null; + if(user.Username == "admin") return null; + + return user.Provider.RetrieveUserData(user, key); + } + + /// + /// Sets user data. + /// + /// The user. + /// The data key. + /// The data value. + /// true if the data is stored, false otherwise. + public static bool SetUserData(UserInfo user, string key, string data) { + if(user == null) return false; + if(string.IsNullOrEmpty(key)) return false; + if(user.Username == "admin") return false; + if(user.Provider.UsersDataReadOnly) return false; + + bool done = user.Provider.StoreUserData(user, key, data); + + if(done) Log.LogEntry("User data stored for " + user.Username, EntryType.General, Log.SystemUsername); + else Log.LogEntry("Could not store user data for " + user.Username, EntryType.Error, Log.SystemUsername); + + return done; + } + + /// + /// Adds a new User. + /// + /// The Username. + /// The display name. + /// The Password (plain text). + /// The Email address. + /// A value specifying whether or not the account is active. + /// The Provider. If null, the default provider is used. + /// True if the User has been created successfully. + public static bool AddUser(string username, string displayName, string password, string email, bool active, IUsersStorageProviderV30 provider) { + if(FindUser(username) != null) return false; + if(provider == null) provider = Collectors.UsersProviderCollector.GetProvider(Settings.DefaultUsersProvider); + + if(provider.UserAccountsReadOnly) return false; + + UserInfo u = provider.AddUser(username, displayName, password, email, active, DateTime.Now); + if(u == null) { + Log.LogEntry("User creation failed for " + username, EntryType.Error, Log.SystemUsername); + return false; + } + else { + Log.LogEntry("User " + username + " created", EntryType.General, Log.SystemUsername); + Host.Instance.OnUserAccountActivity(u, UserAccountActivity.AccountAdded); + return true; + } + } + + /// + /// Updates a new User. + /// + /// The user to modify. + /// The display name. + /// The Password (plain text, null or empty for no change). + /// The Email address. + /// A value specifying whether or not the account is active. + /// True if the User has been created successfully. + public static bool ModifyUser(UserInfo user, string displayName, string password, string email, bool active) { + if(user.Provider.UserAccountsReadOnly) return false; + + bool done = user.Provider.ModifyUser(user, displayName, password, email, active) != null; + + if(done) { + Log.LogEntry("User " + user.Username + " updated", EntryType.General, Log.SystemUsername); + Host.Instance.OnUserAccountActivity(user, UserAccountActivity.AccountModified); + return true; + } + else { + Log.LogEntry("User update failed for " + user.Username, EntryType.Error, Log.SystemUsername); + return false; + } + } + + /// + /// Removes a User. + /// + /// The User to remove. + /// True if the User has been removed successfully. + public static bool RemoveUser(UserInfo user) { + if(user.Provider.UserAccountsReadOnly) return false; + + bool done = user.Provider.RemoveUser(user); + if(done) { + Log.LogEntry("User " + user.Username + " removed", EntryType.General, Log.SystemUsername); + Host.Instance.OnUserAccountActivity(user, UserAccountActivity.AccountRemoved); + return true; + } + else { + Log.LogEntry("User deletion failed for " + user.Username, EntryType.Error, Log.SystemUsername); + return false; + } + } + + /// + /// Changes the Password of a User. + /// + /// The User to change the password of. + /// The new Password (plain text). + /// true if the Password has been changed successfully, false otherwise. + public static bool ChangePassword(UserInfo user, string newPassword) { + return ModifyUser(user, user.DisplayName, newPassword, user.Email, user.Active); + } + + /// + /// Sends the password reset message to a user. + /// + /// The username. + /// The email. + /// The user registration date/time. + public static void SendPasswordResetMessage(string username, string email, DateTime dateTime) { + string mainLink = Settings.MainUrl + "Login.aspx?ResetCode=" + Tools.ComputeSecurityHash(username, email, dateTime) + "&Username=" + Tools.UrlEncode(username); + string body = Settings.Provider.GetMetaDataItem(MetaDataItem.PasswordResetProcedureMessage, null).Replace("##USERNAME##", + username).Replace("##LINK##", mainLink).Replace("##WIKITITLE##", + Settings.WikiTitle).Replace("##EMAILADDRESS##", Settings.ContactEmail); + + EmailTools.AsyncSendEmail(email, Settings.SenderEmail, + Settings.WikiTitle + " - " + Exchanger.ResourceExchanger.GetResource("ResetPassword"), body, false); + } + + /// + /// Changes the Email address of a User. + /// + /// The User to change the Email address of. + /// The new Email address. + /// true if the Email address has been changed successfully, false otherwise. + public static bool ChangeEmail(UserInfo user, string newEmail) { + return ModifyUser(user, user.DisplayName, null, newEmail, user.Active); + } + + /// + /// Sets the Active/Inactive status of a User. + /// + /// The User. + /// The status. + /// True if the User's status has been changed successfully. + public static bool SetActivationStatus(UserInfo user, bool active) { + return ModifyUser(user, user.DisplayName, null, user.Email, active); + } + + /// + /// Gets all the user groups. + /// + /// All the user groups, sorted by name. + public static List GetUserGroups() { + List result = new List(50); + + int count = 0; + foreach(IUsersStorageProviderV30 prov in Collectors.UsersProviderCollector.AllProviders) { + count++; + result.AddRange(prov.GetUserGroups()); + } + + if(count > 1) { + result.Sort(new UserGroupComparer()); + } + + return result; + } + + /// + /// Gets all the user groups in a provider. + /// + /// The provider. + /// The user groups, sorted by name. + public static List GetUserGroups(IUsersStorageProviderV30 provider) { + return new List(provider.GetUserGroups()); + } + + /// + /// Gets all the user groups a user is member of. + /// + /// The user. + /// All the user groups the user is member of, sorted by name. + public static List GetUserGroupsForUser(UserInfo user) { + UserGroup[] allGroups = user.Provider.GetUserGroups(); + + List result = new List(allGroups.Length); + + StringComparer comp = StringComparer.OrdinalIgnoreCase; + + foreach(UserGroup group in allGroups) { + if(Array.Find(user.Groups, delegate(string g) { + return comp.Compare(g, group.Name) == 0; + }) != null) { + result.Add(group); + } + } + + return result; + } + + /// + /// Finds a user group. + /// + /// The name of the user group to find. + /// The object or null if no data is found. + public static UserGroup FindUserGroup(string name) { + List allGroups = GetUserGroups(); + int index = allGroups.BinarySearch(new UserGroup(name, "", null), new UserGroupComparer()); + + if(index < 0) return null; + else return allGroups[index]; + } + + /// + /// Adds a new user group to a specific provider. + /// + /// The name of the group. + /// The description of the group. + /// The target provider. + /// true if the groups is added, false otherwise. + public static bool AddUserGroup(string name, string description, IUsersStorageProviderV30 provider) { + if(provider == null) provider = Collectors.UsersProviderCollector.GetProvider(Settings.DefaultUsersProvider); + + if(provider.UserGroupsReadOnly) return false; + + if(FindUserGroup(name) != null) return false; + + UserGroup result = provider.AddUserGroup(name, description); + + if(result != null) { + Host.Instance.OnUserGroupActivity(result, UserGroupActivity.GroupAdded); + Log.LogEntry("User Group " + name + " created", EntryType.General, Log.SystemUsername); + } + else Log.LogEntry("Creation failed for User Group " + name, EntryType.Error, Log.SystemUsername); + + return result != null; + } + + /// + /// Adds a new user group to the default provider. + /// + /// The name of the group. + /// The description of the group. + /// true if the groups is added, false otherwise. + public static bool AddUserGroup(string name, string description) { + return AddUserGroup(name, description, + Collectors.UsersProviderCollector.GetProvider(Settings.DefaultUsersProvider)); + } + + /// + /// Modifies a user group. + /// + /// The user group to modify. + /// The new description. + /// true if the user group is modified, false otherwise. + public static bool ModifyUserGroup(UserGroup group, string description) { + if(group.Provider.UserGroupsReadOnly) return false; + + UserGroup result = group.Provider.ModifyUserGroup(group, description); + + if(result != null) { + Host.Instance.OnUserGroupActivity(result, UserGroupActivity.GroupModified); + Log.LogEntry("User Group " + group.Name + " updated", EntryType.General, Log.SystemUsername); + } + else Log.LogEntry("Update failed for User Group " + result.Name, EntryType.Error, Log.SystemUsername); + + return result != null; + } + + /// + /// Removes a user group. + /// + /// The user group to remove. + /// true if the user group is removed, false otherwise. + public static bool RemoveUserGroup(UserGroup group) { + if(group.Provider.UserGroupsReadOnly) return false; + + bool done = group.Provider.RemoveUserGroup(group); + + if(done) { + Host.Instance.OnUserGroupActivity(group, UserGroupActivity.GroupRemoved); + Log.LogEntry("User Group " + group.Name + " deleted", EntryType.General, Log.SystemUsername); + } + else Log.LogEntry("Deletion failed for User Group " + group.Name, EntryType.Error, Log.SystemUsername); + + return done; + } + + /// + /// Sets the group memberships of a user account. + /// + /// The user account. + /// The groups the user account is member of. + /// true if the membership is set, false otherwise. + public static bool SetUserMembership(UserInfo user, string[] groups) { + if(user.Provider.GroupMembershipReadOnly) return false; + + UserInfo result = user.Provider.SetUserMembership(user, groups); + + if(result != null) { + Host.Instance.OnUserAccountActivity(result, UserAccountActivity.AccountMembershipChanged); + Log.LogEntry("Group membership set for User " + user.Username, EntryType.General, Log.SystemUsername); + } + else Log.LogEntry("Could not set group membership for User " + user.Username, EntryType.Error, Log.SystemUsername); + + return result != null; + } + + /// + /// Creates the correct link of a User. + /// + /// The Username. + /// The User link. + public static string UserLink(string username) { + return UserLink(username, false); + } + + /// + /// Creates the correct link of a User. + /// + /// The Username. + /// A value indicating whether to open the link in a new window. + /// The User link. + public static string UserLink(string username, bool newWindow) { + if(username != null && (username.EndsWith("+" + Log.SystemUsername) || username == Log.SystemUsername)) return username; + + UserInfo u = FindUser(username); + if(u == null && username.Equals("admin")) u = new UserInfo("admin", null, Settings.ContactEmail, true, DateTime.Now, null); + if(u != null) { + return @"" + + GetDisplayName(u) + ""; + } + else return username; + } + + /// + /// Gets the display name of a user. + /// + /// The user. + /// The display name. + public static string GetDisplayName(UserInfo user) { + if(string.IsNullOrEmpty(user.DisplayName)) return user.Username; + else return user.DisplayName; + } + + /// + /// Tries to automatically login a user using the current HttpContext, + /// through any provider that supports the operation. + /// + /// The current HttpContext. + /// The correct UserInfo, or null. + public static UserInfo TryAutoLogin(HttpContext context) { + // Try default provider first + IUsersStorageProviderV30 defaultProvider = + Collectors.UsersProviderCollector.GetProvider(Settings.DefaultUsersProvider) as IUsersStorageProviderV30; + + if(defaultProvider != null) { + UserInfo temp = defaultProvider.TryAutoLogin(context); + if(temp != null) return temp; + } + + // Then try all other providers + IUsersStorageProviderV30[] providers = Collectors.UsersProviderCollector.AllProviders; + foreach(IUsersStorageProviderV30 p in providers) { + IUsersStorageProviderV30 extProv = p as IUsersStorageProviderV30; + if(extProv != null && extProv != defaultProvider) { + UserInfo temp = extProv.TryAutoLogin(context); + if(temp != null) return temp; + } + } + return null; + } + + /// + /// Tries to manually login a user using all the available methods. + /// + /// The username. + /// The password. + /// The correct UserInfo, or null. + public static UserInfo TryLogin(string username, string password) { + if(username == "admin" && password == Settings.MasterPassword) { + return GetAdministratorAccount(); + } + + // Try default provider first + IUsersStorageProviderV30 defaultProvider = + Collectors.UsersProviderCollector.GetProvider(Settings.DefaultUsersProvider) as IUsersStorageProviderV30; + + if(defaultProvider != null) { + UserInfo temp = defaultProvider.TryManualLogin(username, password); + if(temp != null) return temp; + } + + // Then try all other providers + IUsersStorageProviderV30[] providers = Collectors.UsersProviderCollector.AllProviders; + foreach(IUsersStorageProviderV30 p in providers) { + IUsersStorageProviderV30 extProv = p as IUsersStorageProviderV30; + if(extProv != null && extProv != defaultProvider) { + UserInfo temp = extProv.TryManualLogin(username, password); + if(temp != null) return temp; + } + } + return null; + } + + /// + /// Tries to login a user through the cookie-stored authentication data. + /// + /// The username. + /// The login key. + /// The correct UserInfo object, or null. + public static UserInfo TryCookieLogin(string username, string loginKey) { + if(username == "admin" && loginKey == ComputeLoginKey(username, Settings.ContactEmail, DateTime.MinValue)) { + // Just return, no notification to providers because the "admin" account is fictitious + return GetAdministratorAccount(); + } + + UserInfo user = FindUser(username); + + if(user != null && user.Active) { + if(loginKey == ComputeLoginKey(user.Username, user.Email, user.DateTime)) { + // Notify provider + user.Provider.NotifyCookieLogin(user); + return user; + } + } + return null; + } + + /// + /// Notifies to the proper provider that a user has logged out. + /// + /// The username. + public static void NotifyLogout(string username) { + UserInfo user = FindUser(username); + if(user != null) { + IUsersStorageProviderV30 prov = user.Provider as IUsersStorageProviderV30; + if(prov != null) prov.NotifyLogout(user); + } + } + + /// + /// Copmputes the login key. + /// + /// The username. + /// The email. + /// The registration date/time. + /// The login key. + public static string ComputeLoginKey(string username, string email, DateTime dateTime) { + if(username == null) throw new ArgumentNullException("username"); + if(email == null) throw new ArgumentNullException("email"); + + return Tools.ComputeSecurityHash(username, email, dateTime); + } + + /// + /// Sets the email notification status for a page. + /// + /// The user for which to set the notification status. + /// The page subject of the notification. + /// A value indicating whether page changes should be notified. + /// A value indicating whether discussion messages should be notified. + /// true if the notification is set, false otherwise. + public static bool SetEmailNotification(UserInfo user, PageInfo page, bool pageChanges, bool discussionMessages) { + if(user == null || page == null) return false; + + // Get user's data + // Depending on the status of pageChanges and discussionMessages, + // either remove existing entries (if any) or add new entries + // In the process, remove entries that refer to inexistent pages + + // Format + // Page1:Page2:Page3 + + string pageChangeData = user.Provider.RetrieveUserData(user, PageChangesKey); + string discussionMessagesData = user.Provider.RetrieveUserData(user, DiscussionMessagesKey); + + if(pageChangeData == null) pageChangeData = ""; + if(discussionMessagesData == null) discussionMessagesData = ""; + + string[] pageChangesEntries = pageChangeData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + string[] discussionMessagesEntries = discussionMessagesData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + + List pageChangesResult = new List(pageChangesEntries.Length + 1); + List discussionMessagesResult = new List(discussionMessagesEntries.Length + 1); + + string lowercasePage = page.FullName.ToLowerInvariant(); + + bool added = false; + foreach(string entry in pageChangesEntries) { + if(entry.ToLowerInvariant() == lowercasePage) { + if(pageChanges) { + pageChangesResult.Add(entry); + added = true; + } + } + else if(Pages.FindPage(entry) != null) pageChangesResult.Add(entry); + } + if(!added && pageChanges) pageChangesResult.Add(page.FullName); + + added = false; + foreach(string entry in discussionMessagesEntries) { + if(entry.ToLowerInvariant() == lowercasePage) { + if(discussionMessages) { + discussionMessagesResult.Add(entry); + added = true; + } + } + else if(Pages.FindPage(entry) != null) discussionMessagesResult.Add(entry); + } + if(!added && discussionMessages) discussionMessagesResult.Add(page.FullName); + + string newPageChangesData = string.Join(":", pageChangesResult.ToArray()); + string newDiscussionMessagesData = string.Join(":", discussionMessagesResult.ToArray()); + + bool done = user.Provider.StoreUserData(user, PageChangesKey, newPageChangesData) & + user.Provider.StoreUserData(user, DiscussionMessagesKey, newDiscussionMessagesData); + + return done; + } + + /// + /// Sets the email notification status for a namespace. + /// + /// The user for which to set the notification status. + /// The namespace subject of the notification. + /// A value indicating whether page changes should be notified. + /// A value indicating whether discussion messages should be notified. + /// true if the notification is set, false otherwise. + public static bool SetEmailNotification(UserInfo user, NamespaceInfo nspace, bool pageChanges, bool discussionMessages) { + if(user == null) return false; + + // Get user's data + // Depending on the status of pageChanges and discussionMessages, + // either remove existing entries (if any) or add new entries + // In the process, remove entries that refer to inexistent pages + + // Format + // Namespace1:Namespace2:Namespace3 + + string pageChangeData = user.Provider.RetrieveUserData(user, NamespacePageChangesKey); + string discussionMessagesData = user.Provider.RetrieveUserData(user, NamespaceDiscussionMessagesKey); + + if(pageChangeData == null) pageChangeData = ""; + if(discussionMessagesData == null) discussionMessagesData = ""; + + string[] pageChangesEntries = pageChangeData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + string[] discussionMessagesEntries = discussionMessagesData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + + List pageChangesResult = new List(pageChangesEntries.Length + 1); + List discussionMessagesResult = new List(discussionMessagesEntries.Length + 1); + + string namespaceName = nspace != null ? nspace.Name : ""; + string lowercaseNamespace = nspace != null ? nspace.Name.ToLowerInvariant() : ""; + + bool added = false; + foreach(string entry in pageChangesEntries) { + if(entry.ToLowerInvariant() == lowercaseNamespace) { + if(pageChanges) { + pageChangesResult.Add(entry); + added = true; + } + } + else { + if(entry == "") pageChangesResult.Add(""); + else if(Pages.FindNamespace(entry) != null) pageChangesResult.Add(entry); + } + } + if(!added && pageChanges) pageChangesResult.Add(namespaceName); + + added = false; + foreach(string entry in discussionMessagesEntries) { + if(entry.ToLowerInvariant() == lowercaseNamespace) { + if(discussionMessages) { + discussionMessagesResult.Add(entry); + added = true; + } + } + else { + if(entry == "") discussionMessagesResult.Add(""); + else if(Pages.FindNamespace(entry) != null) discussionMessagesResult.Add(entry); + } + } + if(!added && discussionMessages) discussionMessagesResult.Add(namespaceName); + + string newPageChangesData = string.Join(":", pageChangesResult.ToArray()); + string newDiscussionMessagesData = string.Join(":", discussionMessagesResult.ToArray()); + + bool done = user.Provider.StoreUserData(user, NamespacePageChangesKey, newPageChangesData) & + user.Provider.StoreUserData(user, NamespaceDiscussionMessagesKey, newDiscussionMessagesData); + + return done; + } + + /// + /// Gets the email notification status for a page. + /// + /// The user for which to get the notification status. + /// The page subject of the notification. + /// A value indicating whether page changes should be notified. + /// A value indicating whether discussion messages should be notified. + public static void GetEmailNotification(UserInfo user, PageInfo page, out bool pageChanges, out bool discussionMessages) { + pageChanges = false; + discussionMessages = false; + + if(user == null || page == null) return; + + string pageChangeData = user.Provider.RetrieveUserData(user, PageChangesKey); + string discussionMessagesData = user.Provider.RetrieveUserData(user, DiscussionMessagesKey); + + if(pageChangeData == null) pageChangeData = ""; + if(discussionMessagesData == null) discussionMessagesData = ""; + + pageChangeData = pageChangeData.ToLowerInvariant(); + discussionMessagesData = discussionMessagesData.ToLowerInvariant(); + + string[] pageChangeEntries = pageChangeData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + string[] discussionMessagesEntries = discussionMessagesData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + + string lowercasePage = page.FullName.ToLowerInvariant(); + + // Elements in the array are already lowercase + pageChanges = Array.Find(pageChangeEntries, delegate(string elem) { return elem == lowercasePage; }) != null; + discussionMessages = Array.Find(discussionMessagesEntries, delegate(string elem) { return elem == lowercasePage; }) != null; + } + + /// + /// Gets the email notification status for a namespace. + /// + /// The user for which to get the notification status. + /// The namespace subject of the notification (null for the root). + /// A value indicating whether page changes should be notified. + /// A value indicating whether discussion messages should be notified. + public static void GetEmailNotification(UserInfo user, NamespaceInfo nspace, out bool pageChanges, out bool discussionMessages) { + pageChanges = false; + discussionMessages = false; + + if(user == null) return; + + string lowercaseNamespaces = nspace != null ? nspace.Name.ToLowerInvariant() : ""; + + string pageChangesData = user.Provider.RetrieveUserData(user, NamespacePageChangesKey); + string discussionMessagesData = user.Provider.RetrieveUserData(user, NamespaceDiscussionMessagesKey); + + if(pageChangesData == null) pageChangesData = ""; + if(discussionMessagesData == null) discussionMessagesData = ""; + + pageChangesData = pageChangesData.ToLowerInvariant(); + discussionMessagesData = discussionMessagesData.ToLowerInvariant(); + + string[] pageChangeEntries = pageChangesData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + string[] discussionMessagesEntries = discussionMessagesData.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + + // Elements in the array are already lowercase + pageChanges = Array.Find(pageChangeEntries, delegate(string elem) { return elem == lowercaseNamespaces; }) != null; + discussionMessages = Array.Find(discussionMessagesEntries, delegate(string elem) { return elem == lowercaseNamespaces; }) != null; + } + + /// + /// Gets all the users that must be notified of a page change. + /// + /// The page. + /// The users to be notified. + public static UserInfo[] GetUsersToNotifyForPageChange(PageInfo page) { + if(page == null) return new UserInfo[0]; + + UserInfo[] specific = GetUsersToNotify(page, PageChangesKey); + UserInfo[] nspace = GetUsersToNotify(Pages.FindNamespace(NameTools.GetNamespace(page.FullName)), + NamespacePageChangesKey); + + UserInfo[] temp = MergeArrays(specific, nspace); + List result = new List(temp.Length); + + // Verify read permissions + foreach(UserInfo user in temp) { + if(AuthChecker.CheckActionForPage(page, Actions.ForPages.ReadPage, user.Username, user.Groups)) { + result.Add(user); + } + } + + return result.ToArray(); + } + + /// + /// Gets all the users that must be notified of a discussion message. + /// + /// The page. + /// The users to be notified. + public static UserInfo[] GetUsersToNotifyForDiscussionMessages(PageInfo page) { + if(page == null) return new UserInfo[0]; + + UserInfo[] specific = GetUsersToNotify(page, DiscussionMessagesKey); + UserInfo[] nspace = GetUsersToNotify(Pages.FindNamespace(NameTools.GetNamespace(page.FullName)), + NamespaceDiscussionMessagesKey); + + UserInfo[] temp = MergeArrays(specific, nspace); + List result = new List(temp.Length); + + // Verify read permissions + foreach(UserInfo user in temp) { + if(AuthChecker.CheckActionForPage(page, Actions.ForPages.ReadDiscussion, user.Username, user.Groups)) { + result.Add(user); + } + } + + return result.ToArray(); + } + + /// + /// Merges two arrays of users, removing duplicates. + /// + /// The first array. + /// The second array. + /// The merged users. + private static UserInfo[] MergeArrays(UserInfo[] array1, UserInfo[] array2) { + List result = new List(array1.Length + array2.Length); + result.AddRange(array1); + + UsernameComparer comp = new UsernameComparer(); + foreach(UserInfo user in array2) { + bool found = false; + foreach(UserInfo present in result) { + if(comp.Compare(present, user) == 0) { + found = true; + break; + } + } + + if(!found) { + result.Add(user); + } + } + + return result.ToArray(); + } + + /// + /// Gets the users to notify for either a page change or a discussion message. + /// + /// The page. + /// The key to look for in the user's data. + /// The users to be notified. + private static UserInfo[] GetUsersToNotify(PageInfo page, string key) { + List result = new List(200); + + string lowercasePage = page.FullName.ToLowerInvariant(); + + foreach(IUsersStorageProviderV30 prov in Collectors.UsersProviderCollector.AllProviders) { + IDictionary users = prov.GetUsersWithData(key); + + string[] fields; + foreach(KeyValuePair pair in users) { + fields = pair.Value.ToLowerInvariant().Split(':'); + + if(Array.Find(fields, delegate(string elem) { return elem == lowercasePage; }) != null) { + result.Add(pair.Key); + } + } + } + + return result.ToArray(); + } + + /// + /// Gets the users to notify for either a page change or a discussion message in a namespace. + /// + /// The namespace (null for the root). + /// The key to look for in the user's data. + /// The users to be notified. + private static UserInfo[] GetUsersToNotify(NamespaceInfo nspace, string key) { + List result = new List(200); + + string lowercaseNamespace = nspace != null ? nspace.Name.ToLowerInvariant() : ""; + + foreach(IUsersStorageProviderV30 prov in Collectors.UsersProviderCollector.AllProviders) { + IDictionary users = prov.GetUsersWithData(key); + + string[] fields; + foreach(KeyValuePair pair in users) { + fields = pair.Value.ToLowerInvariant().Split(':'); + + if(Array.Find(fields, delegate(string elem) { return elem == lowercaseNamespace; }) != null) { + result.Add(pair.Key); + } + } + } + + return result.ToArray(); + } + + } + +} diff --git a/Core/UsersStorageProvider.cs b/Core/UsersStorageProvider.cs new file mode 100644 index 0000000..0521411 --- /dev/null +++ b/Core/UsersStorageProvider.cs @@ -0,0 +1,970 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Implements a Users Storage Provider. + /// + public class UsersStorageProvider : IUsersStorageProviderV30 { + + private const string UsersFile = "Users.cs"; + private const string UsersDataFile = "UsersData.cs"; + private const string GroupsFile = "Groups.cs"; + + private readonly ComponentInformation info = new ComponentInformation("Local Users Provider", + "ScrewTurn Software", Settings.WikiVersion, "http://www.screwturn.eu", null); + + private IHostV30 host; + + private UserGroup[] groupsCache = null; + private UserInfo[] usersCache = null; + + private string GetFullPath(string filename) { + return Path.Combine(host.GetSettingValue(SettingName.PublicDirectory), filename); + } + + /// + /// Initializes the Provider. + /// + /// The Host of the Provider. + /// The Configuration data, if any. + /// If host or config are null. + /// If config is not valid or is incorrect. + public void Init(IHostV30 host, string config) { + if(host == null) throw new ArgumentNullException("host"); + if(config == null) throw new ArgumentNullException("config"); + + this.host = host; + + if(!LocalProvidersTools.CheckWritePermissions(host.GetSettingValue(SettingName.PublicDirectory))) { + throw new InvalidConfigurationException("Cannot write into the public directory - check permissions"); + } + + if(!File.Exists(GetFullPath(UsersFile))) { + File.Create(GetFullPath(UsersFile)).Close(); + } + if(!File.Exists(GetFullPath(UsersDataFile))) { + File.Create(GetFullPath(UsersDataFile)).Close(); + } + if(!File.Exists(GetFullPath(GroupsFile))) { + File.Create(GetFullPath(GroupsFile)).Close(); + } + + VerifyAndPerformUpgrade(); + } + + /// + /// Verifies the need for a data upgrade, and performs it when needed. + /// + private void VerifyAndPerformUpgrade() { + // Load file lines + // Parse first line (if any) with old (v2) algorithm + // If parsing is successful, then the file must be converted + // Conversion consists in removing the 'ADMIN|USER' field, creating the proper default groups and setting user membership + + // Structure v2: + // Username|PasswordHash|Email|Active-Inactive|DateTime|Admin-User + + //string[] lines = File.ReadAllLines(GetFullPath(UsersFile)); + // Use this method because version 2.0 file might have started with a blank line + string[] lines = File.ReadAllText(GetFullPath(UsersFile)).Replace("\r", "").Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + if(lines.Length > 0) { + bool upgradeIsNeeded = false; + LocalUserInfo[] users = new LocalUserInfo[lines.Length]; + bool[] oldStyleAdmin = new bool[lines.Length]; // Values are valid only if upgradeIsNeeded=true + + char[] splitter = new char[] { '|' }; + + for(int i = 0; i < lines.Length; i++) { + string line = lines[i]; + + string[] fields = line.Split(splitter, StringSplitOptions.RemoveEmptyEntries); + + string displayName = null; + + if(fields.Length == 6) { + if(fields[5] == "ADMIN" || fields[5] == "USER") { + // Version 2.0 + upgradeIsNeeded = true; + oldStyleAdmin[i] = fields[5] == "ADMIN"; + } + else { + // Version 3.0 with DisplayName specified + oldStyleAdmin[i] = false; + displayName = fields[5]; + } + } + else { + // Can be a version 3.0 file, with empty DisplayName + oldStyleAdmin[i] = false; + } + + users[i] = new LocalUserInfo(fields[0], displayName, fields[2], + fields[3].ToLowerInvariant() == "active", DateTime.Parse(fields[4]), this, fields[1]); + } + + if(upgradeIsNeeded) { + // Dump users + // Create default groups + // Set membership for old users + // Tell the host to set the permissions for the default groups + + string backupFile = GetFullPath(Path.GetFileNameWithoutExtension(UsersFile) + "_v2" + Path.GetExtension(UsersFile)); + File.Copy(GetFullPath(UsersFile), backupFile); + + host.LogEntry("Upgrading users format from 2.0 to 3.0", LogEntryType.General, null, this); + + DumpUsers(users); + UserGroup adminsGroup = AddUserGroup(host.GetSettingValue(SettingName.AdministratorsGroup), "Built-in Administrators"); + UserGroup usersGroup = AddUserGroup(host.GetSettingValue(SettingName.UsersGroup), "Built-in Users"); + + for(int i = 0; i < users.Length; i++) { + if(oldStyleAdmin[i]) { + SetUserMembership(users[i], new string[] { adminsGroup.Name }); + } + else { + SetUserMembership(users[i], new string[] { usersGroup.Name }); + } + } + + host.UpgradeSecurityFlagsToGroupsAcl(adminsGroup, usersGroup); + } + } + } + + /// + /// Method invoked on shutdown. + /// + /// This method might not be invoked in some cases. + public void Shutdown() { } + + /// + /// Gets the Information about the Provider. + /// + public ComponentInformation Information { + get { return info; } + } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + public string ConfigHelpHtml { + get { return null; } + } + + /// + /// Gets a value indicating whether user accounts are read-only. + /// + public bool UserAccountsReadOnly { + get { return false; } + } + + /// + /// Gets a value indicating whether user groups are read-only. If so, the provider + /// should support default user groups as defined in the wiki configuration. + /// + public bool UserGroupsReadOnly { + get { return false; } + } + + /// + /// Gets a value indicating whether group membership is read-only (if + /// is false, then this property must be false). If this property is true, the provider + /// should return membership data compatible with default user groups. + /// + public bool GroupMembershipReadOnly { + get { return false; } + } + + /// + /// Gets a value indicating whether users' data is read-only. + /// + public bool UsersDataReadOnly { + get { return false; } + } + + /// + /// Tests a Password for a User account. + /// + /// The User account. + /// The Password to test. + /// True if the Password is correct. + /// If user or password are null. + public bool TestAccount(UserInfo user, string password) { + if(user == null) throw new ArgumentNullException("user"); + if(password == null) throw new ArgumentNullException("password"); + + return TryManualLogin(user.Username, password) != null; + } + + /// + /// Gets the complete list of Users. + /// + /// All the Users, sorted by username. + public UserInfo[] GetUsers() { + lock(this) { + if(usersCache == null) { + + UserGroup[] groups = GetUserGroups(); + + string[] lines = File.ReadAllLines(GetFullPath(UsersFile)); + + UserInfo[] result = new UserInfo[lines.Length]; + + char[] splitter = new char[] { '|' }; + + string[] fields; + for(int i = 0; i < lines.Length; i++) { + fields = lines[i].Split(splitter, StringSplitOptions.RemoveEmptyEntries); + + // Structure (version 3.0 - file previously converted): + // Username|PasswordHash|Email|Active-Inactive|DateTime[|DisplayName] + + string displayName = fields.Length == 6 ? fields[5] : null; + + result[i] = new LocalUserInfo(fields[0], displayName, fields[2], fields[3].ToLowerInvariant().Equals("active"), + DateTime.Parse(fields[4]), this, fields[1]); + + result[i].Groups = GetGroupsForUser(result[i].Username, groups); + } + + Array.Sort(result, new UsernameComparer()); + + usersCache = result; + } + + return usersCache; + } + } + + /// + /// Gets the names of all the groups a user is member of. + /// + /// The username. + /// The groups. + /// The names of the groups the user is member of. + private string[] GetGroupsForUser(string user, UserGroup[] groups) { + List result = new List(3); + + foreach(UserGroup group in groups) { + if(Array.Find(group.Users, delegate(string u) { return u == user; }) != null) { + result.Add(group.Name); + } + } + + return result.ToArray(); + } + + /// + /// Loads a proper local instance of a user account. + /// + /// The user account. + /// The local instance, or null. + private LocalUserInfo LoadLocalInstance(UserInfo user) { + UserInfo[] users = GetUsers(); + UsernameComparer comp = new UsernameComparer(); + for(int i = 0; i < users.Length; i++) { + if(comp.Compare(users[i], user) == 0) return users[i] as LocalUserInfo; + } + return null; + } + + /// + /// Searches for a User. + /// + /// The User to search for. + /// True if the User already exists. + private bool UserExists(UserInfo user) { + UserInfo[] users = GetUsers(); + UsernameComparer comp = new UsernameComparer(); + for(int i = 0; i < users.Length; i++) { + if(comp.Compare(users[i], user) == 0) return true; + } + return false; + } + + /// + /// Adds a new User. + /// + /// The Username. + /// The display name (can be null). + /// The Password. + /// The Email address. + /// A value specifying whether or not the account is active. + /// The Account creation Date/Time. + /// The correct object or null. + /// If username, password or email are null. + /// If username, password or email are empty. + public UserInfo AddUser(string username, string displayName, string password, string email, bool active, DateTime dateTime) { + if(username == null) throw new ArgumentNullException("username"); + if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username"); + if(password == null) throw new ArgumentNullException("password"); + if(password.Length == 0) throw new ArgumentException("Password cannot be empty", "password"); + if(email == null) throw new ArgumentNullException("email"); + if(email.Length == 0) throw new ArgumentException("Email cannot be empty", "email"); + + lock(this) { + if(UserExists(new UserInfo(username, displayName, "", true, DateTime.Now, this))) return null; + + BackupUsersFile(); + + StringBuilder sb = new StringBuilder(); + sb.Append(username); + sb.Append("|"); + sb.Append(Hash.Compute(password)); + sb.Append("|"); + sb.Append(email); + sb.Append("|"); + sb.Append(active ? "ACTIVE" : "INACTIVE"); + sb.Append("|"); + sb.Append(dateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss")); + // ADMIN|USER no more used in version 3.0 + //sb.Append("|"); + //sb.Append(admin ? "ADMIN" : "USER"); + if(!string.IsNullOrEmpty(displayName)) { + sb.Append("|"); + sb.Append(displayName); + } + sb.Append("\r\n"); + File.AppendAllText(GetFullPath(UsersFile), sb.ToString()); + + usersCache = null; + + return new LocalUserInfo(username, displayName, email, active, dateTime, this, Hash.Compute(password)); + } + } + + /// + /// Modifies a User. + /// + /// The Username of the user to modify. + /// The new display name (can be null). + /// The new Password (null or blank to keep the current password). + /// The new Email address. + /// A value indicating whether the account is active. + /// The correct object or null. + /// If user or newEmail are null. + /// If newEmail is empty. + public UserInfo ModifyUser(UserInfo user, string newDisplayName, string newPassword, string newEmail, bool newActive) { + if(user == null) throw new ArgumentNullException("user"); + if(newEmail == null) throw new ArgumentNullException("newEmail"); + if(newEmail.Length == 0) throw new ArgumentException("New Email cannot be empty", "newEmail"); + + lock(this) { + LocalUserInfo local = LoadLocalInstance(user); + if(local == null) return null; + + UserInfo[] allUsers = GetUsers(); + UsernameComparer comp = new UsernameComparer(); + + usersCache = null; + + for(int i = 0; i < allUsers.Length; i++) { + if(comp.Compare(allUsers[i], user) == 0) { + LocalUserInfo result = new LocalUserInfo(user.Username, newDisplayName, newEmail, + newActive, user.DateTime, this, + string.IsNullOrEmpty(newPassword) ? local.PasswordHash : Hash.Compute(newPassword)); + result.Groups = allUsers[i].Groups; + allUsers[i] = result; + DumpUsers(allUsers); + return result; + } + } + } + + return null; + } + + /// + /// Removes a User. + /// + /// The User to remove. + /// True if the User has been removed successfully. + /// If user is null. + public bool RemoveUser(UserInfo user) { + if(user == null) throw new ArgumentNullException("user"); + + lock(this) { + UserInfo[] users = GetUsers(); + UsernameComparer comp = new UsernameComparer(); + int idx = -1; + for(int i = 0; i < users.Length; i++) { + if(comp.Compare(users[i], user) == 0) { + idx = i; + break; + } + } + if(idx < 0) return false; + + // Remove user's data + string lowercaseUsername = user.Username.ToLowerInvariant(); + string[] lines = File.ReadAllLines(GetFullPath(UsersDataFile)); + List newLines = new List(lines.Length); + string[] fields; + for(int i = 0; i < lines.Length; i++) { + fields = lines[i].Split('|'); + if(fields[0].ToLowerInvariant() != lowercaseUsername) { + newLines.Add(lines[i]); + } + } + File.WriteAllLines(GetFullPath(UsersDataFile), newLines.ToArray()); + + // Remove user + List tmp = new List(users); + tmp.Remove(tmp[idx]); + DumpUsers(tmp.ToArray()); + + usersCache = null; + } + return true; + } + + private void BackupUsersFile() { + lock(this) { + File.Copy(GetFullPath(UsersFile), + GetFullPath(Path.GetFileNameWithoutExtension(UsersFile) + + ".bak" + Path.GetExtension(UsersFile)), true); + } + } + + /// + /// Writes on disk all the Users. + /// + /// The User list. + /// This method does not lock resources, therefore a lock is need in the caller. + private void DumpUsers(UserInfo[] users) { + lock(this) { + BackupUsersFile(); + + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < users.Length; i++) { + LocalUserInfo u = (LocalUserInfo)users[i]; + sb.Append(u.Username); + sb.Append("|"); + sb.Append(u.PasswordHash); + sb.Append("|"); + sb.Append(u.Email); + sb.Append("|"); + sb.Append(u.Active ? "ACTIVE" : "INACTIVE"); + sb.Append("|"); + sb.Append(u.DateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss")); + // ADMIN|USER no more used in version 3.0 + //sb.Append("|"); + //sb.Append(u.Admin ? "ADMIN" : "USER"); + if(!string.IsNullOrEmpty(u.DisplayName)) { + sb.Append("|"); + sb.Append(u.DisplayName); + } + sb.Append("\r\n"); + } + File.WriteAllText(GetFullPath(UsersFile), sb.ToString()); + } + } + + /// + /// Finds a user group. + /// + /// The name of the group to find. + /// The or null if no data is found. + private UserGroup FindGroup(string name) { + lock(this) { + UserGroup[] allUsers = GetUserGroups(); + UserGroupComparer comp = new UserGroupComparer(); + UserGroup target = new UserGroup(name, "", this); + + foreach(UserGroup g in allUsers) { + if(comp.Compare(g, target) == 0) return g; + } + + return null; + } + } + + /// + /// Gets all the user groups. + /// + /// All the groups, sorted by name. + public UserGroup[] GetUserGroups() { + lock(this) { + if(groupsCache == null) { + + string[] lines = File.ReadAllLines(GetFullPath(GroupsFile)); + + UserGroup[] result = new UserGroup[lines.Length]; + + string[] fields; + string[] users; + for(int count = 0; count < lines.Length; count++) { + // Structure - description can be empty + // Name|Description|User1|User2|... + + fields = lines[count].Split('|'); + users = new string[fields.Length - 2]; + + for(int i = 0; i < fields.Length - 2; i++) { + users[i] = fields[i + 2]; + } + + result[count] = new UserGroup(fields[0], fields[1], this); + result[count].Users = users; + } + + Array.Sort(result, new UserGroupComparer()); + + groupsCache = result; + } + + return groupsCache; + } + } + + /// + /// Adds a new user group. + /// + /// The name of the group. + /// The description of the group. + /// The correct object or null. + /// If name or description are null. + /// If name is empty. + public UserGroup AddUserGroup(string name, string description) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(description == null) throw new ArgumentNullException("description"); + + lock(this) { + if(FindGroup(name) != null) return null; + + BackupGroupsFile(); + + groupsCache = null; + + // Structure - description can be empty + // Name|Description|User1|User2|... + + File.AppendAllText(GetFullPath(GroupsFile), + name + "|" + description + "\r\n"); + + return new UserGroup(name, description, this); + } + } + + private void BackupGroupsFile() { + lock(this) { + File.Copy(GetFullPath(GroupsFile), + GetFullPath(Path.GetFileNameWithoutExtension(GroupsFile) + + ".bak" + Path.GetExtension(GroupsFile)), true); + } + } + + /// + /// Dumps user groups on disk. + /// + /// The user groups to dump. + private void DumpUserGroups(UserGroup[] groups) { + lock(this) { + StringBuilder sb = new StringBuilder(1000); + foreach(UserGroup group in groups) { + // Structure - description can be empty + // Name|Description|User1|User2|... + + sb.Append(group.Name); + sb.Append("|"); + sb.Append(group.Description); + if(group.Users.Length > 0) { + foreach(string user in group.Users) { + sb.Append("|"); + sb.Append(user); + } + } + sb.Append("\r\n"); + } + BackupGroupsFile(); + File.WriteAllText(GetFullPath(GroupsFile), sb.ToString()); + } + } + + /// + /// Modifies a user group. + /// + /// The group to modify. + /// The new description of the group. + /// The correct object or null. + /// If group or description are null. + public UserGroup ModifyUserGroup(UserGroup group, string description) { + if(group == null) throw new ArgumentNullException("group"); + if(description == null) throw new ArgumentNullException("description"); + + lock(this) { + UserGroup[] allGroups = GetUserGroups(); + + groupsCache = null; + + UserGroupComparer comp = new UserGroupComparer(); + for(int i = 0; i < allGroups.Length; i++) { + if(comp.Compare(allGroups[i], group) == 0) { + allGroups[i].Description = description; + DumpUserGroups(allGroups); + return allGroups[i]; + } + } + + return null; + } + } + + /// + /// Removes a user group. + /// + /// The group to remove. + /// true if the group is removed, false otherwise. + /// If group is null. + public bool RemoveUserGroup(UserGroup group) { + if(group == null) throw new ArgumentNullException("group"); + + lock(this) { + UserGroup[] allGroups = GetUserGroups(); + + List result = new List(allGroups.Length); + + UserGroupComparer comp = new UserGroupComparer(); + + foreach(UserGroup g in allGroups) { + if(comp.Compare(g, group) != 0) { + result.Add(g); + } + } + + DumpUserGroups(result.ToArray()); + + groupsCache = null; + + return result.Count == allGroups.Length - 1; + } + } + + /// + /// Sets the group memberships of a user account. + /// + /// The user account. + /// The groups the user account is member of. + /// The correct object or null. + /// If user or groups are null. + public UserInfo SetUserMembership(UserInfo user, string[] groups) { + if(user == null) throw new ArgumentNullException("user"); + if(groups == null) throw new ArgumentNullException("groups"); + + lock(this) { + foreach(string g in groups) { + if(FindGroup(g) == null) return null; + } + + LocalUserInfo local = LoadLocalInstance(user); + if(local == null) return null; + + UserGroup[] allGroups = GetUserGroups(); + + List users; + for(int i = 0; i < allGroups.Length; i++) { + + users = new List(allGroups[i].Users); + + if(IsSelected(allGroups[i], groups)) { + // Current group is one of the selected, add user to it + if(!users.Contains(user.Username)) users.Add(user.Username); + } + else { + // Current group is not bound with the user, remove user + users.Remove(user.Username); + } + + allGroups[i].Users = users.ToArray(); + } + + groupsCache = null; + usersCache = null; + + DumpUserGroups(allGroups); + + LocalUserInfo result = new LocalUserInfo(local.Username, local.DisplayName, local.Email, local.Active, + local.DateTime, this, local.PasswordHash); + result.Groups = groups; + + return result; + } + } + + /// + /// Determines whether a user group is contained in an array of user group names. + /// + /// The user group to check. + /// The user group names array. + /// true if users contains user.Name, false otherwise. + private static bool IsSelected(UserGroup group, string[] groups) { + StringComparer comp = StringComparer.OrdinalIgnoreCase; + return Array.Find(groups, delegate(string g) { return comp.Compare(g, group.Name) == 0; }) != null; + } + + /// + /// Tries to login a user directly through the provider. + /// + /// The username. + /// The password. + /// The correct UserInfo object, or null. + /// If username or password are null. + public UserInfo TryManualLogin(string username, string password) { + if(username == null) throw new ArgumentNullException("username"); + if(password == null) throw new ArgumentNullException("password"); + + // Shortcut + if(username.Length == 0) return null; + if(password.Length == 0) return null; + + lock(this) { + string hash = Hash.Compute(password); + UserInfo[] all = GetUsers(); + foreach(UserInfo u in all) { + if(u.Active && + //string.Compare(u.Username, username, false, System.Globalization.CultureInfo.InvariantCulture) == 0 && + //string.Compare(((LocalUserInfo)u).PasswordHash, hash, false, System.Globalization.CultureInfo.InvariantCulture) == 0) { + string.CompareOrdinal(u.Username, username) == 0 && + string.CompareOrdinal(((LocalUserInfo)u).PasswordHash, hash) == 0) { + return u; + } + } + } + return null; + } + + /// + /// Tries to login a user directly through the provider using + /// the current HttpContext and without username/password. + /// + /// The current HttpContext. + /// The correct UserInfo object, or null. + /// If context is null. + public UserInfo TryAutoLogin(System.Web.HttpContext context) { + if(context == null) throw new ArgumentNullException("context"); + + return null; + } + + /// + /// Tries to retrieve the information about a user account. + /// + /// The username. + /// The correct UserInfo object, or null. + /// If username is null. + /// If username is empty. + public UserInfo GetUser(string username) { + if(username == null) throw new ArgumentNullException("username"); + if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username"); + + lock(this) { + UserInfo[] all = GetUsers(); + foreach(UserInfo u in all) { + if(string.Compare(u.Username, username, false, System.Globalization.CultureInfo.InvariantCulture) == 0) { + return u; + } + } + } + return null; + } + + /// + /// Tries to retrieve the information about a user account. + /// + /// The email address. + /// The first user found with the specified email address, or null. + /// If email is null. + /// If email is empty. + public UserInfo GetUserByEmail(string email) { + if(email == null) throw new ArgumentNullException("email"); + if(email.Length == 0) throw new ArgumentException("Email cannot be empty", "email"); + + lock(this) { + foreach(UserInfo user in GetUsers()) { + if(user.Email == email) return user; + } + } + return null; + } + + /// + /// Notifies the provider that a user has logged in through the authentication cookie. + /// + /// The user who has logged in. + /// If user is null. + public void NotifyCookieLogin(UserInfo user) { + if(user == null) throw new ArgumentNullException("user"); + // Nothing to do + } + + /// + /// Notifies the provider that a user has logged out. + /// + /// The user who has logged out. + /// If user is null. + public void NotifyLogout(UserInfo user) { + if(user == null) throw new ArgumentNullException("user"); + // Nothing to do + } + + /// + /// Stores a user data element, overwriting the previous one if present. + /// + /// The user the data belongs to. + /// The key of the data element (case insensitive). + /// The value of the data element, null for deleting the data. + /// true if the data element is stored, false otherwise. + /// If user or key are null. + /// If key is empty. + public bool StoreUserData(UserInfo user, string key, string value) { + if(user == null) throw new ArgumentNullException("user"); + if(key == null) throw new ArgumentNullException("key"); + if(key.Length == 0) throw new ArgumentException("Key cannot be empty", "key"); + + // Format + // User|Key|Value + + lock(this) { + if(GetUser(user.Username) == null) return false; + + // Find a previously existing key and replace it if found + // If not found, add a new line + + string lowercaseUsername = user.Username.ToLowerInvariant(); + string lowercaseKey = key.ToLowerInvariant(); + + string[] lines = File.ReadAllLines(GetFullPath(UsersDataFile)); + + string[] fields; + for(int i = 0; i < lines.Length; i++) { + fields = lines[i].Split('|'); + if(fields[0].ToLowerInvariant() == lowercaseUsername && fields[1].ToLowerInvariant() == lowercaseKey) { + if(value != null) { + // Replace the value, then save file + lines[i] = fields[0] + "|" + fields[1] + "|" + value; + } + else { + // Remove the element + string[] newLines = new string[lines.Length - 1]; + Array.Copy(lines, newLines, i); + Array.Copy(lines, i + 1, newLines, i, lines.Length - i - 1); + lines = newLines; + } + File.WriteAllLines(GetFullPath(UsersDataFile), lines); + return true; + } + } + + // If the program gets here, the element was not present, append it + File.AppendAllText(GetFullPath(UsersDataFile), user.Username + "|" + key + "|" + value + "\r\n"); + + return true; + } + } + + /// + /// Gets a user data element, if any. + /// + /// The user the data belongs to. + /// The key of the data element. + /// The value of the data element, or null if the element is not found. + /// If user or key are null. + /// If key is empty. + public string RetrieveUserData(UserInfo user, string key) { + if(user == null) throw new ArgumentNullException("user"); + if(key == null) throw new ArgumentNullException("key"); + if(key.Length == 0) throw new ArgumentException("Key cannot be empty", "key"); + + lock(this) { + string lowercaseUsername = user.Username.ToLowerInvariant(); + string lowercaseKey = key.ToLowerInvariant(); + + string[] lines = File.ReadAllLines(GetFullPath(UsersDataFile)); + + string[] fields; + foreach(string line in lines) { + fields = line.Split('|'); + + if(fields[0].ToLowerInvariant() == lowercaseUsername && fields[1].ToLowerInvariant() == lowercaseKey) { + return fields[2]; + } + } + } + + return null; + } + + /// + /// Retrieves all the user data elements for a user. + /// + /// The user. + /// The user data elements (key->value). + /// If user is null. + public IDictionary RetrieveAllUserData(UserInfo user) { + if(user == null) throw new ArgumentNullException("user"); + + lock(this) { + string lowercaseUsername = user.Username.ToLowerInvariant(); + + string[] lines = File.ReadAllLines(GetFullPath(UsersDataFile)); + + Dictionary result = new Dictionary(10); + + string[] fields; + foreach(string line in lines) { + fields = line.Split('|'); + + if(fields[0].ToLowerInvariant() == lowercaseUsername) { + result.Add(fields[1], fields[2]); + } + } + + return result; + } + } + + /// + /// Gets all the users that have the specified element in their data. + /// + /// The key of the data. + /// The users and the data. + /// If key is null. + /// If key is empty. + public IDictionary GetUsersWithData(string key) { + if(key == null) throw new ArgumentNullException("key"); + if(key.Length == 0) throw new ArgumentException("Key cannot be empty", "key"); + + lock(this) { + UserInfo[] allUsers = GetUsers(); + + string[] lines = File.ReadAllLines(GetFullPath(UsersDataFile)); + + Dictionary result = new Dictionary(lines.Length / 4); + + string[] fields; + foreach(string line in lines) { + fields = line.Split('|'); + + if(fields[1] == key) { + UserInfo currentUser = Array.Find(allUsers, delegate(UserInfo user) { + return user.Username == fields[0]; + }); + if(currentUser != null) result.Add(currentUser, fields[2]); + } + } + + return result; + } + } + + } + +} diff --git a/Documentation.shfb b/Documentation.shfb new file mode 100644 index 0000000..9d5dbb0 --- /dev/null +++ b/Documentation.shfb @@ -0,0 +1,64 @@ + + + + + + + + + + + + + Namespace containing the ScrewTurn Wiki engine. + Namespace containing objects that implement the ACL Engine. + Namespace containing objects that are used to import data from other wiki engines. + Namespace containing base types used to build the ScrewTurn Wiki engine core and plugins. + + Namespace containing generic helper providers. + Namespace containing base types used to build the SQL-based storage providers. + Namespace containing SQL Server storage providers. + Namespace containing base types used for building an extensible index-based search engine. + + ScrewTurn Wiki documentation. + Summary, Parameter, Returns, AutoDocumentCtors, Namespace, TypeParameter + Attributes, InheritedMembers, InheritedFrameworkMembers, Protected, SealedProtected + + + .\Help\ + + + True + True + + HtmlHelp1x + False + 2.0.50727 + False + True + False + + ScrewTurn Wiki Developer's Documentation + ScrewTurnWiki + en-US + http://www.screwturn.eu + Copyright 2006-2009 Dario Solera + + + ScrewTurn Wiki Developer's Documentation + + Local + Msdn + Blank + Prototype + Guid + Standard + False + True + False + Hierarchical + True + ms.vsipcc+, ms.vsexpresscc+ + 3.0.0.149 + BelowNamespaces + \ No newline at end of file diff --git a/Help/ScrewTurnWiki.chm b/Help/ScrewTurnWiki.chm new file mode 100644 index 0000000..6fb5efa Binary files /dev/null and b/Help/ScrewTurnWiki.chm differ diff --git a/Install - Readme.txt b/Install - Readme.txt new file mode 100644 index 0000000..9d5ce36 --- /dev/null +++ b/Install - Readme.txt @@ -0,0 +1,7 @@ + +In order to install and run ScrewTurn Wiki, you must first compile the application as described +in the Build directory, then follow the instructions that are generated along with the binary packages. + +Alternatively, you can download the appropriate compiled package from the application website [1]. + +[1] http://www.screwturn.eu diff --git a/License - GPLv2.txt b/License - GPLv2.txt new file mode 100644 index 0000000..f90922e --- /dev/null +++ b/License - GPLv2.txt @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/PluginFramework/Actions.cs b/PluginFramework/Actions.cs new file mode 100644 index 0000000..4fa9846 --- /dev/null +++ b/PluginFramework/Actions.cs @@ -0,0 +1,437 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.AclEngine; + +namespace ScrewTurn.Wiki { + + /// + /// Contains actions for resources. + /// + public static class Actions { + + /// + /// The full control action. + /// + public const string FullControl = AclEntry.FullControlAction; + + /// + /// Contains actions for global resources. + /// + public static class ForGlobals { + + /// + /// The master prefix for global resources ('G'). + /// + public const string ResourceMasterPrefix = "G"; + + /// + /// Manage user accounts. + /// + public const string ManageAccounts = "Man_Acc"; + /// + /// Manage user groups. + /// + public const string ManageGroups = "Man_Grp"; + /// + /// Manage pages and categories. + /// + public const string ManagePagesAndCategories = "Man_PgCat"; + /// + /// Manage page discussions. + /// + public const string ManageDiscussions = "Man_Disc"; + /// + /// Manage namespaces. + /// + public const string ManageNamespaces = "Man_Ns"; + /// + /// Manage configuration. + /// + public const string ManageConfiguration = "Man_Conf"; + /// + /// Manage providers. + /// + public const string ManageProviders = "Man_Prov"; + /// + /// Manage files. + /// + public const string ManageFiles = "Man_Files"; + /// + /// Manage snippets and templates. + /// + public const string ManageSnippetsAndTemplates = "Man_Snips_Temps"; + /// + /// Manage navigation paths. + /// + public const string ManageNavigationPaths = "Man_NavPath"; + /// + /// Manage meta-files. + /// + public const string ManageMetaFiles = "Man_MetaFiles"; + /// + /// Manage permissions. + /// + public const string ManagePermissions = "Man_Perms"; + + /// + /// Gets an array containing all actions. + /// + public static readonly string[] All = new string[] { + ManageAccounts, + ManageGroups, + ManagePagesAndCategories, + ManageDiscussions, + ManageNamespaces, + ManageConfiguration, + ManageProviders, + ManageFiles, + ManageSnippetsAndTemplates, + ManageNavigationPaths, + ManageMetaFiles, + ManagePermissions + }; + + /// + /// Gets the full name of an action. + /// + /// The internal name. + /// The full name. + public static string GetFullName(string name) { + if(name == FullControl) return Exchanger.ResourceExchanger.GetResource("Action_FullControl"); + else return Exchanger.ResourceExchanger.GetResource("Action_" + name); + } + + } + + /// + /// Contains actions for namespaces. + /// + public static class ForNamespaces { + + /// + /// The master prefix for namespaces ('N.'). + /// + public const string ResourceMasterPrefix = "N."; + + /// + /// The local escalation policies. + /// + public static readonly Dictionary LocalEscalators = new Dictionary() { + { ReadPages, new string[] { CreatePages, ModifyPages, DeletePages, ManagePages } }, + { ModifyPages, new string[] { ManagePages } }, + { DeletePages, new string[] { ManagePages } }, + { CreatePages, new string[] { ManagePages } }, + { ReadDiscussion, new string[] { PostDiscussion, ManageDiscussion } }, + { PostDiscussion, new string[] { ManageDiscussion } }, + { ManageDiscussion, new string[] { ManagePages } }, + { DownloadAttachments, new string[] { UploadAttachments, DeleteAttachments } }, + { UploadAttachments, new string[] { DeleteAttachments } } + }; + + /// + /// The global escalation policies. + /// + public static readonly Dictionary GlobalEscalators = new Dictionary() { + { ReadPages, new string[] { Actions.ForGlobals.ManagePagesAndCategories, Actions.ForGlobals.ManageNamespaces } }, + { CreatePages, new string[] { Actions.ForGlobals.ManagePagesAndCategories, Actions.ForGlobals.ManageNamespaces } }, + { ModifyPages, new string[] { Actions.ForGlobals.ManagePagesAndCategories, Actions.ForGlobals.ManageNamespaces } }, + { ReadDiscussion, new string[] { Actions.ForGlobals.ManageDiscussions } }, + { PostDiscussion, new string[] { Actions.ForGlobals.ManageDiscussions } }, + { ManageDiscussion, new string[] { Actions.ForGlobals.ManageDiscussions } }, + { DeletePages, new string[] { Actions.ForGlobals.ManagePagesAndCategories, Actions.ForGlobals.ManageNamespaces } }, + { ManagePages, new string[] { Actions.ForGlobals.ManagePagesAndCategories, Actions.ForGlobals.ManageNamespaces } }, + { ManageCategories, new string[] { Actions.ForGlobals.ManagePagesAndCategories } }, + { DownloadAttachments, new string[] { Actions.ForGlobals.ManageFiles } }, + { UploadAttachments, new string[] { Actions.ForGlobals.ManageFiles } }, + { DeleteAttachments, new string[] { Actions.ForGlobals.ManageFiles } } + }; + + /// + /// Read pages. + /// + public const string ReadPages = "Rd_Pg"; + /// + /// Create pages. + /// + public const string CreatePages = "Crt_Pg"; + /// + /// Modify pages. + /// + public const string ModifyPages = "Mod_Pg"; + /// + /// Delete pages. + /// + public const string DeletePages = "Del_Pg"; + /// + /// Manage pages. + /// + public const string ManagePages = "Man_Pg"; + /// + /// Read page discussions. + /// + public const string ReadDiscussion = "Rd_Disc"; + /// + /// Post messages in page discussions. + /// + public const string PostDiscussion = "Pst_Disc"; + /// + /// Manage messages in page discussions. + /// + public const string ManageDiscussion = "Man_Disc"; + /// + /// Manage categories. + /// + public const string ManageCategories = "Man_Cat"; + /// + /// Download attachments. + /// + public const string DownloadAttachments = "Down_Attn"; + /// + /// Upload attachments. + /// + public const string UploadAttachments = "Up_Attn"; + /// + /// Delete attachments. + /// + public const string DeleteAttachments = "Del_Attn"; + + /// + /// Gets an array containing all actions. + /// + public static readonly string[] All = new string[] { + ReadPages, + CreatePages, + ModifyPages, + DeletePages, + ManagePages, + ReadDiscussion, + PostDiscussion, + ManageDiscussion, + ManageCategories, + DownloadAttachments, + UploadAttachments, + DeleteAttachments + }; + + /// + /// Gets the full name of an action. + /// + /// The internal name. + /// The full name. + public static string GetFullName(string name) { + if(name == FullControl) return Exchanger.ResourceExchanger.GetResource("Action_FullControl"); + else return Exchanger.ResourceExchanger.GetResource("Action_" + name); + } + + } + + /// + /// Contains actions for pages. + /// + public static class ForPages { + + /// + /// The master prefix for pages ('P.'). + /// + public const string ResourceMasterPrefix = "P."; + + /// + /// The local escalation policies. + /// + public static readonly Dictionary LocalEscalators = new Dictionary() { + { ReadPage, new string[] { ModifyPage, ManagePage } }, + { ModifyPage, new string[] { ManagePage } }, + { ReadDiscussion, new string[] { PostDiscussion, ManageDiscussion } }, + { PostDiscussion, new string[] { ManageDiscussion } }, + { ManageDiscussion, new string[] { ManagePage } }, + { DownloadAttachments, new string[] { UploadAttachments, DeleteAttachments } }, + { UploadAttachments, new string[] { DeleteAttachments } } + }; + + /// + /// The namespace escalation policies. + /// + public static readonly Dictionary NamespaceEscalators = new Dictionary() { + { ReadPage, new string[] { Actions.ForNamespaces.ReadPages, Actions.ForNamespaces.ModifyPages, Actions.ForNamespaces.ManagePages, Actions.ForNamespaces.CreatePages, Actions.ForNamespaces.DeletePages } }, + { ModifyPage, new string[] { Actions.ForNamespaces.CreatePages, Actions.ForNamespaces.ModifyPages, Actions.ForNamespaces.ManagePages, Actions.ForNamespaces.CreatePages, Actions.ForNamespaces.DeletePages } }, + { ManagePage, new string[] { Actions.ForNamespaces.ManagePages } }, + { ReadDiscussion, new string[] { Actions.ForNamespaces.ReadDiscussion, Actions.ForNamespaces.PostDiscussion, Actions.ForNamespaces.ManageDiscussion } }, + { PostDiscussion, new string[] { Actions.ForNamespaces.PostDiscussion, Actions.ForNamespaces.ManageDiscussion } }, + { ManageDiscussion, new string[] { Actions.ForNamespaces.ManageDiscussion } }, + { DownloadAttachments, new string[] { Actions.ForNamespaces.DownloadAttachments, Actions.ForNamespaces.UploadAttachments, Actions.ForNamespaces.DeleteAttachments } }, + { ManageCategories, new string[] { Actions.ForNamespaces.ManageCategories } }, + { UploadAttachments, new string[] { Actions.ForNamespaces.UploadAttachments } }, + { DeleteAttachments, new string[] { Actions.ForNamespaces.DeleteAttachments } } + }; + + /// + /// The global escalation policies. + /// + public static readonly Dictionary GlobalEscalators = new Dictionary() { + { ReadPage, new string[] { Actions.ForGlobals.ManagePagesAndCategories, Actions.ForGlobals.ManageNamespaces } }, + { ModifyPage, new string[] { Actions.ForGlobals.ManagePagesAndCategories, Actions.ForGlobals.ManageNamespaces } }, + { ManagePage, new string[] { Actions.ForGlobals.ManagePagesAndCategories, Actions.ForGlobals.ManageNamespaces } }, + { ReadDiscussion, new string[] { Actions.ForGlobals.ManageDiscussions } }, + { PostDiscussion, new string[] { Actions.ForGlobals.ManageDiscussions } }, + { ManageDiscussion, new string[] { Actions.ForGlobals.ManageDiscussions } }, + { ManageCategories, new string[] { Actions.ForGlobals.ManagePagesAndCategories } }, + { DownloadAttachments, new string[] { Actions.ForGlobals.ManageFiles } }, + { UploadAttachments, new string[] { Actions.ForGlobals.ManageFiles } }, + { DeleteAttachments, new string[] { Actions.ForGlobals.ManageFiles } } + }; + + /// + /// Read the page. + /// + public const string ReadPage = "Rd_1Pg"; + /// + /// Modify the page. + /// + public const string ModifyPage = "Mod_1Pg"; + /// + /// Manage the page. + /// + public const string ManagePage = "Man_1Pg"; + /// + /// Read page discussion. + /// + public const string ReadDiscussion = "Rd_1Disc"; + /// + /// Post messages in page discussion. + /// + public const string PostDiscussion = "Pst_1Disc"; + /// + /// Manage page discussion. + /// + public const string ManageDiscussion = "Man_1Disc"; + /// + /// Manage the categories of the page. + /// + public const string ManageCategories = "Man_1Cat"; + /// + /// Download attachments. + /// + public const string DownloadAttachments = "Down_1Attn"; + /// + /// Upload attachments. + /// + public const string UploadAttachments = "Up_1Attn"; + /// + /// Delete attachments. + /// + public const string DeleteAttachments = "Del_1Attn"; + + /// + /// Gets an array containing all actions. + /// + public static readonly string[] All = new string[] { + ReadPage, + ModifyPage, + ManagePage, + ReadDiscussion, + PostDiscussion, + ManageDiscussion, + ManageCategories, + DownloadAttachments, + UploadAttachments, + DeleteAttachments + }; + + /// + /// Gets the full name of an action. + /// + /// The internal name. + /// The full name. + public static string GetFullName(string name) { + if(name == FullControl) return Exchanger.ResourceExchanger.GetResource("Action_FullControl"); + else return Exchanger.ResourceExchanger.GetResource("Action_" + name); + } + + } + + /// + /// Contains actions for file directories. + /// + public static class ForDirectories { + + /// + /// The master prefix for directories ('D.'). + /// + public const string ResourceMasterPrefix = "D."; + + /// + /// The local escalation policies. + /// + public static readonly Dictionary LocalEscalators = new Dictionary() { + { List, new string[] { DownloadFiles, UploadFiles, DeleteFiles, CreateDirectories, DeleteDirectories } }, + { DownloadFiles, new string[] { UploadFiles, DeleteFiles, CreateDirectories, DeleteDirectories } }, + { UploadFiles, new string[] { DeleteFiles } }, + { CreateDirectories, new string[] { DeleteDirectories } } + }; + + /// + /// The global escalation policies. + /// + public static readonly Dictionary GlobalEscalators = new Dictionary() { + { List, new string[] { Actions.ForGlobals.ManageFiles } }, + { DownloadFiles, new string[] { Actions.ForGlobals.ManageFiles } }, + { UploadFiles, new string[] { Actions.ForGlobals.ManageFiles } }, + { DeleteFiles, new string[] { Actions.ForGlobals.ManageFiles } }, + { CreateDirectories, new string[] { Actions.ForGlobals.ManageFiles } }, + { DeleteDirectories, new string[] { Actions.ForGlobals.ManageFiles } } + }; + + /// + /// List files and directories. + /// + public const string List = "List"; + /// + /// Download files. + /// + public const string DownloadFiles = "Down_Files"; + /// + /// Upload files. + /// + public const string UploadFiles = "Up_Files"; + /// + /// Delete files. + /// + public const string DeleteFiles = "Del_Files"; + /// + /// Create directories. + /// + public const string CreateDirectories = "Crt_Dirs"; + /// + /// Delete directories. + /// + public const string DeleteDirectories = "Del_Dirs"; + + /// + /// Gets an array containing all actions. + /// + public static readonly string[] All = new string[] { + List, + DownloadFiles, + UploadFiles, + DeleteFiles, + CreateDirectories, + DeleteDirectories + }; + + /// + /// Gets the full name of an action. + /// + /// The internal name. + /// The full name. + public static string GetFullName(string name) { + if(name == FullControl) return Exchanger.ResourceExchanger.GetResource("Action_FullControl"); + else return Exchanger.ResourceExchanger.GetResource("Action_" + name); + } + + } + + } + +} diff --git a/PluginFramework/CategoryInfo.cs b/PluginFramework/CategoryInfo.cs new file mode 100644 index 0000000..0cbe1fb --- /dev/null +++ b/PluginFramework/CategoryInfo.cs @@ -0,0 +1,93 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Globalization; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Represents a Page Category. A page can be binded with one or more categories (within the same Provider); this class manages this binding. + /// + public class CategoryInfo { + + /// + /// The namespace of the Category. + /// + protected string nspace; + /// + /// The Name of the Category. + /// + protected string name; + /// + /// The Provider that handles the Category. + /// + protected IPagesStorageProviderV30 provider; + /// + /// The Pages of the Category. + /// + protected string[] pages = new string[0]; + + /// + /// Initializes a new instance of the class. + /// + /// The Full Name of the Category. + /// The Storage that manages the category. + public CategoryInfo(string fullName, IPagesStorageProviderV30 provider) { + NameTools.ExpandFullName(fullName, out nspace, out name); + this.provider = provider; + } + + /// + /// Gets or sets the full name of the Category, such as 'Namespace.Category' or 'Category'. + /// + public string FullName { + get { return NameTools.GetFullName(nspace, name); } + set { NameTools.ExpandFullName(value, out nspace, out name); } + } + + /// + /// Gets or sets the Provider that manages the Category. + /// + public IPagesStorageProviderV30 Provider { + get { return provider; } + set { provider = value; } + } + + /// + /// Gets or sets the Page array, containing their names. + /// + public string[] Pages { + get { return pages; } + set { pages = value; } + } + + /// + /// Gets a string representation of the current object. + /// + /// The string representation. + public override string ToString() { + return NameTools.GetFullName(nspace, name); + } + + } + + /// + /// Compares two CategoryInfo objects, using the FullName as parameter. + /// + /// The comparison is case insensitive. + public class CategoryNameComparer : IComparer { + + /// + /// Compares two objects, using the FullName as parameter. + /// + /// The first object. + /// The second object. + /// The comparison result (-1, 0 or 1). + public int Compare(CategoryInfo x, CategoryInfo y) { + return StringComparer.OrdinalIgnoreCase.Compare(x.FullName, y.FullName); + } + + } + +} diff --git a/PluginFramework/ComponentInformation.cs b/PluginFramework/ComponentInformation.cs new file mode 100644 index 0000000..b78f59c --- /dev/null +++ b/PluginFramework/ComponentInformation.cs @@ -0,0 +1,93 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Contains information about a Provider. + /// + public class ComponentInformation { + + /// + /// The Name of the Component. + /// + protected string name; + /// + /// The Author of the Component. + /// + protected string author; + /// + /// The component version. + /// + protected string version; + /// + /// The info URL of the Component/Author. + /// + protected string url; + /// + /// The component update URL which should point to a text file containing one or two rows (separated by \r\n or \n): + /// 1. A list of increasing versions separated by pipes, such as "1.0.0|1.0.1|1.0.2" (without quotes) + /// 2. (optional) The absolute HTTP URL of the latest DLL, for example "http://www.server.com/update/MyAssembly.dll" (without quotes) + /// The second row should only be present if the provider can be updated automatically without any type of user + /// intervention, i.e. by simply replacing the DLL and restarting the wiki. If the DLL contains multiple providers, + /// they are all updated (obviously). The new DLL must have the same name of the being-replaced DLL (in other words, + /// a provider must reside in the same DLL forever in order to be updated automatically). + /// + protected string updateUrl; + + /// + /// Initializes a new instance of the ComponentInformation class. + /// + /// The Name of the Component. + /// The Author of the Component. + /// The component version. + /// The info URL of the Component/Author. + /// The update URL of the component, or null. + public ComponentInformation(string name, string author, string version, string url, string updateUrl) { + this.name = name; + this.author = author; + this.version = version; + this.url = url; + this.updateUrl = updateUrl; + } + + /// + /// Gets the Name of the Component. + /// + public string Name { + get { return name; } + } + + /// + /// Gets the Author of the Component. + /// + public string Author { + get { return author; } + } + + /// + /// Gets the component version. + /// + public string Version { + get { return version; } + } + + /// + /// Gets the info URL of the Component/Author. + /// + public string Url { + get { return url; } + } + + /// + /// Gets the update URL of the component. + /// + public string UpdateUrl { + get { return updateUrl; } + } + + } + +} diff --git a/PluginFramework/ContentTemplate.cs b/PluginFramework/ContentTemplate.cs new file mode 100644 index 0000000..e47fa30 --- /dev/null +++ b/PluginFramework/ContentTemplate.cs @@ -0,0 +1,78 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Contains a template for page content. + /// + public class ContentTemplate { + + /// + /// The name of the template. + /// + protected string name; + /// + /// The content of the template. + /// + protected string content; + /// + /// The provider handling the template. + /// + protected IPagesStorageProviderV30 provider; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the template. + /// The content of the template. + /// The provider handling the template. + public ContentTemplate(string name, string content, IPagesStorageProviderV30 provider) { + this.name = name; + this.content = content; + this.provider = provider; + } + + /// + /// Gets the name of the template. + /// + public string Name { + get { return name; } + } + + /// + /// Gets the content of the template. + /// + public string Content { + get { return content; } + } + + /// + /// Gets the provider handling the template. + /// + public IPagesStorageProviderV30 Provider { + get { return provider; } + } + + } + + /// + /// Compares two objects. + /// + public class ContentTemplateNameComparer : IComparer { + + /// + /// Compares the name of two objects. + /// + /// The first . + /// The second . + /// The result of the comparison (1, 0 or -1). + public int Compare(ContentTemplate x, ContentTemplate y) { + return StringComparer.OrdinalIgnoreCase.Compare(x.Name, y.Name); + } + + } + +} diff --git a/PluginFramework/ContextInformation.cs b/PluginFramework/ContextInformation.cs new file mode 100644 index 0000000..a11aae7 --- /dev/null +++ b/PluginFramework/ContextInformation.cs @@ -0,0 +1,150 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Web; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Contains information about the Context of the page formatting. + /// + public class ContextInformation { + + private bool forIndexing; + private bool forWysiwyg; + private FormattingContext context; + private PageInfo page; + private string language; + private HttpContext httpContext; + private string username; + private string[] groups; + + /// + /// Initializes a new instance of the FormatContext class. + /// + /// A value indicating whether the formatting is being done for content indexing. + /// A value indicating whether the formatting is being done for display in the WYSIWYG editor. + /// The formatting context. + /// The Page Information, if any, null otherwise. + /// The current Thread's language (for example "en-US"). + /// The current HTTP Context object. + /// The current User's Username (or null). + /// The groups the user is member of (or null). + public ContextInformation(bool forIndexing, bool forWysiwyg, FormattingContext context, PageInfo page, string language, HttpContext httpContext, string username, string[] groups) { + this.forIndexing = forIndexing; + this.forWysiwyg = forWysiwyg; + this.context = context; + this.page = page; + this.language = language; + this.httpContext = httpContext; + this.username = username; + this.groups = groups; + } + + /// + /// Gets a value indicating whether the formatting is being done for content indexing. + /// + public bool ForIndexing { + get { return forIndexing; } + } + + /// + /// Gets a value indicating whether the formatting is being done for display in the WYSIWYG editor. + /// + public bool ForWysiwyg { + get { return forWysiwyg; } + } + + /// + /// Gets the formatting context. + /// + public FormattingContext Context { + get { return context; } + } + + /// + /// Gets the Page Information. + /// + public PageInfo Page { + get { return page; } + } + + /// + /// Gets the current Thread's Language (for example en-US). + /// + public string Language { + get { return language; } + } + + /// + /// Gets the current HTTP Context object. + /// + public HttpContext HttpContext { + get { return httpContext; } + } + + /// + /// Gets the Username of the current User (or null). + /// + /// If the Username is not available, the return value is null. + public string Username { + get { return username; } + } + + /// + /// Gets the groups the user is member of (or null). + /// + public string[] Groups { + get { return groups; } + } + + } + + /// + /// Lists formatting contexts. + /// + public enum FormattingContext { + /// + /// The overall header. + /// + Header, + /// + /// The overall footer. + /// + Footer, + /// + /// The sidebar. + /// + Sidebar, + /// + /// The page header. + /// + PageHeader, + /// + /// The page footer. + /// + PageFooter, + /// + /// The page content. + /// + PageContent, + /// + /// Transcluded page content. + /// + TranscludedPageContent, + /// + /// The body of a message. + /// + MessageBody, + /// + /// Any other context. + /// + Other, + /// + /// No know context. + /// + Unknown + } + +} diff --git a/PluginFramework/Exchanger.cs b/PluginFramework/Exchanger.cs new file mode 100644 index 0000000..5846dcf --- /dev/null +++ b/PluginFramework/Exchanger.cs @@ -0,0 +1,25 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki { + + /// + /// Class used for exchaning data between the Core library and the Wiki engine. + /// + public static class Exchanger { + + private static IResourceExchanger resourceExchanger; + + /// + /// Gets or sets the singleton instance of the Resource Exchanger object. + /// + public static IResourceExchanger ResourceExchanger { + get { return resourceExchanger; } + set { resourceExchanger = value; } + } + + } + +} diff --git a/PluginFramework/FileActivityEventArgs.cs b/PluginFramework/FileActivityEventArgs.cs new file mode 100644 index 0000000..04c5088 --- /dev/null +++ b/PluginFramework/FileActivityEventArgs.cs @@ -0,0 +1,139 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Contains arguments for the File Activity event. + /// + public class FileActivityEventArgs : EventArgs { + + private StFileInfo file; + private string oldFileName; + private StDirectoryInfo directory; + private string oldDirectoryName; + private PageInfo page; + private FileActivity activity; + + /// + /// Initializes a new instance of the class. + /// + /// The file that changed, if any (full path). + /// The old name of the file, if any (full path). + /// The directory that changed, if any (full path). + /// The old name of the directory, if any (full path). + /// The page owning the attachment, if any. + /// The activity. + public FileActivityEventArgs(StFileInfo file, string oldFileName, + StDirectoryInfo directory, string oldDirectoryName, + PageInfo page, FileActivity activity) { + + this.file = file; + this.oldFileName = oldFileName; + this.directory = directory; + this.oldDirectoryName = oldDirectoryName; + this.page = page; + this.activity = activity; + } + + /// + /// Gets the provider. + /// + public IFilesStorageProviderV30 Provider { + get { + if(file != null) return file.Provider; + else if(directory != null) return directory.Provider; + else return null; + } + } + + /// + /// Gets the file that changed, if any. + /// + public StFileInfo File { + get { return file; } + } + + /// + /// Gets the old name of the file, if any. + /// + public string OldFileName { + get { return oldFileName; } + } + + /// + /// Gets the directory that changed, if any. + /// + public StDirectoryInfo Directory { + get { return directory; } + } + + /// + /// Gets the old name of the directory, if any. + /// + public string OldDirectoryName { + get { return oldDirectoryName; } + } + + /// + /// Gets the page owning the attachment, if any. + /// + public PageInfo Page { + get { return page; } + } + + /// + /// Gets the activity. + /// + public FileActivity Activity { + get { return activity; } + } + + } + + /// + /// Lists legal file activities. + /// + public enum FileActivity { + /// + /// A file has been uploaded. + /// + FileUploaded, + /// + /// A file has been renamed. + /// + FileRenamed, + /// + /// A file has been deleted. + /// + FileDeleted, + /// + /// A directory has been created. + /// + DirectoryCreated, + /// + /// A directory has been renamed. + /// + DirectoryRenamed, + /// + /// A directory (and all its contents) has been deleted. + /// + DirectoryDeleted, + /// + /// An attachment has been uploaded. + /// + AttachmentUploaded, + /// + /// An attachment has been renamed. + /// + AttachmentRenamed, + /// + /// An attachment has been deleted. + /// + AttachmentDeleted + } + +} diff --git a/PluginFramework/FileDetails.cs b/PluginFramework/FileDetails.cs new file mode 100644 index 0000000..3abbedd --- /dev/null +++ b/PluginFramework/FileDetails.cs @@ -0,0 +1,52 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Contains information about a file. + /// + public class FileDetails { + + private long size; + private DateTime lastModified; + private int retrievalCount; + + /// + /// Initializes a new instance of the class. + /// + /// The size of the file in bytes. + /// The modification date/time. + /// The number of times the file was retrieved. + public FileDetails(long size, DateTime lastModified, int retrievalCount) { + this.size = size; + this.lastModified = lastModified; + this.retrievalCount = retrievalCount; + } + + /// + /// Gets the size of the file in bytes. + /// + public long Size { + get { return size; } + } + + /// + /// Gets the modification date/time. + /// + public DateTime LastModified { + get { return lastModified; } + } + + /// + /// Gets the number of times the file was retrieved. + /// + public int RetrievalCount { + get { return retrievalCount; } + } + + } + +} diff --git a/PluginFramework/ICacheProvider.cs b/PluginFramework/ICacheProvider.cs new file mode 100644 index 0000000..644b352 --- /dev/null +++ b/PluginFramework/ICacheProvider.cs @@ -0,0 +1,180 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Provides an interface for implementing a Cache Provider for ScrewTurn Wiki. + /// + /// The Cache should preferably reside in RAM for performance purposes. + public interface ICacheProviderV30 : IProviderV30 { + + /// + /// Gets or sets the number of users online. + /// + int OnlineUsers { + get; + set; + } + + /// + /// Gets the value of a Pseudo-cache item, previously stored in the cache. + /// + /// The name of the item being requested. + /// The value of the item, or null if the item is not found. + /// If is null. + /// If is empty. + string GetPseudoCacheValue(string name); + + /// + /// Sets the value of a Pseudo-cache item. + /// + /// The name of the item being stored. + /// The value of the item. If the value is null, then the item should be removed from the cache. + /// If is null. + /// If is empty. + void SetPseudoCacheValue(string name, string value); + + /// + /// Gets the Content of a Page, previously stored in cache. + /// + /// The Page Info object related to the Content being requested. + /// The Page Content object, or null if the item is not found. + /// If is null. + PageContent GetPageContent(PageInfo pageInfo); + + /// + /// Sets the Content of a Page. + /// + /// The Page Info object related to the Content being stored. + /// The Content of the Page. + /// If or content are null. + void SetPageContent(PageInfo pageInfo, PageContent content); + + /// + /// Gets the partially-formatted content (text) of a Page, previously stored in the cache. + /// + /// The Page Info object related to the content being requested. + /// The partially-formatted content, or null if the item is not found. + /// If is null. + string GetFormattedPageContent(PageInfo pageInfo); + + /// + /// Sets the partially-preformatted content (text) of a Page. + /// + /// The Page Info object related to the content being stored. + /// The partially-preformatted content. + /// If or are null. + void SetFormattedPageContent(PageInfo pageInfo, string content); + + /// + /// Removes a Page from the cache. + /// + /// The Page Info object related to the Page that has to be removed. + /// If is null. + void RemovePage(PageInfo pageInfo); + + /// + /// Clears the Page Content cache. + /// + void ClearPageContentCache(); + + /// + /// Clears the Pseudo-Cache. + /// + void ClearPseudoCache(); + + /// + /// Reduces the size of the Page Content cache, removing the least-recently used items. + /// + /// The number of Pages to remove. + /// If is less than or equal to zero. + void CutCache(int cutSize); + + /// + /// Gets the number of Pages whose content is currently stored in the cache. + /// + int PageCacheUsage { + get; + } + + /// + /// Gets the numer of Pages whose formatted content is currently stored in the cache. + /// + int FormatterPageCacheUsage { + get; + } + + /// + /// Adds or updates an editing session. + /// + /// The edited Page. + /// The User who is editing the Page. + /// If or are null. + /// If or are empty. + void RenewEditingSession(string page, string user); + + /// + /// Cancels an editing session. + /// + /// The Page. + /// The User. + /// If or are null. + /// If or are empty. + void CancelEditingSession(string page, string user); + + /// + /// Finds whether a Page is being edited by a different user. + /// + /// The Page. + /// The User who is requesting the status of the Page. + /// True if the Page is being edited by another User. + /// If or are null. + /// If or are empty. + bool IsPageBeingEdited(string page, string currentUser); + + /// + /// Gets the username of the user who's editing a page. + /// + /// The page. + /// The username. + /// If is null. + /// If is empty. + string WhosEditing(string page); + + /// + /// Adds the redirection information for a page (overwrites the previous value, if any). + /// + /// The source page. + /// The destination page. + /// If or are null. + /// If or are empty. + void AddRedirection(string source, string destination); + + /// + /// Gets the destination of a redirection. + /// + /// The source page. + /// The destination page, if any, null otherwise. + /// If is null. + /// If is empty. + string GetRedirectionDestination(string source); + + /// + /// Removes a pge from both sources and destinations. + /// + /// The name of the page. + /// If is null. + /// If is empty. + void RemovePageFromRedirections(string name); + + /// + /// Clears all the redirections information. + /// + void ClearRedirections(); + + } + +} diff --git a/PluginFramework/IFilesStorageProvider.cs b/PluginFramework/IFilesStorageProvider.cs new file mode 100644 index 0000000..8ac9ed3 --- /dev/null +++ b/PluginFramework/IFilesStorageProvider.cs @@ -0,0 +1,224 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// It is the interface that must be implemented in order to create a custom Files Storage Provider for ScrewTurn Wiki. + /// + /// A class that implements this interface should not have any kind of data caching. + /// All directory paths are specified in a UNIX-like fashion, for example "/my/directory/myfile.jpg". + /// All paths must start with '/'. All Directory paths must end with '/'. + /// All paths are case-insensitive. + public interface IFilesStorageProviderV30 : IStorageProviderV30 { + + /// + /// Lists the Files in the specified Directory. + /// + /// The full directory name, for example "/my/directory". Null, empty or "/" for the root directory. + /// The list of Files in the directory. + /// If does not exist. + string[] ListFiles(string directory); + + /// + /// Lists the Directories in the specified directory. + /// + /// The full directory name, for example "/my/directory". Null, empty or "/" for the root directory. + /// The list of Directories in the Directory. + /// If does not exist. + string[] ListDirectories(string directory); + + /// + /// Stores a file. + /// + /// The full name of the file. + /// A Stream object used as source of a byte stream, + /// i.e. the method reads from the Stream and stores the content properly. + /// true to overwrite an existing file. + /// true if the File is stored, false otherwise. + /// If overwrite is false and File already exists, the method returns false. + /// If os are null. + /// If is empty or does not support reading. + bool StoreFile(string fullName, Stream sourceStream, bool overwrite); + + /// + /// Retrieves a File. + /// + /// The full name of the File. + /// A Stream object used as destination of a byte stream, + /// i.e. the method writes to the Stream the file content. + /// A value indicating whether or not to count this retrieval in the statistics. + /// true if the file is retrieved, false otherwise. + /// If os are null. + /// If is empty or does not support writing, or if does not exist. + bool RetrieveFile(string fullName, Stream destinationStream, bool countHit); + + /// + /// Sets the number of times a file was retrieved. + /// + /// The full name of the file. + /// The count to set. + /// If is null. + /// If is empty. + /// If is less than zero. + void SetFileRetrievalCount(string fullName, int count); + + /// + /// Gets the details of a file. + /// + /// The full name of the file. + /// The details, or null if the file does not exist. + /// If is null. + /// If is empty. + FileDetails GetFileDetails(string fullName); + + /// + /// Deletes a File. + /// + /// The full name of the File. + /// true if the File is deleted, false otherwise. + /// If is null. + /// If is empty or it does not exist. + bool DeleteFile(string fullName); + + /// + /// Renames or moves a File. + /// + /// The old full name of the File. + /// The new full name of the File. + /// true if the File is renamed, false otherwise. + /// If or are null. + /// If or are empty, or if the old file does not exist, or if the new file already exist. + bool RenameFile(string oldFullName, string newFullName); + + /// + /// Creates a new Directory. + /// + /// The path to create the new Directory in. + /// The name of the new Directory. + /// true if the Directory is created, false otherwise. + /// If path is "/my/directory" and name is "newdir", a new directory named "/my/directory/newdir" is created. + /// If or are null. + /// If is empty or if the directory does not exist, or if the new directory already exists. + bool CreateDirectory(string path, string name); + + /// + /// Deletes a Directory and all of its content. + /// + /// The full path of the Directory. + /// true if the Directory is delete, false otherwise. + /// If is null. + /// If is empty or if it equals '/' or it does not exist. + bool DeleteDirectory(string fullPath); + + /// + /// Renames or moves a Directory. + /// + /// The old full path of the Directory. + /// The new full path of the Directory. + /// true if the Directory is renamed, false otherwise. + /// If or are null. + /// If or are empty or equal to '/', + /// or if the old directory does not exist or the new directory already exists. + bool RenameDirectory(string oldFullPath, string newFullPath); + + /// + /// The the names of the pages with attachments. + /// + /// The names of the pages with attachments. + string[] GetPagesWithAttachments(); + + /// + /// Returns the names of the Attachments of a Page. + /// + /// The Page Info object that owns the Attachments. + /// The names, or an empty list. + /// If is null. + string[] ListPageAttachments(PageInfo pageInfo); + + /// + /// Stores a Page Attachment. + /// + /// The Page Info that owns the Attachment. + /// The name of the Attachment, for example "myfile.jpg". + /// A Stream object used as source of a byte stream, + /// i.e. the method reads from the Stream and stores the content properly. + /// true to overwrite an existing Attachment. + /// true if the Attachment is stored, false otherwise. + /// If overwrite is false and Attachment already exists, the method returns false. + /// If , or are null. + /// If is empty or if does not support reading. + bool StorePageAttachment(PageInfo pageInfo, string name, Stream sourceStream, bool overwrite); + + /// + /// Retrieves a Page Attachment. + /// + /// The Page Info that owns the Attachment. + /// The name of the Attachment, for example "myfile.jpg". + /// A Stream object used as destination of a byte stream, + /// i.e. the method writes to the Stream the file content. + /// A value indicating whether or not to count this retrieval in the statistics. + /// true if the Attachment is retrieved, false otherwise. + /// If , or are null. + /// If is empty or if does not support writing, + /// or if the page does not have attachments or if the attachment does not exist. + bool RetrievePageAttachment(PageInfo pageInfo, string name, Stream destinationStream, bool countHit); + + /// + /// Sets the number of times a page attachment was retrieved. + /// + /// The page. + /// The name of the attachment. + /// The count to set. + /// If or are null. + /// If is empty. + /// If is less than zero. + void SetPageAttachmentRetrievalCount(PageInfo pageInfo, string name, int count); + + /// + /// Gets the details of a page attachment. + /// + /// The page that owns the attachment. + /// The name of the attachment, for example "myfile.jpg". + /// The details of the attachment, or null if the attachment does not exist. + /// If or are null. + /// If is empty. + FileDetails GetPageAttachmentDetails(PageInfo pageInfo, string name); + + /// + /// Deletes a Page Attachment. + /// + /// The Page Info that owns the Attachment. + /// The name of the Attachment, for example "myfile.jpg". + /// true if the Attachment is deleted, false otherwise. + /// If or are null. + /// If is empty or if the page or attachment do not exist. + bool DeletePageAttachment(PageInfo pageInfo, string name); + + /// + /// Renames a Page Attachment. + /// + /// The Page Info that owns the Attachment. + /// The old name of the Attachment. + /// The new name of the Attachment. + /// true if the Attachment is renamed, false otherwise. + /// If , or are null. + /// If , or are empty, + /// or if the page or old attachment do not exist, or the new attachment name already exists. + bool RenamePageAttachment(PageInfo pageInfo, string oldName, string newName); + + /// + /// Notifies the Provider that a Page has been renamed. + /// + /// The old Page Info object. + /// The new Page Info object. + /// If or are null. + /// If the new page is already in use. + void NotifyPageRenaming(PageInfo oldPage, PageInfo newPage); + + } + +} diff --git a/PluginFramework/IFormatterProvider.cs b/PluginFramework/IFormatterProvider.cs new file mode 100644 index 0000000..d4e49e4 --- /dev/null +++ b/PluginFramework/IFormatterProvider.cs @@ -0,0 +1,70 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// It is the interface that must be implemented in order to create a custom Formatter Provider for ScrewTurn Wiki. + /// + public interface IFormatterProviderV30 : IProviderV30 { + + /// + /// Specifies whether or not to execute Phase 1. + /// + bool PerformPhase1 { get; } + + /// + /// Specifies whether or not to execute Phase 2. + /// + bool PerformPhase2 { get; } + + /// + /// Specifies whether or not to execute Phase 3. + /// + bool PerformPhase3 { get; } + + /// + /// Gets the execution priority of the provider (0 lowest, 100 highest). + /// + int ExecutionPriority { get; } + + /// + /// Performs a Formatting phase. + /// + /// The raw content to Format. + /// The Context information. + /// The Phase. + /// The Formatted content. + string Format(string raw, ContextInformation context, FormattingPhase phase); + + /// + /// Prepares the title of an item for display (always during phase 3). + /// + /// The input title. + /// The context information. + /// The prepared title (no markup allowed). + string PrepareTitle(string title, ContextInformation context); + + } + + /// + /// Enumerates formatting Phases. + /// + public enum FormattingPhase { + /// + /// Phase 1, performed before the internal formatting step. + /// + Phase1, + /// + /// Phase 2, performed after the internal formatting step. + /// + Phase2, + /// + /// Phase 3, performed before sending the page content to the client. + /// + Phase3 + } + +} diff --git a/PluginFramework/IHost.cs b/PluginFramework/IHost.cs new file mode 100644 index 0000000..b38b33a --- /dev/null +++ b/PluginFramework/IHost.cs @@ -0,0 +1,655 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net.Mail; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// It is the interface that the ScrewTurn Wiki's Host object implements. + /// + public interface IHostV30 { + + /// + /// Gets the values of the Wiki Settings. + /// + /// The Setting's Name. + /// The Setting's value. + string GetSettingValue(SettingName name); + + /// + /// Gets the list of the Users. + /// + /// The users. + UserInfo[] GetUsers(); + + /// + /// Finds a user by username. + /// + /// The username. + /// The , or null if no users are found. + /// If is null. + /// If is empty. + UserInfo FindUser(string username); + + /// + /// Gets the authenticated user in the current session, if any. + /// + /// The authenticated user, or null if no user is authenticated. + /// If the built-it admin user is authenticated, the returned user + /// has admin as Username. + UserInfo GetCurrentUser(); + + /// + /// Gets the list of the user groups. + /// + /// The groups. + UserGroup[] GetUserGroups(); + + /// + /// Finds a user group by name. + /// + /// The name. + /// The , or null if no groups are found. + /// If is null. + /// If is empty. + UserGroup FindUserGroup(string name); + + /// + /// Checks whether an action is allowed for a global resource. + /// + /// The action (see class) + /// The user. + /// true if the action is allowed, false otherwise. + /// If or are null. + /// If is empty. + bool CheckActionForGlobals(string action, UserInfo user); + + /// + /// Checks whether an action is allowed for a namespace. + /// + /// The namespace (null for the root). + /// The action (see class) + /// The user. + /// true if the action is allowed, false otherwise. + /// If or are null. + /// If is empty. + bool CheckActionForNamespace(NamespaceInfo nspace, string action, UserInfo user); + + /// + /// Checks whether an action is allowed for a page. + /// + /// The page. + /// The action (see class) + /// The user. + /// true if the action is allowed, false otherwise. + /// If , or are null. + /// If is empty. + bool CheckActionForPage(PageInfo page, string action, UserInfo user); + + /// + /// Checks whether an action is allowed for a directory. + /// + /// The directory. + /// The action (see ). + /// The user. + /// true if the action is allowed, false otherwise. + /// If , or are null. + /// If is empty. + bool CheckActionForDirectory(StDirectoryInfo directory, string action, UserInfo user); + + /// + /// Gets the theme in use for a namespace. + /// + /// The namespace (null for the root). + /// The theme. + string GetTheme(NamespaceInfo nspace); + + /// + /// Gets the list of the namespaces. + /// + /// The namespaces. + NamespaceInfo[] GetNamespaces(); + + /// + /// Finds a namespace by name. + /// + /// The name. + /// The , or null if no namespaces are found. + NamespaceInfo FindNamespace(string name); + + /// + /// Gets the list of the Pages in a namespace. + /// + /// The namespace (null for the root). + /// The pages. + PageInfo[] GetPages(NamespaceInfo nspace); + + /// + /// Gets the list of the Categories in a namespace. + /// + /// The namespace (null for the root). + /// The categories. + CategoryInfo[] GetCategories(NamespaceInfo nspace); + + /// + /// Gets the list of Snippets. + /// + /// The snippets. + Snippet[] GetSnippets(); + + /// + /// Gets the list of Navigation Paths in a namespace. + /// + /// The namespace (null for the root). + /// The navigation paths. + NavigationPath[] GetNavigationPaths(NamespaceInfo nspace); + + /// + /// Gets the Categories of a Page. + /// + /// The Page. + /// The Categories. + /// If is null. + CategoryInfo[] GetCategoriesPerPage(PageInfo page); + + /// + /// Gets the WikiPage with the specified full Name. + /// + /// The full Name of the Page. + /// The Wiki Page, or null if no pages are found. + /// If is null. + /// If is empty. + PageInfo FindPage(string fullName); + + /// + /// Gets the Content of a Page. + /// + /// The Page. + /// The Page Content. + /// If is null. + PageContent GetPageContent(PageInfo page); + + /// + /// Gets the Backup/Revision numbers of a Page. + /// + /// The Page. + /// The Backup/Revision numbers. + /// If is null. + int[] GetBackups(PageInfo page); + + /// + /// Gets the Content of a Page Backup. + /// + /// The Page. + /// The revision. + /// The Backup Content. + /// If is null. + /// If is less than zero. + PageContent GetBackupContent(PageInfo page, int revision); + + /// + /// Gets the formatted content of a Page, retrieving it from the cache (if available). + /// + /// The Page. + /// The formatted content of the Page. + /// If is null. + string GetFormattedContent(PageInfo page); + + /// + /// Formats a block of WikiMarkup, using the built-in formatter only. + /// + /// The block of WikiMarkup. + /// The formatted content. + /// If is null. + string Format(string raw); + + /// + /// Prepares content for indexing in the search engine, performing bare-bones formatting and removing all WikiMarkup and XML-like characters. + /// + /// The page being indexed, if any, null otherwise. + /// The string to prepare. + /// The sanitized string. + /// If is null. + string PrepareContentForIndexing(PageInfo page, string content); + + /// + /// Prepares a title for indexing in the search engine, removing all WikiMarkup and XML-like characters. + /// + /// The page being indexed, if any, null otherwise. + /// The title to prepare. + /// The sanitized string. + /// If is null. + string PrepareTitleForIndexing(PageInfo page, string title); + + /// + /// Performs a search. + /// + /// The search query. + /// A value indicating whether to perform a full-text search. + /// A value indicating whether to search the names of files and attachments. + /// The search options. + /// The search results. + /// If is null. + /// If is empty. + SearchResultCollection PerformSearch(string query, bool fullText, bool filesAndAttachments, SearchOptions options); + + /// + /// Lists directories in a directory. + /// + /// The directory (null for the root, first invocation). + /// The directories. + StDirectoryInfo[] ListDirectories(StDirectoryInfo directory); + + /// + /// Lists files in a directory. + /// + /// The directory (null for the root, first invocation). + /// The files. + StFileInfo[] ListFiles(StDirectoryInfo directory); + + /// + /// Lists page attachments. + /// + /// The page. + /// The attachments. + /// If is null. + StFileInfo[] ListPageAttachments(PageInfo page); + + /// + /// Sends an Email. + /// + /// The Recipient Email address. + /// The Sender's Email address. + /// The Subject. + /// The Body. + /// True if the message is HTML. + /// True if the message has been sent successfully. + /// If , , or are null. + /// If , , or are empty. + bool SendEmail(string recipient, string sender, string subject, string body, bool html); + + /// + /// Logs a new message. + /// + /// The Message. + /// The Entry Type. + /// The user, or null. If null, the system will log "PluginName+System". + /// The Component that calls the method. The caller cannot be null. + /// If or are null. + /// If is empty. + void LogEntry(string message, LogEntryType entryType, string user, object caller); + + /// + /// Aligns a Date and Time object to the User's Time Zone preferences. + /// + /// The Date/Time to align. + /// The aligned Date/Time. + /// The method takes care of daylight saving settings. + DateTime AlignDateTimeWithPreferences(DateTime dt); + + /// + /// Clears the cache. + /// + /// The part of the cache to clear. + void ClearCache(CacheData data); + + /// + /// Adds an item in the Editing Toolbar. + /// + /// The item to add. + /// The text of the item showed in the toolbar. + /// The value of the item, placed in the content: if item is ToolbarItem.SpecialTagWrap, separate start and end tag with a pipe. + /// If or are null. + /// If or are empty, or if they contain single or double quotes, + /// or if does not contain a pipe when is SpecialTagWrap. + void AddToolbarItem(ToolbarItem item, string text, string value); + + /// + /// Gets the default provider of the specified type. + /// + /// The type of the provider ( + /// , + /// , + /// , + /// , + /// ). + /// The Full type name of the default provider of the specified type or null. + string GetDefaultProvider(Type providerType); + + /// + /// Gets the pages storage providers, either enabled or disabled. + /// + /// true to get enabled providers, false to get disabled providers. + /// The providers. + IPagesStorageProviderV30[] GetPagesStorageProviders(bool enabled); + + /// + /// Gets the users storage providers, either enabled or disabled. + /// + /// true to get enabled providers, false to get disabled providers. + /// The providers. + IUsersStorageProviderV30[] GetUsersStorageProviders(bool enabled); + + /// + /// Gets the files storage providers, either enabled or disabled. + /// + /// true to get enabled providers, false to get disabled providers. + /// The providers. + IFilesStorageProviderV30[] GetFilesStorageProviders(bool enabled); + + /// + /// Gets the cache providers, either enabled or disabled. + /// + /// true to get enabled providers, false to get disabled providers. + /// The providers. + ICacheProviderV30[] GetCacheProviders(bool enabled); + + /// + /// Gets the formatter providers, either enabled or disabled. + /// + /// true to get enabled providers, false to get disabled providers. + /// The providers. + IFormatterProviderV30[] GetFormatterProviders(bool enabled); + + /// + /// Gets the current settings storage provider. + /// + /// The settings storage provider. + ISettingsStorageProviderV30 GetSettingsStorageProvider(); + + /// + /// Gets the configuration of a provider. + /// + /// The type name of the provider, such as 'Vendor.Namespace.Provider'. + /// The configuration (can be empty or null). + /// If is null. + /// If is empty. + string GetProviderConfiguration(string providerTypeName); + + /// + /// Sets the configuration of a provider. + /// + /// The provider of which to set the configuration. + /// The configuration to set. + /// true if the configuration is set, false otherwise. + /// If is null. + bool SetProviderConfiguration(IProviderV30 provider, string configuration); + + /// + /// Upgrades the old Page Status to use the new ACL facilities. + /// + /// The page of which to upgrade the status. + /// The old status ('L' = Locked, 'P' = Public). + /// true if the operation succeeded, false otherwise. + /// If is null. + /// If is invalid. + bool UpgradePageStatusToAcl(PageInfo page, char oldStatus); + + /// + /// Upgrades the old security flags to use the new ACL facilities and user groups support. + /// + /// The administrators group. + /// The users group. + /// true if the operation succeeded, false otherwise. + /// If or are null. + bool UpgradeSecurityFlagsToGroupsAcl(UserGroup administrators, UserGroup users); + + /// + /// Event fired whenever an activity is performed on a User Account. + /// + event EventHandler UserAccountActivity; + + /// + /// Event fired whenever an activity is performed on a user group. + /// + event EventHandler UserGroupActivity; + + /// + /// Event fired whenever an activity is performed on a namespace. + /// + event EventHandler NamespaceActivity; + + /// + /// Even fired whenever an activity is performed on a Page. + /// + event EventHandler PageActivity; + + /// + /// Event fired whenever an activity is performed on a file, directory or attachment. + /// + event EventHandler FileActivity; + + } + + /// + /// Enumerates the Types of Log Entries. + /// + public enum LogEntryType { + /// + /// Represents a simple Message. + /// + General, + /// + /// Represents a Warning. + /// + Warning, + /// + /// Represents an Error. + /// + Error + } + + /// + /// Enumerates the Setting values' names. + /// + public enum SettingName { + /// + /// The Title of the Wiki. + /// + WikiTitle, + /// + /// The Main URL of the Wiki. + /// + MainUrl, + /// + /// The default page of the root namespace. + /// + RootNamespaceDefaultPage, + /// + /// The Contact Email. + /// + ContactEmail, + /// + /// The Sender Email. + /// + SenderEmail, + /// + /// The Date/Time format. + /// + DateTimeFormat, + /// + /// The default Language (for example en-US). + /// + DefaultLanguage, + /// + /// The default Time Zone (a string representing the shift in minutes respect to the Greenwich time, for example -120). + /// + DefaultTimeZone, + /// + /// The Themes directory. + /// + ThemesDirectory, + /// + /// The Public directory. + /// + PublicDirectory, + /// + /// A value (true/false) indicating whether users can create new accounts. + /// + UsersCanRegister, + /// + /// The regex used to validate usernames. + /// + UsernameRegex, + /// + /// The regex used to validate passwords. + /// + PasswordRegex, + /// + /// The regex used to validate email addresses. + /// + EmailRegex, + /// + /// The regex used to validate the main Wiki URL. + /// + MainUrlRegex, + /// + /// A value (true/false) indicating whether to activate page editing with a double click. + /// + EnableDoubleClickEditing, + /// + /// A value (true/false) indicating whether to process single line breaks in WikiMarkup. + /// + ProcessSingleLineBreaks, + /// + /// The account activation mode. + /// + AccountActivationMode, + /// + /// The file types allowed for upload. + /// + AllowedFileTypes, + /// + /// A value (true/false) indicating whether to disable the automatic version check. + /// + DisableAutomaticVersionCheck, + /// + /// A value (true/false) indicating whether to disable the breadcrumbs trail. + /// + DisableBreadcrumbsTrail, + /// + /// A value (true/false) indicating whether to disable the page cache. + /// + DisableCache, + /// + /// A value (true/false) indicating whether to disable the captcha control in public functionalities. + /// + DisableCaptchaControl, + /// + /// A value (true/false) indicating whether to disable concurrent page editing. + /// + DisableConcurrentEditing, + /// + /// A value (true/false) indicating whether to enable HTTP compression. + /// + EnableHttpCompression, + /// + /// A value (true/false) indicating whether to enable View State compression. + /// + EnableViewStateCompression, + /// + /// The logging level. + /// + LoggingLevel, + /// + /// The max size for uploaded files (bytes). + /// + MaxFileSize, + /// + /// The extension used for pages. + /// + PageExtension, + /// + /// A value (true/false) indicating whether to SCRIPT tags are allowed in WikiMarkup. + /// + ScriptTagsAllowed, + /// + /// The version of the Wiki Engine. + /// + WikiVersion, + /// + /// The max size, in KB, of the log. + /// + MaxLogSize, + /// + /// The max number of recent changes to log. + /// + MaxRecentChanges, + /// + /// The size of the cache (# of pages). + /// + CacheSize, + /// + /// The # of pages to remove fromm the cache when it is full. + /// + CacheCutSize, + /// + /// The timeout, in seconds, after which a page editing session is considered to be dead. + /// + EditingSessionTimeout, + /// + /// The default administrators group. + /// + AdministratorsGroup, + /// + /// The default users group. + /// + UsersGroup, + /// + /// The default anonymous users group. + /// + AnonymousGroup, + /// + /// The page change moderation mode. + /// + ChangeModerationMode, + /// + /// The default pages provider. + /// + DefaultPagesStorageProvider, + /// + /// The default users provider. + /// + DefaultUsersStorageProvider, + /// + /// The default files provider. + /// + DefaultFilesStorageProvider, + /// + /// The default cache provider. + /// + DefaultCacheProvider + } + + /// + /// Enumerates the parts of the cache that can be cleared. + /// + public enum CacheData { + /// + /// All the pages data. + /// + Pages, + /// + /// All the meta-files (Header, Footer, Sidebar, etc.). + /// + MetaFiles + } + + /// + /// Enumerates the toolbar items that can be added. + /// + public enum ToolbarItem { + /// + /// A Special Tag that is inserted in the text. + /// + SpecialTag, + /// + /// A special tag that wraps the selected text. + /// + SpecialTagWrap + } + +} diff --git a/PluginFramework/IPagesStorageProvider.cs b/PluginFramework/IPagesStorageProvider.cs new file mode 100644 index 0000000..a70b611 --- /dev/null +++ b/PluginFramework/IPagesStorageProvider.cs @@ -0,0 +1,514 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// It is the interface that must be implemented in order to create a custom Pages Storage Provider for ScrewTurn Wiki. + /// + /// A class that implements this interface should not have any kind of data caching. + public interface IPagesStorageProviderV30 : IStorageProviderV30 { + + /// + /// Gets a namespace. + /// + /// The name of the namespace. + /// The , or null if no namespace is found. + /// If is null. + /// If is empty. + NamespaceInfo GetNamespace(string name); + + /// + /// Gets all the sub-namespaces. + /// + /// The sub-namespaces, sorted by name. + NamespaceInfo[] GetNamespaces(); + + /// + /// Adds a new namespace. + /// + /// The name of the namespace. + /// The correct object. + /// If is null. + /// If is empty. + NamespaceInfo AddNamespace(string name); + + /// + /// Renames a namespace. + /// + /// The namespace to rename. + /// The new name of the namespace. + /// The correct object. + /// If or are null. + /// If is empty. + NamespaceInfo RenameNamespace(NamespaceInfo nspace, string newName); + + /// + /// Sets the default page of a namespace. + /// + /// The namespace of which to set the default page. + /// The page to use as default page, or null. + /// The correct object. + /// If is null. + NamespaceInfo SetNamespaceDefaultPage(NamespaceInfo nspace, PageInfo page); + + /// + /// Removes a namespace. + /// + /// The namespace to remove. + /// true if the namespace is removed, false otherwise. + /// If is null. + bool RemoveNamespace(NamespaceInfo nspace); + + /// + /// Moves a page from its namespace into another. + /// + /// The page to move. + /// The destination namespace (null for the root). + /// A value indicating whether to copy the page categories in the destination + /// namespace, if not already available. + /// The correct instance of . + /// If is null. + PageInfo MovePage(PageInfo page, NamespaceInfo destination, bool copyCategories); + + /// + /// Gets a category. + /// + /// The full name of the category. + /// The , or null if no category is found. + /// If is null. + /// If is empty. + CategoryInfo GetCategory(string fullName); + + /// + /// Gets all the Categories in a namespace. + /// + /// The namespace. + /// All the Categories in the namespace, sorted by name. + CategoryInfo[] GetCategories(NamespaceInfo nspace); + + /// + /// Gets all the categories of a page. + /// + /// The page. + /// The categories, sorted by name. + /// If is null. + CategoryInfo[] GetCategoriesForPage(PageInfo page); + + /// + /// Adds a Category. + /// + /// The target namespace (null for the root). + /// The Category name. + /// The correct CategoryInfo object. + /// The method should set category's Pages to an empty array. + /// If is null. + /// If is empty. + CategoryInfo AddCategory(string nspace, string name); + + /// + /// Renames a Category. + /// + /// The Category to rename. + /// The new Name. + /// The correct CategoryInfo object. + /// If or are null. + /// If is empty. + CategoryInfo RenameCategory(CategoryInfo category, string newName); + + /// + /// Removes a Category. + /// + /// The Category to remove. + /// True if the Category has been removed successfully. + /// If is null. + bool RemoveCategory(CategoryInfo category); + + /// + /// Merges two Categories. + /// + /// The source Category. + /// The destination Category. + /// The correct object. + /// The destination Category remains, while the source Category is deleted, and all its Pages re-bound + /// in the destination Category. + /// If or are null. + CategoryInfo MergeCategories(CategoryInfo source, CategoryInfo destination); + + /// + /// Performs a search in the index. + /// + /// The search parameters. + /// The results. + /// If is null. + SearchResultCollection PerformSearch(SearchParameters parameters); + + /// + /// Rebuilds the search index. + /// + void RebuildIndex(); + + /// + /// Gets some statistics about the search engine index. + /// + /// The total number of documents. + /// The total number of unique words. + /// The total number of word-document occurrences. + /// The approximated size, in bytes, of the search engine index. + void GetIndexStats(out int documentCount, out int wordCount, out int occurrenceCount, out long size); + + /// + /// Gets a value indicating whether the search engine index is corrupted and needs to be rebuilt. + /// + bool IsIndexCorrupted { get; } + + /// + /// Gets a page. + /// + /// The full name of the page. + /// The , or null if no page is found. + /// If is null. + /// If is empty. + PageInfo GetPage(string fullName); + + /// + /// Gets all the Pages in a namespace. + /// + /// The namespace (null for the root). + /// All the Pages in the namespace, sorted by name. + PageInfo[] GetPages(NamespaceInfo nspace); + + /// + /// Gets all the pages in a namespace that are bound to zero categories. + /// + /// The namespace (null for the root). + /// The pages, sorted by name. + PageInfo[] GetUncategorizedPages(NamespaceInfo nspace); + + /// + /// Gets the Content of a Page. + /// + /// The Page. + /// The Page Content object, null if the page does not exist or is null, + /// or an empty instance if the content could not be retrieved (). + PageContent GetContent(PageInfo page); + + /// + /// Gets the content of a draft of a Page. + /// + /// The Page. + /// The draft, or null if no draft exists. + /// If is null. + PageContent GetDraft(PageInfo page); + + /// + /// Deletes a draft of a Page. + /// + /// The page. + /// true if the draft is deleted, false otherwise. + /// If is null. + bool DeleteDraft(PageInfo page); + + /// + /// Gets the Backup/Revision numbers of a Page. + /// + /// The Page to get the Backups of. + /// The Backup/Revision numbers. + /// If is null. + int[] GetBackups(PageInfo page); + + /// + /// Gets the Content of a Backup of a Page. + /// + /// The Page to get the backup of. + /// The Backup/Revision number. + /// The Page Backup. + /// If is null. + /// If is less than zero. + PageContent GetBackupContent(PageInfo page, int revision); + + /// + /// Forces to overwrite or create a Backup. + /// + /// The Backup content. + /// The revision. + /// True if the Backup has been created successfully. + /// If is null. + /// If is less than zero. + bool SetBackupContent(PageContent content, int revision); + + /// + /// Adds a Page. + /// + /// The target namespace (null for the root). + /// The Page Name. + /// The creation Date/Time. + /// The correct PageInfo object or null. + /// This method should not create the content of the Page. + /// If is null. + /// If is empty. + PageInfo AddPage(string nspace, string name, DateTime creationDateTime); + + /// + /// Renames a Page. + /// + /// The Page to rename. + /// The new Name. + /// The correct object. + /// If or are null. + /// If is empty. + PageInfo RenamePage(PageInfo page, string newName); + + /// + /// Modifies the Content of a Page. + /// + /// The Page. + /// The Title of the Page. + /// The Username. + /// The Date/Time. + /// The Comment of the editor, about this revision. + /// The Page Content. + /// The keywords, usually used for SEO. + /// The description, usually used for SEO. + /// The save mode for this modification. + /// true if the Page has been modified successfully, false otherwise. + /// If saveMode equals Draft and a draft already exists, it is overwritten. + /// If , or are null. + /// If or are empty. + bool ModifyPage(PageInfo page, string title, string username, DateTime dateTime, string comment, string content, + string[] keywords, string description, SaveMode saveMode); + + /// + /// Performs the rollback of a Page to a specified revision. + /// + /// The Page to rollback. + /// The Revision to rollback the Page to. + /// true if the rollback succeeded, false otherwise. + /// If is null. + /// If is less than zero. + bool RollbackPage(PageInfo page, int revision); + + /// + /// Deletes the Backups of a Page, up to a specified revision. + /// + /// The Page to delete the backups of. + /// The newest revision to delete (newer revision are kept) or -1 to delete all the Backups. + /// true if the deletion succeeded, false otherwise. + /// If is null. + /// If is less than -1. + bool DeleteBackups(PageInfo page, int revision); + + /// + /// Removes a Page. + /// + /// The Page to remove. + /// True if the Page is removed successfully. + /// If is null. + bool RemovePage(PageInfo page); + + /// + /// Binds a Page with one or more Categories. + /// + /// The Page to bind. + /// The Categories to bind the Page with. + /// True if the binding succeeded. + /// After a successful operation, the Page is bound with all and only the categories passed as argument. + /// If or are null. + bool RebindPage(PageInfo page, string[] categories); + + /// + /// Gets the Page Messages. + /// + /// The Page. + /// The list of the first-level Messages, containing the replies properly nested, sorted by date/time. + /// If is null. + Message[] GetMessages(PageInfo page); + + /// + /// Gets the total number of Messages in a Page Discussion. + /// + /// The Page. + /// The number of messages. + /// If is null. + int GetMessageCount(PageInfo page); + + /// + /// Removes all messages for a page and stores the new messages. + /// + /// The page. + /// The new messages to store. + /// true if the messages are stored, false otherwise. + /// If or are null. + bool BulkStoreMessages(PageInfo page, Message[] messages); + + /// + /// Adds a new Message to a Page. + /// + /// The Page. + /// The Username. + /// The Subject. + /// The Date/Time. + /// The Body. + /// The Parent Message ID, or -1. + /// True if the Message is added successfully. + /// If , , or are null. + /// If or are empty. + /// If is less than -1. + bool AddMessage(PageInfo page, string username, string subject, DateTime dateTime, string body, int parent); + + /// + /// Removes a Message. + /// + /// The Page. + /// The ID of the Message to remove. + /// A value specifying whether or not to remove the replies. + /// True if the Message is removed successfully. + /// If is null. + /// If is less than zero. + bool RemoveMessage(PageInfo page, int id, bool removeReplies); + + /// + /// Modifies a Message. + /// + /// The Page. + /// The ID of the Message to modify. + /// The Username. + /// The Subject. + /// The Date/Time. + /// The Body. + /// True if the Message is modified successfully. + /// If , , or are null. + /// If is less than zero. + /// If or are empty. + bool ModifyMessage(PageInfo page, int id, string username, string subject, DateTime dateTime, string body); + + /// + /// Gets all the Navigation Paths in a Namespace. + /// + /// The Namespace. + /// All the Navigation Paths, sorted by name. + NavigationPath[] GetNavigationPaths(NamespaceInfo nspace); + + /// + /// Adds a new Navigation Path. + /// + /// The target namespace (null for the root). + /// The Name of the Path. + /// The Pages array. + /// The correct object. + /// If or are null. + /// If or are empty. + NavigationPath AddNavigationPath(string nspace, string name, PageInfo[] pages); + + /// + /// Modifies an existing navigation path. + /// + /// The navigation path to modify. + /// The new pages array. + /// The correct object. + /// If or are null. + /// If is empty. + NavigationPath ModifyNavigationPath(NavigationPath path, PageInfo[] pages); + + /// + /// Removes a Navigation Path. + /// + /// The navigation path to remove. + /// true if the path is removed, false otherwise. + /// If is null. + bool RemoveNavigationPath(NavigationPath path); + + /// + /// Gets all the snippets. + /// + /// All the snippets, sorted by name. + Snippet[] GetSnippets(); + + /// + /// Adds a new snippet. + /// + /// The name of the snippet. + /// The content of the snippet. + /// The correct object. + /// If or are null. + /// If is empty. + Snippet AddSnippet(string name, string content); + + /// + /// Modifies an existing snippet. + /// + /// The name of the snippet to modify. + /// The content of the snippet. + /// The correct object. + /// If or are null. + /// If is empty. + Snippet ModifySnippet(string name, string content); + + /// + /// Removes a new Snippet. + /// + /// The Name of the Snippet to remove. + /// true if the snippet is removed, false otherwise. + /// If is null. + /// If is empty. + bool RemoveSnippet(string name); + + /// + /// Gets all the content templates. + /// + /// All the content templates, sorted by name. + ContentTemplate[] GetContentTemplates(); + + /// + /// Adds a new content template. + /// + /// The name of template. + /// The content of the template. + /// The correct object. + /// If or are null. + /// If is empty. + ContentTemplate AddContentTemplate(string name, string content); + + /// + /// Modifies an existing content template. + /// + /// The name of the template to modify. + /// The content of the template. + /// The correct object. + /// If or are null. + /// If is empty. + ContentTemplate ModifyContentTemplate(string name, string content); + + /// + /// Removes a content template. + /// + /// The name of the template to remove. + /// true if the template is removed, false otherwise. + /// If is null. + /// If is empty. + bool RemoveContentTemplate(string name); + + } + + /// + /// Lists legal saving modes. + /// + public enum SaveMode { + /// + /// Save the content. + /// + Normal, + /// + /// Backup the previous content, then save the current content. + /// + Backup, + /// + /// Save the content as draft. + /// + Draft + } + +} diff --git a/PluginFramework/IProvider.cs b/PluginFramework/IProvider.cs new file mode 100644 index 0000000..a128e42 --- /dev/null +++ b/PluginFramework/IProvider.cs @@ -0,0 +1,73 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// The base interface that all the Providers must implement. All the Provider Type-specific interfaces inherit from this one or from a one, either directly or from a derived interface. + /// + /// This interface should not be implemented directly by a class. + public interface IProviderV30 { + + /// + /// Initializes the Storage Provider. + /// + /// The Host of the Component. + /// The Configuration data, if any. + /// If or are null. + /// If is not valid or is incorrect. + void Init(IHostV30 host, string config); + + /// + /// Method invoked on shutdown. + /// + /// This method might not be invoked in some cases. + void Shutdown(); + + /// + /// Gets the Information about the Provider. + /// + ComponentInformation Information { get; } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + string ConfigHelpHtml { get; } + + } + + /// + /// Represents errors that occur while decoding the Provider's Configuration String. + /// + public class InvalidConfigurationException : Exception { + + /// + /// Initializes a new instance of the InvalidConfigurationException class. + /// + public InvalidConfigurationException() : base() { } + + /// + /// Initializes a new instance of the InvalidConfigurationException class. + /// + /// The error message. + public InvalidConfigurationException(string message) : base(message) { } + + /// + /// Initializes a new instance of the InvalidConfigurationException class. + /// + /// The error message. + /// The inner Exception. + public InvalidConfigurationException(string message, Exception innerException) : base(message, innerException) { } + + /// + /// Initializes a new instance of the InvalidConfigurationException class. + /// + /// The serialization info. + /// The streaming context. + public InvalidConfigurationException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + + } + +} diff --git a/PluginFramework/IResourceExchanger.cs b/PluginFramework/IResourceExchanger.cs new file mode 100644 index 0000000..e1f815d --- /dev/null +++ b/PluginFramework/IResourceExchanger.cs @@ -0,0 +1,22 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki { + + /// + /// Exposes methods for exchanging Resources. + /// + public interface IResourceExchanger { + + /// + /// Gets a Resource String. + /// + /// The Name of the Resource. + /// The Resource String. + string GetResource(string name); + + } + +} diff --git a/PluginFramework/ISettingsStorageProvider.cs b/PluginFramework/ISettingsStorageProvider.cs new file mode 100644 index 0000000..10e3f7a --- /dev/null +++ b/PluginFramework/ISettingsStorageProvider.cs @@ -0,0 +1,406 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.AclEngine; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// The interface that must be implemented in order to create a custom Settings and Log Storage Provider for ScrewTurn Wiki. + /// + /// A class that implements this interface should have some kind of data caching. + /// The Provider should not use the method during the execution of + /// the method, nor access the wiki log. + public interface ISettingsStorageProviderV30 : IProviderV30 { + + /// + /// Retrieves the value of a Setting. + /// + /// The name of the Setting. + /// The value of the Setting, or null. + /// If is null. + /// If is empty. + string GetSetting(string name); + + /// + /// Stores the value of a Setting. + /// + /// The name of the Setting. + /// The value of the Setting. Value cannot contain CR and LF characters, which will be removed. + /// True if the Setting is stored, false otherwise. + /// This method stores the Value immediately. + /// If is null. + /// If is empty. + bool SetSetting(string name, string value); + + /// + /// Gets the all the setting values. + /// + /// All the settings. + IDictionary GetAllSettings(); + + /// + /// Starts a Bulk update of the Settings so that a bulk of settings can be set before storing them. + /// + void BeginBulkUpdate(); + + /// + /// Ends a Bulk update of the Settings and stores the settings. + /// + void EndBulkUpdate(); + + /// + /// Records a message to the System Log. + /// + /// The Log Message. + /// The Type of the Entry. + /// The User. + /// This method should not write messages to the Log using the method IHost.LogEntry. + /// This method should also never throw exceptions (except for parameter validation). + /// If or are null. + /// If or are empty. + void LogEntry(string message, EntryType entryType, string user); + + /// + /// Gets all the Log Entries, sorted by date/time (oldest to newest). + /// + /// The Log Entries. + LogEntry[] GetLogEntries(); + + /// + /// Clear the Log. + /// + void ClearLog(); + + /// + /// Gets the current size of the Log, in KB. + /// + int LogSize { get; } + + /// + /// Gets a meta-data item's content. + /// + /// The item. + /// The tag that specifies the context (usually the namespace). + /// The content. + string GetMetaDataItem(MetaDataItem item, string tag); + + /// + /// Sets a meta-data items' content. + /// + /// The item. + /// The tag that specifies the context (usually the namespace). + /// The content. + /// true if the content is set, false otherwise. + bool SetMetaDataItem(MetaDataItem item, string tag, string content); + + /// + /// Gets the recent changes of the Wiki. + /// + /// The recent Changes, oldest to newest. + RecentChange[] GetRecentChanges(); + + /// + /// Adds a new change. + /// + /// The page name. + /// The page title. + /// The message subject (or null). + /// The date/time. + /// The user. + /// The change. + /// The description (optional). + /// true if the change is saved, false otherwise. + /// If , or are null. + /// If , or are empty. + bool AddRecentChange(string page, string title, string messageSubject, DateTime dateTime, string user, Change change, string descr); + + /// + /// Lists the stored plugin assemblies. + /// + /// + string[] ListPluginAssemblies(); + + /// + /// Stores a plugin's assembly, overwriting existing ones if present. + /// + /// The file name of the assembly, such as "Assembly.dll". + /// The assembly content. + /// true if the assembly is stored, false otherwise. + /// If or are null. + /// If or are empty. + bool StorePluginAssembly(string filename, byte[] assembly); + + /// + /// Retrieves a plugin's assembly. + /// + /// The file name of the assembly. + /// The assembly content, or null. + /// If is null. + /// If is empty. + byte[] RetrievePluginAssembly(string filename); + + /// + /// Removes a plugin's assembly. + /// + /// The file name of the assembly to remove, such as "Assembly.dll". + /// true if the assembly is removed, false otherwise. + /// If is null. + /// If is empty. + bool DeletePluginAssembly(string filename); + + /// + /// Sets the status of a plugin. + /// + /// The Type name of the plugin. + /// The plugin status. + /// true if the status is stored, false otherwise. + /// If is null. + /// If is empty. + bool SetPluginStatus(string typeName, bool enabled); + + /// + /// Gets the status of a plugin. + /// + /// The Type name of the plugin. + /// The status (false for disabled, true for enabled), or true if no status is found. + /// If is null. + /// If is empty. + bool GetPluginStatus(string typeName); + + /// + /// Sets the configuration of a plugin. + /// + /// The Type name of the plugin. + /// The configuration. + /// true if the configuration is stored, false otherwise. + /// If is null. + /// If is empty. + bool SetPluginConfiguration(string typeName, string config); + + /// + /// Gets the configuration of a plugin. + /// + /// The Type name of the plugin. + /// The plugin configuration, or String.Empty. + /// If is null. + /// If is empty. + string GetPluginConfiguration(string typeName); + + /// + /// Gets the ACL Manager instance. + /// + IAclManager AclManager { get; } + + /// + /// Stores the outgoing links of a page, overwriting existing data. + /// + /// The full name of the page. + /// The full names of the pages that page links to. + /// true if the outgoing links are stored, false otherwise. + /// If or are null. + /// If or are empty. + bool StoreOutgoingLinks(string page, string[] outgoingLinks); + + /// + /// Gets the outgoing links of a page. + /// + /// The full name of the page. + /// The outgoing links. + /// If is null. + /// If is empty. + string[] GetOutgoingLinks(string page); + + /// + /// Gets all the outgoing links stored. + /// + /// The outgoing links, in a dictionary in the form page->outgoing_links. + IDictionary GetAllOutgoingLinks(); + + /// + /// Deletes the outgoing links of a page and all the target links that include the page. + /// + /// The full name of the page. + /// true if the links are deleted, false otherwise. + /// If is null. + /// If is empty. + bool DeleteOutgoingLinks(string page); + + /// + /// Updates all outgoing links data for a page rename. + /// + /// The old page name. + /// The new page name. + /// true if the data is updated, false otherwise. + /// If or are null. + /// If or are empty. + bool UpdateOutgoingLinksForRename(string oldName, string newName); + + } + + /// + /// Lists legal meta-data items (global items have a integer value greater than or equal to 100. + /// + public enum MetaDataItem { + // Numbers < 100 -> global items + // Numbers >= 100 -> namespace-specific items + + /// + /// The account activation message which is sent to the newly registered users. + /// + AccountActivationMessage = 0, + /// + /// The password reset message which is sent to the users who reset their password. + /// + PasswordResetProcedureMessage = 7, + /// + /// The notice that appears and replaces the text in the Login page. + /// + LoginNotice = 2, + /// + /// The notice that appears and replaces the text in the Access Denied page. + /// + AccessDeniedNotice = 8, + /// + /// The message that is sent when a page is modified. + /// + PageChangeMessage = 3, + /// + /// The message that is sent when a new message is posted on a discussion. + /// + DiscussionChangeMessage = 4, + /// + /// The message sent when a page draft requires approval. + /// + ApproveDraftMessage = 5, + /// + /// The notice that appears and replates the text in the Register page. + /// + RegisterNotice = 6, + + /// + /// The notice that appears in the editing page. + /// + EditNotice = 100, + /// + /// The wiki footer. + /// + Footer = 101, + /// + /// The wiki header. + /// + Header = 102, + /// + /// The custom content of the HTML Head tag. + /// + HtmlHead = 103, + /// + /// The pages footer. + /// + PageFooter = 104, + /// + /// The pages header. + /// + PageHeader = 105, + /// + /// The content of the sidebar. + /// + Sidebar = 106 + } + + /// + /// Represents a Log Entry. + /// + public class LogEntry { + + private EntryType type; + private DateTime dateTime; + private string message; + private string user; + + /// + /// Initializes a new instance of the LogEntry class. + /// + /// The type of the Entry + /// The DateTime. + /// The Message. + /// The User. + public LogEntry(EntryType type, DateTime dateTime, string message, string user) { + this.type = type; + this.dateTime = dateTime; + this.message = message; + this.user = user; + } + + /// + /// Gets the EntryType. + /// + public EntryType EntryType { + get { return type; } + } + + /// + /// Gets the DateTime. + /// + public DateTime DateTime { + get { return dateTime; } + } + + /// + /// Gets the Message. + /// + public string Message { + get { return message; } + } + + /// + /// Gets the User. + /// + public string User { + get { return user; } + } + + } + + /// + /// Enumerates the Types of Log Entries. + /// + public enum EntryType { + /// + /// Represents a simple Message. + /// + General, + /// + /// Represents a Warning. + /// + Warning, + /// + /// Represents an Error. + /// + Error + } + + /// + /// Lists legal logging level values. + /// + public enum LoggingLevel { + /// + /// All messages are logged. + /// + AllMessages = 3, + /// + /// Warnings and errors are logged. + /// + WarningsAndErrors = 2, + /// + /// Errors only are logged. + /// + ErrorsOnly = 1, + /// + /// Logging is completely disabled. + /// + DisableLog = 0 + } + +} diff --git a/PluginFramework/IStorageProvider.cs b/PluginFramework/IStorageProvider.cs new file mode 100644 index 0000000..4359a55 --- /dev/null +++ b/PluginFramework/IStorageProvider.cs @@ -0,0 +1,21 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// The base interface that all the Storage Providers must implement. All the Provider Type-specific interfaces inherit from this one or from a one, either directly or from a derived interface + /// + /// This interface should not be implemented directly by a class. + public interface IStorageProviderV30 : IProviderV30 { + + /// + /// Gets a value specifying whether the provider is read-only, i.e. it can only provide data and not store it. + /// + bool ReadOnly { get; } + + } + +} diff --git a/PluginFramework/IUsersStorageProvider.cs b/PluginFramework/IUsersStorageProvider.cs new file mode 100644 index 0000000..0559d64 --- /dev/null +++ b/PluginFramework/IUsersStorageProvider.cs @@ -0,0 +1,220 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Web; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// It is the interface that must be implemented in order to create a custom Users Storage Provider for ScrewTurn Wiki. + /// + /// A class that implements this class should not have any kind of data caching. + public interface IUsersStorageProviderV30 : IProviderV30 { + + /// + /// Tests a Password for a User account. + /// + /// The User account. + /// The Password to test. + /// True if the Password is correct. + /// If or are null. + bool TestAccount(UserInfo user, string password); + + /// + /// Gets the complete list of Users. + /// + /// All the Users, sorted by username. + UserInfo[] GetUsers(); + + /// + /// Adds a new User. + /// + /// The Username. + /// The display name (can be null). + /// The Password. + /// The Email address. + /// A value indicating whether the account is active. + /// The Account creation Date/Time. + /// The correct object or null. + /// If , or are null. + /// If , or are empty. + UserInfo AddUser(string username, string displayName, string password, string email, bool active, DateTime dateTime); + + /// + /// Modifies a User. + /// + /// The Username of the user to modify. + /// The new display name (can be null). + /// The new Password (null or blank to keep the current password). + /// The new Email address. + /// A value indicating whether the account is active. + /// The correct object or null. + /// If or are null. + /// If is empty. + UserInfo ModifyUser(UserInfo user, string newDisplayName, string newPassword, string newEmail, bool newActive); + + /// + /// Removes a User. + /// + /// The User to remove. + /// True if the User has been removed successfully. + /// If is null. + bool RemoveUser(UserInfo user); + + /// + /// Gets all the user groups. + /// + /// All the groups, sorted by name. + UserGroup[] GetUserGroups(); + + /// + /// Adds a new user group. + /// + /// The name of the group. + /// The description of the group. + /// The correct object or null. + /// If or are null. + /// If is empty. + UserGroup AddUserGroup(string name, string description); + + /// + /// Modifies a user group. + /// + /// The group to modify. + /// The new description of the group. + /// The correct object or null. + /// If or are null. + UserGroup ModifyUserGroup(UserGroup group, string description); + + /// + /// Removes a user group. + /// + /// The group to remove. + /// true if the group is removed, false otherwise. + /// If is null. + bool RemoveUserGroup(UserGroup group); + + /// + /// Sets the group memberships of a user account. + /// + /// The user account. + /// The groups the user account is member of. + /// The correct object or null. + /// If or are null. + UserInfo SetUserMembership(UserInfo user, string[] groups); + + /// + /// Tries to login a user directly through the provider. + /// + /// The username. + /// The password. + /// The correct UserInfo object, or null. + /// If or are null. + UserInfo TryManualLogin(string username, string password); + + /// + /// Tries to login a user directly through the provider using + /// the current HttpContext and without username/password. + /// + /// The current HttpContext. + /// The correct UserInfo object, or null. + /// If is null. + UserInfo TryAutoLogin(HttpContext context); + + /// + /// Gets a user account. + /// + /// The username. + /// The , or null. + /// If is null. + /// If is empty. + UserInfo GetUser(string username); + + /// + /// Gets a user account. + /// + /// The email address. + /// The first user found with the specified email address, or null. + /// If is null. + /// If is empty. + UserInfo GetUserByEmail(string email); + + /// + /// Notifies the provider that a user has logged in through the authentication cookie. + /// + /// The user who has logged in. + /// If is null. + void NotifyCookieLogin(UserInfo user); + + /// + /// Notifies the provider that a user has logged out. + /// + /// The user who has logged out. + /// If is null. + void NotifyLogout(UserInfo user); + + /// + /// Stores a user data element, overwriting the previous one if present. + /// + /// The user the data belongs to. + /// The key of the data element (case insensitive). + /// The value of the data element, null for deleting the data. + /// true if the data element is stored, false otherwise. + /// If or are null. + /// If is empty. + bool StoreUserData(UserInfo user, string key, string value); + + /// + /// Gets a user data element, if any. + /// + /// The user the data belongs to. + /// The key of the data element. + /// The value of the data element, or null if the element is not found. + /// If or are null. + /// If is empty. + string RetrieveUserData(UserInfo user, string key); + + /// + /// Retrieves all the user data elements for a user. + /// + /// The user. + /// The user data elements (key->value). + /// If is null. + IDictionary RetrieveAllUserData(UserInfo user); + + /// + /// Gets all the users that have the specified element in their data. + /// + /// The key of the data. + /// The users and the data. + /// If is null. + /// If is empty. + IDictionary GetUsersWithData(string key); + + /// + /// Gets a value indicating whether user accounts are read-only. + /// + bool UserAccountsReadOnly { get; } + + /// + /// Gets a value indicating whether user groups are read-only. If so, the provider + /// should support default user groups as defined in the wiki configuration. + /// + bool UserGroupsReadOnly { get; } + + /// + /// Gets a value indicating whether group membership is read-only (if + /// is false, then this property must be false). If this property is true, the provider + /// should return membership data compatible with default user groups. + /// + bool GroupMembershipReadOnly { get; } + + /// + /// Gets a value indicating whether users' data is read-only. + /// + bool UsersDataReadOnly { get; } + + } + +} diff --git a/PluginFramework/Message.cs b/PluginFramework/Message.cs new file mode 100644 index 0000000..c41285c --- /dev/null +++ b/PluginFramework/Message.cs @@ -0,0 +1,131 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Represents a Page Discussion Message. + /// + public class Message { + + /// + /// The Message ID. + /// + protected int id; + /// + /// The Username. + /// + protected string username; + /// + /// The Subject. + /// + protected string subject; + /// + /// The Date/Time. + /// + protected DateTime dateTime; + /// + /// The Body. + /// + protected string body; + /// + /// The Replies. + /// + protected Message[] replies = new Message[0]; + + /// + /// Initializes a new instance of the Message class. + /// + /// The ID of the Message. + /// The Username of the User. + /// The Subject of the Message. + /// The Date/Time of the Message. + /// The body of the Message. + public Message(int id, string username, string subject, DateTime dateTime, string body) { + this.id = id; + this.username = username; + this.subject = subject; + this.dateTime = dateTime; + this.body = body; + } + + /// + /// Gets or sets the Message ID. + /// + public int ID { + get { return id; } + set { id = value; } + } + + /// + /// Gets or sets the Username. + /// + public string Username { + get { return username; } + set { username = value; } + } + + /// + /// Gets or sets the Subject. + /// + public string Subject { + get { return subject; } + set { subject = value; } + } + + /// + /// Gets or sets the Date/Time. + /// + public DateTime DateTime { + get { return dateTime; } + set { dateTime = value; } + } + + /// + /// Gets or sets the Body. + /// + public string Body { + get { return body; } + set { body = value; } + } + + /// + /// Gets or sets the Replies. + /// + public Message[] Replies { + get { return replies; } + set { replies = value; } + } + + } + + /// + /// Compares two Message object using their Date/Time as parameter. + /// + public class MessageDateTimeComparer : IComparer { + + bool reverse = false; + + /// + /// Initializes a new instance of the MessageDateTimeComparer class. + /// + /// True to compare in reverse order (bigger to smaller). + public MessageDateTimeComparer(bool reverse) { + this.reverse = reverse; + } + + /// + /// Compares two Message objects. + /// + /// The first object. + /// The second object. + /// The result of the comparison (1, 0 or -1). + public int Compare(Message x, Message y) { + if(!reverse) return x.DateTime.CompareTo(y.DateTime); + else return y.DateTime.CompareTo(x.DateTime); + } + } + +} diff --git a/PluginFramework/MessageDocument.cs b/PluginFramework/MessageDocument.cs new file mode 100644 index 0000000..2cec6b5 --- /dev/null +++ b/PluginFramework/MessageDocument.cs @@ -0,0 +1,137 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Represents a message for use with the search engine. + /// + public class MessageDocument : IDocument { + + /// + /// The type tag for a . + /// + public const string StandardTypeTag = "M"; + + /// + /// Gets the document name for a message. + /// + /// The page. + /// The message ID. + /// The document name. + public static string GetDocumentName(PageInfo page, int messageID) { + if(page == null) throw new ArgumentNullException("page"); + return page.FullName + "..." + messageID.ToString(); + } + + /// + /// Gets the page name and message ID from a document name. + /// + /// The document name. + /// The page name. + /// The message ID. + public static void GetMessageDetails(string documentName, out string pageName, out int messageID) { + if(documentName == null) throw new ArgumentNullException("documentName"); + if(documentName.Length == 0) throw new ArgumentException("Document Name cannot be empty", "documentName"); + + int lastThreeDotsIndex = documentName.LastIndexOf("..."); + if(lastThreeDotsIndex == -1) throw new ArgumentException("Document Name has an invalid format", "documentName"); + + pageName = documentName.Substring(0, lastThreeDotsIndex); + messageID = int.Parse(documentName.Substring(lastThreeDotsIndex + 3)); + } + + private uint id; + private string name, title, typeTag; + private DateTime dateTime; + private int messageID; + private PageInfo pageInfo; + + private Tokenizer tokenizer; + + /// + /// Initializes a new instance of the class. + /// + /// The page. + /// The message ID. + /// The dumped document data. + /// The tokenizer. + public MessageDocument(PageInfo pageInfo, int messageID, DumpedDocument dumpedDocument, Tokenizer tokenizer) { + if(dumpedDocument == null) throw new ArgumentNullException("dumpedDocument"); + if(tokenizer == null) throw new ArgumentNullException("tokenizer"); + + this.pageInfo = pageInfo; + this.messageID = messageID; + id = dumpedDocument.ID; + name = dumpedDocument.Name; + typeTag = dumpedDocument.TypeTag; + title = dumpedDocument.Title; + dateTime = dumpedDocument.DateTime; + this.tokenizer = tokenizer; + } + + /// + /// Gets or sets the globally unique ID of the document. + /// + public uint ID { + get { return id; } + set { id = value; } + } + + /// + /// Gets the globally-unique name of the document. + /// + public string Name { + get { return name; } + } + + /// + /// Gets the title of the document, if any. + /// + public string Title { + get { return title; } + } + + /// + /// Gets the tag for the document type. + /// + public string TypeTag { + get { return typeTag; } + } + + /// + /// Gets the document date/time. + /// + public DateTime DateTime { + get { return dateTime; } + } + + /// + /// Performs the tokenization of the document content. + /// + /// The content to tokenize. + /// The extracted words and their positions. + public WordInfo[] Tokenize(string content) { + return tokenizer(content); + } + + /// + /// Gets the message ID. + /// + public int MessageID { + get { return messageID; } + } + + /// + /// Gets the page information. + /// + public PageInfo PageInfo { + get { return pageInfo; } + } + + } + +} diff --git a/PluginFramework/NameTools.cs b/PluginFramework/NameTools.cs new file mode 100644 index 0000000..eb799f2 --- /dev/null +++ b/PluginFramework/NameTools.cs @@ -0,0 +1,75 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Implements useful tools for handling full object names. + /// + public static class NameTools { + + /// + /// Gets the full name of a page from the namespace and local name. + /// + /// The namespace (null for the root). + /// The local name. + /// The full name. + public static string GetFullName(string nspace, string name) { + return (!string.IsNullOrEmpty(nspace) ? nspace + "." : "") + name; + } + + /// + /// Expands a full name into the namespace and local name. + /// + /// The full name to expand. + /// The namespace. + /// The local name. + public static void ExpandFullName(string fullName, out string nspace, out string name) { + if(fullName == null) { + nspace = null; + name = null; + } + else { + string[] fields = fullName.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + if(fields.Length == 0) { + nspace = null; + name = null; + } + else if(fields.Length == 1) { + nspace = null; + name = fields[0]; + } + else { + nspace = fields[0]; + name = fields[1]; + } + } + } + + /// + /// Extracts the namespace from a full name. + /// + /// The full name. + /// The namespace, or null. + public static string GetNamespace(string fullName) { + string nspace, name; + ExpandFullName(fullName, out nspace, out name); + return nspace; + } + + /// + /// Extracts the local name from a full name. + /// + /// The full name. + /// The local name. + public static string GetLocalName(string fullName) { + string nspace, name; + ExpandFullName(fullName, out nspace, out name); + return name; + } + + } + +} diff --git a/PluginFramework/NamespaceActivityEventArgs.cs b/PluginFramework/NamespaceActivityEventArgs.cs new file mode 100644 index 0000000..dcbce50 --- /dev/null +++ b/PluginFramework/NamespaceActivityEventArgs.cs @@ -0,0 +1,75 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Contains arguments for Namespace activity events. + /// + public class NamespaceActivityEventArgs : EventArgs { + + private NamespaceInfo nspace; + private string nspaceOldName; + private NamespaceActivity activity; + + /// + /// Initializes a new instance of the class. + /// + /// The namespace the activity refers to. + /// The old name of the renamed namespace, or null. + /// The activity. + public NamespaceActivityEventArgs(NamespaceInfo nspace, string nspaceOldName, NamespaceActivity activity) { + this.nspace = nspace; + this.nspaceOldName = nspaceOldName; + this.activity = activity; + } + + /// + /// Gets the namespace the activity refers to. + /// + public NamespaceInfo NamespaceInfo { + get { return nspace; } + } + + /// + /// Gets the old name of the renamed namespace, or null. + /// + public string NamespaceOldName { + get { return nspaceOldName; } + } + + /// + /// Gets the activity. + /// + public NamespaceActivity Activity { + get { return activity; } + } + + } + + /// + /// Lists legal namespace activity types. + /// + public enum NamespaceActivity { + /// + /// A namespace has been added. + /// + NamespaceAdded, + /// + /// A namespace has been renamed. + /// + NamespaceRenamed, + /// + /// A namespace has been modified. + /// + NamespaceModified, + /// + /// A namespace has been removed. + /// + NamespaceRemoved + } + +} diff --git a/PluginFramework/NamespaceInfo.cs b/PluginFramework/NamespaceInfo.cs new file mode 100644 index 0000000..e43f5d3 --- /dev/null +++ b/PluginFramework/NamespaceInfo.cs @@ -0,0 +1,92 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Represents a namespace. + /// + public class NamespaceInfo { + + /// + /// The name of the namespace. + /// + protected string name; + /// + /// The provider of the namespace. + /// + protected IPagesStorageProviderV30 provider; + /// + /// The default page of the namespace (can be null). + /// + protected PageInfo defaultPage; + + /// + /// Initializes a new instance of the class. + /// + /// The namespace name. + /// The provider. + /// The default page, or null. + public NamespaceInfo(string name, IPagesStorageProviderV30 provider, PageInfo defaultPage) { + this.name = name; + this.provider = provider; + this.defaultPage = defaultPage; + } + + /// + /// Gets or sets the name of the namespace. + /// + public string Name { + get { return name; } + set { name = value; } + } + + /// + /// Gets or sets the provider. + /// + public IPagesStorageProviderV30 Provider { + get { return provider; } + set { provider = value; } + } + + /// + /// Gets or sets the default page, or null. + /// + public PageInfo DefaultPage { + get { return defaultPage; } + set { defaultPage = value; } + } + + /// + /// Gets a string representation of the current object. + /// + /// The string representation. + public override string ToString() { + return name; + } + + } + + /// + /// Compares two objects, using the Name as parameter. + /// + public class NamespaceComparer : IComparer { + + /// + /// Compares two objects, using the Name as parameter. + /// + /// The first object. + /// The second object. + /// The comparison result (-1, 0 or 1). + public int Compare(NamespaceInfo x, NamespaceInfo y) { + if(x == null && y == null) return 0; + else if(x == null && y != null) return -1; + else if(x != null && y == null) return 1; + else return StringComparer.OrdinalIgnoreCase.Compare(x.Name, y.Name); + } + + } + +} diff --git a/PluginFramework/NavigationPath.cs b/PluginFramework/NavigationPath.cs new file mode 100644 index 0000000..5ae90c6 --- /dev/null +++ b/PluginFramework/NavigationPath.cs @@ -0,0 +1,83 @@ + +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Represents a Navigation Path. + /// + public class NavigationPath { + + /// + /// The namespace of the Navigation Path (null for the root). + /// + protected string nspace; + /// + /// The Name of the Navigation Path. + /// + protected string name; + /// + /// The names of the Pages in the Navigation Path. + /// + protected string[] pages; + /// + /// The Provider that handles the Navigation Path. + /// + protected IPagesStorageProviderV30 provider; + + /// + /// Initializes a new instance of the NavigationPath class. + /// + /// The Full Name of the Navigation Path. + /// The Provider + public NavigationPath(string fullName, IPagesStorageProviderV30 provider) { + NameTools.ExpandFullName(fullName, out nspace, out name); + this.provider = provider; + pages = new string[0]; + } + + /// + /// Gets or sets the full name of the Navigation Path, such as 'Namespace.Path' or 'Path'. + /// + public string FullName { + get { return NameTools.GetFullName(nspace, name); } + set { NameTools.ExpandFullName(value, out nspace, out name); } + } + + /// + /// Gets or sets the Pages of the Path. + /// + public string[] Pages { + get { return pages; } + set { pages = value; } + } + + /// + /// Gets the Provider. + /// + public IPagesStorageProviderV30 Provider { + get { return provider; } + } + + } + + /// + /// Compares two objects, using FullName as the comparison parameter. + /// + public class NavigationPathComparer : IComparer { + + /// + /// Compares two Navigation Paths's FullName. + /// + /// The first object. + /// The second object. + /// The result of the comparison (1, 0 or -1). + public int Compare(NavigationPath x, NavigationPath y) { + return StringComparer.OrdinalIgnoreCase.Compare(x.FullName, y.FullName); + } + + } + +} diff --git a/PluginFramework/PageActivityEventArgs.cs b/PluginFramework/PageActivityEventArgs.cs new file mode 100644 index 0000000..21ddc5d --- /dev/null +++ b/PluginFramework/PageActivityEventArgs.cs @@ -0,0 +1,109 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Contains arguments for Page Activity events. + /// + public class PageActivityEventArgs : EventArgs { + + private PageInfo page; + private string pageOldName; + private string author; + private PageActivity activity; + + /// + /// Initializes a new instance of the class. + /// + /// The page the activity refers to. + /// The old name of the renamed page, or null. + /// The author of the activity, if available, null otherwise. + /// The activity. + public PageActivityEventArgs(PageInfo page, string pageOldName, string author, PageActivity activity) { + this.page = page; + this.pageOldName = pageOldName; + this.author = author; + this.activity = activity; + } + + /// + /// Gets the page the activity refers to. + /// + public PageInfo Page { + get { return page; } + } + + /// + /// Gets the old name of the renamed page, or null. + /// + public string PageOldName { + get { return pageOldName; } + } + + /// + /// Gets the author of the activity. + /// + public string Author { + get { return author; } + } + + /// + /// Gets the activity. + /// + public PageActivity Activity { + get { return activity; } + } + + } + + /// + /// Lists legal Page Activity types. + /// + public enum PageActivity { + /// + /// A page has been created. + /// + PageCreated, + /// + /// A page has been modified. + /// + PageModified, + /// + /// A draft for a page has been saved. + /// + PageDraftSaved, + /// + /// A page has been renamed. + /// + PageRenamed, + /// + /// A page has been rolled back. + /// + PageRolledBack, + /// + /// A page's backups have been deleted. + /// + PageBackupsDeleted, + /// + /// A page has been deleted. + /// + PageDeleted, + /// + /// A message has been posted to a page discussion. + /// + MessagePosted, + /// + /// A message has been modified. + /// + MessageModified, + /// + /// A message has been deleted from a page discussion. + /// + MessageDeleted + } + +} diff --git a/PluginFramework/PageContent.cs b/PluginFramework/PageContent.cs new file mode 100644 index 0000000..70a040c --- /dev/null +++ b/PluginFramework/PageContent.cs @@ -0,0 +1,171 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Contains the Content of a Page. + /// + public class PageContent { + + /// + /// The PageInfo object. + /// + protected PageInfo pageInfo; + /// + /// The Title of the Page. + /// + protected string title; + /// + /// The Username of the user who modified the Page. + /// + protected string user; + /// + /// The Date/Time of the last modification. + /// + protected DateTime lastModified; + /// + /// The comment of the editor, about this revision. + /// + protected string comment; + /// + /// The Content of the Page (WikiMarkup). + /// + protected string content; + /// + /// The keywords, usually used for SEO. + /// + protected string[] keywords; + /// + /// The page description, usually used for SEO. + /// + protected string description; + /// + /// The Pages linked in this Page (both existent and inexistent). + /// + protected string[] linkedPages = new string[0]; + + /// + /// Initializes a new instance of the class. + /// + /// The PageInfo object. + /// The Title. + /// The User that last modified the Page. + /// The last modification Date and Time. + /// The Comment of the editor, about this revision. + /// The unparsed Content. + /// The keywords, usually used for SEO, or null. + /// The description, usually used for SEO, or null. + public PageContent(PageInfo pageInfo, string title, string user, DateTime lastModified, string comment, string content, + string[] keywords, string description) { + + this.pageInfo = pageInfo; + this.title = title; + this.user = user; + this.lastModified = lastModified; + this.content = content; + this.comment = comment; + this.keywords = keywords != null ? keywords : new string[0]; + this.description = description; + } + + /// + /// Gets the PageInfo. + /// + public PageInfo PageInfo { + get { return pageInfo; } + } + + /// + /// Gets the Title. + /// + public string Title { + get { return title; } + } + + /// + /// Gets the User. + /// + public string User { + get { return user; } + } + + /// + /// Gets the last modification Date and Time. + /// + public DateTime LastModified { + get { return lastModified; } + } + + /// + /// Gets the Comment of the editor, about this revision. + /// + public string Comment { + get { return comment; } + } + + /// + /// Gets the unformatted Content. + /// + public string Content { + get { return content; } + } + + /// + /// Gets the keywords, usually used for SEO. + /// + public string[] Keywords { + get { return keywords; } + } + + /// + /// Gets the description, usually used for SEO. + /// + public string Description { + get { return description; } + } + + /// + /// Gets or sets the Linked Pages, both existent and inexistent. + /// + public string[] LinkedPages { + get { return linkedPages; } + set { linkedPages = value; } + } + + /// + /// Determines whether the current instance was built using . + /// + /// True if the instance is empty, false otherwise. + public bool IsEmpty() { + return this is EmptyPageContent; + } + + /// + /// Gets an empty instance of . + /// + /// The page. + /// The instance. + public static PageContent GetEmpty(PageInfo page) { + return new EmptyPageContent(page); + } + + /// + /// Represents an empty page content. + /// + private class EmptyPageContent : PageContent { + + /// + /// Initializes a new instance of the class. + /// + /// The page the content refers to. + public EmptyPageContent(PageInfo page) + : base(page, "", "", DateTime.MinValue, "", "", null, "") { } + + } + + } + +} diff --git a/PluginFramework/PageDocument.cs b/PluginFramework/PageDocument.cs new file mode 100644 index 0000000..9a73b52 --- /dev/null +++ b/PluginFramework/PageDocument.cs @@ -0,0 +1,120 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Represents a page for use with the search engine. + /// + public class PageDocument : IDocument { + + /// + /// The type tag for a . + /// + public const string StandardTypeTag = "P"; + + /// + /// Gets the document name for a Page. + /// + /// The page. + /// The document name. + public static string GetDocumentName(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + return page.FullName; + } + + /// + /// Gets the page name from a document name. + /// + /// The document name. + /// The page name. + public static string GetPageName(string documentName) { + if(documentName == null) throw new ArgumentNullException("documentName"); + if(documentName.Length == 0) throw new ArgumentException("Document Name cannot be empty", "documentName"); + return documentName; + } + + private uint id; + private string name, title, typeTag; + private DateTime dateTime; + private PageInfo pageInfo; + + private Tokenizer tokenizer; + + /// + /// Initializes a new instance of the class. + /// + /// The page. + /// The dumped document data. + /// The tokenizer. + public PageDocument(PageInfo pageInfo, DumpedDocument dumpedDocument, Tokenizer tokenizer) { + if(dumpedDocument == null) throw new ArgumentNullException("dumpedDocument"); + if(tokenizer == null) throw new ArgumentNullException("tokenizer"); + + this.pageInfo = pageInfo; + id = dumpedDocument.ID; + name = dumpedDocument.Name; + typeTag = dumpedDocument.TypeTag; + title = dumpedDocument.Title; + dateTime = dumpedDocument.DateTime; + this.tokenizer = tokenizer; + } + + /// + /// Gets or sets the globally unique ID of the document. + /// + public uint ID { + get { return id; } + set { id = value; } + } + + /// + /// Gets the globally-unique name of the document. + /// + public string Name { + get { return name; } + } + + /// + /// Gets the title of the document, if any. + /// + public string Title { + get { return title; } + } + + /// + /// Gets the tag for the document type. + /// + public string TypeTag { + get { return typeTag; } + } + + /// + /// Gets the document date/time. + /// + public DateTime DateTime { + get { return dateTime; } + } + + /// + /// Performs the tokenization of the document content. + /// + /// The content to tokenize. + /// The extracted words and their positions. + public WordInfo[] Tokenize(string content) { + return tokenizer(content); + } + + /// + /// Gets the page information. + /// + public PageInfo PageInfo { + get { return pageInfo; } + } + + } + +} diff --git a/PluginFramework/PageInfo.cs b/PluginFramework/PageInfo.cs new file mode 100644 index 0000000..0f2edae --- /dev/null +++ b/PluginFramework/PageInfo.cs @@ -0,0 +1,109 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Globalization; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Contains basic information about a Page. + /// + public class PageInfo { + + /// + /// The namespace of the Page. + /// + protected string nspace; + /// + /// The Name of the Page. + /// + protected string name; + /// + /// The Provider that handles the Page. + /// + protected IPagesStorageProviderV30 provider; + /// + /// A value specifying whether the Page should NOT be cached by the engine. + /// + protected bool nonCached; + /// + /// The Page creation Date/Time. + /// + protected DateTime creationDateTime; + + /// + /// Initializes a new instance of the class. + /// + /// The Full Name of the Page. + /// The Pages Storage Provider that manages this Page. + /// The Page creation Date/Time. + public PageInfo(string fullName, IPagesStorageProviderV30 provider, DateTime creationDateTime) { + NameTools.ExpandFullName(fullName, out nspace, out name); + this.provider = provider; + this.creationDateTime = creationDateTime; + } + + /// + /// Gets or sets the full name of the Page, such as 'Namespace.Page' or 'Page'. + /// + public string FullName { + get { return NameTools.GetFullName(nspace, name); } + set { NameTools.ExpandFullName(value, out nspace, out name); } + } + + /// + /// Gets or sets the Pages Storage Provider. + /// + public IPagesStorageProviderV30 Provider { + get { return provider; } + set { provider = value; } + } + + /// + /// Gets or sets a value specifying whether the Page should NOT be cached by the engine. + /// + public bool NonCached { + get { return nonCached; } + set { nonCached = value; } + } + + /// + /// Gets or sets the creation Date/Time. + /// + public DateTime CreationDateTime { + get { return creationDateTime; } + set { creationDateTime = value; } + } + + /// + /// Converts the current PageInfo to a string. + /// + /// The string. + public override string ToString() { + string result = NameTools.GetFullName(nspace, name); + result += " [" + provider.Information.Name + "]"; + return result; + } + + } + + /// + /// Compares two objects, using the FullName as parameter. + /// + /// The comparison is case insensitive. + public class PageNameComparer : IComparer { + + /// + /// Compares two objects, using the FullName as parameter. + /// + /// The first object. + /// The second object. + /// The comparison result (-1, 0 or 1). + public int Compare(PageInfo x, PageInfo y) { + return StringComparer.OrdinalIgnoreCase.Compare(x.FullName, y.FullName); + } + + } + +} diff --git a/PluginFramework/PluginFramework.csproj b/PluginFramework/PluginFramework.csproj new file mode 100644 index 0000000..3aa743a --- /dev/null +++ b/PluginFramework/PluginFramework.csproj @@ -0,0 +1,119 @@ + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {531A83D6-76F9-4014-91C5-295818E2D948} + Library + Properties + ScrewTurn.Wiki.PluginFramework + ScrewTurn.Wiki.PluginFramework + false + + + + + 2.0 + + + v3.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + 512 + true + bin\Debug\ScrewTurn.Wiki.PluginFramework.XML + + + none + true + bin\Release\ + TRACE + prompt + 4 + 512 + bin\Release\ScrewTurn.Wiki.PluginFramework.xml + true + + + + + 3.5 + + + + + + + + AssemblyVersion.cs + + + + + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81} + AclEngine + + + {2DF980A6-4742-49B1-A090-DE79314644D0} + SearchEngine + + + + + \ No newline at end of file diff --git a/PluginFramework/Properties/AssemblyInfo.cs b/PluginFramework/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e373bfb --- /dev/null +++ b/PluginFramework/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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("ScrewTurn Wiki Plugin Framework")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// 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("ba74187b-8c11-4344-84fd-3d1b9b370063")] diff --git a/PluginFramework/RecentChange.cs b/PluginFramework/RecentChange.cs new file mode 100644 index 0000000..14be581 --- /dev/null +++ b/PluginFramework/RecentChange.cs @@ -0,0 +1,126 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Represents a Change. + /// + public class RecentChange { + + private string page; + private string title; + private string messageSubject = null; + private DateTime dateTime; + private string user; + private Change change; + private string descr = null; + + /// + /// Initializes a new instance of the class. + /// + /// The page pame. + /// The page title. + /// The message subject (or null). + /// The date/time. + /// The user. + /// The change. + /// The description (optional). + public RecentChange(string page, string title, string messageSubject, DateTime dateTime, string user, Change change, string descr) { + this.page = page; + this.title = title; + this.messageSubject = messageSubject; + this.dateTime = dateTime; + this.user = user; + this.change = change; + this.descr = descr; + } + + /// + /// Gets the page name. + /// + public string Page { + get { return page; } + } + + /// + /// Gets the page title. + /// + public string Title { + get { return title; } + } + + /// + /// Gets the message subject (or null). + /// + public string MessageSubject { + get { return messageSubject; } + } + + /// + /// Gets the date/time. + /// + public DateTime DateTime { + get { return dateTime; } + } + + /// + /// Gets the user. + /// + public string User { + get { return user; } + } + + /// + /// Gets the change. + /// + public Change Change { + get { return change; } + } + + /// + /// Gets the description (optional). + /// + public string Description { + get { return descr; } + } + + } + + /// + /// Lists possible changes. + /// + public enum Change { + /// + /// A page was updated. + /// + PageUpdated, + /// + /// A page was deleted. + /// + PageDeleted, + /// + /// A page was rolled back. + /// + PageRolledBack, + /// + /// A page was renamed. + /// + PageRenamed, + /// + /// A message was posted to a page discussion. + /// + MessagePosted, + /// + /// A message was deleted from a page discussion. + /// + MessageDeleted, + /// + /// A message was edited in a page discussion. + /// + MessageEdited + } + +} diff --git a/PluginFramework/Snippet.cs b/PluginFramework/Snippet.cs new file mode 100644 index 0000000..391879a --- /dev/null +++ b/PluginFramework/Snippet.cs @@ -0,0 +1,79 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Globalization; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Contains a Snippet. + /// + public class Snippet { + + /// + /// The name of the Snippet. + /// + protected string name; + /// + /// The content of the Snippet. + /// + protected string content; + /// + /// The Provider that handles the Snippet. + /// + protected IPagesStorageProviderV30 provider; + + /// + /// Initializes a new instance of the Snippet class. + /// + /// The Name of the Snippet. + /// The Content of the Snippet. + /// The Provider of the Snippet. + public Snippet(string name, string content, IPagesStorageProviderV30 provider) { + this.name = name; + this.content = content; + this.provider = provider; + } + + /// + /// Gets the Name of the Snippet. + /// + public string Name { + get { return name; } + } + + /// + /// Gets or sets the Content of the Snippet. + /// + public string Content { + get { return content; } + } + + /// + /// Gets the Provider of the Snippet. + /// + public IPagesStorageProviderV30 Provider { + get { return provider; } + } + + } + + /// + /// Compares two objects. + /// + public class SnippetNameComparer : IComparer { + + /// + /// Compares the name of two objects. + /// + /// The first . + /// The second . + /// The result of the comparison (1, 0 or -1). + public int Compare(Snippet x, Snippet y) { + return StringComparer.OrdinalIgnoreCase.Compare(x.Name, y.Name); + } + + } + +} diff --git a/PluginFramework/StDirectoryInfo.cs b/PluginFramework/StDirectoryInfo.cs new file mode 100644 index 0000000..60b97f5 --- /dev/null +++ b/PluginFramework/StDirectoryInfo.cs @@ -0,0 +1,57 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Contains information about a directory. + /// + /// This class is only used for provider-host communication. + public class StDirectoryInfo { + + private string fullPath; + private IFilesStorageProviderV30 provider; + + /// + /// Initializes a new instance of the class. + /// + /// The full path of the directory, for example /dir/sub/ or /. + /// The provider that handles the directory. + public StDirectoryInfo(string fullPath, IFilesStorageProviderV30 provider) { + this.fullPath = fullPath; + this.provider = provider; + } + + /// + /// Gets the full path of the directory, for example /dir/sub/ or /. + /// + public string FullPath { + get { return fullPath; } + } + + /// + /// Gets the provider that handles the directory. + /// + public IFilesStorageProviderV30 Provider { + get { return provider; } + } + + /// + /// Gets the directory of a file. + /// + /// The full file path, such as '/file.txt' or '/directory/sub/file.txt'. + /// The directory, such as '/' or '/directory/sub/'. + public static string GetDirectory(string filePath) { + if(!filePath.StartsWith("/")) filePath = "/" + filePath; + + int lastIndex = filePath.LastIndexOf("/"); + if(lastIndex == 0) return "/"; + else return filePath.Substring(0, lastIndex + 1); + } + + } + +} diff --git a/PluginFramework/StFileInfo.cs b/PluginFramework/StFileInfo.cs new file mode 100644 index 0000000..f4610d1 --- /dev/null +++ b/PluginFramework/StFileInfo.cs @@ -0,0 +1,67 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Contains information about a file. + /// + /// This class is only used for provider-host communication. + public class StFileInfo : FileDetails { + + private string fullName; + private IFilesStorageProviderV30 provider; + + /// + /// Initializes a new instance of the class. + /// + /// The size of the file in bytes. + /// The last modification date/time. + /// The download count. + /// The full name of the file, for example /dir/sub/file.txt or /file.txt. + /// The provider that handles the file. + public StFileInfo(long size, DateTime lastModified, int downloadCount, string fullName, IFilesStorageProviderV30 provider) + : base(size, lastModified, downloadCount) { + + this.fullName = fullName; + this.provider = provider; + } + + /// + /// Initializes a new instance of the class. + /// + /// The file details. + /// The full name. + /// The provider. + public StFileInfo(FileDetails details, string fullName, IFilesStorageProviderV30 provider) + : this(details.Size, details.LastModified, details.RetrievalCount, fullName, provider) { + } + + /// + /// Gets the full name of the file, for example /dir/sub/file.txt or /file.txt. + /// + public string FullName { + get { return fullName; } + } + + /// + /// Gets the provider that handles the file. + /// + public IFilesStorageProviderV30 Provider { + get { return provider; } + } + + /// + /// Gets the directory path of the file. + /// + /// The directory path. + public string GetDirectory() { + return StDirectoryInfo.GetDirectory(fullName); + } + + } + +} diff --git a/PluginFramework/StandardAclManager.cs b/PluginFramework/StandardAclManager.cs new file mode 100644 index 0000000..ca46774 --- /dev/null +++ b/PluginFramework/StandardAclManager.cs @@ -0,0 +1,15 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.AclEngine; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Implements a standard ACL Manager. + /// + public class StandardAclManager : AclManagerBase { + } + +} diff --git a/PluginFramework/StandardIndex.cs b/PluginFramework/StandardIndex.cs new file mode 100644 index 0000000..bf34958 --- /dev/null +++ b/PluginFramework/StandardIndex.cs @@ -0,0 +1,15 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Implements a standard index class. + /// + public class StandardIndex : InMemoryIndexBase { + } + +} diff --git a/PluginFramework/Tokenizer.cs b/PluginFramework/Tokenizer.cs new file mode 100644 index 0000000..bf83569 --- /dev/null +++ b/PluginFramework/Tokenizer.cs @@ -0,0 +1,16 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Defines a delegate that tokenizes strings. + /// + /// The content to tokenize. + /// The tokenized words. + public delegate WordInfo[] Tokenizer(string content); + +} diff --git a/PluginFramework/UserAccountActivityEventArgs.cs b/PluginFramework/UserAccountActivityEventArgs.cs new file mode 100644 index 0000000..48bcb04 --- /dev/null +++ b/PluginFramework/UserAccountActivityEventArgs.cs @@ -0,0 +1,73 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Contains arguments for User Account Activity events. + /// + public class UserAccountActivityEventArgs : EventArgs { + + private UserInfo user; + private UserAccountActivity activity; + + /// + /// Initializes a new instance of the UserAccountActivityEventArgs class. + /// + /// The User Info the activity refers to. + /// The activity performed. + public UserAccountActivityEventArgs(UserInfo user, UserAccountActivity activity) { + this.user = user; + this.activity = activity; + } + + /// + /// Gets the user the activity refers to. + /// + public UserInfo User { + get { return user; } + } + + /// + /// Gets the activity performed. + /// + public UserAccountActivity Activity { + get { return activity; } + } + + } + + /// + /// Lists legal User Account Activity types. + /// + public enum UserAccountActivity { + /// + /// A user account has been added. + /// + AccountAdded, + /// + /// A user account has been activated. + /// + AccountActivated, + /// + /// A user account has been deactivated. + /// + AccountDeactivated, + /// + /// A user account has been modified (email, password). + /// + AccountModified, + /// + /// A user account has been removed. + /// + AccountRemoved, + /// + /// A user account group membership has been changed. + /// + AccountMembershipChanged + } + +} diff --git a/PluginFramework/UserGroup.cs b/PluginFramework/UserGroup.cs new file mode 100644 index 0000000..66a61ac --- /dev/null +++ b/PluginFramework/UserGroup.cs @@ -0,0 +1,93 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Represents a group of users. + /// + public class UserGroup { + + /// + /// The group name. + /// + protected string name; + /// + /// The group description. + /// + protected string description; + /// + /// The users in the group. + /// + protected string[] users = new string[0]; + /// + /// The provider that handles the user group. + /// + protected IUsersStorageProviderV30 provider; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the group. + /// The description of the group. + /// The Users Storage Provider that handles the user group. + public UserGroup(string name, string description, IUsersStorageProviderV30 provider) { + this.name = name; + this.description = description; + this.provider = provider; + } + + /// + /// Gets or sets the name of the user group. + /// + public string Name { + get { return name; } + set { name = value; } + } + + /// + /// Gets or sets the description of the group. + /// + public string Description { + get { return description; } + set { description = value; } + } + + /// + /// Gets or sets the users in the group. + /// + public string[] Users { + get { return users; } + set { users = value; } + } + + /// + /// Gets or sets the provider that handles the user group. + /// + public IUsersStorageProviderV30 Provider { + get { return provider; } + set { provider = value; } + } + + } + + /// + /// Implements a comparer for objects, using Name as parameter. + /// + public class UserGroupComparer : IComparer { + + /// + /// Compares two objects, using Name as parameter. + /// + /// The first object. + /// The second object. + /// The comparison result. + public int Compare(UserGroup x, UserGroup y) { + return StringComparer.OrdinalIgnoreCase.Compare(x.Name, y.Name); + } + + } + +} diff --git a/PluginFramework/UserGroupActivityEventArgs.cs b/PluginFramework/UserGroupActivityEventArgs.cs new file mode 100644 index 0000000..4e291d4 --- /dev/null +++ b/PluginFramework/UserGroupActivityEventArgs.cs @@ -0,0 +1,61 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Contains arguments for the User Group Activity events. + /// + public class UserGroupActivityEventArgs : EventArgs { + + private UserGroup group; + private UserGroupActivity activity; + + /// + /// Initializes a new instance of the class. + /// + /// The user group the activity refers to. + /// The activity performed. + public UserGroupActivityEventArgs(UserGroup group, UserGroupActivity activity) { + this.group = group; + this.activity = activity; + } + + /// + /// Gets the user group the activity refers to. + /// + public UserGroup Group { + get { return group; } + } + + /// + /// Gets the activity performed. + /// + public UserGroupActivity Activity { + get { return activity; } + } + + } + + /// + /// Lists legal user group activity types. + /// + public enum UserGroupActivity { + /// + /// A group has been added. + /// + GroupAdded, + /// + /// A group has been removed. + /// + GroupRemoved, + /// + /// A group has been modified. + /// + GroupModified + } + +} diff --git a/PluginFramework/UserInfo.cs b/PluginFramework/UserInfo.cs new file mode 100644 index 0000000..b10db24 --- /dev/null +++ b/PluginFramework/UserInfo.cs @@ -0,0 +1,144 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Globalization; + +namespace ScrewTurn.Wiki.PluginFramework { + + /// + /// Describes a User. + /// + public class UserInfo { + + /// + /// The Username of the User. + /// + protected string username; + /// + /// The display name of the User. + /// + protected string displayName; + /// + /// The Email address of the User. + /// + protected string email; + /// + /// A value indicating whether the user account is active. + /// + protected bool active; + /// + /// The account creation date/time. + /// + protected DateTime dateTime; + /// + /// The names of the groups the user is member of. + /// + protected string[] groups; + /// + /// The Provider that handles the User. + /// + protected IUsersStorageProviderV30 provider; + + /// + /// Initializes a new instance of the UserInfo class. + /// + /// The Username. + /// The display name. + /// The Email. + /// Specifies whether the Account is active or not. + /// The creation DateTime. + /// The Users Storage Provider that manages the User. + public UserInfo(string username, string displayName, string email, bool active, DateTime dateTime, IUsersStorageProviderV30 provider) { + this.username = username; + this.displayName = displayName; + this.email = email; + this.active = active; + this.dateTime = dateTime; + this.provider = provider; + } + + /// + /// Gets the Username. + /// + public string Username { + get { return username; } + } + + /// + /// Gets or sets the display name. + /// + public string DisplayName { + get { return displayName; } + set { displayName = value; } + } + + /// + /// Gets or sets the Email. + /// + public string Email { + get { return email; } + set { email = value; } + } + + /// + /// Gets or sets a value specifying whether the Account is active or not. + /// + public bool Active { + get { return active; } + set { active = value; } + } + + /// + /// Gets the creation DateTime. + /// + public DateTime DateTime { + get { return dateTime; } + set { dateTime = value; } + } + + /// + /// Gets or sets the names of the groups the user is member of. + /// + public string[] Groups { + get { return groups; } + set { groups = value; } + } + + /// + /// Gets or sets the Users Storage Provider. + /// + public IUsersStorageProviderV30 Provider { + get { return provider; } + set { provider = value; } + } + + /// + /// Converts the current instance to a string. + /// + /// The string. + public override string ToString() { + return username; + } + + } + + /// + /// Provides a method for comparing two UserInfo objects, comparing their Username. + /// + /// The comparison is case unsensitive. + public class UsernameComparer : IComparer { + + /// + /// Compares two UserInfo objects, comparing their Username. + /// + /// The first object. + /// The second object. + /// The comparison result (-1, 0 or 1). + public int Compare(UserInfo x, UserInfo y) { + return StringComparer.OrdinalIgnoreCase.Compare(x.Username, y.Username); + } + + } + +} diff --git a/PluginPack-Tests/DownloadCounterTests.cs b/PluginPack-Tests/DownloadCounterTests.cs new file mode 100644 index 0000000..7cfffe7 --- /dev/null +++ b/PluginPack-Tests/DownloadCounterTests.cs @@ -0,0 +1,100 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using RMC = Rhino.Mocks.Constraints; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Plugins.PluginPack.Tests { + + [TestFixture] + public class DownloadCounterTests { + + [Test] + public void Format() { + MockRepository mocks = new MockRepository(); + + IFilesStorageProviderV30 prov = mocks.StrictMock(); + + string content = string.Format( +@"This is a test page. + + + + + + + + + + + + +", DateTime.Today.AddDays(-60), prov.GetType().FullName); + + // */file.zip was downloaded 13 times + // attn.zip was downloaded 56 times + // Expected output: 138-2-16-68 + + IHostV30 host = mocks.StrictMock(); + host.LogEntry(null, LogEntryType.Warning, null, null); + LastCall.On(host).IgnoreArguments().Repeat.Any(); + Expect.Call(host.GetSettingValue(SettingName.DefaultFilesStorageProvider)).Return( + prov.GetType().FullName).Repeat.Times(4); + Expect.Call(host.GetFilesStorageProviders(true)).Return( + new IFilesStorageProviderV30[] { prov }).Repeat.Times(8); + + StFileInfo[] myFiles = new StFileInfo[] { + new StFileInfo(1000, DateTime.Now, 13, "/my/File.zip", prov), + new StFileInfo(10000, DateTime.Now, 1000, "/my/other-file.zip", prov) + }; + StFileInfo[] myOtherFiles = new StFileInfo[] { + new StFileInfo(1000, DateTime.Now, 13, "/my/OTHER/file.zip", prov), + new StFileInfo(10000, DateTime.Now, 2000, "/my/OTHER/other-file.zip", prov) + }; + + StFileInfo[] attachments = new StFileInfo[] { + new StFileInfo(2000, DateTime.Now, 56, "aTTn.zip", prov), + new StFileInfo(20000, DateTime.Now, 1000, "other-attn.zip", prov) + }; + + // /my/* + Expect.Call(host.ListFiles(null)).IgnoreArguments().Constraints( + RMC.Is.Matching( + delegate(StDirectoryInfo dir) { + return dir.FullPath == "/my/"; + })).Return(myFiles).Repeat.Times(2); + + // /my/other/* + Expect.Call(host.ListFiles(null)).IgnoreArguments().Constraints( + RMC.Is.Matching( + delegate(StDirectoryInfo dir) { + return dir.FullPath == "/my/other/"; + })).Return(myOtherFiles).Repeat.Times(2); + + PageInfo page = new PageInfo("page", null, DateTime.Now); + + Expect.Call(host.FindPage("page")).Return(page).Repeat.Times(4); + Expect.Call(host.FindPage("inexistent-page")).Return(null).Repeat.Twice(); + + Expect.Call(host.ListPageAttachments(page)).Return(attachments).Repeat.Times(4); + + mocks.ReplayAll(); + + DownloadCounter counter = new DownloadCounter(); + counter.Init(host, ""); + + string output = counter.Format(content, null, FormattingPhase.Phase3); + + Assert.IsTrue(output == @"This is a test page. +138-2-16-68" || output == @"This is a test page. +138-2-16-69", "Wrong output"); + + mocks.VerifyAll(); + } + + } + +} diff --git a/PluginPack-Tests/PagesSandboxTests.cs b/PluginPack-Tests/PagesSandboxTests.cs new file mode 100644 index 0000000..e27352d --- /dev/null +++ b/PluginPack-Tests/PagesSandboxTests.cs @@ -0,0 +1,26 @@ + +using System; +using NUnit.Framework; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.Tests; + +namespace ScrewTurn.Wiki.Plugins.PluginPack.Tests { + + /*[TestFixture] + public class PagesSandboxTests : PagesStorageProviderTestScaffolding { + + public override IPagesStorageProviderV30 GetProvider() { + IPagesStorageProviderV30 prov = new PagesSandbox(); + prov.Init(MockHost(), ""); + + return prov; + } + + [Test] + public void Init_ExistingPages() { + Assert.Fail(); + } + + }*/ + +} diff --git a/PluginPack-Tests/PluginPack-Tests.csproj b/PluginPack-Tests/PluginPack-Tests.csproj new file mode 100644 index 0000000..2083156 --- /dev/null +++ b/PluginPack-Tests/PluginPack-Tests.csproj @@ -0,0 +1,85 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {C657F6C0-05E5-4873-97A4-B91ED1F51D85} + Library + Properties + ScrewTurn.Wiki.Plugins.PluginPack.Tests + ScrewTurn.Wiki.Plugins.PluginPack.Tests + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + none + true + bin\Release\ + TRACE + prompt + 4 + true + + + + False + ..\References\Tools\NUnit\framework\nunit.framework.dll + + + False + ..\References\Tools\Rhino.Mocks\Rhino.Mocks.dll + + + + 3.5 + + + + + + + AssemblyVersion.cs + + + + + + + + {531A83D6-76F9-4014-91C5-295818E2D948} + PluginFramework + + + {88212C14-10A0-4D46-8203-D48534465181} + PluginPack + + + {2DF980A6-4742-49B1-A090-DE79314644D0} + SearchEngine + + + {F865670A-DEDE-41B5-B426-48D73C3B5B1C} + TestScaffolding + + + + + \ No newline at end of file diff --git a/PluginPack-Tests/Properties/AssemblyInfo.cs b/PluginPack-Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..39dd5f5 --- /dev/null +++ b/PluginPack-Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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("ScrewTurn Wiki PluginPack Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// 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("f2d48225-bec9-486c-9e84-7e1e0bcf6d15")] diff --git a/PluginPack/DownloadCounter.cs b/PluginPack/DownloadCounter.cs new file mode 100644 index 0000000..4e9775e --- /dev/null +++ b/PluginPack/DownloadCounter.cs @@ -0,0 +1,373 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Plugins.PluginPack { + + /// + /// Implements a formatter provider that counts download of files and attachments. + /// + public class DownloadCounter : IFormatterProviderV30 { + + private static readonly DateTime DefaultStartDate = new DateTime(2009, 1, 1); + private const string CountPlaceholder = "#count#"; + private const string DailyPlaceholder = "#daily#"; + private const string WeeklyPlaceholder = "#weekly#"; + private const string MonthlyPlaceholder = "#monthly#"; + + private IHostV30 _host; + private string _config; + private bool _enableLogging = true; + private static readonly ComponentInformation Info = new ComponentInformation("Download Counter", "ScrewTurn Software", "3.0.0.206", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/DownloadCounter.txt"); + + private static readonly Regex XmlRegex = new Regex(@"\(.+?)\<\/countDownloads\>", + RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + /// + /// Specifies whether or not to execute Phase 1. + /// + public bool PerformPhase1 { + get { return false; } + } + + /// + /// Specifies whether or not to execute Phase 2. + /// + public bool PerformPhase2 { + get { return false; } + } + + /// + /// Specifies whether or not to execute Phase 3. + /// + public bool PerformPhase3 { + get { return true; } + } + + /// + /// Gets the execution priority of the provider (0 lowest, 100 highest). + /// + public int ExecutionPriority { + get { return 50; } + } + + /// + /// Performs a Formatting phase. + /// + /// The raw content to Format. + /// The Context information. + /// The Phase. + /// The Formatted content. + public string Format(string raw, ContextInformation context, FormattingPhase phase) { + // + // + // + // + // All downloads are grouped together + // Pattern placeholders: #COUNT#, #DAILY#, #WEEKLY#, #MONTHLY# (case insensitive) + // Pattern example: "Downloaded #COUNT# times (#MONTHLY#/month)!" + // StartDate omitted -> 2009/01/01 + // Provider omitted -> default + // File/attachment/page not found -> ignored + + StringBuilder buffer = new StringBuilder(raw); + + KeyValuePair block = FindAndRemoveFirstOccurrence(buffer); + + while(block.Key != -1) { + string blockHash = "DownCount-" + block.Value.ToString(); + + string result = null; + + if(System.Web.HttpContext.Current != null) { + result = System.Web.HttpContext.Current.Cache[blockHash] as string; + } + + if(result == null) { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(block.Value); + + string pattern; + DateTime startDate; + GetRootAttributes(doc, out pattern, out startDate); + + double downloads = CountAllDownloads(doc); + + double timeSpanInDays = (DateTime.Now - startDate).TotalDays; + + int dailyDownloads = (int)Math.Round(downloads / timeSpanInDays); + int weeklyDownloads = (int)Math.Round(downloads / (timeSpanInDays / 7D)); + int monthlyDownloads = (int)Math.Round(downloads / (timeSpanInDays / 30D)); + + result = BuildResult(pattern, (int)downloads, dailyDownloads, weeklyDownloads, monthlyDownloads); + + if(System.Web.HttpContext.Current != null) { + System.Web.HttpContext.Current.Cache.Add(blockHash, result, null, DateTime.Now.AddMinutes(10), + System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null); + } + } + + buffer.Insert(block.Key, result); + + block = FindAndRemoveFirstOccurrence(buffer); + } + + return buffer.ToString(); + } + + /// + /// Builds the result. + /// + /// The result pattern. + /// The downloads. + /// The daily downloads. + /// The weekly downloads. + /// The monthly downloads. + /// The result. + private static string BuildResult(string pattern, int downloads, int daily, int weekly, int monthly) { + StringBuilder buffer = new StringBuilder(pattern); + + ReplacePlaceholder(buffer, CountPlaceholder, downloads.ToString()); + ReplacePlaceholder(buffer, DailyPlaceholder, daily.ToString()); + ReplacePlaceholder(buffer, WeeklyPlaceholder, weekly.ToString()); + ReplacePlaceholder(buffer, MonthlyPlaceholder, monthly.ToString()); + + return buffer.ToString(); + } + + /// + /// Replaces a placeholder with its value. + /// + /// The buffer. + /// The placeholder. + /// The value. + private static void ReplacePlaceholder(StringBuilder buffer, string placeholder, string value) { + int index = -1; + + do { + index = buffer.ToString().ToLowerInvariant().IndexOf(placeholder); + if(index != -1) { + buffer.Remove(index, placeholder.Length); + buffer.Insert(index, value); + } + } while(index != -1); + } + + /// + /// Gets the root attributes. + /// + /// The XML document. + /// The pattern. + /// The start date/time. + private static void GetRootAttributes(XmlDocument doc, out string pattern, out DateTime startDate) { + XmlNodeList root = doc.GetElementsByTagName("countDownloads"); + + pattern = TryGetAttribute(root[0], "pattern"); + string startDateTemp = TryGetAttribute(root[0], "startDate"); + + if(!DateTime.TryParseExact(startDateTemp, "yyyy'/'MM'/'dd", null, System.Globalization.DateTimeStyles.AssumeLocal, out startDate)) { + startDate = DefaultStartDate; + } + } + + /// + /// Tries to get the value of an attribute. + /// + /// The node. + /// The name of the attribute. + /// The value of the attribute or null if no value is available. + private static string TryGetAttribute(XmlNode node, string attribute) { + XmlAttribute attr = node.Attributes[attribute]; + if(attr != null) return attr.Value; + else return null; + } + + /// + /// Counts all the downloads. + /// + /// The XML document. + /// The download count. + private int CountAllDownloads(XmlDocument doc) { + XmlNodeList files = doc.GetElementsByTagName("file"); + XmlNodeList attachments = doc.GetElementsByTagName("attachment"); + + int count = 0; + + foreach(XmlNode node in files) { + string name = TryGetAttribute(node, "name"); + string provider = TryGetAttribute(node, "provider"); + + count += CountDownloads(name, provider); + } + + foreach(XmlNode node in attachments) { + string name = TryGetAttribute(node, "name"); + string page = TryGetAttribute(node, "page"); + string provider = TryGetAttribute(node, "provider"); + + count += CountDownloads(name, page, provider); + } + + return count; + } + + /// + /// Counts the downloads of a file. + /// + /// The full file path. + /// The provider or null or string.Empty. + /// The downloads. + private int CountDownloads(string fullFilePath, string providerName) { + if(string.IsNullOrEmpty(fullFilePath)) return 0; + + IFilesStorageProviderV30 provider = GetProvider(providerName); + if(provider == null) return 0; + + if(!fullFilePath.StartsWith("/")) fullFilePath = "/" + fullFilePath; + + string directory = StDirectoryInfo.GetDirectory(fullFilePath); + StFileInfo[] files = _host.ListFiles(new StDirectoryInfo(directory, provider)); + + fullFilePath = fullFilePath.ToLowerInvariant(); + + foreach(StFileInfo file in files) { + if(file.FullName.ToLowerInvariant() == fullFilePath) { + return file.RetrievalCount; + } + } + + LogWarning("File " + provider.GetType().FullName + fullFilePath + " not found"); + return 0; + } + + /// + /// Counts the downloads of a file. + /// + /// The name of the attachment. + /// The full name of the page. + /// The provider or null or string.Empty. + /// The downloads. + private int CountDownloads(string attachmentName, string pageName, string providerName) { + if(string.IsNullOrEmpty(attachmentName)) return 0; + if(string.IsNullOrEmpty(pageName)) return 0; + + PageInfo page = _host.FindPage(pageName); + if(page == null) { + LogWarning("Page " + pageName + " not found"); + return 0; + } + + IFilesStorageProviderV30 provider = GetProvider(providerName); + if(provider == null) return 0; + + StFileInfo[] attachments = _host.ListPageAttachments(page); + + attachmentName = attachmentName.ToLowerInvariant(); + + foreach(StFileInfo attn in attachments) { + if(attn.FullName.ToLowerInvariant() == attachmentName) { + return attn.RetrievalCount; + } + } + + LogWarning("Attachment " + provider.GetType().FullName + "(" + pageName + ") " + attachmentName + " not found"); + return 0; + } + + /// + /// Gets the specified provider or the default one. + /// + /// The provider. + /// + private IFilesStorageProviderV30 GetProvider(string provider) { + if(string.IsNullOrEmpty(provider)) provider = _host.GetSettingValue(SettingName.DefaultFilesStorageProvider); + provider = provider.ToLowerInvariant(); + + IFilesStorageProviderV30[] all = _host.GetFilesStorageProviders(true); + foreach(IFilesStorageProviderV30 prov in all) { + if(prov.GetType().FullName.ToLowerInvariant() == provider) return prov; + } + + LogWarning("Provider " + provider + " not found"); + return null; + } + + /// + /// Finds and removes the first occurrence of the XML markup. + /// + /// The buffer. + /// The index-content data. + private static KeyValuePair FindAndRemoveFirstOccurrence(StringBuilder buffer) { + Match match = XmlRegex.Match(buffer.ToString()); + + if(match.Success) { + buffer.Remove(match.Index, match.Length); + + return new KeyValuePair(match.Index, match.Value); + } + + return new KeyValuePair(-1, null); + } + + /// + /// Logs a warning. + /// + /// The message. + private void LogWarning(string message) { + if(_enableLogging) { + _host.LogEntry(message, LogEntryType.Warning, null, this); + } + } + + /// + /// Prepares the title of an item for display (always during phase 3). + /// + /// The input title. + /// The context information. + /// The prepared title (no markup allowed). + public string PrepareTitle(string title, ContextInformation context) { + return title; + } + + /// + /// Initializes the Storage Provider. + /// + /// The Host of the Component. + /// The Configuration data, if any. + /// If the configuration string is not valid, the methoud should throw a . + public void Init(IHostV30 host, string config) { + this._host = host; + this._config = config != null ? config : ""; + + if(this._config.ToLowerInvariant() == "nolog") _enableLogging = false; + } + + /// + /// Method invoked on shutdown. + /// + /// This method might not be invoked in some cases. + public void Shutdown() { + // Nothing to do + } + + /// + /// Gets the Information about the Provider. + /// + public ComponentInformation Information { + get { return Info; } + } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + public string ConfigHelpHtml { + get { return "Specify nolog for disabling warning log messages for non-existent files or attachments."; } + } + + } + +} diff --git a/PluginPack/MultilanguageContentPlugin.cs b/PluginPack/MultilanguageContentPlugin.cs new file mode 100644 index 0000000..3156f28 --- /dev/null +++ b/PluginPack/MultilanguageContentPlugin.cs @@ -0,0 +1,147 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Plugins.PluginPack { + + /// + /// Implements a Formatter Provider that allows to write multi-language content in Wiki Pages. + /// + public class MultilanguageContentPlugin : IFormatterProviderV30 { + + private IHostV30 host; + private string config; + private ComponentInformation info = new ComponentInformation("Multilanguage Content Plugin", "ScrewTurn Software", "3.0.0.180", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/Multilanguage.txt"); + + private string defaultLanguage = "en-us"; + private bool displayWarning = false; + + private const string DivStyle = "padding: 2px; margin-bottom: 10px; font-size: 11px; background-color: #FFFFC4; border: solid 1px #DDDDDD;"; + private const string StandardMessage = @"
    The content of this Page is localized in your language. To change the language settings, please go to the Language Selection page.
    "; + private const string NotLocalizedMessage = @"
    The content of this Page is not available in your language, and it is displayed in the default language of the Wiki. To change the language settings, please go to the Language Selection page.
    "; + + /// + /// Specifies whether or not to execute Phase 1. + /// + public bool PerformPhase1 { + get { return false; } + } + + /// + /// Specifies whether or not to execute Phase 2. + /// + public bool PerformPhase2 { + get { return false; } + } + + /// + /// Specifies whether or not to execute Phase 3. + /// + public bool PerformPhase3 { + get { return true; } + } + + /// + /// Performs a Formatting phase. + /// + /// The raw content to Format. + /// The Context information. + /// The Phase. + /// The Formatted content. + public string Format(string raw, ContextInformation context, FormattingPhase phase) { + string result = ExtractLocalizedContent(context.Language, raw); // Try to load localized content + bool notLocalized = false; + bool noLocalization = false; + if(result == null) { + result = ExtractLocalizedContent(defaultLanguage, raw); // Load content in the default language + notLocalized = true; + noLocalization = false; + } + if(result == null) { + result = raw; // The Page is not localized, return all the content + notLocalized = false; + noLocalization = true; + } + if(displayWarning && !noLocalization && context.Page != null) { + if(notLocalized) return NotLocalizedMessage + result; + else return StandardMessage + result; + } + else return result; + } + + private string ExtractLocalizedContent(string language, string content) { + string head = "\\<" + language + "\\>", tail = "\\<\\/" + language + "\\>"; + Regex regex = new Regex(head + "(.+?)" + tail, RegexOptions.IgnoreCase | RegexOptions.Singleline); + Match match = regex.Match(content); + StringBuilder sb = new StringBuilder(1000); + while(match.Success) { + sb.Append(match.Groups[1].Value); + match = regex.Match(content, match.Index + match.Length); + } + if(sb.Length > 0) return sb.ToString(); + else return null; + } + + /// + /// Initializes the Storage Provider. + /// + /// The Host of the Component. + /// The Configuration data, if any. + /// If the configuration string is not valid, the methoud should throw a . + public void Init(IHostV30 host, string config) { + this.host = host; + this.config = config != null ? config : ""; + defaultLanguage = host.GetSettingValue(SettingName.DefaultLanguage); + displayWarning = config.ToLowerInvariant().Equals("display warning"); + } + + /// + /// Method invoked on shutdown. + /// + /// This method might not be invoked in some cases. + public void Shutdown() { } + + /// + /// Gets the Information about the Provider. + /// + public ComponentInformation Information { + get { return info; } + } + + /// + /// Gets the execution priority of the provider (0 lowest, 100 highest). + /// + public int ExecutionPriority { + get { return 50; } + } + + /// + /// Prepares the title of an item for display (always during phase 3). + /// + /// The input title. + /// The context information. + /// The prepared title (no markup allowed). + public string PrepareTitle(string title, ContextInformation context) { + string result = ExtractLocalizedContent(context.Language, title); // Try to load localized content + if(context.ForIndexing || result == null) { + result = ExtractLocalizedContent(defaultLanguage, title); // Load content in the default language + } + if(result == null) { + result = title; // The Page is not localized, return all the content + } + return result; + } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + public string ConfigHelpHtml { + get { return "Specify 'display warning' to notify the user of the available content languages."; } + } + + } + +} diff --git a/PluginPack/PagesSandbox.cs b/PluginPack/PagesSandbox.cs new file mode 100644 index 0000000..fdfe59c --- /dev/null +++ b/PluginPack/PagesSandbox.cs @@ -0,0 +1,798 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki.Plugins.PluginPack { + + /// + /// Implements a sandbox plugin for pages. + /// + public class PagesSandbox { + + private IHostV30 host; + private string config; + private static readonly ComponentInformation info = new ComponentInformation("Pages Sandbox", "ScrewTurn Software", "3.0.0.180", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/PagesSandbox.txt"); + + private List allNamespaces = new List(5); + + private List allPages; + private Dictionary allContents; + private Dictionary> allBackups; + private Dictionary allDrafts; + + private List allCategories; + + private uint freeDocumentId = 1; + private uint freeWordId = 1; + private IInMemoryIndex index; + + /// + /// Gets a namespace. + /// + /// The name of the namespace (cannot be null or empty). + /// The , or null if no namespace is found. + public NamespaceInfo GetNamespace(string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty"); + + return allNamespaces.Find(n => n.Name == name); + } + + /// + /// Gets all the sub-namespaces. + /// + /// The sub-namespaces, sorted by name. + public NamespaceInfo[] GetNamespaces() { + lock(this) { + return allNamespaces.ToArray(); + } + } + + /// + /// Adds a new namespace. + /// + /// The name of the namespace. + /// The correct object. + public NamespaceInfo AddNamespace(string name) { + throw new NotImplementedException(); + /*if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + lock(this) { + if(GetNamespace(name) != null) return null; + + // This does not compile unless PagesSandbox implements IPagesStorageProviderV30 + NamespaceInfo newSpace = new NamespaceInfo(name, this, null); + + allNamespaces.Add(newSpace); + + return newSpace; + }*/ + } + + /// + /// Renames a namespace. + /// + /// The namespace to rename. + /// The new name of the namespace. + /// The correct object. + public NamespaceInfo RenameNamespace(NamespaceInfo nspace, string newName) { + if(nspace == null) throw new ArgumentNullException("nspace"); + if(newName == null) throw new ArgumentNullException("newName"); + if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); + + lock(this) { + if(GetNamespace(newName) != null) return null; + + nspace.Name = newName; + + return nspace; + } + } + + /// + /// Sets the default page of a namespace. + /// + /// The namespace of which to set the default page. + /// The page to use as default page, or null. + /// The correct object. + public NamespaceInfo SetNamespaceDefaultPage(NamespaceInfo nspace, PageInfo page) { + if(nspace == null) throw new ArgumentNullException("nspace"); + + lock(this) { + nspace.DefaultPage = page; + + return nspace; + } + } + + /// + /// Removes a namespace. + /// + /// The namespace to remove. + /// true if the namespace is removed, false otherwise. + public bool RemoveNamespace(NamespaceInfo nspace) { + if(nspace == null) throw new ArgumentNullException("nspace"); + + lock(this) { + return allNamespaces.Remove(nspace); + } + } + + /// + /// Moves a page from its namespace into another. + /// + /// The page to move. + /// The destination namespace (null for the root). + /// A value indicating whether to copy the page categories in the destination + /// namespace, if not already available. + /// The correct instance of . + public PageInfo MovePage(PageInfo page, NamespaceInfo destination, bool copyCategories) { + throw new NotImplementedException(); + } + + /// + /// Gets a category. + /// + /// The full name of the category. + /// The , or null if no category is found. + public CategoryInfo GetCategory(string fullName) { + throw new NotImplementedException(); + } + + /// + /// Gets all the Categories in a namespace. + /// + /// The namespace. + /// All the Categories in the namespace, sorted by name. + public CategoryInfo[] GetCategories(NamespaceInfo nspace) { + throw new NotImplementedException(); + } + + /// + /// Gets all the categories of a page. + /// + /// The page. + /// The categories, sorted by name. + public CategoryInfo[] GetCategoriesForPage(PageInfo page) { + throw new NotImplementedException(); + } + + /// + /// Adds a Category. + /// + /// The target namespace (null for the root). + /// The Category name. + /// The correct CategoryInfo object. + /// The moethod should set category's Pages to an empty array. + public CategoryInfo AddCategory(string nspace, string name) { + throw new NotImplementedException(); + } + + /// + /// Renames a Category. + /// + /// The Category to rename. + /// The new Name. + /// The correct CategoryInfo object. + public CategoryInfo RenameCategory(CategoryInfo category, string newName) { + throw new NotImplementedException(); + } + + /// + /// Removes a Category. + /// + /// The Category to remove. + /// True if the Category has been removed successfully. + public bool RemoveCategory(CategoryInfo category) { + throw new NotImplementedException(); + } + + /// + /// Merges two Categories. + /// + /// The source Category. + /// The destination Category. + /// The correct object. + /// The destination Category remains, while the source Category is deleted, and all its Pages re-bound + /// in the destination Category. + public CategoryInfo MergeCategories(CategoryInfo source, CategoryInfo destination) { + throw new NotImplementedException(); + } + + /// + /// Performs a search in the index. + /// + /// The search parameters. + /// The results. + public SearchResultCollection PerformSearch(SearchParameters parameters) { + throw new NotImplementedException(); + } + + /// + /// Rebuilds the search index. + /// + public void RebuildIndex() { + throw new NotImplementedException(); + } + + /// + /// Gets some statistics about the search engine index. + /// + /// The total number of documents. + /// The total number of unique words. + /// The total number of word-document occurrences. + /// The approximated size, in bytes, of the search engine index. + public void GetIndexStats(out int documentCount, out int wordCount, out int occurrenceCount, out long size) { + throw new NotImplementedException(); + } + + /// + /// Gets a value indicating whether the search engine index is corrupted and needs to be rebuilt. + /// + public bool IsIndexCorrupted { + get { throw new NotImplementedException(); } + } + + /// + /// Gets a page. + /// + /// The full name of the page. + /// The , or null if no page is found. + public PageInfo GetPage(string fullName) { + throw new NotImplementedException(); + } + + /// + /// Gets all the Pages in a namespace. + /// + /// The namespace (null for the root). + /// All the Pages in the namespace, sorted by name. + public PageInfo[] GetPages(NamespaceInfo nspace) { + throw new NotImplementedException(); + } + + /// + /// Gets all the pages in a namespace that are bound to zero categories. + /// + /// The namespace (null for the root). + /// The pages, sorted by name. + public PageInfo[] GetUncategorizedPages(NamespaceInfo nspace) { + throw new NotImplementedException(); + } + + /// + /// Gets the Content of a Page. + /// + /// The Page. + /// The Page Content object. + public PageContent GetContent(PageInfo page) { + throw new NotImplementedException(); + } + + /// + /// Gets the content of a draft of a Page. + /// + /// The Page. + /// The draft, or null if no draft exists. + public PageContent GetDraft(PageInfo page) { + throw new NotImplementedException(); + } + + /// + /// Deletes a draft of a Page. + /// + /// The page. + /// true if the draft is deleted, false otherwise. + public bool DeleteDraft(PageInfo page) { + throw new NotImplementedException(); + } + + /// + /// Gets the Backup/Revision numbers of a Page. + /// + /// The Page to get the Backups of. + /// The Backup/Revision numbers. + public int[] GetBackups(PageInfo page) { + throw new NotImplementedException(); + } + + /// + /// Gets the Content of a Backup of a Page. + /// + /// The Page to get the backup of. + /// The Backup/Revision number. + /// The Page Backup. + public PageContent GetBackupContent(PageInfo page, int revision) { + throw new NotImplementedException(); + } + + /// + /// Forces to overwrite or create a Backup. + /// + /// The Backup content. + /// The revision. + /// True if the Backup has been created successfully. + public bool SetBackupContent(PageContent content, int revision) { + throw new NotImplementedException(); + } + + /// + /// Adds a Page. + /// + /// The target namespace (null for the root). + /// The Page Name. + /// The creation Date/Time. + /// The correct PageInfo object or null. + /// This method should not create the content of the Page. + public PageInfo AddPage(string nspace, string name, DateTime creationDateTime) { + throw new NotImplementedException(); + } + + /// + /// Renames a Page. + /// + /// The Page to rename. + /// The new Name. + /// The correct object. + public PageInfo RenamePage(PageInfo page, string newName) { + throw new NotImplementedException(); + } + + /// + /// Modifies the Content of a Page. + /// + /// The Page. + /// The Title of the Page. + /// The Username. + /// The Date/Time. + /// The Comment of the editor, about this revision. + /// The Page Content. + /// The keywords, usually used for SEO. + /// The description, usually used for SEO. + /// The save mode for this modification. + /// true if the Page has been modified successfully, false otherwise. + /// If saveMode equals Draft and a draft already exists, it is overwritten. + public bool ModifyPage(PageInfo page, string title, string username, DateTime dateTime, string comment, string content, string[] keywords, string description, SaveMode saveMode) { + throw new NotImplementedException(); + } + + /// + /// Performs the rollback of a Page to a specified revision. + /// + /// The Page to rollback. + /// The Revision to rollback the Page to. + /// true if the rollback succeeded, false otherwise. + public bool RollbackPage(PageInfo page, int revision) { + throw new NotImplementedException(); + } + + /// + /// Deletes the Backups of a Page, up to a specified revision. + /// + /// The Page to delete the backups of. + /// The newest revision to delete (newer revision are kept) or -1 to delete all the Backups. + /// true if the deletion succeeded, false otherwise. + public bool DeleteBackups(PageInfo page, int revision) { + throw new NotImplementedException(); + } + + /// + /// Removes a Page. + /// + /// The Page to remove. + /// True if the Page is removed successfully. + public bool RemovePage(PageInfo page) { + throw new NotImplementedException(); + } + + /// + /// Binds a Page with one or more Categories. + /// + /// The Page to bind. + /// The Categories to bind the Page with. + /// True if the binding succeeded. + /// After a successful operation, the Page is bound with all and only the categories passed as argument. + public bool RebindPage(PageInfo page, string[] categories) { + throw new NotImplementedException(); + } + + /// + /// Gets the Page Messages. + /// + /// The Page. + /// The list of the first-level Messages, containing the replies properly nested, sorted by date/time. + public Message[] GetMessages(PageInfo page) { + throw new NotImplementedException(); + } + + /// + /// Gets the total number of Messages in a Page Discussion. + /// + /// The Page. + /// The number of messages. + public int GetMessageCount(PageInfo page) { + throw new NotImplementedException(); + } + + /// + /// Removes all messages for a page and stores the new messages. + /// + /// The page. + /// The new messages to store. + /// true if the messages are stored, false otherwise. + public bool BulkStoreMessages(PageInfo page, Message[] messages) { + throw new NotImplementedException(); + } + + /// + /// Adds a new Message to a Page. + /// + /// The Page. + /// The Username. + /// The Subject. + /// The Date/Time. + /// The Body. + /// The Parent Message ID, or -1. + /// True if the Message is added successfully. + public bool AddMessage(PageInfo page, string username, string subject, DateTime dateTime, string body, int parent) { + throw new NotImplementedException(); + } + + /// + /// Removes a Message. + /// + /// The Page. + /// The ID of the Message to remove. + /// A value specifying whether or not to remove the replies. + /// True if the Message is removed successfully. + public bool RemoveMessage(PageInfo page, int id, bool removeReplies) { + throw new NotImplementedException(); + } + + /// + /// Modifies a Message. + /// + /// The Page. + /// The ID of the Message to modify. + /// The Username. + /// The Subject. + /// The Date/Time. + /// The Body. + /// True if the Message is modified successfully. + public bool ModifyMessage(PageInfo page, int id, string username, string subject, DateTime dateTime, string body) { + throw new NotImplementedException(); + } + + /// + /// Gets all the Navigation Paths in a Namespace. + /// + /// The Namespace. + /// All the Navigation Paths, sorted by name. + public NavigationPath[] GetNavigationPaths(NamespaceInfo nspace) { + throw new NotImplementedException(); + } + + /// + /// Adds a new Navigation Path. + /// + /// The target namespace (null for the root). + /// The Name of the Path. + /// The Pages array. + /// The correct object. + public NavigationPath AddNavigationPath(string nspace, string name, PageInfo[] pages) { + throw new NotImplementedException(); + } + + /// + /// Modifies an existing navigation path. + /// + /// The navigation path to modify. + /// The new pages array. + /// The correct object. + public NavigationPath ModifyNavigationPath(NavigationPath path, PageInfo[] pages) { + throw new NotImplementedException(); + } + + /// + /// Removes a Navigation Path. + /// + /// The navigation path to remove. + /// true if the path is removed, false otherwise. + public bool RemoveNavigationPath(NavigationPath path) { + throw new NotImplementedException(); + } + + /// + /// Gets all the snippets. + /// + /// All the snippets, sorted by name. + public Snippet[] GetSnippets() { + throw new NotImplementedException(); + } + + /// + /// Adds a new snippet. + /// + /// The name of the snippet. + /// The content of the snippet. + /// The correct object. + public Snippet AddSnippet(string name, string content) { + throw new NotImplementedException(); + } + + /// + /// Modifies an existing snippet. + /// + /// The name of the snippet to modify. + /// The content of the snippet. + /// The correct object. + public Snippet ModifySnippet(string name, string content) { + throw new NotImplementedException(); + } + + /// + /// Removes a new Snippet. + /// + /// The Name of the Snippet to remove. + /// true if the snippet is removed, false otherwise. + public bool RemoveSnippet(string name) { + throw new NotImplementedException(); + } + + /// + /// Gets all the content templates. + /// + /// All the content templates, sorted by name. + public ContentTemplate[] GetContentTemplates() { + throw new NotImplementedException(); + } + + /// + /// Adds a new content template. + /// + /// The name of template. + /// The content of the template. + /// The correct object. + public ContentTemplate AddContentTemplate(string name, string content) { + throw new NotImplementedException(); + } + + /// + /// Modifies an existing content template. + /// + /// The name of the template to modify. + /// The content of the template. + /// The correct object. + public ContentTemplate ModifyContentTemplate(string name, string content) { + throw new NotImplementedException(); + } + + /// + /// Removes a content template. + /// + /// The name of the template to remove. + /// true if the template is removed, false otherwise. + public bool RemoveContentTemplate(string name) { + throw new NotImplementedException(); + } + + /// + /// Gets a value specifying whether the provider is read-only, i.e. it can only provide data and not store it. + /// + public bool ReadOnly { + get { throw new NotImplementedException(); } + } + + /// + /// Initializes the Storage Provider. + /// + /// The Host of the Component. + /// The Configuration data, if any. + /// If the configuration string is not valid, the methoud should throw a . + public void Init(IHostV30 host, string config) { + if(host == null) throw new ArgumentNullException("host"); + if(config == null) throw new ArgumentNullException("config"); + + this.host = host; + this.config = config; + + allPages = new List(50); + allContents = new Dictionary(50); + allBackups = new Dictionary>(50); + allDrafts = new Dictionary(5); + + allCategories = new List(10); + + // Prepare search index + index = new StandardIndex(); + index.SetBuildDocumentDelegate(BuildDocumentHandler); + index.IndexChanged += index_IndexChanged; + } + + private void index_IndexChanged(object sender, IndexChangedEventArgs e) { + lock(this) { + if(e.Change == IndexChangeType.DocumentAdded) { + List newWords = new List(e.ChangeData.Words.Count); + foreach(DumpedWord w in e.ChangeData.Words) { + newWords.Add(new WordId(w.Text, freeWordId)); + freeWordId++; + } + + e.Result = new IndexStorerResult(freeDocumentId, newWords); + freeDocumentId++; + } + else e.Result = null; + } + } + + /// + /// Handles the construction of an for the search engine. + /// + /// The input dumped document. + /// The resulting . + private IDocument BuildDocumentHandler(DumpedDocument dumpedDocument) { + if(dumpedDocument.TypeTag == PageDocument.StandardTypeTag) { + string pageName = PageDocument.GetPageName(dumpedDocument.Name); + + PageInfo page = GetPage(pageName); + + if(page == null) return null; + else return new PageDocument(page, dumpedDocument, TokenizeContent); + } + else if(dumpedDocument.TypeTag == MessageDocument.StandardTypeTag) { + string pageFullName; + int id; + MessageDocument.GetMessageDetails(dumpedDocument.Name, out pageFullName, out id); + + PageInfo page = GetPage(pageFullName); + if(page == null) return null; + else return new MessageDocument(page, id, dumpedDocument, TokenizeContent); + } + else return null; + } + + /// + /// Tokenizes page content. + /// + /// The content to tokenize. + /// The tokenized words. + private static WordInfo[] TokenizeContent(string content) { + WordInfo[] words = SearchEngine.Tools.Tokenize(content); + return words; + } + + /// + /// Indexes a page. + /// + /// The content of the page. + /// The number of indexed words, including duplicates. + private int IndexPage(PageContent content) { + lock(this) { + string documentName = PageDocument.GetDocumentName(content.PageInfo); + + DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(content.PageInfo, content.Title), + PageDocument.StandardTypeTag, content.LastModified); + + // Store the document + // The content should always be prepared using IHost.PrepareForSearchEngineIndexing() + return index.StoreDocument(new PageDocument(content.PageInfo, ddoc, TokenizeContent), + content.Keywords, host.PrepareContentForIndexing(content.PageInfo, content.Content), null); + } + } + + /// + /// Removes a page from the search engine index. + /// + /// The content of the page to remove. + private void UnindexPage(PageContent content) { + lock(this) { + string documentName = PageDocument.GetDocumentName(content.PageInfo); + + DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(content.PageInfo, content.Title), + PageDocument.StandardTypeTag, content.LastModified); + index.RemoveDocument(new PageDocument(content.PageInfo, ddoc, TokenizeContent), null); + } + } + + /// + /// Indexes a message. + /// + /// The page. + /// The message ID. + /// The subject. + /// The date/time. + /// The body. + /// The number of indexed words, including duplicates. + private int IndexMessage(PageInfo page, int id, string subject, DateTime dateTime, string body) { + lock(this) { + // Trim "RE:" to avoid polluting the search engine index + if(subject.ToLowerInvariant().StartsWith("re:") && subject.Length > 3) subject = subject.Substring(3).Trim(); + + string documentName = MessageDocument.GetDocumentName(page, id); + + DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(null, subject), + MessageDocument.StandardTypeTag, dateTime); + + // Store the document + // The content should always be prepared using IHost.PrepareForSearchEngineIndexing() + return index.StoreDocument(new MessageDocument(page, id, ddoc, TokenizeContent), null, + host.PrepareContentForIndexing(null, body), null); + } + } + + /// + /// Indexes a message tree. + /// + /// The page. + /// The tree root. + private void IndexMessageTree(PageInfo page, Message root) { + IndexMessage(page, root.ID, root.Subject, root.DateTime, root.Body); + foreach(Message reply in root.Replies) { + IndexMessageTree(page, reply); + } + } + + /// + /// Removes a message from the search engine index. + /// + /// The page. + /// The message ID. + /// The subject. + /// The date/time. + /// The body. + /// The number of indexed words, including duplicates. + private void UnindexMessage(PageInfo page, int id, string subject, DateTime dateTime, string body) { + lock(this) { + // Trim "RE:" to avoid polluting the search engine index + if(subject.ToLowerInvariant().StartsWith("re:") && subject.Length > 3) subject = subject.Substring(3).Trim(); + + string documentName = MessageDocument.GetDocumentName(page, id); + + DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(null, subject), + MessageDocument.StandardTypeTag, DateTime.Now); + index.RemoveDocument(new MessageDocument(page, id, ddoc, TokenizeContent), null); + } + } + + /// + /// Removes a message tree from the search engine index. + /// + /// The page. + /// The tree root. + private void UnindexMessageTree(PageInfo page, Message root) { + UnindexMessage(page, root.ID, root.Subject, root.DateTime, root.Body); + foreach(Message reply in root.Replies) { + UnindexMessageTree(page, reply); + } + } + + /// + /// Method invoked on shutdown. + /// + /// This method might not be invoked in some cases. + public void Shutdown() { + // Nothing do to + } + + /// + /// Gets the Information about the Provider. + /// + public ComponentInformation Information { + get { return info; } + } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + public string ConfigHelpHtml { + get { return null; } + } + + } + +} diff --git a/PluginPack/PluginPack.csproj b/PluginPack/PluginPack.csproj new file mode 100644 index 0000000..311f1b2 --- /dev/null +++ b/PluginPack/PluginPack.csproj @@ -0,0 +1,119 @@ + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {88212C14-10A0-4D46-8203-D48534465181} + Library + Properties + ScrewTurn.Wiki.Plugins.PluginPack + PluginPack + + + 2.0 + + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + OnOutputUpdated + v3.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + 512 + true + bin\Debug\PluginPack.XML + + + none + true + bin\Release\ + TRACE + prompt + 4 + 512 + true + bin\Release\PluginPack.XML + + + + + 3.5 + + + + + + + + AssemblyVersion.cs + + + + + + + + + + {531A83D6-76F9-4014-91C5-295818E2D948} + PluginFramework + + + {2DF980A6-4742-49B1-A090-DE79314644D0} + SearchEngine + + + + + False + .NET Framework 2.0 %28x86%29 + true + + + False + .NET Framework 3.0 %28x86%29 + false + + + False + .NET Framework 3.5 + false + + + + + + + + + + + + \ No newline at end of file diff --git a/PluginPack/Properties/AssemblyInfo.cs b/PluginPack/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..551de80 --- /dev/null +++ b/PluginPack/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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("ScrewTurn Wiki Plugin Pack")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// 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("275862ff-5852-4e38-a8c5-e8d447414f58")] diff --git a/PluginPack/Resources/UnfuddleTickets.xsl b/PluginPack/Resources/UnfuddleTickets.xsl new file mode 100644 index 0000000..63acaef --- /dev/null +++ b/PluginPack/Resources/UnfuddleTickets.xsl @@ -0,0 +1,101 @@ + + + + +
    + + +

    Priority: Highest

    +
    + + + +

    Priority: High

    +
    + + + +

    Priority: Normal

    +
    + + + +

    Priority: Low

    +
    + + + +

    Priority: Lowest

    +
    + +
    +
    + + + + + + + + + + + + + + +
    + # + + Summary + + Status + + Priority + + Milestone +
    +
    +
    +
    + + + + priority_{priority} tablerowalternate + + + priority_{priority} tablerow + + + + + + + + + + + + + + + + Highest + High + Normal + Low + Lowest + + + + + + None + + + + + + + +
    diff --git a/PluginPack/UnfuddleTickets.cs b/PluginPack/UnfuddleTickets.cs new file mode 100644 index 0000000..a17fde0 --- /dev/null +++ b/PluginPack/UnfuddleTickets.cs @@ -0,0 +1,292 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; +using System.Web.Caching; +using System.Xml; +using System.Xml.Xsl; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Plugins.PluginPack { + + /// + /// Implements a formatter that display tickets from Unfuddle. + /// + public class UnfuddleTickets : IFormatterProviderV30 { + + private const string ConfigHelpHtmlValue = "Config consists of three lines:
    <Url> - The base url to the Unfuddle API (i.e. http://account_name.unfuddle.com/api/v1/projects/project_ID)
    <Username> - The username to the unfuddle account to use for authentication
    <Password> - The password to the unfuddle account to use for authentication
    "; + private const string LoadErrorMessage = "Unable to load ticket report at this time."; + private static readonly Regex UnfuddleRegex = new Regex(@"{unfuddle}", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private static readonly ComponentInformation Info = new ComponentInformation("Unfuddle Tickets", "ScrewTurn Software", "3.0.0.204", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/UnfuddleTickets.txt"); + + private string _config; + private IHostV30 _host; + private string _baseUrl; + private string _username; + private string _password; + + #region IFormatterProviderV30 Members + + /// + /// Specifies whether or not to execute Phase 1. + /// + public bool PerformPhase1 { + get { + return false; + } + } + + /// + /// Specifies whether or not to execute Phase 2. + /// + public bool PerformPhase2 { + get { + return false; + } + } + + /// + /// Specifies whether or not to execute Phase 3. + /// + public bool PerformPhase3 { + get { + return true; + } + } + + /// + /// Gets the execution priority of the provider (0 lowest, 100 highest). + /// + public int ExecutionPriority { + get { + return 50; + } + } + + /// + /// Gets the Information about the Provider. + /// + public ComponentInformation Information { + get { + return Info; + } + } + + /// + /// Performs a Formatting phase. + /// + /// The raw content to Format. + /// The Context information. + /// The Phase. + /// The Formatted content. + public string Format(string raw, ContextInformation context, FormattingPhase phase) { + var buffer = new StringBuilder(raw); + + var block = FindAndRemoveFirstOccurrence(buffer); + + if(block.Key != -1) { + string unfuddleTickets = null; + if(HttpContext.Current != null) + unfuddleTickets = HttpContext.Current.Cache["UnfuddleTicketsStore"] as string; + + if(string.IsNullOrEmpty(unfuddleTickets)) + unfuddleTickets = LoadUnfuddleTicketsFromWeb(); + + if(string.IsNullOrEmpty(unfuddleTickets)) + unfuddleTickets = LoadErrorMessage; + + do { + buffer.Insert(block.Key, unfuddleTickets); + block = FindAndRemoveFirstOccurrence(buffer); + } while(block.Key != -1); + } + + return buffer.ToString(); + } + + /// + /// Prepares the title of an item for display (always during phase 3). + /// + /// The input title. + /// The context information. + /// The prepared title (no markup allowed). + public string PrepareTitle(string title, ContextInformation context) { + return title; + } + + /// + /// Initializes the Storage Provider. + /// + /// The Host of the Component. + /// The Configuration data, if any. + /// If the configuration string is not valid, the methoud should throw a . + public void Init(IHostV30 host, string config) { + _host = host; + _config = config ?? string.Empty; + var configEntries = _config.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries); + + if(configEntries.Length != 3) + throw new InvalidConfigurationException("Configuration missing required parameters."); + + _baseUrl = configEntries[0]; + + if(_baseUrl.EndsWith("/")) + _baseUrl = _baseUrl.Substring(0, _baseUrl.Length - 1); + + _username = configEntries[1]; + _password = configEntries[2]; + } + + /// + /// Method invoked on shutdown. + /// + /// This method might not be invoked in some cases. + public void Shutdown() { + // Nothing to do + } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + public string ConfigHelpHtml { + get { + return ConfigHelpHtmlValue; + } + } + + #endregion + + #region Private Methods + + /// + /// Finds and removes the first occurrence of the custom tag. + /// + /// The buffer. + /// The index->content data. + private static KeyValuePair FindAndRemoveFirstOccurrence(StringBuilder buffer) { + Match match = UnfuddleRegex.Match(buffer.ToString()); + + if(match.Success) { + buffer.Remove(match.Index, match.Length); + + return new KeyValuePair(match.Index, match.Value); + } + + return new KeyValuePair(-1, null); + } + + /// + /// Builds an xml document from API calls to Unfuddle.com then runs them through an Xslt to format them. + /// + /// An html string that contains the tables to display the ticket information, or null + private string LoadUnfuddleTicketsFromWeb() { + var xml = BuildXmlFromApiCalls(); + if(xml == null) + return null; + + var settings = new XsltSettings { + EnableScript = true, + EnableDocumentFunction = true + }; + var xsl = new XslCompiledTransform(true); + using(var reader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream("ScrewTurn.Wiki.Plugins.PluginPack.Resources.UnfuddleTickets.xsl"))) { + xsl.Load(reader, settings, new XmlUrlResolver()); + } + + string results; + using(var sw = new StringWriter()) { + using(var xnr = new XmlNodeReader(xml)) { + xsl.Transform(xnr, null, sw); + } + results = sw.ToString(); + } + + HttpContext.Current.Cache.Add("UnfuddleTicketsStore", results, null, DateTime.Now.AddMinutes(10), + Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); + return results; + } + + /// + /// Builds 3 Xml Documents, the first two are lookups for Milestone, and People information, the second is the + /// ticket information. + /// + /// + private XmlDocument BuildXmlFromApiCalls() { + var milestones = GetXml("/milestones", _username, _password); + if(milestones == null) { + LogWarning("Exception occurred while pulling unfuddled ticket information from the API."); + return null; + } + + var people = GetXml("/people", _username, _password); + if(people == null) { + LogWarning("Exception occurred while pulling unfuddled ticket information from the API."); + return null; + } + + var tickets = GetXml("/ticket_reports/dynamic?sort_by=priority&sort_direction=DESC&conditions_string=status-neq-closed&group_by=priority&fields_string=number,priority,summary,milestone,status,version", _username, _password); + if(tickets == null) { + LogWarning("Exception occurred while pulling unfuddled ticket information from the API."); + return null; + } + + var results = new XmlDocument(); + results.AppendChild(results.CreateXmlDeclaration("1.0", "UTF-8", string.Empty)); + var element = results.CreateElement("root"); + results.AppendChild(element); + element.AppendChild(results.ImportNode(milestones.ChildNodes[1], true)); + element.AppendChild(results.ImportNode(people.ChildNodes[1], true)); + element.AppendChild(results.ImportNode(tickets.ChildNodes[1], true)); + + return results; + } + + /// + /// Produces an API call, then returns the results as an Xml Document + /// + /// The Url to the specific API call + /// An unfuddle account username + /// The password to above unfuddle account + /// + private XmlDocument GetXml(string Url, string Username, string Password) { + try { + var results = new XmlDocument(); + Url = string.Format("{0}{1}", _baseUrl, Url); + var request = WebRequest.Create(Url); + request.Credentials = new NetworkCredential(Username, Password); + var response = request.GetResponse(); + using(var reader = new StreamReader(response.GetResponseStream())) { + var xmlString = reader.ReadToEnd(); + try { + results.LoadXml(xmlString); + } + catch { + LogWarning("Received Unexpected Response from Unfuddle Server."); + } + } + return results; + } + catch(Exception ex) { + LogWarning(string.Format("Exception occurred: {0}", ex.Message)); + return null; + } + } + + /// + /// Logs a warning. + /// + /// The message. + private void LogWarning(string message) { + _host.LogEntry(message, LogEntryType.Warning, null, this); + } + + #endregion + + } + +} diff --git a/References/Lib/Anthem.NET/Anthem-1.5.2-Source.zip b/References/Lib/Anthem.NET/Anthem-1.5.2-Source.zip new file mode 100644 index 0000000..1e1a9a2 Binary files /dev/null and b/References/Lib/Anthem.NET/Anthem-1.5.2-Source.zip differ diff --git a/References/Lib/Anthem.NET/Anthem.XML b/References/Lib/Anthem.NET/Anthem.XML new file mode 100644 index 0000000..1fdb21d --- /dev/null +++ b/References/Lib/Anthem.NET/Anthem.XML @@ -0,0 +1,4618 @@ + + + + Anthem + + + + + Creates an updatable control in the Web Parts control set for hosting WebPart controls on a Web page. + + + + + Controls that implement this interface can have their HTML + updated on the client page after a call back returns. + + + + + Gets or sets a value which indicates whether the control should be updated after + the current callback. Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + + + + + + Excecutes , + then sets to true. + + + + + Excecutes , + then sets to true. + + + + + Excecutes , + then sets to true. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable radio button control. + + + + + Controls that implement this interface will implement a callback + for each supported event, unless EnableCallBack is false; + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Use OnPreRender. AddAttributesToRender is not called in ASP.NET 1.1. + + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the Checkbox state automatically calls back to + the server when clicked. Mimics the AutoPostBack property. + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable control that displays a link to another Web page. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable control that displays a summary of all validation errors inline on a Web page, in a message box, or both. + + + + + If ShowMessageBox=true, use Anthem.Manager to display the validation + summary in a javascript alert. + + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value which indicates whether the control should display the validation summary as a client-side alert. + + true if the control should display the validation summary on the client after a callback; otherwise, false. The default is true. + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + An updatable data bound list control that displays items using templates. + + + Builtin commands (Edit, Delete, etc.) will use callbacks if + is true. Child controls will use + callbacks if is true. + + + + + Controls that implement this interface act as a container for + other controls that may generate postbacks. When AddCallBacks + is true, this control will inject callbacks into all the + child controls. + + + + + Gets or sets a value indicating whether the control should convert child control postbacks into callbacks. + + + true if the control should convert child control postbacks to + callbacks; otherwise, false. The default is + true. + + + Only controls that recognizes will be converted. + + + + + Excecutes , + then sets to true. + + + + + Excecutes , + then sets to true. + + + + + Excecutes , + then sets to true. + + + + + Excecutes , + then sets to true. + + + + + Excecutes , + then sets to true. + + + + + Excecutes , + then sets to true. + + + + + Renders the control inside of a <div> element to the specified HTML writer. + + A that contains the output stream to render on the client. + + + + Raises the event and registers this control with . + + A that contains the event data. + + + + Renders the control contents. will determine if + the control is rendered on the client. + + A that contains the output stream to render on the client. + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should convert child control postbacks into callbacks. + + + true if the control should convert child control postbacks to + callbacks; otherwise, false. The default is + true. + + + Only controls that recognizes will be converted. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Displays an updatable check box that allows the user to select a true or false condition. Uses callbacks if is true. + + + + + Use OnPreRender with CheckBox. AddAttributesToRender is not + called in ASP.NET 1.1 + + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the Checkbox state automatically calls back to + the server when clicked. Mimics the AutoPostBack property. + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + An updatable data-bound list control that allows custom layout by repeating a + specified template for each item displayed in the list. + + + + + Raises the and sets + to true. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should convert child control postbacks into callbacks. + + + true if the control should convert child control postbacks to + callbacks; otherwise, false. The default is + true. + + + Only controls that recognizes will be converted. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable container for dynamically added server controls on the Web page. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should convert child control postbacks into callbacks. + + + true if the control should convert child control postbacks to + callbacks; otherwise, false. The default is + true. + + + Only controls that recognizes will be converted. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + The Manager class is responsible for managing all of the interaction between ASP.NET + and the Anthem controls. + + + + Adds the script to the control's attribute collection. + + If the attribute already exists, the script is prepended to the existing + value. + + The control to modify. + The attribute to modify. + The script to add to the attribute. + + + Adds the script to the item's attribute collection. + The control to modify. + The to modify. + The attribute to modify. + The script to add. + + + + Add the script to a list of scripts to be evaluated on the client during the + callback response processing. + + To not include <script></script> tags. + + + Anthem.Manager.AddScriptForClientSideEval("alert('Hello');"); + + + The script to evaluate. + + + + Obtains a reference to a clinet-side javascript function that causes, when + invoked, the client to callback to the server. + + + + + Obtains a reference to a clinet-side javascript function that causes, when + invoked, the client to callback to the server. + + + + + Obtains a reference to a clinet-side javascript function that causes, when + invoked, the client to callback to the server. + + + + + Obtains a reference to a clinet-side javascript function that causes, when + invoked, the client to callback to the server. + + + + Returns a value indicating if the control is visible on the client. + + + Returns a refernce to . + + + Returns the input string ending with a semicolon (;). + + + Registers the page with . + + + Registers the control with . + + + + Registers the client script with the Page object and with Anthem.Manager using a + type, key, and script literal. + + The type of the client script to register. + The key of the client script to register. + The client script literal to register. + + A client script is uniquely identified by its key and its type. + Scripts with the same key and type are considered duplicates. + Only one script with a given type and key pair can be registered with the page. + Attempting to register a script that is already registered does not create a + duplicate of the script. + Call the Page.IsClientScriptBlockRegistered method to + determine whether a client script with a given key and type pair is already + registered and avoid unnecessarily attempting to add the script. + In this overload of the RegisterClientScriptBlock method, + you must make sure that the script provided in the script parameter + is wrapped in a <script> element block. + The RegisterClientScriptBlock method adds a script block to the + top of the rendered page when it is first rendered. All script blocks are + rendered in the <head> of the page during callback processing after + the page updates have been applied. + + + + + Registers the client script with the Page object and with Anthem.Manager using a + type, key, and script literal. + + The type of the client script to register. + The key of the client script to register. + The client script literal to register. + A Boolean value indicating whether to add + script tags. + + A client script is uniquely identified by its key and its type. + Scripts with the same key and type are considered duplicates. + Only one script with a given type and key pair can be registered with the page. + Attempting to register a script that is already registered does not create a + duplicate of the script. + Call the IsClientScriptBlockRegistered method to + determine whether a client script with a given key and type pair is already + registered and avoid unnecessarily attempting to add the script. + In this overload of the RegisterClientScriptBlock method, you can indicate + whether the script provided in the script parameter is wrapped with a <script> + element block by using the addScriptTags parameter. Setting addScriptTags to + true indicates that script tags will be added automatically. + The RegisterClientScriptBlock method adds a script block to the + top of the rendered page when it is first rendered. All script blocks are + rendered in the <head> of the page during callback processing after + the page updates have been applied. + + + + + Registers the client script include with the Page object and Anthem.Manager + using a key, and a URL. + + The key of the client script include to register. + The URL of the client script include to register. + + This overload of the RegisterClientScriptInclude method takes key and + url parameters to identify the script include. + To resolve the client URL, use the ResolveClientUrl method. This method + uses the context of the URL on which it is called to resolve the path. + This method adds a script block at the top of the rendered page during + the initial load and in the <head> element block after each callback. + + + + + + Registers the client script include with the Page object and Anthem.Manager + using a type, a key, and a URL. + + The type of the client script include to register. + The key of the client script include to register. + The URL of the client script include to register. + + This overload of the RegisterClientScriptInclude method takes key and + url parameters to identify the script, as well as a type parameter to specify + the identification of the client script include. You specify the type based + on the object that will be accessing the resource. For instance, when using a + Page instance to access the resource, you specify the Page type. + To resolve the client URL, use the ResolveClientUrl method. This method + uses the context of the URL on which it is called to resolve the path. + This method adds a script block at the top of the rendered page during + the initial load and in the <head> element block after each callback. + + + + + + Registers the client script resource with the Page object and with Anthem.Manager + using a type and a resource name. + + The type of the client script resource to register. + The name of the client script resource to register. + + The RegisterClientScriptResource method is used when accessing compiled-in + resources from assemblies through the WebResource.axd HTTP handler. The + RegisterClientScriptResource method registers the script with the Page object + and prevents duplicate scripts. This method wraps the contents of the resource + URL with a <script> element block. + + + + + Registers the client script with Anthem.Manager using a key and a script literal. + + The key of the client script to register. + The client script literal to register. + + A client script is uniquely identified by its key. + Scripts with the same key are considered duplicates. + Only one script with a given key can be registered with Anthem.Manager. + Attempting to register a script that is already registered does not create a + duplicate of the script. + The script block added by the RegisterPageScriptBlock can be wrapped + with a <script> element block, though this is not required. + The script block added by RegisterPageScriptBlock will only be rendered + by Anthem.Manager during callback processing. If you want to render the script + during the initial load use RegisterClientScriptBlock or RegisterStartupScript. + The script block added by RegisterPageScriptBlock will be rendered in + the <head> element block after the page updates have been applied. + + + + + Registers the client script with the Page object and with Anthem.Manager using a + type, key, and script literal. + + The type of the client script to register. + The key of the client script to register. + The client script literal to register. + + A client script is uniquely identified by its key and its type. + Scripts with the same key and type are considered duplicates. + Only one script with a given type and key pair can be registered with the page. + Attempting to register a script that is already registered does not create a + duplicate of the script. + Call the Page.IsClientScriptBlockRegistered method to + determine whether a client script with a given key and type pair is already + registered and avoid unnecessarily attempting to add the script. + In this overload of the RegisterStartupScript method, + you must make sure that the script provided in the script parameter + is wrapped in a <script> element block. + The script block added by the RegisterStartupScript method executes + when the page finishes loading but before the page's OnLoad event is raised. + All script blocks are rendered in the <head> of the page during callback + processing after the page updates have been applied. + + + + + Registers the client script with the Page object and with Anthem.Manager using a + type, key, and script literal. + + The type of the client script to register. + The key of the client script to register. + The client script literal to register. + A Boolean value indicating whether to add + script tags. + + A client script is uniquely identified by its key and its type. + Scripts with the same key and type are considered duplicates. + Only one script with a given type and key pair can be registered with the page. + Attempting to register a script that is already registered does not create a + duplicate of the script. + Call the Page.IsClientScriptBlockRegistered method to + determine whether a client script with a given key and type pair is already + registered and avoid unnecessarily attempting to add the script. + In this overload of the RegisterStartupScript method, you can indicate + whether the script provided in the script parameter is wrapped with a <script> + element block by using the addScriptTags parameter. Setting addScriptTags to + true indicates that script tags will be added automatically. + The script block added by the RegisterStartupScript method executes + when the page finishes loading but before the page's OnLoad event is raised. + All script blocks are rendered in the <head> of the page during callback + processing after the page updates have been applied. + + + + Sets the visibility of the control on the client. + + + + This method needs to be called by custom controls that want their + innerHTML to be automatically updated on the client pages during + call backs. Call this at the top of the Render override. The + parentTagName argument should be "div" or "span" depending on the + type of control. It's this parent element that actually gets its + innerHTML updated after call backs. + + + + + + + + This method needs to be called by custom controls that want their + innerHTML to be automatically updated on the client pages during + call backs. Call this at the bottom of the Render override. + + + + + + + Writes the val and error to the callback response. + + + + Writes the val to sb in a format that can be interpreted by Anthem.js on the + client. + + + + + This is an empty method used as the target for the + Anthem_FireEvent function. That function sets the + __EVENTTARGET to the desired ID which causes the + appropriate event to fire on the server so nothing + needs to be done here. + + + + + Add generic callbacks events to all the child controls in the + container. This is used by template controls (eg. DataGrid). + + The container control. + + true if the control should be enabled on the client during a + callback. + + The text to display during a callback. + The javascript function to execute before starting the callback. + + The javascript function to execute after the callback response is + received. + + + The javascript function to execute if the callback is cancelled by the + pre-callback function. + + + + + Returns a flattened list of all the non-Anthem controls + in a container. Container controls such as Panel are not + expanded. + + The container control. + An ArrayList of non-Anthem controls. + + + + Add a generic callback to the target control. + + The target control is most often the same as the control that + is raising the event, but the GridView (for example) is the target for all of it's + generated child controls. + + + + Returns a hashtable of the HTML for the AJAX-ified controls + on the page. These strings are returned to the client which + updates the page (using innerHTML). + This method is pretty messy but it seems to work (it's hard to + to tell without proper unit tests--shame on me). + + + + + + + Used to catch Response.Redirect() during a callback. If it is a redirect + the response is converted back into a normal response and the appropriate + javascript is returned to redirect the client. + + + + + + + A string which uniquely identifies the current callback. + + + + + The method to invoke during the callback. + + + + + A string value which indicates whether the callback is being made + using XMLHttpRequest or a hidden IFRAME. + + + + + When true, Anthem.Manager will include all page level scripts in the + callback response. Use this if you add or show 3rd party controls during + the callback that add or change client scripts in the page. + + + + + Returns true if the current POST is a callback. + + + + + + + + + + + + + Raises the event and registers the control + with . + + A . + + + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Forces the server control to output content and trace information. + + + + + + + + + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Creates an updatable control that compares the value entered by the user in an input control with the value entered in another input control, or with a constant value. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable text box control for user input. + + + + + Adds the onchange attribute to invoke a callback from the client, then renders + the attributes of the control to the output stream. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the Checkbox state automatically calls back to + the server when clicked. Mimics the AutoPostBack property. + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Applying this attribute to your public methods makes it possible + to invoke them from JavaScript in your client pages. + + + + + Creates an updatable control that allows the user to select a single item from a drop-down list. + + + + + Adds the onchange attribute to invoke a callback from the client, then renders + the attributes of the control to the output stream. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the Checkbox state automatically calls back to + the server when clicked. Mimics the AutoPostBack property. + + + + + Override Items collection to force PersistenceMode.InnerProperty. This will cause the control to + wrap the ListItems inside of an <Items> tag which the Visual Studio designer will validate. + If you don't do this, the designer will complain that the "Element 'ListItem' is not a known element." + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable control that acts as a container for a group of controls. + + + + + Raises the + event and sets to true. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable list box control that allows single or multiple item selection. + + + + + Adds the onchange attribute to invoke a callback from the client, then renders + the attributes of the control to the output stream. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the Checkbox state automatically calls back to + the server when clicked. Mimics the AutoPostBack property. + + + + + Override Items collection to force PersistenceMode.InnerProperty. This will cause the control to + wrap the ListItems inside of an <Items> tag which the Visual Studio designer will validate. + If you don't do this, the designer will complain that the "Element 'ListItem' is not a known element." + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable table control. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should convert child control postbacks into callbacks. + + + true if the control should convert child control postbacks to + callbacks; otherwise, false. The default is + true. + + + Only controls that recognizes will be converted. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable control that checks whether the value of an input control is within a specified range of values. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable control that acts as a container for other controls. + + + When AddCallBacks is true (the default), this control will + attempt to coerce every child control into using callbacks instead of postbacks. + The is a great way to quickly convert a section of an existing page, or the + entire page, into a simple Ajax page. Unfortunately, if there are several + child controls in the panel, updates can be slow and the page may appear to + blink. It is usually a much better idea to convert specific controls into + their Anthem equivalent and use UpdateAfterCallBack judiciously. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should convert child control postbacks into callbacks. + + + true if the control should convert child control postbacks to + callbacks; otherwise, false. The default is + true. + + + Only controls that recognizes will be converted. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable hyperlink-style button control on a Web page that responds to mouse clicks using callbacks. + + + + + Adds the onclick attribute to invoke a callback from the client, then renders + the attributes of the control to the output stream. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable control that displays an image and responds to mouse clicks on the image using callbacks. + + + + + Adds the onclick attribute to invoke a callback from the client, then renders + the attributes of the control to the output stream. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + Gets or sets the url of the image to display on the client during the callback. + + If the HTML element that invoked the callback is an <image> then the src property of the + element is updated during the callback to the value of ImageUrlDuringCallBack, otherwise the + associated <label> text is updated during the callback. + If the element is not an <image>, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable text box control and a browse button that allow users to select a file to upload to the server. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable multi selection check box group that can be dynamically created by binding the control to a data source. Uses callbacks if is true. + + + + + Adds the onclick attribute to invoke a callback from the client. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the Checkbox state automatically calls back to + the server when clicked. Mimics the AutoPostBack property. + + + + + Override Items collection to force PersistenceMode.InnerProperty. This will cause the control to + wrap the ListItems inside of an <Items> tag which the Visual Studio designer will validate. + If you don't do this, the designer will complain that the "Element 'ListItem' is not a known element." + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Represents a control that acts as a container for a group of controls within a control. + + + + + Creates an updatable control that validates whether the value of an associated input control matches the pattern specified by a regular expression. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable control that generates a list of items in a bulletd format. + + + + + Adds the onclick attribute to invoke a callback from the client, then renders + the attributes of the control to the output stream. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Override Items collection to force PersistenceMode.InnerProperty. This will cause the control to + wrap the ListItems inside of an <Items> tag which the Visual Studio designer will validate. + If you don't do this, the designer will complain that the "Element 'ListItem' is not a known element." + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable control that displays the values of a single record from a data source using user-defined templates. The FormView control allows you to edit, delete, and insert records using callbacks. + + + + + Excecutes , + then sets to true. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should convert child control postbacks into callbacks. + + + true if the control should convert child control postbacks to + callbacks; otherwise, false. The default is + true. + + + Only controls that recognizes will be converted. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Displays an updatable push button control on the web page that uses callbacks. + + + + + Adds the onclick attribute to invoke a callback from the client, then renders + the attributes of the control to the output stream. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Displays an updatable advertisement banner on a web page. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable data bound list control that displays the items from data source in a table. The DataGrid control allows you to select, sort, and edit these items using callbacks. + + + + + Excecutes , + then sets to true. + + + + + Excecutes , + then sets to true. + + + + + Excecutes , + then sets to true. + + + + + Excecutes , + then sets to true. + + + + + Excecutes , + then sets to true. + + + + + Excecutes , + then sets to true. + + + + + Excecutes , + then sets to true. + + + + + Excecutes , + then sets to true. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should convert child control postbacks into callbacks. + + + true if the control should convert child control postbacks to + callbacks; otherwise, false. The default is + true. + + + Only controls that recognizes will be converted. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable control that performs user-defined validation on an input control. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable label control, which displays text on a Web page. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable control that displays an image on a Web page. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable control that displays the values of a data source in a table where each column represents a field and each row represents a record. The GridView control allows you to select, sort, and edit these items using callbacks. + + + + + Excecutes , + then sets to true. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should convert child control postbacks into callbacks. + + + true if the control should convert child control postbacks to + callbacks; otherwise, false. The default is + true. + + + Only controls that recognizes will be converted. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable control that makes the associated input control a required field. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable list control that encapsulates a group of radio button controls. + + + + + Excecutes , + then sets to true. + + + + + Adds the onclick attribute to each item to invoke a callback from the client, + then renders the item. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the Checkbox state automatically calls back to + the server when clicked. Mimics the AutoPostBack property. + + + + + Override Items collection to force PersistenceMode.InnerProperty. This will cause the control to + wrap the ListItems inside of an <Items> tag which the Visual Studio designer will validate. + If you don't do this, the designer will complain that the "Element 'ListItem' is not a known element." + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Displays an updatable single-month calendar that allows the user to select dates and move to the next or previous month using callbacks. + + + + + Excecutes , + then sets to true. + + + + + Excecutes , + then sets to true. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable timer control. + + + + + This method is called by the Timer control each time the timer expires. + + + + + Starts the timer. + + + + + Stops the timer. + + + + + Raises the event. + + + + + Registers the client side script for the control. + + + + + Raises the event and registers the control + with . + + A . + + + + True if the timer is enabled on the client. The timer will continue to call the + DoTick method while it is enabled. + + + + + The number of milliseconds between each call to DoTick. + + + + + Occurs when the timer expires. + + + + + Visible is inherited from Control, but ignored by Timer. + + + + + Creates an updatable hidden field used to store a non-displayed value. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + + Creates an updatable control that displays the values of a single record from a data source in a table, where each data row represents a field of the record. The DetailsView control allows you to edit, delete, and insert records using callbacks. + + + + + Excecutes , + then sets to true. + + + + + Renders the server control wrapped in an additional element so that the + element.innerHTML can be updated after a callback. + + + + + Raises the event and registers the control + with . + + A . + + + + Forces the server control to output content and trace information. + + + + + Gets or sets the javascript function to execute on the client if the callback is + cancelled. See . + + + + + Gets or sets a value indicating whether the control uses callbacks instead of postbacks to post data to the server. + + + true if the the control uses callbacks; otherwise, + false. The default is true. + + + + + Gets or sets a value indicating whether the control is enabled on the client during callbacks. + + + true if the the control is enabled; otherwise, + false. The default is true. + + Not all HTML elements support this property. + + + + Gets or sets the javascript function to execute on the client after the callback + response is received. + + + The callback response is passed into the PostCallBackFunction as the one and only + parameter. + + + + function AfterCallBack(result) { + if (result.error != null && result.error.length > 0) { + alert(result.error); + } + } + + + + + + Gets or sets the javascript function to execute on the client before the callback + is made. + + The function should return false on the client to cancel the callback. + + + Gets or sets the text to display on the client during the callback. + + If the HTML element that invoked the callback has a text value (such as <input + type="button" value="Run">) then the text of the element is updated during the + callback, otherwise the associated <label> text is updated during the callback. + If the element does not have a text value, and if there is no associated <label>, + then this property is ignored. + + + + + Gets or sets a value indicating whether the control should convert child control postbacks into callbacks. + + + true if the control should convert child control postbacks to + callbacks; otherwise, false. The default is + true. + + + Only controls that recognizes will be converted. + + + + + Gets or sets a value indicating whether the control should be updated after each callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + <anthem:Label id="label" runat="server" AutoUpdateAfterCallBack="true" /> + + + + + + Gets or sets a value which indicates whether the control should be updated after the current callback. + Also see . + + + true if the the control should be updated; otherwise, + false. The default is false. + + + + this.Label = "Count = " + count; + this.Label.UpdateAfterCallBack = true; + + + + + + Overrides the Visible property so that Anthem.Manager can track the visibility. + + + true if the control is rendered on the client; otherwise + false. The default is true. + + + + diff --git a/References/Lib/Anthem.NET/Anthem.dll b/References/Lib/Anthem.NET/Anthem.dll new file mode 100644 index 0000000..fa4217c Binary files /dev/null and b/References/Lib/Anthem.NET/Anthem.dll differ diff --git a/References/Lib/Anthem.NET/License.txt b/References/Lib/Anthem.NET/License.txt new file mode 100644 index 0000000..e899ab1 --- /dev/null +++ b/References/Lib/Anthem.NET/License.txt @@ -0,0 +1,3 @@ +http://sourceforge.net/projects/anthem-dot-net/ + +Anthem.NET is released to public domain. \ No newline at end of file diff --git a/References/Lib/MySQL/CHANGES b/References/Lib/MySQL/CHANGES new file mode 100644 index 0000000..3997c8a --- /dev/null +++ b/References/Lib/MySQL/CHANGES @@ -0,0 +1,757 @@ +Version 6.0.4 +- fixed regression where using stored procs with datasets (bug #44460) +- fixed compilation under VS 2005 (bug #44822) +- fixed support for bool data type in entity framework support +- fixed regression where a user could no longer call GetBytes on a guid column (bug #44507) +- made some fixes to the syntax coloring of sql inside visual studio +- fixed display name of triggers inside server explorer +- fixed trigger editing +- fixed tokenization of escaped backslashes (bug #44960) +- fixed conversion of binary(16) type to guid for entity framework models (bug #44986) +- migrated from CTC to VSCT for our menus and buttons. Also the VisualStudio project will also + now load and builder under VS2005 or VS2008 loading the proper targets files as needed +- fixed global Add New menu options with the Visual STudio integration +- improved ad-hoc registration utility so that it handles VS 2005 and VS 2008 properly +- added SunConnect registration +- fixed sql that is generated for commands in data set (bug #44512) +- unsigned values are now handled. Please read the release notes! + +Version 6.0.3 - 4/22/09 +- fixed broken connection prompting +- fixed installer that was no longer referencing the right folders for Visual Studio assemblies [bug #44141] +- made integer column types returned with table and column collections use the same data type + when referencing MySql 4.1 servers and MySql 5.0+ servers (bug #44123) +- lots of unreported bug fixes + +Version 6.0.2 +- fixed registration problem with Visual studio (bug #44064) +- fixed problem where stored procedures and functions could not be deleted from server explorer +- changed function generation so that it ignores schema just like we did with tables in 6.0.1 +- fixed problem with new tokenizer that caused some values with equals to not be parsed + correctly (bug #44318) + +Version 6.0.1 +- Fixed problem with the foreign key columns schema collection +- Fixed problem with index column schema collection +- Fixed problem with generation of medium and longtext columns in entity models +- Changed entity framework generation so that schemas are not written out. This allows + a single model to swing from one database to another by just changing the connection + string (bug #43274) +- fixed problem in sql generation that caused decimal values to be generated incorrectly + when using a certain cultures (bug #43574) + +Version 6.0 +- Massive speedups +- Improved sql tokenizing speed greatly. Patch submitted by Maxim Mass (bug #36836) +- Fixed possible overflow bug in MySqlPacket.ReadLong (bug #36997) +- Added UDF schema collection +- Fixed lingering problem related to bug #37239. If two columns had the same name but different case then an exception would be thrown. +- Developed and integrated a new sql tokenizer. This tokenizer now recognizes all comment types and is approx. 40% faster. +- fixed membership provider so that calling GetPassword with an incorrect password will throw the appropriate exception (bug #38939) +- Implemented initial entity framework support + +Version 5.2.7 +- fixed procedure parameters collection so that an exception is thrown if we can't get the + parameters. Also used this to optimize the procedure cache optimization +- Added "nvarchar" and "nchar" to possible data types returned by the DataSourceInformation + schema collection so procs that use those types with parameters will work (bug #39409) +- fixed problem where the connector would incorrectly report the length of utf8 columns on servers + 6.0 and later. This was caused by 6.0 now using 4 bytes for utf8 columns +- fixed bug in role provider that was causing it to not correctly fetch the application + id which caused it to incorrectly report roles (bug #44414) +- fixed Visual Studio 2005 solution so that it builds +- fixed potential endless loop in CompressedStream in case where end of stream occurs before + packet is fully read (bug #43678) +- fixed ReservedWords schema collection to not incorrectly include blank words and to use the + right column name +- fixed display of trigger names +- restructured the connection open error handling so that socket exceptions come through + as the inner exception (bug #45021) + +Version 5.2.6 +- cleaned up how stored procedure execution operated when the user does or does not have execute privs + on the routine (bug #40139) +- fixed problem with datetime formatting that causes an extra space to be included between the + date and time when sent to the server (bug #41021) +- fixed sproc parameter parsing so that a space between the type and size spec would not cause a problem + (bug #41034) +- removed code from profile provider that overrode Name and Description properties in error. This had the + effect of not allowing you to override those values in the web.config (ug #40871) +- fixed "metadatacollections" collection to include foreign key columns +- fixed GetAllProfiles (which is used by several methods). It had a typo in the SQL and was not + including several important columns (bug #41654) +- fixed problem where the core method GetUser was not properly checking the application id + when retrieving user id values. This would mean that in some cases methods like + ValidateUser would return success when they shouldn't. (bug #42574) This triggered several + other cleanups in all the providers +- fixed problem with execution of LOAD DATA LOCAL INFILE (which also affected MySqlBulkLoader). + When the driver encountered some type of error opening the local file for transport it would + still attempt to close the file which would yield a null reference exception (bug #43332) +- fixed Sql null value exception when an attempt was made to reset the password and + require question and answer was false. (bug #41408) +- fixed MySqlDataReader.GetGuid to be more robust in how it attempts to read guid values (bug #41452) +- fixed bug where attempting to clear a pool twice would result in a null reference exception (bug #42801) +- Fixed problem with index column schema collection +- Fixed RemoveUsersFromRoles and DeleteRole where they were not calling transactionscope.Complete. + This meant that on systems where the provider tables are transaction aware the changes could be + rolled back in every case (bug #43553) +- Fixed typo in the collection returned when you request the MetaDataCollections collection. The + NumberOfRestrictions column was missing the trailing s. (bug #43990) +- fixed index and index column schema collections that would fail if you specified the + any restrictions more specific than table name (bug #43991) +- removed a couple of unnecessary lines from NativeDriver.Open() (bug #44028) +- added connection string option 'use affected rows'. (bug #44194) +- changed MySqlPool so that a freshly pulled connection will issue it's ping and reset + outside of the idlepool lock. This lessens some of the contention on the idle pool. + +Version 5.2.5 - 11/14/2008 +- fixed problem with package registration that kept the DDEX provider from working (bug #40726) + +Version 5.2.4 - 11/11/2008 (Happy Birthday to me!) +- fixed web providers autogenerateschema option where it would fail if no schema is + present at all (bug #39072) +- fixed problem with installer where it would fail silently if the visual studio paths + do not exist or match what is in the registry. (bug #39019) +- Better support for the Guid data type. +- backported fix for lingering problem related to bug #37239. If two columns had the same name but + different case then an exception would be thrown. +- fixed stored procedure parameter parsing when used inside server explorer. (bug #39252) +- fixed time data type so that negative values are handled properly (bug #39275) +- added runtime check for the mono platform to our Membership provider. The mono runtime + as of 1.9.1 did not support the methods needed for hashed passwords (bug #38895) +- fixed problem where negative time values with a zero hour would return as postive + values (bug #39294) +- fixed problem where using a stored procedure with parameters with a table adapter + was no longer working after our parameter schema changes (bug #39252) +- Defaulting max allowed packet to 1024 to account for the possible case where the + value doesn't come in as a server variable +- fixed bug #39728 by making MySqlConnectionStringBuilder.GetConnectionString an internal method. + It should not have been publicly available anyway. It is used internally by the + MySqlConnection.ConnectionString property +- implemented Disposable pattern on MySqlTransaction class so including one in a using statement + and then not calling commit will cause a rollback when the using exits (bug #39817) +- fixed MySqlScript object so that it handles scripts with user variables +- fixed bug where specifying 'functions return string=yes' would cause strings to be returned + using the 'binary' charset which would not properly render some characters. Now the + connection character set is used. (bug #40076) +- fixed problem that caused in use connection strings to be modified when a pooled connection + timed out and was cancelled. (bug #40091) +- fixed problem where using respect binary flags would not use the connection char set + and therefore return strings with a bad encoding. +- fixed bug where provider was attempting to use the new parameters I_S view on servers + that didn't have it (bug #40382) +- fixed problem where CharSetMap.GetDefaultCollation and CharSetMap.GetMaxLengths + might have a thread sync issue on high load systems. They were not locking + the static collections there were initializing. (bug #40231) +- added GetSByte to the reader for returning tinyint columns (bug #40571) + +Version 5.2.3 - 8/14/08 +- Increased the speed of MySqlDataReader.GetOrdinal dramatically by using a couple + of hashes for lookups +- Fixed problem where some tables that support the web providers used the latin1 + character set instead of the database default. (bug #36444) +- Changed how the procedure schema collection is retrieved. If 'use procedure bodies=true' + then we select on the mysql.proc table directly as this is up to 50x faster than our current + IS implementation. If 'use procedure bodies=false', then the IS collection is queried. + (bug #36694) +- Fixed problem with our GetOrdinal speedup where we would attempt to add an already existing + key to a hash when a resultset had more than 1 column with the same name. (bug #37239) +- small fix to how we were allowing in/out and out parameters to slide through parameter + serialization. Before we were setting the AllowUserVariables connection setting but that + had the unfortunate side effect of setting the value for all connections that shared that + connection string. This way we isolate it just to our particular command. + This may fix bug #37104 +- Fixed documentation surrounding use of ? vs @ for parameters (bug #37349) +- Reduced network traffic for the normal case where the web provider schema is up + to date (bug #37469) +- Improved error reporting when a timeout occurs. It no longer uses a message like + 'reading from stream failed'. (bug #38119) +- fixed problem where adding a non-existent user to a role would not auto-create the + user record (bug #38243) +- moved string escaping routine from the MySqlString class to the MySqlHelper class + and made it public and static. (bug #36205) +- Fixed problem where column metadata was not being read with the correct characterset + (bug #38721) +- Fixed problem where the uninstall was not cleaning up the state files (bug #38534) +- Added 'Functions Return String' connection string option + +Version 5.2.2 - +- Fixed profile provider that would throw an exception if you were updating + a profile that already existed. +- Fixed problem where new parameter code prevented stored procedures from being + altered in Visual Studio (bug #34940) +- Fixed problem where the TableAdapter wizard was no longer able to generate commands + using stored procedures because of our change to using @ instead of ? (bug #34941) +- Fixed problem in datagrid code related to creating a new table. This problem may + have been introduced with .NET 2.0 SP1. +- Fixed guid type so that a null value is still returned as guid type (bug #35041) +- Added support for using the new PARAMETERS I_S view when running against + a 6.0 server +- Fixed bug with the membership provider where the min non alpha numeric option + was not working correctly. +- Fixed bug where calling GetPassword on a membership user when the password answer + is null would cause an exception (bug #35332) +- Fixed bug where retrieving passwords that are encrypted was not returning proper + passwords (bug #35336) +- Fixed problem with profile provider where properties that were specified without + a given type could not be retrieved properly (bug #36000) +- Implemented interactive session connection string option +- The procedure parameters schema collection has been altered to match what is coming + with MySQL 6.0. Some fields have been removed and others combined. Please review + your application for incompatibilities. +- Removed some unnecessary locking from the pool manager and also reworked the pooling + code to not use a semaphore (bug #34001) + + +Version 5.2.1 - 2/27/2008 +- Tons of fixes in providers. The actually work now. :) +- Fixed new parameter handling code so that procedures and functions now work + (bug #34699) +- Fixed problem with Visual Studio 2008 integration that caused popup menus on + server explorer nodes to not function +- Fixed web providers so that they don't attempt to check their schema or cache + their application id if the connection string has not been set. (bug #34451) +- Fixed installer that did not install the DDEX provider binary if + the Visual Studio 2005 component was not selected. (bug #34674) +- Fixed password validation logic for creating users and changing passwords. + It actually works now. (bug #34792) + +Version 5.2 - 2/11/2008 +. Added ClearPool and ClearAllPools features +. DDEX provider now works under Visual Studio 2008 +. Added support for DbDataAdapter UpdateBatchSize. Batching is fully supported + including collapsing inserts down into the multi-value form if possible. +. Added MySqlScript class for executing scripts against a database. +. Marked connection string option 'Use Old Syntax' as obsolete and changed code to + allow both @ and ? as paramter markers. +. Finished MySqlBulkLoader class +. BINARY(16) columns are now returned as Guid objects +. Moved code that creates the PerfMon stuff from the assemblies to the Wix installer +. Added Allow User Variables connection string option so that users can use user variables + without getting missing parameter exceptions + +Version 5.1.7 + - Fixed problem with DDEX provider that could sometimes prevent table altering when working with + 4.1 servers (bug #30603) + - Fixed problem with pooling code where connections pooled from the pool were added + twice to the in use pool. This would cause a semaphore full exception + when an attempt is made to release them back to the pool (bug #36688) + - Reversed order of Datetime and DateTime enums for MySqlDbType so that VB users won't get + autocorrection to Datetime (bug #37406) + - Uncommented access denied error enumeration value (bug #37398) + - Improved documentation concerning autoincrement columns and the DataColumn class (bug #37350) + - Fixed problem where executing a command that results in a fatal exception would not + close the connection. (bug #37991) + - Fixed problem where executing a command with a null connection object would result in + a null reference exception instead of an InvalidOp (bug #38276) + +Version 5.1.6 + - Fixed problem where parameters lists were not showing when you tried to alter a routine + in server explorer. (bug #34359) + - Fixed a problem in procedure cache where it was possible to get into a race condition + and cause a memory leak (bug #34338) + - Fixed problem where attempting to use an isolation level other than the default with + a transaction scope would use the default instead (bug #34448) + - Fixed problem that causes the TableAdapter wizard to only generate insert statements. + The problem was that our code to retrieve index columns was broken. (bug #31338) + - Fixed problem with connections staying open after being used with SqlDataSource. + The problem was that we were not returning an enumerator for our reader with the + closeReader option set to true when we were supposed to. (Bug #34460) + - Fixed problem where the bit data type would continue to return null values + once it saw a null value in a previous row (bug #36313) + - Fixed problem with MembershipUser.GetPassword where attempting to retrieve a + password on a user where password Q&A is not required would throw an exception (bug #36159) + - Fixed a problem with MembershipUser.GetNumberOfUsersOnline. + It actually works now :) (bug #36157) + - Fixed documentation that still stated that setting port to -1 was necessary for + a named pipe connection (bug #35356) + - Fixed data type processing so that geometry fields are returned as binary. (bug #36081) + - Fixed problem that kept our provider from showing up in the provider list when configuring + a new connection from a SqlDataSource + - Fixed problem where setting the ConnectionString property of MySqlConnection to null + would throw an exception (bug #35619) + +Version 5.1.5 - 2/11/2008 + - Fixed problem with membership provider where FindUserByEmail would fail trying to add + a second parameter with the same name as the first (bug #33347) + - Fixed long standing problem with compression over a network. It's now fast again. (bug #27865) + - Fixed nant compilation problem on mono (bug #33508) + - Fixed problem where connection state reported through the state change handler was not + showing Open (bug #34082) + - Incorporated some connection string cache optimizations sent to us by Maxim Mass (bug #34000) + - Fixed problem with opening a connection that was previously closed by sudden server disconnection (bug #33909) + - Fixed code to yeild better exception when using different connection strings in a single transaction (bug #34204) + - Small bugfix and speed enhancement in Statement.TokenizeSql (bug #34220) + - Added connection string option 'Treat Tiny As Boolean' so applications that expect TINYINT(1) + to return an integer will not break (bug #34052) + +Version 5.1.4 - 11/12/2007 + - Fixed issue where column name metadata was not using the charset given on the connection string + (Bug #31185) + - Fixed problem where closing the connection before the reader where the reader was + opened with CloseConnection would cause a object disposed exception to be thrown + - Fixed problem with installer where the installation would fail if the performance + counter categories had already been removed for some reason + - Fixed problem with attempting to use a command with a connection that is not open. + The problem was caused by the introduction of the internal method SoftClosed that is + used with transactions. (Bug #31262) + - Fixed problem where attempting to enlist in a distributed transaction would succeed + even though Connector/Net doesn't currently support that. (Bug #31703) + - Fixed problem with web providers not being usable on medium trust systems + - Fixed problem with installer where attempting to install over a failed uninstall could + leave multiple clients registered in machine.config. (Bug #31731) + - Marked MySqlDbType.Datetime obsolete; replaced with DateTime (just corrected capitalization) (Bug #26344) + - fixed code where we were returning bit(1) as boolean but mysql treats tinyint(1) as boolean + (bug #27959) + - Added the Default Command Timeout connection string option to allow DDEX users to set + an initial command timeout for very large databases (bug #27958) + +Version 5.1.3 - 9/19/2007 + - Fixed problem with using a stored procedure that takes a parameter as a select routine + for a TableAdapter wizard. (Bug #29098) + - Fixed problem with creating users using hashed passwords when machineKey is set + to AutoGenerate. We now correctly throw an exception if you are requesting + encrypted passwords but it works ok for hashed passwords. (Bug #29235) + - Fixed problem with selecting users for roles in the web admin tool. The problem was that + we had a simple syntax error in our database lookup code. (Bug #29236) + - Added AutoEnlist connection string option. Setting it to false will prevent the connection + from automatically enlisting in any current transaction + - Changed membership schema to allow null values for email. This allows all the + overrides for Membership.CreateUser to work. + - Added 'Respect Binary Flags' connection string option to allow existing applications + to select the old behavior of not always respecting the binary flags of a columns. + - Added ability to use blobs to store true UTF-8 data (see help) + - Help is now integrated into Visual Studio 2005 and includes content other than the API + - Fixed problem reported by user where MySqlMembershipProvider.GetUser was attempting to + reuse the connection to update a table while a reader was open on it. + - Fixed problem with membership schema where the password key column was not large enough + - Added feature where bit columns that have the value 0 or 1 are returned as bool + - Added Foreign Key Columns metadata collection + - Reworked how foreign key metadata is collected to make it more robust and faster + - Changed DDEX provider to use the core providers schema routines for + foreign keys and foreign key columns + - Fixed index and foreign key enumerators inside the DDEX provider to work + with the new binary respect behavior of 5.1 + - Added code to implement better TransactionScope support. This code is brand new and will be + heavily refactored in 5.2. (bug #28709) + - Fixed problem where connecting to pre-4.1 servers would result in a crash. This was caused + by the Field object referring to metadata columns that are not populated on pre-4.1 servers. + (bug #29476) + - Commandbuilder now defaults ReturnGeneratedIdentifiers to true. This means that autogenerated + columns will be returned in the default case. + - Exceptions generated during BeginExecuteReader and BeginExecuteNonQuery will now ben thrown + once the End versions of those methods are called. + +Version 5.1.2 - 6/12/2007 + - Fixed integration with the Website Administration Tool. Before this fix, the test link + was visible but did not work and user management did not work. + - Reintroduced code to properly return field values as byte[] when the binary flag is present. + (See release notes) + - Fixed problem preventing the DataSet Designer or TableAdapter wizard from being + able to generate insert, update, and delete statements. (Bug #26347) + - Fixed problem preventing use of stored procedures for insert, update, and + delete statements in a table adapter. + - Fixed problem where text columns would not appear in the Visual Studio query builder (Bug #28437) + +Version 5.1.1 + - Fixed password property on MySqlConnectionStringBuilder to use PasswordPropertyText + attribute. This causes dots to show instead of actual password text. + - Fixed problem with pooling on the compact framework. It should work now. + - Install now calls VS setup only when VS integration is selected. (bug #28260) + - MySqlRoleProvider.GetRolesForUser now works correctly (bug #28251) + - Installer now adds a default connection string to machine.config. This will prevent errors related to a missing + connection string. You will need to override this value in your web.config files. + - Changed installer to only give option to install Visual Studio integration if Visual Studio 8 is installed + +Version 5.1.0 - 5/1/2007 + + - Added Membership and Role provider contributed by Sean Wright (thanks!) + - Now compiles for .NET CF 2.0 + - Rewrote stored procedure parsing code using a new SQL tokenizer. Really nasty + procedures including nested comments are now supported. + - GetSchema will now report objects relative to the currently selected database. + What this means is that passing in null as a database restriction will report + objects on the currently selected database only. + +Version 5.0.10 - + - Fixed problem with pooling where a bogus host info combined with a pool with a minimum + size > 0 can combine to create an exception. The pool fails to create from the bogus + host info but the driver finalizer code will still try to remove the driver from + a non-existant pool. (bug #36432) + - Fixed problem where supplying the connection reset config option can cause login to fail + when there is room to make a new connection and the pool has no idle connections. + - Fixed MySqlConnectionStringBuilder to first remove old keyword settings when setting + a value that was previously set (bug #37955) + - Fixed problem where executing a command that results in a fatal exception would not + close the connection. (bug #37991) + - Fixed problem with byte and unsigned byte values writing more too many bytes when + using prepared statements (bug #37968) + - Fixed writing of single and double values to write out the proper number of digits + (bug #33322) + - Fixed GetSchema to work properly when querying for a collection with non-English + locale (bug #35459) + +Version 5.0.9 - 4/14/08 + + - Fixed problem where fields that were blobs but did not include the BLOB flag were treated + as binary when they should have been treated as text. (Bug #30233) + - Changed from using Array.Copy to Buffer.BlockCopy in MySqlDataReader.GetBytes. This + helps with memory usage as we expect the source and destination arrays to not be overlapping. + (Bug #31090) + - Fixed problem that prevented commands from being executed from the state change + handler. Not sure why you would want to do this but... (bug #30964) + - Fixed problem with connection string caching where our collection class was + using case insensitive semantics and this causes cases where a user orginally + used the wrong case for a user id and then fixed it to still get access denied + errors. (Bug #31433) + - improved the speed of load data local infile significantly + - fixed MySqlDateTime.ToString() to properly return the date value (Bug #32010) + - fixed problem where string parameters who have their size set after their value could cause exceptions + (Bug #32094) + - fixed problem where old code was preventing creating parameter objects with non-input direction using + just a constructor (Bug #32093) + - fixed problem where a syntax error in a set of batch statements could leave the data adapter in a state + that appears hung (bug #31930) + - fixed the MySqlException class to set the server error code in the Data[] hash so that + DbProviderFactory users can access the server error code (Bug #27436) + - fixed problem where changing the connection string of a connection to one that changes + the parameter marker after the connection had been assigned to a command but before + the connection is opened can cause parameters to not be found (bug #13991) + - added implementation of MySqlCommandBuilder methods QuoteIdentifier and UnquoteIdentifier + (bug #35492) + - some fixed to cancel and timeout operations so that they are more dependable + - fixed problem where cloning a parameter that has not yet had its type set would yeild + a cloned paramter that would no longer infer it's type from the value set + +Version 5.0.8 8/16/2007 + + Bug #28706 Log messages are truncated + - Fixed a problem with compression over a network. We were letting the inflate stream read + directly from the network stream. Under certain situations, two bytes were being left unread + and this messed up our byte counts. Now we are using a WeakReference to an internal buffer + that we read the compressed data into before inflating. (Bug #28204) + - Fixed problem where we were not closing prepared statement handles + when commands are disposed. + - Fixed problem where any attempt to not read all the records returned + from a select where each row of the select is greater than 1024 bytes + would hang the driver. + - Fixed problem where usage advisor warnings for unnecessary field conversions + and not reading all rows of a resultset would output even if you + did not request usage advisor warnings. (Bug #29124) + - Changed behavior of ConnectionString property. It now only returns the connection + string given to it. It will not attempt to track changes to the current + database when the users uses the ChangeDatabase method. (Bug #29123) + - Fixed problem with calling stored procedures in databases that have hyphens + in their names. We were not using backticks to quote the database and sproc name + when querying for metadata. (Bug #29526) + - Fixed problem where a statement that has parameters that is executed without + defining those parameters would throw a System.FormatException rather than + a MySqlException (bug #29312) + - Fixed problem where a command timing out just after it actually finished would cause + an exception to be thrown on the command timeout thread which would then be seen + as an unhandled exception. + - Fixed bug where Connecor/Net was hand building some date time patterns rather than using + the patterns provided under CultureInfo. This caused problems with some calendars that do + not support the same ranges as Gregorian. (Bug #29931) + - Fixed problem where MySqlConnection.BeginTransaction checked the drivers + status var before checking if the connection was open. The result was that the + driver could report an invalid condition on a previously opened connection. + - Fixed problem where an attempt to open a connection max pool size times while + the server is down will prevent any further attempts due to the pool semaphore + being full. (Bug #29409) + - Fixed some serious issues with command timeout and cancel that could present + as exceptions about thread ownership. The issue was that not all queries + cancel the same. Some produce resultsets while others don't. ExecuteReader + had to be changed to check for this. + - Fixed problem where date columns that appear in keys caused updates to + fail (bug #30077) + - Added code to suppress finalizers for low level socket objects and then + added a finalizer to the protocol object so pooled connections will get + closed on process exit + - Fixed problem where attempting to load a reader into a datatable using a table + with a multi-column primary key would result in multiple constraints being added + to the datatable. No test case added to the 1.0 tree as loading a datatable + with a reader is a .Net 2.0 thing. (Bug #30204) + - Fixed the database schema collection so that it works on servers that are not properly + respecting the lower_case_table_names setting. + - Moved installer to Inno Setup + +Version 5.0.7 5/16/2007 + + Bugs fixed + ---------- + Bug #27269 MySqlConnection.Clone does not mimic SqlConnection.Clone behaviour + Bug #27289 Transaction is not rolledback when connection close + Bug #26147 "User Id" problem during connection creation + Bug #27240 Property value characterset not retrieved/fetched in conn wizard + Bug #25947 CreateFormat/CreateParameters Column of DataTypes collection incorrect for CHAR + Bug #27765 Logging does not work + Bug #27679 MySqlCommandBuilder.DeriveParameters ignores UNSIGNED flag + Bug #27668 FillSchema and Stored Proc with an out parameter + Bug #28167 Poor performance building connection string (thanks Maxim!) + Bug #26041 Connection Protocol Property Error in PropertyGrid Control + Bug #26754 EnlistTransaction throws false MySqlExeption "Already enlisted" + Bug #23342 Incorrect parsing of Datetime object for MySQL versions below 4.1 + + Other changes + ------------- + Installer now works if XML comments are found in machine.config + GetSchema will now attempt to unquote identifiers passed in as restrictions + The MySqlCommand object now properly clones the CommandType and CommandTimeout properties + +Version 5.0.6 3/21/2007 + + Bugs fixed + ---------- + Bug #27135 MySqlParameterCollection and parameters added with Insert Method + Bug #27253 Installer : Company info is different + Bug #27187 cmd.Parameters.RemoveAt("Id") will cause an error if the last item is requested + Bug #27093 Exception when using large values in IN UInt64 parameters + Bug #27221 describe SQL command returns all byte array on MySQL versions older than 4.1.15 + Bug #26960 Connector .NET 5.0.5 / Visual Studio Plugin 1.1.2 + +Version 5.0.5 3/5/2007 + + Bugs fixed + ---------- + Bug #25605 BINARY and VARBINARY is returned as a string + Bug #25569 UpdateRowSource.FirstReturnedRecord does not work + +Version 5.0.4 *unreleased* + + Bugs fixed + ---------- + Bug #25443 ExecuteScalar() hangs when more than one bad result + Bug #24802 Error Handling + Bug #25614 After connection is closed, and opened again UTF-8 characters are not read well + Bug #25625 Crashes when calling with CommandType set to StoredProcedure + Bug #25458 Opening connection hangs + Bug #25651 SELECT does not work properly when WHERE contains UTF-8 characters + Bug #25726 MySqlConnection throws NullReferenceException and ArgumentNullException + Bug #25609 MySqlDataAdapter.FillSchema + Bug #25928 Invalid Registry Entries + Bug #25912 selecting negative time values gets wrong results + Bug #25906 Typo makes GetSchema with DataTypes collection throw an exception + Bug #25907 DataType Column of DataTypes collection does'nt contain the correct CLR Datatype + Bug #25950 DataSourceInformation collection contains incorrect values + Bug #26430 Will not install under Vista + Bug #26152 Opening a connection is really slow + Bug #24373 High CPU utilization when no idle connection + Bug #24957 MySql.Data.Types.MySqlConversionException is not marked as Serializable. + Bug #25603 Critial ConnectionPool Error in Connector.Net 5.03 + Bug #26660 MySqlConnection.GetSchema fails with NullReferenceException for Foreign Keys + + Other changes + ------------- + Return parameters created with DeriveParameters now have the name RETURN_VALUE + Fixed problem with parameter name hashing where the hashes were not getting updated + or removed in certain situations. + Fixed problem with calling stored functions when a return parameter was not given + Fixed problem that prevented use of SchemaOnly or SingleRow command behaviors with + sprocs or prepared statements + Assembly now properly appears in the Visual Studio 2005 Add/Remove Reference dialog + Added MySqlParameterCollection.AddWithValue and marked the Add(name, value) method as obsolete + Added "Use Procedure Bodies" connection string option to allow calling procedures without + using procedure metadata (if possible). + Reverted behavior that required parameter names to start with the parameter marker. We + apologize for this back and forth but we mistakenly changed the behavior to not + match what SqlClient supports. We now support using either syntax for adding + parameters however we also respond exactly like SqlClient in that if you ask for the + index of a parameter using a syntax different than you added the parameter, the result + will be -1. + +Version 5.0.3 12-31-2006 + + Bugs fixed + ---------- + Bug #23687 Deleting a connection to a disconnected server causes a failed assertion + Bug #24565 Inferring DbType fails when reusing commands and the first time the value is nul + Bug #24661 mysql-connector-net-5.0.2-beta Driver.IsTooOld() Error.... + Bug #23905 Stored procedure usages is not thread safe + Bug #25033 Exception raised/ HANG if no SELECT privileges granted for stored procedure call + [this is a correction; a previous change log entry indicated that Connector/Net + no longer required select privs on mysql database. This is not true and select privs + are required. This will be fixed when the server exposes procedure parameters via + information schema.] + Bug #25013 Return Value parameter not found + Bug #25151 Reading from the stream has failed Exception in MySQL Connector 5.0.2 + Bug #22400 Nested transactions + + Other changes + ------------- + SSL now working. [Thanks Alessandro Muzzetta] + Fixed ViewColumns GetSchema collection + Improved CommandBuilder.DeriveParameters to use the procedure cache if possible + +Version 5.0.2 11-3-2006 + + Bugs fixed + ---------- + Bug #23268 System.FormatException when invoking procedure with ENUM input parameter + Bug #23538 Exception thrown when GetSchemaTable is called and "fields" is null. + Bug #23245 Connector Net 5.01 Beta Installer produces Antivirus Error Message + Bug #23758 Unable to connect to any server - IPv6 related + Bug #22882 Registry key 'Global' access denied + Bug #18186 Problem with implementation of PreparedStatement + Bug #23657 Column names with accented characters were not parsed properly causing malformed column names in result sets. + Bug #16126 Connector/NET did not work as a data source for the SqlDataSource object used by ASP.NET 2.0. + + Other changes + ------------- + Increased speed of MySqlParameterCollection.IndexOf(string) orders of magnitude + (parameter name lookups are now strict on use of parameter marker) + Improved character set mappings + Turned MySqlPoolManager into a static class and added a static ctor to + avoid any race conditions related to initializing the hashtable. + (A similar suggestion came in from a user) + Added 'Ignore Prepare' connection string option for disabling prepared + statements application-wide + Added Installer class to provide custom install type procedures such as modifying + machine.config + A nicer exception is displayed if you have added a parameter without the parameter marker. + Load Data Local InFile is working again + +Version 5.0.1 (Beta) + + Bugs fixed + ---------- + Bug #21521 # Symbols not allowed in column/table names. + Bug #21874 MySqlException should derive from DbException + Bug #22042 mysql-connector-net-5.0.0-alpha BeginTransaction + Bug #22452 MySql.Data.MySqlClient.MySqlException: + + Other changes + ------------- + Implemented simple local transactions + Added overloaded methods in MySqlDataReader for getting a column value right from column name + Replaced use of .NET 2.0 compression code with open source zlib library + Fixed compression + Fixed some problems with GetSchema and the Indexes and IndexColumns collections + Fixed shared memory connections + Implemented command canceling for MySQL 5.0.0 and higher + Fixed problem with executing a Fill after a FillSchema + Implemented CommandTimeout for non-batch queries + Fixed socket create code related to IPv6 (thanks Mark!) + Added foreign key support to GetSchema + Fixed installer to add assembly to GAC and machine.config + +Version 5.0.1 (Alpha) + + Bugs fixed + ---------- + Bug #6214 CommandText: Question mark in comment line is being parsed as a parameter [fixed] + + Other changes + ------------- + Implemented Usage Advisor + Added Async query methods + Reimplemented PacketReader/PacketWriter support into MySqlStream class + Added internal implemention of SHA1 so we don't have to distribute the OpenNetCF on mobile devices + Added usage advisor warnings for requesting column values by the wrong type + Reworked connection string classes to be simpler and faster + Added procedure metadata caching + Added perfmon hooks for stored procedure cache hits and misses + Implemented MySqlConnectionBuilder class + Implemented MySqlClientFactory class + Implemented classes and interfaces for ADO.Net 2.0 support + Replaced use of ICSharpCode with .NET 2.0 internal deflate support + Refactored test suite to test all protocols in a single pass + Completely refactored how column values are handled to avoid boxing in some cases + +Version 1.0.8 + + Other changes + ------------- + Implemented a stored procedure cache + Changed how stored procedure metadata is retrieved to allow users without select privs + on mysql.proc to use them + + Bugs fixed or addressed + ----------------------- + Bug #16659 Can't use double quotation marks(") as password access server by Connector/NET [fixed] + Bug #17375 CommandBuilder ignores Unsigned flag at Parameter creation [fixed] + Bug #17749 There is no char type in MySqlDbType [fixed] + Bug #16788 Only byte arrays and strings can be serialized by MySqlBinary [fixed] + Bug #16645 FOUND_ROWS() Bug [can't repeat - added test case] + Bug #18570 Unsigned tinyint (NET byte) incorrectly determined param type from param val [fixed] + Bug #19261 Supplying Input Parameters [fixed] + Bug #19481 Where clause with datetime throws exception [any warning causes the exception] [fixed] + Bug #15077 Error MySqlCommandBuilder.DeriveParameters for sp without parameters. [fixed] + Bug #16934 Unsigned values > 2^63 (UInt64) cannot be used in prepared statements + Bug #19515 DiscoverParameters fails on numeric datatype [fixed] + Bug #17814 Stored procedure fails unless DbType set explicitly [fixed] + Bug #19294 IDataRecord.GetString method should return null for null values [fixed] + Bug #13590 ExecuteScalar returns only Int64 regardless of actual SQL type [added test case] + Bug #19017 GetBytes Error [fixed] + Bug #19936 DataReader already open exception [fixed] + Bug #17106 MySql.Data.MySqlClient.CharSetMap.GetEncoding thread synchronization issue [fixed] + Bug #17736 Selecting a row with with empty date '0000-00-00' results in Read() hanging. [fixed] + Bug #20581 Null Reference Exception when closing reader after stored procedure. + Bug #16884 Invalid DateTime Values from DataReader + Bug #7248 There is already an open DataReader associated with this Connection which must + Bug #22400 Nested transactions + Bug #11991 ExecuteScalar + Bug #14592 Wrong column length returned for VARCHAR UTF8 columns + Bug #18391 Better error handling for the .NET class "MySqlCommand" needed. + Bug #8131 Data Adapter doesn't close connection + Bug #9619 Cannot update row using DbDataAdapter when row contains an invalid date + Bug #15112 MySqlDateTime Constructor + +Version 1.0.7 + + Bugs fixed or addressed + ------------------------- + Bug #13658 connection.state does not update on Ping() [ fixed ] + Bug #13590 ExecuteScalar returns only Int64 regardless of actual SQL type [added test case] + Bug #13662 Prepare() truncates accented character input [already fixed, added test] + Bug #11947 MySQLCommandBuilder mishandling CONCAT() aliased column [updated test case] + Bug #13541 Prepare breaks if a parameter is used more than once [fixed] + Bug #13632 the MySQLCommandBuilder.deriveparameters has not been updated for MySQL 5 + Bug #13753 Exception calling stored procedure with special characters in parameters + Bug #11386 Numeric parameters with Precision and Scale not taken into account by Connector [added test case] + Bug #6902 Errors in parsing stored procedure parameters [fixed before, refixed] + Bug #13927 Multiple Records to same Table in Transaction Problem [fixed] + Bug #14631 "#42000Query was empty" [fixed] + Bug #13806 Does not support Code Page 932 + + Other changes + ------------------------- + Failure to provide parameters for out and inout values is now detected + Changed pooling code to remove issue of skipping Ping() on bogus connnections + +Version 1.0.6 + + Bugs fixed or addressed + ------------------------- + Bug #13036 Returns error when field names contain any of the following chars %<>()/ etc [fixed] + Bug #12835 1.0.5 won't install on system with 1.0.4 installed [fixed] + Bug #12978 Fatal Error C# Compilation [fixed] + Bug #13276 Exception on serialize after inserting null value [fixed] + Bug #13345 Connecting from mono 1.1.8 to MySQL 5.0 using MySQL Connector/Net 1.0.5 + + Other changes + ------------------------- + Changed SocketStream and StreamCreator to be compatible with Mono + Added options to not reset and reconfigure connections pulled from the pool + Connections pulled from the pool that are not reset are also not pinged + +Version 1.0.5 + + Bugs fixed or addressed + ------------------------- + Bug #8667 OUT parameters are not being valued [fixed] + Bug #8574 MySQLCommandBuilder unable to support inline functions [fixed] + Bug #8509 MySqlDataAdapter.FillSchema does not interpret unsigned integer [fixed] + Bug #8630 Executing a query with the SchemaOnly option reads the entire resultset [fixed] + Bug #7398 MySqlParameterCollection doesn't allow parameters without filled in names [fixed] + Bug #7623 Adding MySqlParameter causes error if MySqlDbType is Decimal [fixed] + Bug #8929 Timestamp values with a date > 10/29/9997 cause problems [fixed] + Bug #9237 MySqlDataReader.AffectedRecords not set to -1 [fixed] + Bug #9262 Floating point numbers boundary conditions (MinValue/MaxValue) + (added code but really this isn't a bug) + Bug #7951 Error reading a timestamp column + Bug #10644 Cannot call a stored function directly from Connector/Net + Bug #9722 Connector does not recognize parameters separated by a linefeed + Bug #10281 Clone issue with MySqlConnection + Bug #11450 Connector/Net, current database and stored procedures + Bug diff --git a/References/Lib/MySQL/COPYING b/References/Lib/MySQL/COPYING new file mode 100644 index 0000000..87ccb44 --- /dev/null +++ b/References/Lib/MySQL/COPYING @@ -0,0 +1,343 @@ +GNU General Public License +************************** + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your + freedom to share and change it. By contrast, the GNU General Public + License is intended to guarantee your freedom to share and change free + software--to make sure the software is free for all its users. This + General Public License applies to most of the Free Software + Foundation's software and to any other program whose authors commit to + using it. (Some other Free Software Foundation software is covered by + the GNU Library General Public License instead.) You can apply it to + your programs, too. + + When we speak of free software, we are referring to freedom, not + price. Our General Public Licenses are designed to make sure that you + have the freedom to distribute copies of free software (and charge for + this service if you wish), that you receive source code or can get it + if you want it, that you can change the software or use pieces of it + in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid + anyone to deny you these rights or to ask you to surrender the rights. + These restrictions translate to certain responsibilities for you if you + distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether + gratis or for a fee, you must give the recipients all the rights that + you have. You must make sure that they, too, receive or can get the + source code. And you must show them these terms so they know their + rights. + + We protect your rights with two steps: (1) copyright the software, and + (2) offer you this license which gives you legal permission to copy, + distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain + that everyone understands that there is no warranty for this free + software. If the software is modified by someone else and passed on, we + want its recipients to know that what they have is not the original, so + that any problems introduced by others will not reflect on the original + authors' reputations. + + Finally, any free program is threatened constantly by software + patents. We wish to avoid the danger that redistributors of a free + program will individually obtain patent licenses, in effect making the + program proprietary. To prevent this, we have made it clear that any + patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and + modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains + a notice placed by the copyright holder saying it may be distributed + under the terms of this General Public License. The "Program", below, + refers to any such program or work, and a "work based on the Program" + means either the Program or any derivative work under copyright law: + that is to say, a work containing the Program or a portion of it, + either verbatim or with modifications and/or translated into another + language. (Hereinafter, translation is included without limitation in + the term "modification".) Each licensee is addressed as "you". + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of + running the Program is not restricted, and the output from the Program + is covered only if its contents constitute a work based on the + Program (independent of having been made by running the Program). + Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's + source code as you receive it, in any medium, provided that you + conspicuously and appropriately publish on each copy an appropriate + copyright notice and disclaimer of warranty; keep intact all the + notices that refer to this License and to the absence of any warranty; + and give any other recipients of the Program a copy of this License + along with the Program. + + You may charge a fee for the physical act of transferring a copy, and + you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion + of it, thus forming a work based on the Program, and copy and + distribute such modifications or work under the terms of Section 1 + above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Program, + and can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based + on the Program, the distribution of the whole must be on the terms of + this License, whose permissions for other licensees extend to the + entire whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Program. + + In addition, mere aggregation of another work not based on the Program + with the Program (or with a work based on the Program) on a volume of + a storage or distribution medium does not bring the other work under + the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, + under Section 2) in object code or executable form under the terms of + Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + + The source code for a work means the preferred form of the work for + making modifications to it. For an executable work, complete source + code means all the source code for all modules it contains, plus any + associated interface definition files, plus the scripts used to + control compilation and installation of the executable. However, as a + special exception, the source code distributed need not include + anything that is normally distributed (in either source or binary + form) with the major components (compiler, kernel, and so on) of the + operating system on which the executable runs, unless that component + itself accompanies the executable. + + If distribution of executable or object code is made by offering + access to copy from a designated place, then offering equivalent + access to copy the source code from the same place counts as + distribution of the source code, even though third parties are not + compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program + except as expressly provided under this License. Any attempt + otherwise to copy, modify, sublicense or distribute the Program is + void, and will automatically terminate your rights under this License. + However, parties who have received copies, or rights, from you under + this License will not have their licenses terminated so long as such + parties remain in full compliance. + + 5. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Program or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Program (or any work based on the + Program), you indicate your acceptance of this License to do so, and + all its terms and conditions for copying, distributing or modifying + the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the + Program), the recipient automatically receives a license from the + original licensor to copy, distribute or modify the Program subject to + these terms and conditions. You may not impose any further + restrictions on the recipients' exercise of the rights granted herein. + You are not responsible for enforcing compliance by third parties to + this License. + + 7. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot + distribute so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you + may not distribute the Program at all. For example, if a patent + license would not permit royalty-free redistribution of the Program by + all those who receive copies directly or indirectly through you, then + the only way you could satisfy both it and this License would be to + refrain entirely from distribution of the Program. + + If any portion of this section is held invalid or unenforceable under + any particular circumstance, the balance of the section is intended to + apply and the section as a whole is intended to apply in other + circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system, which is + implemented by public license practices. Many people have made + generous contributions to the wide range of software distributed + through that system in reliance on consistent application of that + system; it is up to the author/donor to decide if he or she is willing + to distribute software through any other system and a licensee cannot + impose that choice. + + This section is intended to make thoroughly clear what is believed to + be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Program under this License + may add an explicit geographical distribution limitation excluding + those countries, so that distribution is permitted only in or among + countries not thus excluded. In such case, this License incorporates + the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions + of the General Public License from time to time. Such new versions will + be similar in spirit to the present version, but may differ in detail to + address new problems or concerns. + + Each version is given a distinguishing version number. If the Program + specifies a version number of this License which applies to it and "any + later version", you have the option of following the terms and conditions + either of that version or of any later version published by the Free + Software Foundation. If the Program does not specify a version number of + this License, you may choose any version ever published by the Free Software + Foundation. + + 10. If you wish to incorporate parts of the Program into other free + programs whose distribution conditions are different, write to the author + to ask for permission. For software which is copyrighted by the Free + Software Foundation, write to the Free Software Foundation; we sometimes + make exceptions for this. Our decision will be guided by the two goals + of preserving the free status of all derivatives of our free software and + of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY + FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN + OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES + PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED + OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS + TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE + PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, + REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING + WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR + REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, + INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING + OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED + TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY + YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER + PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest + to attach them to the start of each source file to most effectively + convey the exclusion of warranty; and each file should have at least + the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + Also add information on how to contact you by electronic and paper mail. + + If the program is interactive, make it output a short notice like this + when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + + The hypothetical commands `show w' and `show c' should show the appropriate + parts of the General Public License. Of course, the commands you use may + be called something other than `show w' and `show c'; they could even be + mouse-clicks or menu items--whatever suits your program. + + You should also get your employer (if you work as a programmer) or your + school, if any, to sign a "copyright disclaimer" for the program, if + necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + + This General Public License does not permit incorporating your program into + proprietary programs. If your program is a subroutine library, you may + consider it more useful to permit linking proprietary applications with the + library. If this is what you want to do, use the GNU Library General + Public License instead of this License. + diff --git a/References/Lib/MySQL/EXCEPTIONS b/References/Lib/MySQL/EXCEPTIONS new file mode 100644 index 0000000..3ccab74 --- /dev/null +++ b/References/Lib/MySQL/EXCEPTIONS @@ -0,0 +1,119 @@ +MySQL FLOSS License Exception + +The MySQL AB Exception for Free/Libre and Open Source +Software-only Applications Using MySQL Client Libraries (the +"FLOSS Exception"). + +Version 0.6, 7 March 2007 + +Exception Intent + +We want specified Free/Libre and Open Source Software (``FLOSS'') +applications to be able to use specified GPL-licensed MySQL client +libraries (the ``Program'') despite the fact that not all FLOSS +licenses are compatible with version 2 of the GNU General Public +License (the ``GPL''). + +Legal Terms and Conditions + +As a special exception to the terms and conditions of version 2.0 +of the GPL: + + 1. You are free to distribute a Derivative Work that is formed + entirely from the Program and one or more works (each, a + "FLOSS Work") licensed under one or more of the licenses + listed below in section 1, as long as: + a. You obey the GPL in all respects for the Program and the + Derivative Work, except for identifiable sections of the + Derivative Work which are not derived from the Program, + and which can reasonably be considered independent and + separate works in themselves, + b. all identifiable sections of the Derivative Work which + are not derived from the Program, and which can + reasonably be considered independent and separate works + in themselves, + i. are distributed subject to one of the FLOSS licenses + listed below, and + ii. the object code or executable form of those sections + are accompanied by the complete corresponding + machine-readable source code for those sections on + the same medium and under the same FLOSS license as + the corresponding object code or executable forms of + those sections, and + c. any works which are aggregated with the Program or with a + Derivative Work on a volume of a storage or distribution + medium in accordance with the GPL, can reasonably be + considered independent and separate works in themselves + which are not derivatives of either the Program, a + Derivative Work or a FLOSS Work. + If the above conditions are not met, then the Program may only + be copied, modified, distributed or used under the terms and + conditions of the GPL or another valid licensing option from + MySQL AB. + + 2. FLOSS License List + +License name Version(s)/Copyright Date +Academic Free License 2.0 +Apache Software License 1.0/1.1/2.0 +Apple Public Source License 2.0 +Artistic license From Perl 5.8.0 +BSD license "July 22 1999" +Common Development and Distribution License (CDDL) 1.0 +Common Public License 1.0 +Eclipse Public License 1.0 +GNU Library or "Lesser" General Public License (LGPL) 2.0/2.1 +Jabber Open Source License 1.0 +MIT license (As listed in file MIT-License.txt) --- +Mozilla Public License (MPL) 1.0/1.1 +Open Software License 2.0 +OpenSSL license (with original SSLeay license) "2003" ("1998") +PHP License 3.0 +Python license (CNRI Python License) --- +Python Software Foundation License 2.1.1 +Sleepycat License "1999" +University of Illinois/NCSA Open Source License --- +W3C License "2001" +X11 License "2001" +Zlib/libpng License --- +Zope Public License 2.0 + + Due to the many variants of some of the above licenses, we + require that any version follow the 2003 version of the Free + Software Foundation's Free Software Definition + (http://www.gnu.org/philosophy/free-sw.html) or version 1.9 of + the Open Source Definition by the Open Source Initiative + (http://www.opensource.org/docs/definition.php). + + 3. Definitions + + a. Terms used, but not defined, herein shall have the + meaning provided in the GPL. + b. Derivative Work means a derivative work under copyright + law. + + 4. Applicability: This FLOSS Exception applies to all Programs + that contain a notice placed by MySQL AB saying that the + Program may be distributed under the terms of this FLOSS + Exception. If you create or distribute a work which is a + Derivative Work of both the Program and any other work + licensed under the GPL, then this FLOSS Exception is not + available for that work; thus, you must remove the FLOSS + Exception notice from that work and comply with the GPL in all + respects, including by retaining all GPL notices. You may + choose to redistribute a copy of the Program exclusively under + the terms of the GPL by removing the FLOSS Exception notice + from that copy of the Program, provided that the copy has + never been modified by you or any third party. + +Appendix A. Qualified Libraries and Packages + +The following is a non-exhaustive list of libraries and packages +which are covered by the FLOSS License Exception. Please note that +this appendix is provided merely as an additional service to +specific FLOSS projects wishing to simplify licensing information +for their users. Compliance with one of the licenses noted under +the "FLOSS license list" section remains a prerequisite. + +Package Name Qualifying License and Version +Apache Portable Runtime (APR) Apache Software License 2.0 \ No newline at end of file diff --git a/References/Lib/MySQL/MySql.Data.chm b/References/Lib/MySQL/MySql.Data.chm new file mode 100644 index 0000000..e012516 Binary files /dev/null and b/References/Lib/MySQL/MySql.Data.chm differ diff --git a/References/Lib/MySQL/README b/References/Lib/MySQL/README new file mode 100644 index 0000000..4fd2e30 --- /dev/null +++ b/References/Lib/MySQL/README @@ -0,0 +1,36 @@ +MySQL Connector/Net 6.0 +MySQL AB's ADO.Net Driver for MySQL +Copyright (c) 2004-2007 MySQL AB + +CONTENTS + +* License +* Documentation Location + + +LICENSE + +MySQL Connector/Net is licensed under the GPL or a commercial license +from MySQL AB. + +If you have licensed this product under the GPL, please see the COPYING +file for more information. + +If you have licensed this product under a commercial license from +MySQL AB, please see the file "MySQLEULA.txt" that comes with this +distribution for the terms of the license. + +If you need non-GPL licenses for commercial distribution please contact +me or . + + +DOCUMENTATION LOCATION + +The documentation currently exists in MSDN help format and is located in the 'doc' +directory. + +-- +This software is OSI Certified Open Source Software. +OSI Certified is a certification mark of the Open Source Initiative. + + diff --git a/References/Lib/MySQL/Release Notes.txt b/References/Lib/MySQL/Release Notes.txt new file mode 100644 index 0000000..0d4af21 --- /dev/null +++ b/References/Lib/MySQL/Release Notes.txt @@ -0,0 +1,42 @@ +Connector/Net 6.0 Release Notes +------------------------------------ + +Welcome to the release notes for Connector/Net 6.0 + +What's new in 6.0.4? +-------------------- +This is the first post-GA release of 6.0 and we really wanted to address some of the bugs that were in the +initial GA release. Here are the highlights: + +* Added bool and guid support to our entity framework bits +* Added SunConnect registration to the installer. Please register! +* Our Visual Studio integration bits should compile better under VS2005 +* Unsigned values are not supported with our entity framework bits +* Many other significant bug fixes. Check the change log! + +Known Issues In 6.0.4 +--------------------- +* The table editor in the Visual Studio integration will shorten the window name when you save. This has no +real effect on the function of the editor but it is not quite correct. + +* Our visual studio editors are completely rewritten and we expect to find some 1.0 type bugs after release. We will +update these quickly and we ask for your patience. + + +Data type support in Entity Framework +------------------------------------- +tinyint(1) columns are transparently treated as boolean columns + +binary(16) columns are transparently treated as guid columns + +unsigned data types are now supported +Unsigned integer types are not directly supported in the entity framework. We are working around +that by mapping those columns to the next higher signed integer type. + +For example, an unsigned integer column will appear in an entity framework model as a +Int64 column. This allows your application to set a value up to the maximum value for +an unsigned int32 column and preserve the value. However, be aware that if you set a signed +value or exceed the maximum value for your underlying database column, data truncation will occur. +The moral of the story is if you are going to use unsigned types (discouraged) then know what +your underlying column types are and don't exceed their max sizes. + diff --git a/References/Lib/MySQL/mysql.data.cf.dll b/References/Lib/MySQL/mysql.data.cf.dll new file mode 100644 index 0000000..5c177fb Binary files /dev/null and b/References/Lib/MySQL/mysql.data.cf.dll differ diff --git a/References/Lib/MySQL/mysql.data.dll b/References/Lib/MySQL/mysql.data.dll new file mode 100644 index 0000000..a879b77 Binary files /dev/null and b/References/Lib/MySQL/mysql.data.dll differ diff --git a/References/Lib/MySQL/mysql.data.entity.dll b/References/Lib/MySQL/mysql.data.entity.dll new file mode 100644 index 0000000..67a83e0 Binary files /dev/null and b/References/Lib/MySQL/mysql.data.entity.dll differ diff --git a/References/Lib/MySQL/mysql.visualstudio.dll b/References/Lib/MySQL/mysql.visualstudio.dll new file mode 100644 index 0000000..09151a6 Binary files /dev/null and b/References/Lib/MySQL/mysql.visualstudio.dll differ diff --git a/References/Lib/MySQL/mysql.web.dll b/References/Lib/MySQL/mysql.web.dll new file mode 100644 index 0000000..fc3a0dc Binary files /dev/null and b/References/Lib/MySQL/mysql.web.dll differ diff --git a/References/Tools/NUnit/NUnitFitTests.html b/References/Tools/NUnit/NUnitFitTests.html new file mode 100644 index 0000000..b7eb5c9 --- /dev/null +++ b/References/Tools/NUnit/NUnitFitTests.html @@ -0,0 +1,277 @@ + + + +

    NUnit Acceptance Tests

    +

    + Developers love self-referential programs! Hence, NUnit has always run all it's + own tests, even those that are not really unit tests. +

    Now, beginning with NUnit 2.4, NUnit has top-level tests using Ward Cunningham's + FIT framework. At this time, the tests are pretty rudimentary, but it's a start + and it's a framework for doing more. +

    Running the Tests

    +

    Open a console or shell window and navigate to the NUnit bin directory, which + contains this file. To run the test under Microsoft .Net, enter the command +

        runFile NUnitFitTests.html TestResults.html .
    + To run it under Mono, enter +
        mono runFile.exe NUnitFitTests.html TestResults.html .
    + Note the space and dot at the end of each command. The results of your test + will be in TestResults.html in the same directory. +

    Platform and CLR Version

    + + + + +
    NUnit.Fixtures.PlatformInfo
    +

    Verify Unit Tests

    +

    + Load and run the NUnit unit tests, verifying that the results are as expected. + When these tests are run on different platforms, different numbers of tests may + be skipped, so the values for Skipped and Run tests are informational only. +

    + The number of tests in each assembly should be constant across all platforms - + any discrepancy usually means that one of the test source files was not + compiled on the platform. There should be no failures and no tests ignored. +

    Note: + At the moment, the nunit.extensions.tests assembly is failing because the + fixture doesn't initialize addins in the test domain. +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NUnit.Fixtures.AssemblyRunner
    AssemblyTests()Run()Skipped()Ignored()Failures()
    nunit.framework.tests.dll397  00
    nunit.core.tests.dll355  00
    nunit.util.tests.dll238  00
    nunit.mocks.tests.dll43  00
    nunit.extensions.tests.dll5  00
    nunit-console.tests.dll40  00
    nunit.uikit.tests.dll34  00
    nunit-gui.tests.dll15  00
    nunit.fixtures.tests.dll6  00
    +

    Code Snippet Tests

    +

    + These tests create a test assembly from a snippet of code and then load and run + the tests that it contains, verifying that the structure of the loaded tests is + as expected and that the number of tests run, skipped, ignored or failed is + correct. +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NUnit.Fixtures.SnippetRunner
    CodeTree()Run()Skipped()Ignored()Failures()
    public class TestClass
    +{
    +}
    +
    EMPTY0000
    using NUnit.Framework;
    +
    +[TestFixture]
    +public class TestClass
    +{
    +}
    +
    TestClass0000
    using NUnit.Framework;
    +
    +[TestFixture]
    +public class TestClass
    +{
    +    [Test]
    +    public void T1() { }
    +    [Test]
    +    public void T2() { }
    +    [Test]
    +    public void T3() { }
    +}
    +
    TestClass
    +>T1
    +>T2
    +>T3
    +
    3000
    using NUnit.Framework;
    +
    +[TestFixture]
    +public class TestClass1
    +{
    +    [Test]
    +    public void T1() { }
    +}
    +
    +[TestFixture]
    +public class TestClass2
    +{
    +    [Test]
    +    public void T2() { }
    +    [Test]
    +    public void T3() { }
    +}
    +
    TestClass1
    +>T1
    +TestClass2
    +>T2
    +>T3
    +
    3000
    using NUnit.Framework;
    +
    +[TestFixture]
    +public class TestClass
    +{
    +    [Test]
    +    public void T1() { }
    +    [Test, Ignore]
    +    public void T2() { }
    +    [Test]
    +    public void T3() { }
    +}
    +
    TestClass
    +>T1
    +>T2
    +>T3
    +
    2010
    using NUnit.Framework;
    +
    +[TestFixture]
    +public class TestClass
    +{
    +    [Test]
    +    public void T1() { }
    +    [Test, Explicit]
    +    public void T2() { }
    +    [Test]
    +    public void T3() { }
    +}
    +
    TestClass
    +>T1
    +>T2
    +>T3
    +
    2100
    +

    Summary Information

    + + + + +
    fit.Summary
    + + diff --git a/References/Tools/NUnit/NUnitTests.config b/References/Tools/NUnit/NUnitTests.config new file mode 100644 index 0000000..ecbd55e --- /dev/null +++ b/References/Tools/NUnit/NUnitTests.config @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/References/Tools/NUnit/NUnitTests.nunit b/References/Tools/NUnit/NUnitTests.nunit new file mode 100644 index 0000000..bb80dd6 --- /dev/null +++ b/References/Tools/NUnit/NUnitTests.nunit @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/References/Tools/NUnit/agent.conf b/References/Tools/NUnit/agent.conf new file mode 100644 index 0000000..b4cf550 --- /dev/null +++ b/References/Tools/NUnit/agent.conf @@ -0,0 +1,4 @@ + + 8080 + . + \ No newline at end of file diff --git a/References/Tools/NUnit/agent.log.conf b/References/Tools/NUnit/agent.log.conf new file mode 100644 index 0000000..d340cad --- /dev/null +++ b/References/Tools/NUnit/agent.log.conf @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/References/Tools/NUnit/clr.bat b/References/Tools/NUnit/clr.bat new file mode 100644 index 0000000..8291f26 --- /dev/null +++ b/References/Tools/NUnit/clr.bat @@ -0,0 +1,96 @@ +@echo off +rem Run a program under a particular version of the .Net framework +rem by setting the COMPLUS_Version environment variable. +rem +rem This command was written by Charlie Poole for the NUnit project. +rem You may use it separately from NUnit at your own risk. + +if "%1"=="/?" goto help +if "%1"=="?" goto help +if "%1"=="" goto GetVersion +if /I "%1"=="off" goto RemoveVersion +if "%2"=="" goto SetVersion +goto main + +:help +echo Control the version of the .Net framework that is used. The +echo command has several forms: +echo. +echo CLR +echo Reports the version of the CLR that has been set +echo. +echo CLR version +echo Sets the local shell environment to use a specific +echo version of the CLR for subsequent commands. +echo. +echo CLR version command [arguments] +echo Executes a single command using the specified CLR version. +echo. +echo CLR off +echo Turns off specific version selection for commands +echo. +echo The CLR version may be specified as vn.n.n or n.n.n. In addition, +echo the following shortcuts are recognized: +echo net-1.0, 1.0 For version 1.0.3705 +echo net-1.1, 1.1 For version 1.1.4322 +echo beta2 For version 2.0.50215 +echo net-2.0, 2.0 For version 2.0.50727 +echo. +echo NOTE: +echo Any specific settings for required or supported runtime in +echo the ^ section of a program's config file will +echo override the version specified by this command, and the +echo command will have no effect. +echo. +goto done + +:main + +setlocal +set CMD= +call :SetVersion %1 +shift /1 + +:loop 'Copy remaining arguments to form the command +if "%1"=="" goto run +set CMD=%CMD% %1 +shift /1 +goto :loop + +:run 'Execute the command +%CMD% +endlocal +goto done + +:SetVersion +set COMPLUS_Version=%1 + +rem Substitute proper format for certain names +if /I "%COMPLUS_Version:~0,1%"=="v" goto useit +if /I "%COMPLUS_Version%"=="net-1.0" set COMPLUS_Version=v1.0.3705&goto report +if /I "%COMPLUS_Version%"=="1.0" set COMPLUS_Version=v1.0.3705&goto report +if /I "%COMPLUS_Version%"=="net-1.1" set COMPLUS_Version=v1.1.4322&goto report +if /I "%COMPLUS_Version%"=="1.1" set COMPLUS_Version=v1.1.4322&goto report +if /I "%COMPLUS_Version%"=="beta2" set COMPLUS_Version=v2.0.50215&goto report +if /I "%COMPLUS_Version%"=="net-2.0" set COMPLUS_Version=v2.0.50727&goto report +if /I "%COMPLUS_Version%"=="2.0" set COMPLUS_Version=v2.0.50727&goto report + +rem Add additional substitutions here, branching to report + +rem assume it's a version number without 'v' +set COMPLUS_Version=v%COMPLUS_Version% + +:report +echo Setting CLR version to %COMPLUS_Version% +goto done + +:GetVersion +if "%COMPLUS_Version%"=="" echo CLR version is not set +if NOT "%COMPLUS_Version%"=="" echo CLR version is set to %COMPLUS_Version% +goto done + +:RemoveVersion +set COMPLUS_Version= +echo CLR version is no longer set + +:done \ No newline at end of file diff --git a/References/Tools/NUnit/framework/nunit.framework.dll b/References/Tools/NUnit/framework/nunit.framework.dll new file mode 100644 index 0000000..2729ddf Binary files /dev/null and b/References/Tools/NUnit/framework/nunit.framework.dll differ diff --git a/References/Tools/NUnit/framework/nunit.framework.xml b/References/Tools/NUnit/framework/nunit.framework.xml new file mode 100644 index 0000000..ccfa9ee --- /dev/null +++ b/References/Tools/NUnit/framework/nunit.framework.xml @@ -0,0 +1,10088 @@ + + + + nunit.framework + + + + + TypeConstraint is the abstract base for constraints + that take a Type as their expected value. + + + + + The Constraint class is the base of all built-in constraints + within NUnit. It provides the operator overloads used to combine + constraints. + + + + + The IConstraintExpression interface is implemented by all + complete and resolvable constraints and expressions. + + + + + Return the top-level constraint for this expression + + + + + + Static UnsetObject used to detect derived constraints + failing to set the actual value. + + + + + The actual value being tested against a constraint + + + + + The display name of this Constraint for use by ToString() + + + + + Argument fields used by ToString(); + + + + + The builder holding this constraint + + + + + Construct a constraint with no arguments + + + + + Construct a constraint with one argument + + + + + Construct a constraint with two arguments + + + + + Sets the ConstraintBuilder holding this constraint + + + + + Write the failure message to the MessageWriter provided + as an argument. The default implementation simply passes + the constraint and the actual value to the writer, which + then displays the constraint description and the value. + + Constraints that need to provide additional details, + such as where the error occured can override this. + + The MessageWriter on which to display the message + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Test whether the constraint is satisfied by an + ActualValueDelegate that returns the value to be tested. + The default implementation simply evaluates the delegate + but derived classes may override it to provide for delayed + processing. + + An ActualValueDelegate + True for success, false for failure + + + + Test whether the constraint is satisfied by a given reference. + The default implementation simply dereferences the value but + derived classes may override it to provide for delayed processing. + + A reference to the value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + Write the actual value for a failing constraint test to a + MessageWriter. The default implementation simply writes + the raw value of actual, leaving it to the writer to + perform any formatting. + + The writer on which the actual value is displayed + + + + Default override of ToString returns the constraint DisplayName + followed by any arguments within angle brackets. + + + + + + This operator creates a constraint that is satisfied only if both + argument constraints are satisfied. + + + + + This operator creates a constraint that is satisfied if either + of the argument constraints is satisfied. + + + + + This operator creates a constraint that is satisfied if the + argument constraint is not satisfied. + + + + + Returns a DelayedConstraint with the specified delay time. + + The delay in milliseconds. + + + + + Returns a DelayedConstraint with the specified delay time + and polling interval. + + The delay in milliseconds. + The interval at which to test the constraint. + + + + + The display name of this Constraint for use by ToString(). + The default value is the name of the constraint with + trailing "Constraint" removed. Derived classes may set + this to another name in their constructors. + + + + + Returns a ConstraintExpression by appending And + to the current constraint. + + + + + Returns a ConstraintExpression by appending And + to the current constraint. + + + + + Returns a ConstraintExpression by appending Or + to the current constraint. + + + + + Class used to detect any derived constraints + that fail to set the actual value in their + Matches override. + + + + + The expected Type used by the constraint + + + + + Construct a TypeConstraint for a given Type + + + + + + Write the actual value for a failing constraint test to a + MessageWriter. TypeConstraints override this method to write + the name of the type. + + The writer on which the actual value is displayed + + + + ExactTypeConstraint is used to test that an object + is of the exact type provided in the constructor + + + + + Construct an ExactTypeConstraint for a given Type + + The expected Type. + + + + Test that an object is of the exact type specified + + The actual value. + True if the tested object is of the exact type provided, otherwise false. + + + + Write the description of this constraint to a MessageWriter + + The MessageWriter to use + + + + InstanceOfTypeConstraint is used to test that an object + is of the same type provided or derived from it. + + + + + Construct an InstanceOfTypeConstraint for the type provided + + The expected Type + + + + Test whether an object is of the specified type or a derived type + + The object to be tested + True if the object is of the provided type or derives from it, otherwise false. + + + + Write a description of this constraint to a MessageWriter + + The MessageWriter to use + + + + AssignableFromConstraint is used to test that an object + can be assigned from a given Type. + + + + + Construct an AssignableFromConstraint for the type provided + + + + + + Test whether an object can be assigned from the specified type + + The object to be tested + True if the object can be assigned a value of the expected Type, otherwise false. + + + + Write a description of this constraint to a MessageWriter + + The MessageWriter to use + + + + AssignableToConstraint is used to test that an object + can be assigned to a given Type. + + + + + Construct an AssignableToConstraint for the type provided + + + + + + Test whether an object can be assigned to the specified type + + The object to be tested + True if the object can be assigned a value of the expected Type, otherwise false. + + + + Write a description of this constraint to a MessageWriter + + The MessageWriter to use + + + + ConstraintBuilder maintains the stacks that are used in + processing a ConstraintExpression. An OperatorStack + is used to hold operators that are waiting for their + operands to be reognized. a ConstraintStack holds + input constraints as well as the results of each + operator applied. + + + + + Initializes a new instance of the class. + + + + + Appends the specified operator to the expression by first + reducing the operator stack and then pushing the new + operator on the stack. + + The operator to push. + + + + Appends the specified constraint to the expresson by pushing + it on the constraint stack. + + The constraint to push. + + + + Sets the top operator right context. + + The right context. + + + + Reduces the operator stack until the topmost item + precedence is greater than or equal to the target precedence. + + The target precedence. + + + + Resolves this instance, returning a Constraint. If the builder + is not currently in a resolvable state, an exception is thrown. + + The resolved constraint + + + + Gets a value indicating whether this instance is resolvable. + + + true if this instance is resolvable; otherwise, false. + + + + + OperatorStack is a type-safe stack for holding ConstraintOperators + + + + + Initializes a new instance of the class. + + The builder. + + + + Pushes the specified operator onto the stack. + + The op. + + + + Pops the topmost operator from the stack. + + + + + + Gets a value indicating whether this is empty. + + true if empty; otherwise, false. + + + + Gets the topmost operator without modifying the stack. + + The top. + + + + ConstraintStack is a type-safe stack for holding Constraints + + + + + Initializes a new instance of the class. + + The builder. + + + + Pushes the specified constraint. As a side effect, + the constraint's builder field is set to the + ConstraintBuilder owning this stack. + + The constraint. + + + + Pops this topmost constrait from the stack. + As a side effect, the constraint's builder + field is set to null. + + + + + + Gets a value indicating whether this is empty. + + true if empty; otherwise, false. + + + + Gets the topmost constraint without modifying the stack. + + The topmost constraint + + + + ThrowsConstraint is used to test the exception thrown by + a delegate by applying a constraint to it. + + + + + Abstract base class used for prefixes + + + + + The base constraint + + + + + Construct given a base constraint + + + + + + Initializes a new instance of the class, + using a constraint to be applied to the exception. + + A constraint to apply to the caught exception. + + + + Executes the code of the delegate and captures any exception. + If a non-null base constraint was provided, it applies that + constraint to the exception. + + A delegate representing the code to be tested + True if an exception is thrown and the constraint succeeds, otherwise false + + + + Converts an ActualValueDelegate to a TestDelegate + before calling the primary overload. + + + + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + Write the actual value for a failing constraint test to a + MessageWriter. The default implementation simply writes + the raw value of actual, leaving it to the writer to + perform any formatting. + + The writer on which the actual value is displayed + + + + Returns the string representation of this constraint + + + + + Get the actual exception thrown - used by Assert.Throws. + + + + + ThrowsNothingConstraint tests that a delegate does not + throw an exception. + + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True if no exception is thrown, otherwise false + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + Write the actual value for a failing constraint test to a + MessageWriter. The default implementation simply writes + the raw value of actual, leaving it to the writer to + perform any formatting. + + The writer on which the actual value is displayed + + + + CollectionConstraint is the abstract base class for + constraints that operate on collections. + + + + + Construct an empty CollectionConstraint + + + + + Construct a CollectionConstraint + + + + + + Determines whether the specified enumerable is empty. + + The enumerable. + + true if the specified enumerable is empty; otherwise, false. + + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Protected method to be implemented by derived classes + + + + + + + CollectionItemsEqualConstraint is the abstract base class for all + collection constraints that apply some notion of item equality + as a part of their operation. + + + + + Construct an empty CollectionConstraint + + + + + Construct a CollectionConstraint + + + + + + Flag the constraint to use the supplied IComparer object. + + The IComparer object to use. + Self. + + + + Flag the constraint to use the supplied IComparer object. + + The IComparer object to use. + Self. + + + + Flag the constraint to use the supplied Comparison object. + + The IComparer object to use. + Self. + + + + Flag the constraint to use the supplied IEqualityComparer object. + + The IComparer object to use. + Self. + + + + Flag the constraint to use the supplied IEqualityComparer object. + + The IComparer object to use. + Self. + + + + Compares two collection members for equality + + + + + Return a new CollectionTally for use in making tests + + The collection to be included in the tally + + + + Flag the constraint to ignore case and return self. + + + + + CollectionTally counts (tallies) the number of + occurences of each object in one or more enumerations. + + + + + Construct a CollectionTally object from a comparer and a collection + + + + + Try to remove an object from the tally + + The object to remove + True if successful, false if the object was not found + + + + Try to remove a set of objects from the tally + + The objects to remove + True if successful, false if any object was not found + + + + The number of objects remaining in the tally + + + + + EmptyCollectionConstraint tests whether a collection is empty. + + + + + Check that the collection is empty + + + + + + + Write the constraint description to a MessageWriter + + + + + + UniqueItemsConstraint tests whether all the items in a + collection are unique. + + + + + Check that all items are unique. + + + + + + + Write a description of this constraint to a MessageWriter + + + + + + CollectionContainsConstraint is used to test whether a collection + contains an expected object as a member. + + + + + Construct a CollectionContainsConstraint + + + + + + Test whether the expected item is contained in the collection + + + + + + + Write a descripton of the constraint to a MessageWriter + + + + + + CollectionEquivalentCOnstraint is used to determine whether two + collections are equivalent. + + + + + Construct a CollectionEquivalentConstraint + + + + + + Test whether two collections are equivalent + + + + + + + Write a description of this constraint to a MessageWriter + + + + + + CollectionSubsetConstraint is used to determine whether + one collection is a subset of another + + + + + Construct a CollectionSubsetConstraint + + The collection that the actual value is expected to be a subset of + + + + Test whether the actual collection is a subset of + the expected collection provided. + + + + + + + Write a description of this constraint to a MessageWriter + + + + + + CollectionOrderedConstraint is used to test whether a collection is ordered. + + + + + Construct a CollectionOrderedConstraint + + + + + Modifies the constraint to use an IComparer and returns self. + + + + + Modifies the constraint to use an IComparer<T> and returns self. + + + + + Modifies the constraint to use a Comparison<T> and returns self. + + + + + Modifies the constraint to test ordering by the value of + a specified property and returns self. + + + + + Test whether the collection is ordered + + + + + + + Write a description of the constraint to a MessageWriter + + + + + + Returns the string representation of the constraint. + + + + + + If used performs a reverse comparison + + + + + EmptyDirectoryConstraint is used to test that a directory is empty + + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + Write the actual value for a failing constraint test to a + MessageWriter. The default implementation simply writes + the raw value of actual, leaving it to the writer to + perform any formatting. + + The writer on which the actual value is displayed + + + + SubDirectoryConstraint is used to test that one directory is a subdirectory of another. + + + + + Initializes a new instance of the class. + + The dir info. + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + Builds a list of DirectoryInfo objects, recursing where necessary + + directory to recurse + list of DirectoryInfo objects from the top level + + + + private method to determine whether a directory is within the path + + top-level directory to search + directory to search for + true if found, false if not + + + + Method to compare two DirectoryInfo objects + + first directory to compare + second directory to compare + true if equivalent, false if not + + + + PathConstraint serves as the abstract base of constraints + that operate on paths and provides several helper methods. + + + + + The expected path used in the constraint + + + + + Flag indicating whether a caseInsensitive comparison should be made + + + + + Construct a PathConstraint for a give expected path + + The expected path + + + + Returns the string representation of this constraint + + + + + Canonicalize the provided path + + + The path in standardized form + + + + Test whether two paths are the same + + The first path + The second path + + + + + Test whether one path is the same as or under another path + + The first path - supposed to be the parent path + The second path - supposed to be the child path + + + + + Modifies the current instance to be case-insensitve + and returns it. + + + + + Modifies the current instance to be case-sensitve + and returns it. + + + + + Summary description for SamePathConstraint. + + + + + Initializes a new instance of the class. + + The expected path + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + SamePathOrUnderConstraint tests that one path is under another + + + + + Initializes a new instance of the class. + + The expected path + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + PropertyExistsConstraint tests that a named property + exists on the object provided through Match. + + Originally, PropertyConstraint provided this feature + in addition to making optional tests on the vaue + of the property. The two constraints are now separate. + + + + + Initializes a new instance of the class. + + The name of the property. + + + + Test whether the property exists for a given object + + The object to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + Write the actual value for a failing constraint test to a + MessageWriter. + + The writer on which the actual value is displayed + + + + Returns the string representation of the constraint. + + + + + + PropertyConstraint extracts a named property and uses + its value as the actual value for a chained constraint. + + + + + Initializes a new instance of the class. + + The name. + The constraint to apply to the property. + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + Write the actual value for a failing constraint test to a + MessageWriter. The default implementation simply writes + the raw value of actual, leaving it to the writer to + perform any formatting. + + The writer on which the actual value is displayed + + + + Returns the string representation of the constraint. + + + + + + ConstraintExpression represents a compound constraint in the + process of being constructed from a series of syntactic elements. + + Individual elements are appended to the expression as they are + reognized. Once an actual Constraint is appended, the expression + returns a resolvable Constraint. + + + + + ConstraintExpressionBase is the abstract base class for the + generated ConstraintExpression class, which represents a + compound constraint in the process of being constructed + from a series of syntactic elements. + + NOTE: ConstraintExpressionBase is aware of some of its + derived classes, which is an apparent violation of + encapsulation. Ideally, these classes would be a + single class, but they must be separated in order to + allow parts to be generated under .NET 1.x and to + provide proper user feedback in syntactically + aware IDEs. + + + + + The ConstraintBuilder holding the elements recognized so far + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the + class passing in a ConstraintBuilder, which may be pre-populated. + + The builder. + + + + Returns a string representation of the expression as it + currently stands. This should only be used for testing, + since it has the side-effect of resolving the expression. + + + + + + Appends an operator to the expression and returns the + resulting expression itself. + + + + + Appends a self-resolving operator to the expression and + returns a new ResolvableConstraintExpression. + + + + + Appends a constraint to the expression and returns that + constraint, which is associated with the current state + of the expression being built. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the + class passing in a ConstraintBuilder, which may be pre-populated. + + The builder. + + + + Returns a new PropertyConstraintExpression, which will either + test for the existence of the named property on the object + being tested or apply any following constraint to that property. + + + + + Returns a new AttributeConstraint checking for the + presence of a particular attribute on an object. + + + + + Returns a new AttributeConstraint checking for the + presence of a particular attribute on an object. + + + + + Returns the constraint provided as an argument - used to allow custom + custom constraints to easily participate in the syntax. + + + + + Returns the constraint provided as an argument - used to allow custom + custom constraints to easily participate in the syntax. + + + + + Returns a constraint that tests two items for equality + + + + + Returns a constraint that tests that two references are the same object + + + + + Returns a constraint that tests whether the + actual value is greater than the suppled argument + + + + + Returns a constraint that tests whether the + actual value is greater than or equal to the suppled argument + + + + + Returns a constraint that tests whether the + actual value is greater than or equal to the suppled argument + + + + + Returns a constraint that tests whether the + actual value is less than the suppled argument + + + + + Returns a constraint that tests whether the + actual value is less than or equal to the suppled argument + + + + + Returns a constraint that tests whether the + actual value is less than or equal to the suppled argument + + + + + Returns a constraint that tests whether the actual + value is of the exact type supplied as an argument. + + + + + Returns a constraint that tests whether the actual + value is of the exact type supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is of the type supplied as an argument or a derived type. + + + + + Returns a constraint that tests whether the actual value + is of the type supplied as an argument or a derived type. + + + + + Returns a constraint that tests whether the actual value + is of the type supplied as an argument or a derived type. + + + + + Returns a constraint that tests whether the actual value + is of the type supplied as an argument or a derived type. + + + + + Returns a constraint that tests whether the actual value + is assignable from the type supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is assignable from the type supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is assignable from the type supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is assignable from the type supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is a collection containing the same elements as the + collection supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is a subset of the collection supplied as an argument. + + + + + Returns a new CollectionContainsConstraint checking for the + presence of a particular object in the collection. + + + + + Returns a new CollectionContainsConstraint checking for the + presence of a particular object in the collection. + + + + + Returns a new ContainsConstraint. This constraint + will, in turn, make use of the appropriate second-level + constraint, depending on the type of the actual argument. + This overload is only used if the item sought is a string, + since any other type implies that we are looking for a + collection member. + + + + + Returns a constraint that succeeds if the actual + value contains the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value contains the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value starts with the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value starts with the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value ends with the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value ends with the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value matches the Regex pattern supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value matches the Regex pattern supplied as an argument. + + + + + Returns a constraint that tests whether the path provided + is the same as an expected path after canonicalization. + + + + + Returns a constraint that tests whether the path provided + is the same path or under an expected path after canonicalization. + + + + + Returns a constraint that tests whether the actual value falls + within a specified range. + + + + + Returns a ConstraintExpression that negates any + following constraint. + + + + + Returns a ConstraintExpression that negates any + following constraint. + + + + + Returns a ConstraintExpression, which will apply + the following constraint to all members of a collection, + succeeding if all of them succeed. + + + + + Returns a ConstraintExpression, which will apply + the following constraint to all members of a collection, + succeeding if at least one of them succeeds. + + + + + Returns a ConstraintExpression, which will apply + the following constraint to all members of a collection, + succeeding if all of them fail. + + + + + Returns a new ConstraintExpression, which will apply the following + constraint to the Length property of the object being tested. + + + + + Returns a new ConstraintExpression, which will apply the following + constraint to the Count property of the object being tested. + + + + + Returns a new ConstraintExpression, which will apply the following + constraint to the Message property of the object being tested. + + + + + Returns a new ConstraintExpression, which will apply the following + constraint to the InnerException property of the object being tested. + + + + + With is currently a NOP - reserved for future use. + + + + + Returns a constraint that tests for null + + + + + Returns a constraint that tests for True + + + + + Returns a constraint that tests for False + + + + + Returns a constraint that tests for NaN + + + + + Returns a constraint that tests for empty + + + + + Returns a constraint that tests whether a collection + contains all unique items. + + + + + Returns a constraint that tests whether an object graph is serializable in binary format. + + + + + Returns a constraint that tests whether an object graph is serializable in xml format. + + + + + Returns a constraint that tests whether a collection is ordered + + + + + RangeConstraint tests whethe two values are within a + specified range. + + + + + Initializes a new instance of the class. + + From. + To. + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + Modifies the constraint to use an IComparer and returns self. + + + + + Modifies the constraint to use an IComparer<T> and returns self. + + + + + Modifies the constraint to use a Comparison<T> and returns self. + + + + + EqualConstraint is able to compare an actual value with the + expected value provided in its constructor. Two objects are + considered equal if both are null, or if both have the same + value. NUnit has special semantics for some object types. + + + + + If true, strings in error messages will be clipped + + + + + NUnitEqualityComparer used to test equality. + + + + + Initializes a new instance of the class. + + The expected value. + + + + Flag the constraint to use a tolerance when determining equality. + + Tolerance value to be used + Self. + + + + Flag the constraint to use the supplied IComparer object. + + The IComparer object to use. + Self. + + + + Flag the constraint to use the supplied IComparer object. + + The IComparer object to use. + Self. + + + + Flag the constraint to use the supplied IComparer object. + + The IComparer object to use. + Self. + + + + Flag the constraint to use the supplied Comparison object. + + The IComparer object to use. + Self. + + + + Flag the constraint to use the supplied IEqualityComparer object. + + The IComparer object to use. + Self. + + + + Flag the constraint to use the supplied IEqualityComparer object. + + The IComparer object to use. + Self. + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write a failure message. Overridden to provide custom + failure messages for EqualConstraint. + + The MessageWriter to write to + + + + Write description of this constraint + + The MessageWriter to write to + + + + Display the failure information for two collections that did not match. + + The MessageWriter on which to display + The expected collection. + The actual collection + The depth of this failure in a set of nested collections + + + + Displays a single line showing the types and sizes of the expected + and actual collections or arrays. If both are identical, the value is + only shown once. + + The MessageWriter on which to display + The expected collection or array + The actual collection or array + The indentation level for the message line + + + + Displays a single line showing the point in the expected and actual + arrays at which the comparison failed. If the arrays have different + structures or dimensions, both values are shown. + + The MessageWriter on which to display + The expected array + The actual array + Index of the failure point in the underlying collections + The indentation level for the message line + + + + Flag the constraint to ignore case and return self. + + + + + Flag the constraint to suppress string clipping + and return self. + + + + + Flag the constraint to compare arrays as collections + and return self. + + + + + Switches the .Within() modifier to interpret its tolerance as + a distance in representable values (see remarks). + + Self. + + Ulp stands for "unit in the last place" and describes the minimum + amount a given value can change. For any integers, an ulp is 1 whole + digit. For floating point values, the accuracy of which is better + for smaller numbers and worse for larger numbers, an ulp depends + on the size of the number. Using ulps for comparison of floating + point results instead of fixed tolerances is safer because it will + automatically compensate for the added inaccuracy of larger numbers. + + + + + Switches the .Within() modifier to interpret its tolerance as + a percentage that the actual values is allowed to deviate from + the expected value. + + Self + + + + Causes the tolerance to be interpreted as a TimeSpan in days. + + Self + + + + Causes the tolerance to be interpreted as a TimeSpan in hours. + + Self + + + + Causes the tolerance to be interpreted as a TimeSpan in minutes. + + Self + + + + Causes the tolerance to be interpreted as a TimeSpan in seconds. + + Self + + + + Causes the tolerance to be interpreted as a TimeSpan in milliseconds. + + Self + + + + Causes the tolerance to be interpreted as a TimeSpan in clock ticks. + + Self + + + + NotConstraint negates the effect of some other constraint + + + + + Initializes a new instance of the class. + + The base constraint to be negated. + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for if the base constraint fails, false if it succeeds + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + Write the actual value for a failing constraint test to a MessageWriter. + + The writer on which the actual value is displayed + + + + AllItemsConstraint applies another constraint to each + item in a collection, succeeding if they all succeed. + + + + + Construct an AllItemsConstraint on top of an existing constraint + + + + + + Apply the item constraint to each item in the collection, + failing if any item fails. + + + + + + + Write a description of this constraint to a MessageWriter + + + + + + SomeItemsConstraint applies another constraint to each + item in a collection, succeeding if any of them succeeds. + + + + + Construct a SomeItemsConstraint on top of an existing constraint + + + + + + Apply the item constraint to each item in the collection, + succeeding if any item succeeds. + + + + + + + Write a description of this constraint to a MessageWriter + + + + + + NoItemConstraint applies another constraint to each + item in a collection, failing if any of them succeeds. + + + + + Construct a SomeItemsConstraint on top of an existing constraint + + + + + + Apply the item constraint to each item in the collection, + failing if any item fails. + + + + + + + Write a description of this constraint to a MessageWriter + + + + + + ContainsConstraint tests a whether a string contains a substring + or a collection contains an object. It postpones the decision of + which test to use until the type of the actual argument is known. + This allows testing whether a string is contained in a collection + or as a substring of another string using the same syntax. + + + + + Initializes a new instance of the class. + + The expected. + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + Flag the constraint to ignore case and return self. + + + + + Static methods used in creating messages + + + + + Static string used when strings are clipped + + + + + Returns the representation of a type as used in NUnitLite. + This is the same as Type.ToString() except for arrays, + which are displayed with their declared sizes. + + + + + + + Converts any control characters in a string + to their escaped representation. + + The string to be converted + The converted string + + + + Return the a string representation for a set of indices into an array + + Array of indices for which a string is needed + + + + Get an array of indices representing the point in a collection or + array corresponding to a single int index into the collection. + + The collection to which the indices apply + Index in the collection + Array of indices + + + + Clip a string to a given length, starting at a particular offset, returning the clipped + string with ellipses representing the removed parts + + The string to be clipped + The maximum permitted length of the result string + The point at which to start clipping + The clipped string + + + + Clip the expected and actual strings in a coordinated fashion, + so that they may be displayed together. + + + + + + + + + Shows the position two strings start to differ. Comparison + starts at the start index. + + The expected string + The actual string + The index in the strings at which comparison should start + Boolean indicating whether case should be ignored + -1 if no mismatch found, or the index where mismatch found + + + + BasicConstraint is the abstract base for constraints that + perform a simple comparison to a constant value. + + + + + Initializes a new instance of the class. + + The expected. + The description. + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + NullConstraint tests that the actual value is null + + + + + Initializes a new instance of the class. + + + + + TrueConstraint tests that the actual value is true + + + + + Initializes a new instance of the class. + + + + + FalseConstraint tests that the actual value is false + + + + + Initializes a new instance of the class. + + + + + NaNConstraint tests that the actual value is a double or float NaN + + + + + Test that the actual value is an NaN + + + + + + + Write the constraint description to a specified writer + + + + + + MessageWriter is the abstract base for classes that write + constraint descriptions and messages in some form. The + class has separate methods for writing various components + of a message, allowing implementations to tailor the + presentation as needed. + + + + + Construct a MessageWriter given a culture + + + + + Method to write single line message with optional args, usually + written to precede the general failure message. + + The message to be written + Any arguments used in formatting the message + + + + Method to write single line message with optional args, usually + written to precede the general failure message, at a givel + indentation level. + + The indentation level of the message + The message to be written + Any arguments used in formatting the message + + + + Display Expected and Actual lines for a constraint. This + is called by MessageWriter's default implementation of + WriteMessageTo and provides the generic two-line display. + + The constraint that failed + + + + Display Expected and Actual lines for given values. This + method may be called by constraints that need more control over + the display of actual and expected values than is provided + by the default implementation. + + The expected value + The actual value causing the failure + + + + Display Expected and Actual lines for given values, including + a tolerance value on the Expected line. + + The expected value + The actual value causing the failure + The tolerance within which the test was made + + + + Display the expected and actual string values on separate lines. + If the mismatch parameter is >=0, an additional line is displayed + line containing a caret that points to the mismatch point. + + The expected string value + The actual string value + The point at which the strings don't match or -1 + If true, case is ignored in locating the point where the strings differ + If true, the strings should be clipped to fit the line + + + + Writes the text for a connector. + + The connector. + + + + Writes the text for a predicate. + + The predicate. + + + + Writes the text for an expected value. + + The expected value. + + + + Writes the text for a modifier + + The modifier. + + + + Writes the text for an actual value. + + The actual value. + + + + Writes the text for a generalized value. + + The value. + + + + Writes the text for a collection value, + starting at a particular point, to a max length + + The collection containing elements to write. + The starting point of the elements to write + The maximum number of elements to write + + + + Abstract method to get the max line length + + + + + Modes in which the tolerance value for a comparison can + be interpreted. + + + + + The tolerance was created with a value, without specifying + how the value would be used. This is used to prevent setting + the mode more than once and is generally changed to Linear + upon execution of the test. + + + + + The tolerance is used as a numeric range within which + two compared values are considered to be equal. + + + + + Interprets the tolerance as the percentage by which + the two compared values my deviate from each other. + + + + + Compares two values based in their distance in + representable numbers. + + + + + The Tolerance class generalizes the notion of a tolerance + within which an equality test succeeds. Normally, it is + used with numeric types, but it can be used with any + type that supports taking a difference between two + objects and comparing that difference to a value. + + + + + Constructs a linear tolerance of a specdified amount + + + + + Constructs a tolerance given an amount and ToleranceMode + + + + + Tests that the current Tolerance is linear with a + numeric value, throwing an exception if it is not. + + + + + Returns an empty Tolerance object, equivalent to + specifying an exact match. + + + + + Gets the ToleranceMode for the current Tolerance + + + + + Gets the value of the current Tolerance instance. + + + + + Returns a new tolerance, using the current amount as a percentage. + + + + + Returns a new tolerance, using the current amount in Ulps. + + + + + Returns a new tolerance with a TimeSpan as the amount, using + the current amount as a number of days. + + + + + Returns a new tolerance with a TimeSpan as the amount, using + the current amount as a number of hours. + + + + + Returns a new tolerance with a TimeSpan as the amount, using + the current amount as a number of minutes. + + + + + Returns a new tolerance with a TimeSpan as the amount, using + the current amount as a number of seconds. + + + + + Returns a new tolerance with a TimeSpan as the amount, using + the current amount as a number of milliseconds. + + + + + Returns a new tolerance with a TimeSpan as the amount, using + the current amount as a number of clock ticks. + + + + + Returns true if the current tolerance is empty. + + + + + The Numerics class contains common operations on numeric values. + + + + + Checks the type of the object, returning true if + the object is a numeric type. + + The object to check + true if the object is a numeric type + + + + Checks the type of the object, returning true if + the object is a floating point numeric type. + + The object to check + true if the object is a floating point numeric type + + + + Checks the type of the object, returning true if + the object is a fixed point numeric type. + + The object to check + true if the object is a fixed point numeric type + + + + Test two numeric values for equality, performing the usual numeric + conversions and using a provided or default tolerance. If the tolerance + provided is Empty, this method may set it to a default tolerance. + + The expected value + The actual value + A reference to the tolerance in effect + True if the values are equal + + + + Compare two numeric values, performing the usual numeric conversions. + + The expected value + The actual value + The relationship of the values to each other + + + + EmptyConstraint tests a whether a string or collection is empty, + postponing the decision about which test is applied until the + type of the actual argument is known. + + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + StringConstraint is the abstract base for constraints + that operate on strings. It supports the IgnoreCase + modifier for string operations. + + + + + The expected value + + + + + Indicates whether tests should be case-insensitive + + + + + Constructs a StringConstraint given an expected value + + The expected value + + + + Modify the constraint to ignore case in matching. + + + + + EmptyStringConstraint tests whether a string is empty. + + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + NullEmptyStringConstraint tests whether a string is either null or empty. + + + + + Constructs a new NullOrEmptyStringConstraint + + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + SubstringConstraint can test whether a string contains + the expected substring. + + + + + Initializes a new instance of the class. + + The expected. + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + StartsWithConstraint can test whether a string starts + with an expected substring. + + + + + Initializes a new instance of the class. + + The expected string + + + + Test whether the constraint is matched by the actual value. + This is a template method, which calls the IsMatch method + of the derived class. + + + + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + EndsWithConstraint can test whether a string ends + with an expected substring. + + + + + Initializes a new instance of the class. + + The expected string + + + + Test whether the constraint is matched by the actual value. + This is a template method, which calls the IsMatch method + of the derived class. + + + + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + RegexConstraint can test whether a string matches + the pattern provided. + + + + + Initializes a new instance of the class. + + The pattern. + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + Abstract base class for constraints that compare values to + determine if one is greater than, equal to or less than + the other. + + + + + The value against which a comparison is to be made + + + + + If true, less than returns success + + + + + if true, equal returns success + + + + + if true, greater than returns success + + + + + The predicate used as a part of the description + + + + + ComparisonAdapter to be used in making the comparison + + + + + Initializes a new instance of the class. + + The value against which to make a comparison. + if set to true less succeeds. + if set to true equal succeeds. + if set to true greater succeeds. + String used in describing the constraint. + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + Modifies the constraint to use an IComparer and returns self + + + + + Modifies the constraint to use an IComparer<T> and returns self + + + + + Modifies the constraint to use a Comparison<T> and returns self + + + + + Tests whether a value is greater than the value supplied to its constructor + + + + + Initializes a new instance of the class. + + The expected value. + + + + Tests whether a value is greater than or equal to the value supplied to its constructor + + + + + Initializes a new instance of the class. + + The expected value. + + + + Tests whether a value is less than the value supplied to its constructor + + + + + Initializes a new instance of the class. + + The expected value. + + + + Tests whether a value is less than or equal to the value supplied to its constructor + + + + + Initializes a new instance of the class. + + The expected value. + + + + SameAsConstraint tests whether an object is identical to + the object passed to its constructor + + + + + Initializes a new instance of the class. + + The expected object. + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + BinaryConstraint is the abstract base of all constraints + that combine two other constraints in some fashion. + + + + + The first constraint being combined + + + + + The second constraint being combined + + + + + Construct a BinaryConstraint from two other constraints + + The first constraint + The second constraint + + + + AndConstraint succeeds only if both members succeed. + + + + + Create an AndConstraint from two other constraints + + The first constraint + The second constraint + + + + Apply both member constraints to an actual value, succeeding + succeeding only if both of them succeed. + + The actual value + True if the constraints both succeeded + + + + Write a description for this contraint to a MessageWriter + + The MessageWriter to receive the description + + + + Write the actual value for a failing constraint test to a + MessageWriter. The default implementation simply writes + the raw value of actual, leaving it to the writer to + perform any formatting. + + The writer on which the actual value is displayed + + + + OrConstraint succeeds if either member succeeds + + + + + Create an OrConstraint from two other constraints + + The first constraint + The second constraint + + + + Apply the member constraints to an actual value, succeeding + succeeding as soon as one of them succeeds. + + The actual value + True if either constraint succeeded + + + + Write a description for this contraint to a MessageWriter + + The MessageWriter to receive the description + + + + Helper class with properties and methods that supply + a number of constraints used in Asserts. + + + + + Returns a new PropertyConstraintExpression, which will either + test for the existence of the named property on the object + being tested or apply any following constraint to that property. + + + + + Returns a new AttributeConstraint checking for the + presence of a particular attribute on an object. + + + + + Returns a new AttributeConstraint checking for the + presence of a particular attribute on an object. + + + + + Returns a constraint that tests two items for equality + + + + + Returns a constraint that tests that two references are the same object + + + + + Returns a constraint that tests whether the + actual value is greater than the suppled argument + + + + + Returns a constraint that tests whether the + actual value is greater than or equal to the suppled argument + + + + + Returns a constraint that tests whether the + actual value is greater than or equal to the suppled argument + + + + + Returns a constraint that tests whether the + actual value is less than the suppled argument + + + + + Returns a constraint that tests whether the + actual value is less than or equal to the suppled argument + + + + + Returns a constraint that tests whether the + actual value is less than or equal to the suppled argument + + + + + Returns a constraint that tests whether the actual + value is of the exact type supplied as an argument. + + + + + Returns a constraint that tests whether the actual + value is of the exact type supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is of the type supplied as an argument or a derived type. + + + + + Returns a constraint that tests whether the actual value + is of the type supplied as an argument or a derived type. + + + + + Returns a constraint that tests whether the actual value + is of the type supplied as an argument or a derived type. + + + + + Returns a constraint that tests whether the actual value + is of the type supplied as an argument or a derived type. + + + + + Returns a constraint that tests whether the actual value + is assignable from the type supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is assignable from the type supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is assignable from the type supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is assignable from the type supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is a collection containing the same elements as the + collection supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is a subset of the collection supplied as an argument. + + + + + Returns a new CollectionContainsConstraint checking for the + presence of a particular object in the collection. + + + + + Returns a new CollectionContainsConstraint checking for the + presence of a particular object in the collection. + + + + + Returns a new ContainsConstraint. This constraint + will, in turn, make use of the appropriate second-level + constraint, depending on the type of the actual argument. + This overload is only used if the item sought is a string, + since any other type implies that we are looking for a + collection member. + + + + + Returns a constraint that succeeds if the actual + value contains the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value contains the substring supplied as an argument. + + + + + Returns a constraint that fails if the actual + value contains the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value starts with the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value starts with the substring supplied as an argument. + + + + + Returns a constraint that fails if the actual + value starts with the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value ends with the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value ends with the substring supplied as an argument. + + + + + Returns a constraint that fails if the actual + value ends with the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value matches the Regex pattern supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value matches the Regex pattern supplied as an argument. + + + + + Returns a constraint that fails if the actual + value matches the pattern supplied as an argument. + + + + + Returns a constraint that tests whether the path provided + is the same as an expected path after canonicalization. + + + + + Returns a constraint that tests whether the path provided + is the same path or under an expected path after canonicalization. + + + + + Returns a constraint that tests whether the actual value falls + within a specified range. + + + + + Returns a ConstraintExpression that negates any + following constraint. + + + + + Returns a ConstraintExpression that negates any + following constraint. + + + + + Returns a ConstraintExpression, which will apply + the following constraint to all members of a collection, + succeeding if all of them succeed. + + + + + Returns a ConstraintExpression, which will apply + the following constraint to all members of a collection, + succeeding if at least one of them succeeds. + + + + + Returns a ConstraintExpression, which will apply + the following constraint to all members of a collection, + succeeding if all of them fail. + + + + + Returns a new ConstraintExpression, which will apply the following + constraint to the Length property of the object being tested. + + + + + Returns a new ConstraintExpression, which will apply the following + constraint to the Count property of the object being tested. + + + + + Returns a new ConstraintExpression, which will apply the following + constraint to the Message property of the object being tested. + + + + + Returns a new ConstraintExpression, which will apply the following + constraint to the InnerException property of the object being tested. + + + + + Returns a constraint that tests for null + + + + + Returns a constraint that tests for True + + + + + Returns a constraint that tests for False + + + + + Returns a constraint that tests for NaN + + + + + Returns a constraint that tests for empty + + + + + Returns a constraint that tests whether a collection + contains all unique items. + + + + + Returns a constraint that tests whether an object graph is serializable in binary format. + + + + + Returns a constraint that tests whether an object graph is serializable in xml format. + + + + + Returns a constraint that tests whether a collection is ordered + + + + + Applies a delay to the match so that a match can be evaluated in the future. + + + + + Creates a new DelayedConstraint + + The inner constraint two decorate + The time interval after which the match is performed + If the value of is less than 0 + + + + Creates a new DelayedConstraint + + The inner constraint two decorate + The time interval after which the match is performed + The time interval used for polling + If the value of is less than 0 + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for if the base constraint fails, false if it succeeds + + + + Test whether the constraint is satisfied by a delegate + + The delegate whose value is to be tested + True for if the base constraint fails, false if it succeeds + + + + Test whether the constraint is satisfied by a given reference. + Overridden to wait for the specified delay period before + calling the base constraint with the dereferenced value. + + A reference to the value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + Write the actual value for a failing constraint test to a MessageWriter. + + The writer on which the actual value is displayed + + + + Returns the string representation of the constraint. + + + + + NUnitComparer encapsulates NUnit's default behavior + in comparing two objects. + + + + + Compares two objects + + + + + + + + Returns the default NUnitComparer. + + + + + Delegate used to delay evaluation of the actual value + to be used in evaluating a constraint + + + + + NUnitEqualityComparer encapsulates NUnit's handling of + equality tests between objects. + + + + + If true, all string comparisons will ignore case + + + + + If true, arrays will be treated as collections, allowing + those of different dimensions to be compared + + + + + If non-zero, equality comparisons within the specified + tolerance will succeed. + + + + + Comparison object used in comparisons for some constraints. + + + + + Compares two objects for equality. + + + + + Helper method to compare two arrays + + + + + Method to compare two DirectoryInfo objects + + first directory to compare + second directory to compare + true if equivalent, false if not + + + + Returns the default NUnitEqualityComparer + + + + + Gets and sets a flag indicating whether case should + be ignored in determining equality. + + + + + Gets and sets a flag indicating that arrays should be + compared as collections, without regard to their shape. + + + + + Gets and sets an external comparer to be used to + test for equality. It is applied to members of + collections, in place of NUnit's own logic. + + + + + Gets and sets a tolerance used to compare objects of + certin types. + + + + + Gets the list of failure points for the last Match performed. + + + + + ComparisonAdapter class centralizes all comparisons of + values in NUnit, adapting to the use of any provided + IComparer, IComparer<T> or Comparison<T> + + + + + Returns a ComparisonAdapter that wraps an IComparer + + + + + Returns a ComparisonAdapter that wraps an IComparer<T> + + + + + Returns a ComparisonAdapter that wraps a Comparison<T> + + + + + Compares two objects + + + + + Gets the default ComparisonAdapter, which wraps an + NUnitComparer object. + + + + + Construct a ComparisonAdapter for an IComparer + + + + + Compares two objects + + + + + + + + Construct a default ComparisonAdapter + + + + + ComparisonAdapter<T> extends ComparisonAdapter and + allows use of an IComparer<T> or Comparison<T> + to actually perform the comparison. + + + + + Construct a ComparisonAdapter for an IComparer<T> + + + + + Compare a Type T to an object + + + + + Construct a ComparisonAdapter for a Comparison<T> + + + + + Compare a Type T to an object + + + + + The ConstraintOperator class is used internally by a + ConstraintBuilder to represent an operator that + modifies or combines constraints. + + Constraint operators use left and right precedence + values to determine whether the top operator on the + stack should be reduced before pushing a new operator. + + + + + The precedence value used when the operator + is about to be pushed to the stack. + + + + + The precedence value used when the operator + is on the top of the stack. + + + + + Reduce produces a constraint from the operator and + any arguments. It takes the arguments from the constraint + stack and pushes the resulting constraint on it. + + + + + + The syntax element preceding this operator + + + + + The syntax element folowing this operator + + + + + The precedence value used when the operator + is about to be pushed to the stack. + + + + + The precedence value used when the operator + is on the top of the stack. + + + + + PrefixOperator takes a single constraint and modifies + it's action in some way. + + + + + Reduce produces a constraint from the operator and + any arguments. It takes the arguments from the constraint + stack and pushes the resulting constraint on it. + + + + + + Returns the constraint created by applying this + prefix to another constraint. + + + + + + + Negates the test of the constraint it wraps. + + + + + Constructs a new NotOperator + + + + + Returns a NotConstraint applied to its argument. + + + + + Abstract base for operators that indicate how to + apply a constraint to items in a collection. + + + + + Constructs a CollectionOperator + + + + + Represents a constraint that succeeds if all the + members of a collection match a base constraint. + + + + + Returns a constraint that will apply the argument + to the members of a collection, succeeding if + they all succeed. + + + + + Represents a constraint that succeeds if any of the + members of a collection match a base constraint. + + + + + Returns a constraint that will apply the argument + to the members of a collection, succeeding if + any of them succeed. + + + + + Represents a constraint that succeeds if none of the + members of a collection match a base constraint. + + + + + Returns a constraint that will apply the argument + to the members of a collection, succeeding if + none of them succeed. + + + + + Represents a constraint that simply wraps the + constraint provided as an argument, without any + further functionality, but which modifes the + order of evaluation because of its precedence. + + + + + Constructor for the WithOperator + + + + + Returns a constraint that wraps its argument + + + + + Abstract base class for operators that are able to reduce to a + constraint whether or not another syntactic element follows. + + + + + Operator used to test for the presence of a named Property + on an object and optionally apply further tests to the + value of that property. + + + + + Constructs a PropOperator for a particular named property + + + + + Reduce produces a constraint from the operator and + any arguments. It takes the arguments from the constraint + stack and pushes the resulting constraint on it. + + + + + + Gets the name of the property to which the operator applies + + + + + Operator that tests for the presence of a particular attribute + on a type and optionally applies further tests to the attribute. + + + + + Construct an AttributeOperator for a particular Type + + The Type of attribute tested + + + + Reduce produces a constraint from the operator and + any arguments. It takes the arguments from the constraint + stack and pushes the resulting constraint on it. + + + + + Operator that tests that an exception is thrown and + optionally applies further tests to the exception. + + + + + Construct a ThrowsOperator + + + + + Reduce produces a constraint from the operator and + any arguments. It takes the arguments from the constraint + stack and pushes the resulting constraint on it. + + + + + Abstract base class for all binary operators + + + + + Reduce produces a constraint from the operator and + any arguments. It takes the arguments from the constraint + stack and pushes the resulting constraint on it. + + + + + + Abstract method that produces a constraint by applying + the operator to its left and right constraint arguments. + + + + + Gets the left precedence of the operator + + + + + Gets the right precedence of the operator + + + + + Operator that requires both it's arguments to succeed + + + + + Construct an AndOperator + + + + + Apply the operator to produce an AndConstraint + + + + + Operator that requires at least one of it's arguments to succeed + + + + + Construct an OrOperator + + + + + Apply the operator to produce an OrConstraint + + + + + BinarySerializableConstraint tests whether + an object is serializable in binary format. + + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + Write the actual value for a failing constraint test to a + MessageWriter. The default implementation simply writes + the raw value of actual, leaving it to the writer to + perform any formatting. + + The writer on which the actual value is displayed + + + + Returns the string representation + + + + + BinarySerializableConstraint tests whether + an object is serializable in binary format. + + + + + Test whether the constraint is satisfied by a given value + + The value to be tested + True for success, false for failure + + + + Write the constraint description to a MessageWriter + + The writer on which the description is displayed + + + + Write the actual value for a failing constraint test to a + MessageWriter. The default implementation simply writes + the raw value of actual, leaving it to the writer to + perform any formatting. + + The writer on which the actual value is displayed + + + + Returns the string representation of this constraint + + + + + ResolvableConstraintExpression is used to represent a compound + constraint being constructed at a point where the last operator + may either terminate the expression or may have additional + qualifying constraints added to it. + + It is used, for example, for a Property element or for + an Exception element, either of which may be optionally + followed by constraints that apply to the property or + exception. + + + + + Create a new instance of ResolvableConstraintExpression + + + + + Create a new instance of ResolvableConstraintExpression, + passing in a pre-populated ConstraintBuilder. + + + + + Resolve the current expression to a Constraint + + + + + Appends an And Operator to the expression + + + + + Appends an Or operator to the expression. + + + + + EqualityAdapter class handles all equality comparisons + that use an IEqualityComparer, IEqualityComparer<T> + or a ComparisonAdapter. + + + + + Compares two objects, returning true if they are equal + + + + + Returns an EqualityAdapter that wraps an IComparer. + + + + + Returns an EqualityAdapter that wraps an IEqualityComparer. + + + + + Returns an EqualityAdapter that wraps an IEqualityComparer<T>. + + + + + Returns an EqualityAdapter that wraps an IComparer<T>. + + + + + Returns an EqualityAdapter that wraps a Comparison<T>. + + + + + AttributeExistsConstraint tests for the presence of a + specified attribute on a Type. + + + + + Constructs an AttributeExistsConstraint for a specific attribute Type + + + + + + Tests whether the object provides the expected attribute. + + A Type, MethodInfo, or other ICustomAttributeProvider + True if the expected attribute is present, otherwise false + + + + Writes the description of the constraint to the specified writer + + + + + AttributeConstraint tests that a specified attribute is present + on a Type or other provider and that the value of the attribute + satisfies some other constraint. + + + + + Constructs an AttributeConstraint for a specified attriute + Type and base constraint. + + + + + + + Determines whether the Type or other provider has the + expected attribute and if its value matches the + additional constraint specified. + + + + + Writes a description of the attribute to the specified writer. + + + + + Writes the actual value supplied to the specified writer. + + + + + Returns a string representation of the constraint. + + + + Helper routines for working with floating point numbers + + + The floating point comparison code is based on this excellent article: + http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm + + + "ULP" means Unit in the Last Place and in the context of this library refers to + the distance between two adjacent floating point numbers. IEEE floating point + numbers can only represent a finite subset of natural numbers, with greater + accuracy for smaller numbers and lower accuracy for very large numbers. + + + If a comparison is allowed "2 ulps" of deviation, that means the values are + allowed to deviate by up to 2 adjacent floating point values, which might be + as low as 0.0000001 for small numbers or as high as 10.0 for large numbers. + + + + + Compares two floating point values for equality + First floating point value to be compared + Second floating point value t be compared + + Maximum number of representable floating point values that are allowed to + be between the left and the right floating point values + + True if both numbers are equal or close to being equal + + + Floating point values can only represent a finite subset of natural numbers. + For example, the values 2.00000000 and 2.00000024 can be stored in a float, + but nothing inbetween them. + + + This comparison will count how many possible floating point values are between + the left and the right number. If the number of possible values between both + numbers is less than or equal to maxUlps, then the numbers are considered as + being equal. + + + Implementation partially follows the code outlined here: + http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ + + + + + Compares two double precision floating point values for equality + First double precision floating point value to be compared + Second double precision floating point value t be compared + + Maximum number of representable double precision floating point values that are + allowed to be between the left and the right double precision floating point values + + True if both numbers are equal or close to being equal + + + Double precision floating point values can only represent a limited series of + natural numbers. For example, the values 2.0000000000000000 and 2.0000000000000004 + can be stored in a double, but nothing inbetween them. + + + This comparison will count how many possible double precision floating point + values are between the left and the right number. If the number of possible + values between both numbers is less than or equal to maxUlps, then the numbers + are considered as being equal. + + + Implementation partially follows the code outlined here: + http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ + + + + + + Reinterprets the memory contents of a floating point value as an integer value + + + Floating point value whose memory contents to reinterpret + + + The memory contents of the floating point value interpreted as an integer + + + + + Reinterprets the memory contents of a double precision floating point + value as an integer value + + + Double precision floating point value whose memory contents to reinterpret + + + The memory contents of the double precision floating point value + interpreted as an integer + + + + + Reinterprets the memory contents of an integer as a floating point value + + Integer value whose memory contents to reinterpret + + The memory contents of the integer value interpreted as a floating point value + + + + + Reinterprets the memory contents of an integer value as a double precision + floating point value + + Integer whose memory contents to reinterpret + + The memory contents of the integer interpreted as a double precision + floating point value + + + + Union of a floating point variable and an integer + + + The union's value as a floating point variable + + + The union's value as an integer + + + The union's value as an unsigned integer + + + Union of a double precision floating point variable and a long + + + The union's value as a double precision floating point variable + + + The union's value as a long + + + The union's value as an unsigned long + + + + Predicate constraint wraps a Predicate in a constraint, + returning success if the predicate is true. + + + + + Construct a PredicateConstraint from a predicate + + + + + Determines whether the predicate succeeds when applied + to the actual value. + + + + + Writes the description to a MessageWriter + + + + + The TestCaseData class represents a set of arguments + and other parameter info to be used for a parameterized + test case. It provides a number of instance modifiers + for use in initializing the test case. + + Note: Instance modifiers are getters that return + the same instance after modifying it's state. + + + + + The ITestCaseData interface is implemented by a class + that is able to return complete testcases for use by + a parameterized test method. + + NOTE: This interface is used in both the framework + and the core, even though that results in two different + types. However, sharing the source code guarantees that + the various implementations will be compatible and that + the core is able to reflect successfully over the + framework implementations of ITestCaseData. + + + + + Gets the argument list to be provided to the test + + + + + Gets the expected result + + + + + Gets the expected exception Type + + + + + Gets the FullName of the expected exception + + + + + Gets the name to be used for the test + + + + + Gets the description of the test + + + + + Gets a value indicating whether this is ignored. + + true if ignored; otherwise, false. + + + + Gets the ignore reason. + + The ignore reason. + + + + The argument list to be provided to the test + + + + + The expected result to be returned + + + + + The expected exception Type + + + + + The FullName of the expected exception + + + + + The name to be used for the test + + + + + The description of the test + + + + + A dictionary of properties, used to add information + to tests without requiring the class to change. + + + + + If true, indicates that the test case is to be ignored + + + + + The reason for ignoring a test case + + + + + Initializes a new instance of the class. + + The arguments. + + + + Initializes a new instance of the class. + + The argument. + + + + Initializes a new instance of the class. + + The first argument. + The second argument. + + + + Initializes a new instance of the class. + + The first argument. + The second argument. + The third argument. + + + + Sets the expected result for the test + + The expected result + A modified TestCaseData + + + + Sets the expected exception type for the test + + Type of the expected exception. + The modified TestCaseData instance + + + + Sets the expected exception type for the test + + FullName of the expected exception. + The modified TestCaseData instance + + + + Sets the name of the test case + + The modified TestCaseData instance + + + + Sets the description for the test case + being constructed. + + The description. + The modified TestCaseData instance. + + + + Applies a category to the test + + + + + + + Applies a named property to the test + + + + + + + + Applies a named property to the test + + + + + + + + Applies a named property to the test + + + + + + + + Ignores this TestCase. + + + + + + Ignores this TestCase, specifying the reason. + + The reason. + + + + + Gets the argument list to be provided to the test + + + + + Gets the expected result + + + + + Gets the expected exception Type + + + + + Gets the FullName of the expected exception + + + + + Gets the name to be used for the test + + + + + Gets the description of the test + + + + + Gets a value indicating whether this is ignored. + + true if ignored; otherwise, false. + + + + Gets the ignore reason. + + The ignore reason. + + + + Gets a list of categories associated with this test. + + + + + Gets the property dictionary for this test + + + + + FactoryAttribute indicates the source to be used to + provide test cases for a test method. + + + + + Construct with the name of the factory - for use with languages + that don't support params arrays. + + An array of the names of the factories that will provide data + + + + Construct with a Type and name - for use with languages + that don't support params arrays. + + The Type that will provide data + The name of the method, property or field that will provide data + + + + The name of a the method, property or fiend to be used as a source + + + + + A Type to be used as a source + + + + + Helper class with properties and methods that supply + a number of constraints used in Asserts. + + + + + Returns a new PropertyConstraintExpression, which will either + test for the existence of the named property on the object + being tested or apply any following constraint to that property. + + + + + Returns a new AttributeConstraint checking for the + presence of a particular attribute on an object. + + + + + Returns a new AttributeConstraint checking for the + presence of a particular attribute on an object. + + + + + Returns a new CollectionContainsConstraint checking for the + presence of a particular object in the collection. + + + + + Returns a ConstraintExpression that negates any + following constraint. + + + + + Returns a ConstraintExpression, which will apply + the following constraint to all members of a collection, + succeeding if all of them succeed. + + + + + Returns a ConstraintExpression, which will apply + the following constraint to all members of a collection, + succeeding if at least one of them succeeds. + + + + + Returns a ConstraintExpression, which will apply + the following constraint to all members of a collection, + succeeding if all of them fail. + + + + + Returns a new ConstraintExpression, which will apply the following + constraint to the Length property of the object being tested. + + + + + Returns a new ConstraintExpression, which will apply the following + constraint to the Count property of the object being tested. + + + + + Returns a new ConstraintExpression, which will apply the following + constraint to the Message property of the object being tested. + + + + + Returns a new ConstraintExpression, which will apply the following + constraint to the InnerException property of the object being tested. + + + + + The List class is a helper class with properties and methods + that supply a number of constraints used with lists and collections. + + + + + List.Map returns a ListMapper, which can be used to map + the original collection to another collection. + + + + + + + Enumeration indicating how the expected message parameter is to be used + + + + Expect an exact match + + + Expect a message containing the parameter string + + + Match the regular expression provided as a parameter + + + Expect a message that starts with the parameter string + + + + ExpectedExceptionAttribute + + + + + + Constructor for a non-specific exception + + + + + Constructor for a given type of exception + + The type of the expected exception + + + + Constructor for a given exception name + + The full name of the expected exception + + + + Gets or sets the expected exception type + + + + + Gets or sets the full Type name of the expected exception + + + + + Gets or sets the expected message text + + + + + Gets or sets the user message displayed in case of failure + + + + + Gets or sets the type of match to be performed on the expected message + + + + + Gets the name of a method to be used as an exception handler + + + + + TestCaseAttribute is used to mark parameterized test cases + and provide them with their arguments. + + + + + Construct a TestCaseAttribute with a list of arguments. + This constructor is not CLS-Compliant + + + + + + Construct a TestCaseAttribute with a single argument + + + + + + Construct a TestCaseAttribute with a two arguments + + + + + + + Construct a TestCaseAttribute with a three arguments + + + + + + + + Gets the list of arguments to a test case + + + + + Gets or sets the expected result. + + The result. + + + + Gets or sets the expected exception. + + The expected exception. + + + + Gets or sets the name the expected exception. + + The expected name of the exception. + + + + Gets or sets the expected message of the expected exception + + The expected message of the exception. + + + + Gets or sets the type of match to be performed on the expected message + + + + + Gets or sets the description. + + The description. + + + + Gets or sets the name of the test. + + The name of the test. + + + + Gets or sets the ignored status of the test + + + + + Gets or sets the ignored status of the test + + + + + Gets the ignore reason. + + The ignore reason. + + + + Thrown when an assertion failed. + + + + + + + The error message that explains + the reason for the exception + The exception that caused the + current exception + + + + Serialization Constructor + + + + + Thrown when a test executes inconclusively. + + + + + The error message that explains + the reason for the exception + + + The error message that explains + the reason for the exception + The exception that caused the + current exception + + + + Serialization Constructor + + + + + Delegate used by tests that execute code and + capture any thrown exception. + + + + + The Assert class contains a collection of static methods that + implement the most common assertions used in NUnit. + + + + + We don't actually want any instances of this object, but some people + like to inherit from it to add other static methods. Hence, the + protected constructor disallows any instances of this object. + + + + + The Equals method throws an AssertionException. This is done + to make sure there is no mistake by calling this function. + + + + + + + override the default ReferenceEquals to throw an AssertionException. This + implementation makes sure there is no mistake in calling this function + as part of Assert. + + + + + + + Helper for Assert.AreEqual(double expected, double actual, ...) + allowing code generation to work consistently. + + The expected value + The actual value + The maximum acceptable difference between the + the expected and the actual + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Throws a with the message and arguments + that are passed in. This allows a test to be cut short, with a result + of success returned to NUnit. + + The message to initialize the with. + Arguments to be used in formatting the message + + + + Throws a with the message and arguments + that are passed in. This allows a test to be cut short, with a result + of success returned to NUnit. + + The message to initialize the with. + + + + Throws a with the message and arguments + that are passed in. This allows a test to be cut short, with a result + of success returned to NUnit. + + + + + Throws an with the message and arguments + that are passed in. This is used by the other Assert functions. + + The message to initialize the with. + Arguments to be used in formatting the message + + + + Throws an with the message that is + passed in. This is used by the other Assert functions. + + The message to initialize the with. + + + + Throws an . + This is used by the other Assert functions. + + + + + Throws an with the message and arguments + that are passed in. This causes the test to be reported as ignored. + + The message to initialize the with. + Arguments to be used in formatting the message + + + + Throws an with the message that is + passed in. This causes the test to be reported as ignored. + + The message to initialize the with. + + + + Throws an . + This causes the test to be reported as ignored. + + + + + Throws an with the message and arguments + that are passed in. This causes the test to be reported as inconclusive. + + The message to initialize the with. + Arguments to be used in formatting the message + + + + Throws an with the message that is + passed in. This causes the test to be reported as inconclusive. + + The message to initialize the with. + + + + Throws an . + This causes the test to be reported as Inconclusive. + + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. + + A Constraint to be applied + The actual value to test + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. + + A Constraint to be applied + The actual value to test + The message that will be displayed on failure + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. + + A Constraint expression to be applied + The actual value to test + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. + + A Constraint expression to be applied + An ActualValueDelegate returning the value to be tested + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. + + A Constraint expression to be applied + An ActualValueDelegate returning the value to be tested + The message that will be displayed on failure + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. + + An ActualValueDelegate returning the value to be tested + A Constraint expression to be applied + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Apply a constraint to a referenced value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. + + A Constraint to be applied + The actual value to test + + + + Apply a constraint to a referenced value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. + + A Constraint to be applied + The actual value to test + The message that will be displayed on failure + + + + Apply a constraint to a referenced value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. + + A Constraint to be applied + The actual value to test + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Asserts that a condition is true. If the condition is false the method throws + an . + + The evaluated condition + The message to display if the condition is false + Arguments to be used in formatting the message + + + + Asserts that a condition is true. If the condition is false the method throws + an . + + The evaluated condition + The message to display if the condition is false + + + + Asserts that a condition is true. If the condition is false the method throws + an . + + The evaluated condition + + + + Asserts that the code represented by a delegate throws an exception + that satisfies the constraint provided. + + A TestDelegate to be executed + A ThrowsConstraint used in the test + + + + Verifies that a delegate throws a particular exception when called. + + A constraint to be satisfied by the exception + A TestSnippet delegate + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Verifies that a delegate throws a particular exception when called. + + A constraint to be satisfied by the exception + A TestSnippet delegate + The message that will be displayed on failure + + + + Verifies that a delegate throws a particular exception when called. + + A constraint to be satisfied by the exception + A TestSnippet delegate + + + + Verifies that a delegate throws a particular exception when called. + + The exception Type expected + A TestSnippet delegate + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Verifies that a delegate throws a particular exception when called. + + The exception Type expected + A TestSnippet delegate + The message that will be displayed on failure + + + + Verifies that a delegate throws a particular exception when called. + + The exception Type expected + A TestSnippet delegate + + + + Verifies that a delegate throws a particular exception when called. + + Type of the expected exception + A TestSnippet delegate + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Verifies that a delegate throws a particular exception when called. + + Type of the expected exception + A TestSnippet delegate + The message that will be displayed on failure + + + + Verifies that a delegate throws a particular exception when called. + + Type of the expected exception + A TestSnippet delegate + + + + Verifies that a delegate throws an exception when called + and returns it. + + A TestDelegate + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Verifies that a delegate throws an exception when called + and returns it. + + A TestDelegate + The message that will be displayed on failure + + + + Verifies that a delegate throws an exception when called + and returns it. + + A TestDelegate + + + + Verifies that a delegate throws an exception of a certain Type + or one derived from it when called and returns it. + + The expected Exception Type + A TestDelegate + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Verifies that a delegate throws an exception of a certain Type + or one derived from it when called and returns it. + + The expected Exception Type + A TestDelegate + The message that will be displayed on failure + + + + Verifies that a delegate throws an exception of a certain Type + or one derived from it when called and returns it. + + The expected Exception Type + A TestDelegate + + + + Verifies that a delegate throws an exception of a certain Type + or one derived from it when called and returns it. + + The expected Exception Type + A TestDelegate + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Verifies that a delegate throws an exception of a certain Type + or one derived from it when called and returns it. + + The expected Exception Type + A TestDelegate + The message that will be displayed on failure + + + + Verifies that a delegate throws an exception of a certain Type + or one derived from it when called and returns it. + + The expected Exception Type + A TestDelegate + + + + Verifies that a delegate does not throw an exception + + A TestSnippet delegate + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Verifies that a delegate does not throw an exception. + + A TestSnippet delegate + The message that will be displayed on failure + + + + Verifies that a delegate does not throw an exception. + + A TestSnippet delegate + + + + Asserts that a condition is true. If the condition is false the method throws + an . + + The evaluated condition + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that a condition is true. If the condition is false the method throws + an . + + The evaluated condition + The message to display in case of failure + + + + Asserts that a condition is true. If the condition is false the method throws + an . + + The evaluated condition + + + + Asserts that a condition is true. If the condition is false the method throws + an . + + The evaluated condition + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that a condition is true. If the condition is false the method throws + an . + + The evaluated condition + The message to display in case of failure + + + + Asserts that a condition is true. If the condition is false the method throws + an . + + The evaluated condition + + + + Asserts that a condition is false. If the condition is true the method throws + an . + + The evaluated condition + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that a condition is false. If the condition is true the method throws + an . + + The evaluated condition + The message to display in case of failure + + + + Asserts that a condition is false. If the condition is true the method throws + an . + + The evaluated condition + + + + Asserts that a condition is false. If the condition is true the method throws + an . + + The evaluated condition + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that a condition is false. If the condition is true the method throws + an . + + The evaluated condition + The message to display in case of failure + + + + Asserts that a condition is false. If the condition is true the method throws + an . + + The evaluated condition + + + + Verifies that the object that is passed in is not equal to null + If the object is null then an + is thrown. + + The object that is to be tested + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the object that is passed in is not equal to null + If the object is null then an + is thrown. + + The object that is to be tested + The message to display in case of failure + + + + Verifies that the object that is passed in is not equal to null + If the object is null then an + is thrown. + + The object that is to be tested + + + + Verifies that the object that is passed in is not equal to null + If the object is null then an + is thrown. + + The object that is to be tested + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the object that is passed in is not equal to null + If the object is null then an + is thrown. + + The object that is to be tested + The message to display in case of failure + + + + Verifies that the object that is passed in is not equal to null + If the object is null then an + is thrown. + + The object that is to be tested + + + + Verifies that the object that is passed in is equal to null + If the object is not null then an + is thrown. + + The object that is to be tested + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the object that is passed in is equal to null + If the object is not null then an + is thrown. + + The object that is to be tested + The message to display in case of failure + + + + Verifies that the object that is passed in is equal to null + If the object is not null then an + is thrown. + + The object that is to be tested + + + + Verifies that the object that is passed in is equal to null + If the object is not null then an + is thrown. + + The object that is to be tested + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the object that is passed in is equal to null + If the object is not null then an + is thrown. + + The object that is to be tested + The message to display in case of failure + + + + Verifies that the object that is passed in is equal to null + If the object is not null then an + is thrown. + + The object that is to be tested + + + + Verifies that the double that is passed in is an NaN value. + If the object is not NaN then an + is thrown. + + The value that is to be tested + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the double that is passed in is an NaN value. + If the object is not NaN then an + is thrown. + + The value that is to be tested + The message to display in case of failure + + + + Verifies that the double that is passed in is an NaN value. + If the object is not NaN then an + is thrown. + + The value that is to be tested + + + + Verifies that the double that is passed in is an NaN value. + If the object is not NaN then an + is thrown. + + The value that is to be tested + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the double that is passed in is an NaN value. + If the object is not NaN then an + is thrown. + + The value that is to be tested + The message to display in case of failure + + + + Verifies that the double that is passed in is an NaN value. + If the object is not NaN then an + is thrown. + + The value that is to be tested + + + + Assert that a string is empty - that is equal to string.Empty + + The string to be tested + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Assert that a string is empty - that is equal to string.Empty + + The string to be tested + The message to display in case of failure + + + + Assert that a string is empty - that is equal to string.Empty + + The string to be tested + + + + Assert that an array, list or other collection is empty + + An array, list or other collection implementing ICollection + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Assert that an array, list or other collection is empty + + An array, list or other collection implementing ICollection + The message to display in case of failure + + + + Assert that an array, list or other collection is empty + + An array, list or other collection implementing ICollection + + + + Assert that a string is not empty - that is not equal to string.Empty + + The string to be tested + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Assert that a string is not empty - that is not equal to string.Empty + + The string to be tested + The message to display in case of failure + + + + Assert that a string is not empty - that is not equal to string.Empty + + The string to be tested + + + + Assert that an array, list or other collection is not empty + + An array, list or other collection implementing ICollection + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Assert that an array, list or other collection is not empty + + An array, list or other collection implementing ICollection + The message to display in case of failure + + + + Assert that an array, list or other collection is not empty + + An array, list or other collection implementing ICollection + + + + Assert that a string is either null or equal to string.Empty + + The string to be tested + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Assert that a string is either null or equal to string.Empty + + The string to be tested + The message to display in case of failure + + + + Assert that a string is either null or equal to string.Empty + + The string to be tested + + + + Assert that a string is not null or empty + + The string to be tested + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Assert that a string is not null or empty + + The string to be tested + The message to display in case of failure + + + + Assert that a string is not null or empty + + The string to be tested + + + + Asserts that an object may be assigned a value of a given Type. + + The expected Type. + The object under examination + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that an object may be assigned a value of a given Type. + + The expected Type. + The object under examination + The message to display in case of failure + + + + Asserts that an object may be assigned a value of a given Type. + + The expected Type. + The object under examination + + + + Asserts that an object may be assigned a value of a given Type. + + The expected Type. + The object under examination + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that an object may be assigned a value of a given Type. + + The expected Type. + The object under examination + The message to display in case of failure + + + + Asserts that an object may be assigned a value of a given Type. + + The expected Type. + The object under examination + + + + Asserts that an object may not be assigned a value of a given Type. + + The expected Type. + The object under examination + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that an object may not be assigned a value of a given Type. + + The expected Type. + The object under examination + The message to display in case of failure + + + + Asserts that an object may not be assigned a value of a given Type. + + The expected Type. + The object under examination + + + + Asserts that an object may not be assigned a value of a given Type. + + The expected Type. + The object under examination + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that an object may not be assigned a value of a given Type. + + The expected Type. + The object under examination + The message to display in case of failure + + + + Asserts that an object may not be assigned a value of a given Type. + + The expected Type. + The object under examination + + + + Asserts that an object is an instance of a given type. + + The expected Type + The object being examined + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that an object is an instance of a given type. + + The expected Type + The object being examined + The message to display in case of failure + + + + Asserts that an object is an instance of a given type. + + The expected Type + The object being examined + + + + Asserts that an object is an instance of a given type. + + The expected Type + The object being examined + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that an object is an instance of a given type. + + The expected Type + The object being examined + The message to display in case of failure + + + + Asserts that an object is an instance of a given type. + + The expected Type + The object being examined + + + + Asserts that an object is an instance of a given type. + + The expected Type + The object being examined + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that an object is an instance of a given type. + + The expected Type + The object being examined + The message to display in case of failure + + + + Asserts that an object is an instance of a given type. + + The expected Type + The object being examined + + + + Asserts that an object is not an instance of a given type. + + The expected Type + The object being examined + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that an object is not an instance of a given type. + + The expected Type + The object being examined + The message to display in case of failure + + + + Asserts that an object is not an instance of a given type. + + The expected Type + The object being examined + + + + Asserts that an object is not an instance of a given type. + + The expected Type + The object being examined + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that an object is not an instance of a given type. + + The expected Type + The object being examined + The message to display in case of failure + + + + Asserts that an object is not an instance of a given type. + + The expected Type + The object being examined + + + + Asserts that an object is not an instance of a given type. + + The expected Type + The object being examined + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that an object is not an instance of a given type. + + The expected Type + The object being examined + The message to display in case of failure + + + + Asserts that an object is not an instance of a given type. + + The expected Type + The object being examined + + + + Verifies that two values are equal. If they are not, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two values are equal. If they are not, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + + + + Verifies that two values are equal. If they are not, then an + is thrown. + + The expected value + The actual value + + + + Verifies that two values are equal. If they are not, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two values are equal. If they are not, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + + + + Verifies that two values are equal. If they are not, then an + is thrown. + + The expected value + The actual value + + + + Verifies that two values are equal. If they are not, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two values are equal. If they are not, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + + + + Verifies that two values are equal. If they are not, then an + is thrown. + + The expected value + The actual value + + + + Verifies that two values are equal. If they are not, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two values are equal. If they are not, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + + + + Verifies that two values are equal. If they are not, then an + is thrown. + + The expected value + The actual value + + + + Verifies that two values are equal. If they are not, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two values are equal. If they are not, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + + + + Verifies that two values are equal. If they are not, then an + is thrown. + + The expected value + The actual value + + + + Verifies that two doubles are equal considering a delta. If the + expected value is infinity then the delta value is ignored. If + they are not equal then an is + thrown. + + The expected value + The actual value + The maximum acceptable difference between the + the expected and the actual + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two doubles are equal considering a delta. If the + expected value is infinity then the delta value is ignored. If + they are not equal then an is + thrown. + + The expected value + The actual value + The maximum acceptable difference between the + the expected and the actual + The message to display in case of failure + + + + Verifies that two doubles are equal considering a delta. If the + expected value is infinity then the delta value is ignored. If + they are not equal then an is + thrown. + + The expected value + The actual value + The maximum acceptable difference between the + the expected and the actual + + + + Verifies that two doubles are equal considering a delta. If the + expected value is infinity then the delta value is ignored. If + they are not equal then an is + thrown. + + The expected value + The actual value + The maximum acceptable difference between the + the expected and the actual + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two doubles are equal considering a delta. If the + expected value is infinity then the delta value is ignored. If + they are not equal then an is + thrown. + + The expected value + The actual value + The maximum acceptable difference between the + the expected and the actual + The message to display in case of failure + + + + Verifies that two doubles are equal considering a delta. If the + expected value is infinity then the delta value is ignored. If + they are not equal then an is + thrown. + + The expected value + The actual value + The maximum acceptable difference between the + the expected and the actual + + + + Verifies that two objects are equal. Two objects are considered + equal if both are null, or if both have the same value. NUnit + has special semantics for some object types. + If they are not equal an is thrown. + + The value that is expected + The actual value + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two objects are equal. Two objects are considered + equal if both are null, or if both have the same value. NUnit + has special semantics for some object types. + If they are not equal an is thrown. + + The value that is expected + The actual value + The message to display in case of failure + + + + Verifies that two objects are equal. Two objects are considered + equal if both are null, or if both have the same value. NUnit + has special semantics for some object types. + If they are not equal an is thrown. + + The value that is expected + The actual value + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + The message to display in case of failure + + + + Verifies that two values are not equal. If they are equal, then an + is thrown. + + The expected value + The actual value + + + + Verifies that two objects are not equal. Two objects are considered + equal if both are null, or if both have the same value. NUnit + has special semantics for some object types. + If they are equal an is thrown. + + The value that is expected + The actual value + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that two objects are not equal. Two objects are considered + equal if both are null, or if both have the same value. NUnit + has special semantics for some object types. + If they are equal an is thrown. + + The value that is expected + The actual value + The message to display in case of failure + + + + Verifies that two objects are not equal. Two objects are considered + equal if both are null, or if both have the same value. NUnit + has special semantics for some object types. + If they are equal an is thrown. + + The value that is expected + The actual value + + + + Asserts that two objects refer to the same object. If they + are not the same an is thrown. + + The expected object + The actual object + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that two objects refer to the same object. If they + are not the same an is thrown. + + The expected object + The actual object + The message to display in case of failure + + + + Asserts that two objects refer to the same object. If they + are not the same an is thrown. + + The expected object + The actual object + + + + Asserts that two objects do not refer to the same object. If they + are the same an is thrown. + + The expected object + The actual object + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that two objects do not refer to the same object. If they + are the same an is thrown. + + The expected object + The actual object + The message to display in case of failure + + + + Asserts that two objects do not refer to the same object. If they + are the same an is thrown. + + The expected object + The actual object + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than the second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + The message to display in case of failure + + + + Verifies that the first value is greater than or equal tothe second + value. If it is not, then an + is thrown. + + The first value, expected to be greater + The second value, expected to be less + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + The message to display in case of failure + + + + Verifies that the first value is less than or equal to the second + value. If it is not, then an + is thrown. + + The first value, expected to be less + The second value, expected to be greater + + + + Asserts that an object is contained in a list. + + The expected object + The list to be examined + The message to display in case of failure + Array of objects to be used in formatting the message + + + + Asserts that an object is contained in a list. + + The expected object + The list to be examined + The message to display in case of failure + + + + Asserts that an object is contained in a list. + + The expected object + The list to be examined + + + + Gets the number of assertions executed so far and + resets the counter to zero. + + + + + RequiredAddinAttribute may be used to indicate the names of any addins + that must be present in order to run some or all of the tests in an + assembly. If the addin is not loaded, the entire assembly is marked + as NotRunnable. + + + + + Initializes a new instance of the class. + + The required addin. + + + + Gets the name of required addin. + + The required addin name. + + + + Marks a test to use a combinatorial join of any argument + data provided. Since this is the default, the attribute is + not needed. + + + + + PropertyAttribute is used to attach information to a test as a name/value pair.. + + + + + Construct a PropertyAttribute with a name and string value + + The name of the property + The property value + + + + Construct a PropertyAttribute with a name and int value + + The name of the property + The property value + + + + Construct a PropertyAttribute with a name and double value + + The name of the property + The property value + + + + Constructor for derived classes that set the + property dictionary directly. + + + + + Constructor for use by derived classes that use the + name of the type as the property name. Derived classes + must ensure that the Type of the property value is + a standard type supported by the BCL. Any custom + types will cause a serialization Exception when + in the client. + + + + + Gets the property dictionary for this attribute + + + + + Default constructor + + + + + Marks a test to use a combinatorial join of any argument + data provided. Since this is the default, the attribute is + not needed. + + + + + Default constructor + + + + + Marks a test to use a combinatorial join of any argument + data provided. Since this is the default, the attribute is + not needed. + + + + + Default constructor + + + + + Adding this attribute to a method within a + class makes the method callable from the NUnit test runner. There is a property + called Description which is optional which you can provide a more detailed test + description. This class cannot be inherited. + + + + [TestFixture] + public class Fixture + { + [Test] + public void MethodToTest() + {} + + [Test(Description = "more detailed description")] + publc void TestDescriptionMethod() + {} + } + + + + + + Descriptive text for this test + + + + + [TestFixture] + public class ExampleClass + {} + + + + + Default constructor + + + + + Construct with a object[] representing a set of arguments. + In .NET 2.0, the arguments may later be separated into + type arguments and constructor arguments. + + + + + + Descriptive text for this fixture + + + + + The arguments originally provided to the attribute + + + + + Gets or sets a value indicating whether this should be ignored. + + true if ignore; otherwise, false. + + + + Gets or sets the ignore reason. May set Ignored as a side effect. + + The ignore reason. + + + + Get or set the type arguments. If not set + explicitly, any leading arguments that are + Types are taken as type arguments. + + + + + A set of Assert methods operationg on one or more collections + + + + + The Equals method throws an AssertionException. This is done + to make sure there is no mistake by calling this function. + + + + + + + override the default ReferenceEquals to throw an AssertionException. This + implementation makes sure there is no mistake in calling this function + as part of Assert. + + + + + + + Asserts that all items contained in collection are of the type specified by expectedType. + + IEnumerable containing objects to be considered + System.Type that all objects in collection must be instances of + + + + Asserts that all items contained in collection are of the type specified by expectedType. + + IEnumerable containing objects to be considered + System.Type that all objects in collection must be instances of + The message that will be displayed on failure + + + + Asserts that all items contained in collection are of the type specified by expectedType. + + IEnumerable containing objects to be considered + System.Type that all objects in collection must be instances of + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Asserts that all items contained in collection are not equal to null. + + IEnumerable containing objects to be considered + + + + Asserts that all items contained in collection are not equal to null. + + IEnumerable containing objects to be considered + The message that will be displayed on failure + + + + Asserts that all items contained in collection are not equal to null. + + IEnumerable of objects to be considered + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Ensures that every object contained in collection exists within the collection + once and only once. + + IEnumerable of objects to be considered + + + + Ensures that every object contained in collection exists within the collection + once and only once. + + IEnumerable of objects to be considered + The message that will be displayed on failure + + + + Ensures that every object contained in collection exists within the collection + once and only once. + + IEnumerable of objects to be considered + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Asserts that expected and actual are exactly equal. The collections must have the same count, + and contain the exact same objects in the same order. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + + + + Asserts that expected and actual are exactly equal. The collections must have the same count, + and contain the exact same objects in the same order. + If comparer is not null then it will be used to compare the objects. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + The IComparer to use in comparing objects from each IEnumerable + + + + Asserts that expected and actual are exactly equal. The collections must have the same count, + and contain the exact same objects in the same order. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + The message that will be displayed on failure + + + + Asserts that expected and actual are exactly equal. The collections must have the same count, + and contain the exact same objects in the same order. + If comparer is not null then it will be used to compare the objects. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + The IComparer to use in comparing objects from each IEnumerable + The message that will be displayed on failure + + + + Asserts that expected and actual are exactly equal. The collections must have the same count, + and contain the exact same objects in the same order. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Asserts that expected and actual are exactly equal. The collections must have the same count, + and contain the exact same objects in the same order. + If comparer is not null then it will be used to compare the objects. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + The IComparer to use in comparing objects from each IEnumerable + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + + + + Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + The message that will be displayed on failure + + + + Asserts that expected and actual are equivalent, containing the same objects but the match may be in any order. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Asserts that expected and actual are not exactly equal. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + + + + Asserts that expected and actual are not exactly equal. + If comparer is not null then it will be used to compare the objects. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + The IComparer to use in comparing objects from each IEnumerable + + + + Asserts that expected and actual are not exactly equal. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + The message that will be displayed on failure + + + + Asserts that expected and actual are not exactly equal. + If comparer is not null then it will be used to compare the objects. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + The IComparer to use in comparing objects from each IEnumerable + The message that will be displayed on failure + + + + Asserts that expected and actual are not exactly equal. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Asserts that expected and actual are not exactly equal. + If comparer is not null then it will be used to compare the objects. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + The IComparer to use in comparing objects from each IEnumerable + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Asserts that expected and actual are not equivalent. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + + + + Asserts that expected and actual are not equivalent. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + The message that will be displayed on failure + + + + Asserts that expected and actual are not equivalent. + + The first IEnumerable of objects to be considered + The second IEnumerable of objects to be considered + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Asserts that collection contains actual as an item. + + IEnumerable of objects to be considered + Object to be found within collection + + + + Asserts that collection contains actual as an item. + + IEnumerable of objects to be considered + Object to be found within collection + The message that will be displayed on failure + + + + Asserts that collection contains actual as an item. + + IEnumerable of objects to be considered + Object to be found within collection + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Asserts that collection does not contain actual as an item. + + IEnumerable of objects to be considered + Object that cannot exist within collection + + + + Asserts that collection does not contain actual as an item. + + IEnumerable of objects to be considered + Object that cannot exist within collection + The message that will be displayed on failure + + + + Asserts that collection does not contain actual as an item. + + IEnumerable of objects to be considered + Object that cannot exist within collection + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Asserts that superset is not a subject of subset. + + The IEnumerable superset to be considered + The IEnumerable subset to be considered + + + + Asserts that superset is not a subject of subset. + + The IEnumerable superset to be considered + The IEnumerable subset to be considered + The message that will be displayed on failure + + + + Asserts that superset is not a subject of subset. + + The IEnumerable superset to be considered + The IEnumerable subset to be considered + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Asserts that superset is a subset of subset. + + The IEnumerable superset to be considered + The IEnumerable subset to be considered + + + + Asserts that superset is a subset of subset. + + The IEnumerable superset to be considered + The IEnumerable subset to be considered + The message that will be displayed on failure + + + + Asserts that superset is a subset of subset. + + The IEnumerable superset to be considered + The IEnumerable subset to be considered + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Assert that an array, list or other collection is empty + + An array, list or other collection implementing IEnumerable + The message to be displayed on failure + Arguments to be used in formatting the message + + + + Assert that an array, list or other collection is empty + + An array, list or other collection implementing IEnumerable + The message to be displayed on failure + + + + Assert that an array,list or other collection is empty + + An array, list or other collection implementing IEnumerable + + + + Assert that an array, list or other collection is empty + + An array, list or other collection implementing IEnumerable + The message to be displayed on failure + Arguments to be used in formatting the message + + + + Assert that an array, list or other collection is empty + + An array, list or other collection implementing IEnumerable + The message to be displayed on failure + + + + Assert that an array,list or other collection is empty + + An array, list or other collection implementing IEnumerable + + + + Assert that an array, list or other collection is ordered + + An array, list or other collection implementing IEnumerable + The message to be displayed on failure + Arguments to be used in formatting the message + + + + Assert that an array, list or other collection is ordered + + An array, list or other collection implementing IEnumerable + The message to be displayed on failure + + + + Assert that an array, list or other collection is ordered + + An array, list or other collection implementing IEnumerable + + + + Assert that an array, list or other collection is ordered + + An array, list or other collection implementing IEnumerable + A custom comparer to perform the comparisons + The message to be displayed on failure + Arguments to be used in formatting the message + + + + Assert that an array, list or other collection is ordered + + An array, list or other collection implementing IEnumerable + A custom comparer to perform the comparisons + The message to be displayed on failure + + + + Assert that an array, list or other collection is ordered + + An array, list or other collection implementing IEnumerable + A custom comparer to perform the comparisons + + + + Abstract base class for attributes that apply to parameters + and supply data for the parameter. + + + + + Gets the data to be provided to the specified parameter + + + + + ValuesAttribute is used to provide literal arguments for + an individual parameter of a test. + + + + + The collection of data to be returned. Must + be set by any derived attribute classes. + + + + + Construct with one argument + + + + + + Construct with two arguments + + + + + + + Construct with three arguments + + + + + + + + Construct with an array of arguments + + + + + + Get the collection of values to be used as arguments + + + + + Thrown when an assertion failed. + + + + + The error message that explains + the reason for the exception + + + The error message that explains + the reason for the exception + The exception that caused the + current exception + + + + Serialization Constructor + + + + + WUsed on a method, marks the test with a timeout value in milliseconds. + The test will be run in a separate thread and is cancelled if the timeout + is exceeded. Used on a method or assembly, sets the default timeout + for all contained test methods. + + + + + Construct a TimeoutAttribute given a time in milliseconds + + The timeout value in milliseconds + + + + Marks a test that must run in the STA, causing it + to run in a separate thread if necessary. + + On methods, you may also use STAThreadAttribute + to serve the same purpose. + + + + + Construct a RequiresSTAAttribute + + + + + Marks a test that must run in the MTA, causing it + to run in a separate thread if necessary. + + On methods, you may also use MTAThreadAttribute + to serve the same purpose. + + + + + Construct a RequiresMTAAttribute + + + + + Marks a test that must run on a separate thread. + + + + + Construct a RequiresThreadAttribute + + + + + Construct a RequiresThreadAttribute, specifying the apartment + + + + + AssertionHelper is an optional base class for user tests, + allowing the use of shorter names for constraints and + asserts and avoiding conflict with the definition of + , from which it inherits much of its + behavior, in certain mock object frameworks. + + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. Works + identically to + + A Constraint to be applied + The actual value to test + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. Works + identically to + + A Constraint to be applied + The actual value to test + The message that will be displayed on failure + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. Works + identically to + + A Constraint to be applied + The actual value to test + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. + + A Constraint expression to be applied + An ActualValueDelegate returning the value to be tested + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. + + A Constraint expression to be applied + An ActualValueDelegate returning the value to be tested + The message that will be displayed on failure + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. + + An ActualValueDelegate returning the value to be tested + A Constraint expression to be applied + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Apply a constraint to a referenced value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. + + A Constraint to be applied + The actual value to test + + + + Apply a constraint to a referenced value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. + + A Constraint to be applied + The actual value to test + The message that will be displayed on failure + + + + Apply a constraint to a referenced value, succeeding if the constraint + is satisfied and throwing an assertion exception on failure. + + A Constraint to be applied + The actual value to test + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Asserts that a condition is true. If the condition is false the method throws + an . Works Identically to + . + + The evaluated condition + The message to display if the condition is false + Arguments to be used in formatting the message + + + + Asserts that a condition is true. If the condition is false the method throws + an . Works Identically to + . + + The evaluated condition + The message to display if the condition is false + + + + Asserts that a condition is true. If the condition is false the method throws + an . Works Identically to . + + The evaluated condition + + + + Asserts that the code represented by a delegate throws an exception + that satisfies the constraint provided. + + A TestDelegate to be executed + A ThrowsConstraint used in the test + + + + Returns a ListMapper based on a collection. + + The original collection + + + + + Attribute used to apply a category to a test + + + + + The name of the category + + + + + Construct attribute for a given category + + The name of the category + + + + Protected constructor uses the Type name as the name + of the category. + + + + + The name of the category + + + + + RandomAttribute is used to supply a set of random values + to a single parameter of a parameterized test. + + + + + Construct a set of doubles from 0.0 to 1.0, + specifying only the count. + + + + + + Construct a set of doubles from min to max + + + + + + + + Construct a set of ints from min to max + + + + + + + + Get the collection of values to be used as arguments + + + + + Attribute used to provide descriptive text about a + test case or fixture. + + + + + Construct the attribute + + Text describing the test + + + + Gets the test description + + + + + ExplicitAttribute marks a test or test fixture so that it will + only be run if explicitly executed from the gui or command line + or if it is included by use of a filter. The test will not be + run simply because an enclosing suite is run. + + + + + Default constructor + + + + + Constructor with a reason + + The reason test is marked explicit + + + + The reason test is marked explicit + + + + + Provides static methods to express the assumptions + that must be met for a test to give a meaningful + result. If an assumption is not met, the test + should produce an inconclusive result. + + + + + The Equals method throws an AssertionException. This is done + to make sure there is no mistake by calling this function. + + + + + + + override the default ReferenceEquals to throw an AssertionException. This + implementation makes sure there is no mistake in calling this function + as part of Assert. + + + + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an InconclusiveException on failure. + + A Constraint expression to be applied + The actual value to test + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an InconclusiveException on failure. + + A Constraint expression to be applied + The actual value to test + The message that will be displayed on failure + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an InconclusiveException on failure. + + A Constraint expression to be applied + The actual value to test + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an InconclusiveException on failure. + + A Constraint expression to be applied + An ActualValueDelegate returning the value to be tested + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an InconclusiveException on failure. + + A Constraint expression to be applied + An ActualValueDelegate returning the value to be tested + The message that will be displayed on failure + + + + Apply a constraint to an actual value, succeeding if the constraint + is satisfied and throwing an InconclusiveException on failure. + + An ActualValueDelegate returning the value to be tested + A Constraint expression to be applied + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Apply a constraint to a referenced value, succeeding if the constraint + is satisfied and throwing an InconclusiveException on failure. + + A Constraint expression to be applied + The actual value to test + + + + Apply a constraint to a referenced value, succeeding if the constraint + is satisfied and throwing an InconclusiveException on failure. + + A Constraint expression to be applied + The actual value to test + The message that will be displayed on failure + + + + Apply a constraint to a referenced value, succeeding if the constraint + is satisfied and throwing an InconclusiveException on failure. + + A Constraint expression to be applied + The actual value to test + The message that will be displayed on failure + Arguments to be used in formatting the message + + + + Asserts that a condition is true. If the condition is false the method throws + an . + + The evaluated condition + The message to display if the condition is false + Arguments to be used in formatting the message + + + + Asserts that a condition is true. If the condition is false the method throws + an . + + The evaluated condition + The message to display if the condition is false + + + + Asserts that a condition is true. If the condition is false the + method throws an . + + The evaluated condition + + + + Asserts that the code represented by a delegate throws an exception + that satisfies the constraint provided. + + A TestDelegate to be executed + A ThrowsConstraint used in the test + + + + GlobalSettings is a place for setting default values used + by the framework in performing asserts. + + + + + Default tolerance for floating point equality + + + + + Helper class with properties and methods that supply + a number of constraints used in Asserts. + + + + + Returns a constraint that tests two items for equality + + + + + Returns a constraint that tests that two references are the same object + + + + + Returns a constraint that tests whether the + actual value is greater than the suppled argument + + + + + Returns a constraint that tests whether the + actual value is greater than or equal to the suppled argument + + + + + Returns a constraint that tests whether the + actual value is greater than or equal to the suppled argument + + + + + Returns a constraint that tests whether the + actual value is less than the suppled argument + + + + + Returns a constraint that tests whether the + actual value is less than or equal to the suppled argument + + + + + Returns a constraint that tests whether the + actual value is less than or equal to the suppled argument + + + + + Returns a constraint that tests whether the actual + value is of the exact type supplied as an argument. + + + + + Returns a constraint that tests whether the actual + value is of the exact type supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is of the type supplied as an argument or a derived type. + + + + + Returns a constraint that tests whether the actual value + is of the type supplied as an argument or a derived type. + + + + + Returns a constraint that tests whether the actual value + is of the type supplied as an argument or a derived type. + + + + + Returns a constraint that tests whether the actual value + is of the type supplied as an argument or a derived type. + + + + + Returns a constraint that tests whether the actual value + is assignable from the type supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is assignable from the type supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is assignable from the type supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is assignable from the type supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is a collection containing the same elements as the + collection supplied as an argument. + + + + + Returns a constraint that tests whether the actual value + is a subset of the collection supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value contains the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value starts with the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value ends with the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value matches the Regex pattern supplied as an argument. + + + + + Returns a constraint that tests whether the path provided + is the same as an expected path after canonicalization. + + + + + Returns a constraint that tests whether the path provided + is the same path or under an expected path after canonicalization. + + + + + Returns a constraint that tests whether the actual value falls + within a specified range. + + + + + Returns a ConstraintExpression that negates any + following constraint. + + + + + Returns a ConstraintExpression, which will apply + the following constraint to all members of a collection, + succeeding if all of them succeed. + + + + + Returns a constraint that tests for null + + + + + Returns a constraint that tests for True + + + + + Returns a constraint that tests for False + + + + + Returns a constraint that tests for NaN + + + + + Returns a constraint that tests for empty + + + + + Returns a constraint that tests whether a collection + contains all unique items. + + + + + Returns a constraint that tests whether an object graph is serializable in binary format. + + + + + Returns a constraint that tests whether an object graph is serializable in xml format. + + + + + Returns a constraint that tests whether a collection is ordered + + + + + Interface implemented by a user fixture in order to + validate any expected exceptions. It is only called + for test methods marked with the ExpectedException + attribute. + + + + + Method to handle an expected exception + + The exception to be handled + + + + Attribute used to mark a test that is to be ignored. + Ignored tests result in a warning message when the + tests are run. + + + + + Constructs the attribute without giving a reason + for ignoring the test. + + + + + Constructs the attribute giving a reason for ignoring the test + + The reason for ignoring the test + + + + The reason for ignoring a test + + + + + Thrown when an assertion failed. + + + + + + + The error message that explains + the reason for the exception + The exception that caused the + current exception + + + + Serialization Constructor + + + + + Abstract base for Attributes that are used to include tests + in the test run based on environmental settings. + + + + + Constructor with no included items specified, for use + with named property syntax. + + + + + Constructor taking one or more included items + + Comma-delimited list of included items + + + + Name of the item that is needed in order for + a test to run. Multiple itemss may be given, + separated by a comma. + + + + + Name of the item to be excluded. Multiple items + may be given, separated by a comma. + + + + + The reason for including or excluding the test + + + + + PlatformAttribute is used to mark a test fixture or an + individual method as applying to a particular platform only. + + + + + Constructor with no platforms specified, for use + with named property syntax. + + + + + Constructor taking one or more platforms + + Comma-deliminted list of platforms + + + + CultureAttribute is used to mark a test fixture or an + individual method as applying to a particular Culture only. + + + + + Constructor with no cultures specified, for use + with named property syntax. + + + + + Constructor taking one or more cultures + + Comma-deliminted list of cultures + + + + RangeAttribute is used to supply a range of values to an + individual parameter of a parameterized test. + + + + + Construct a range of ints using default step of 1 + + + + + + + Construct a range of ints specifying the step size + + + + + + + + Construct a range of longs + + + + + + + + Construct a range of doubles + + + + + + + + Construct a range of floats + + + + + + + + Summary description for SetCultureAttribute. + + + + + Construct given the name of a culture + + + + + + ValueSourceAttribute indicates the source to be used to + provide data for one parameter of a test method. + + + + + Construct with the name of the factory - for use with languages + that don't support params arrays. + + The name of the data source to be used + + + + Construct with a Type and name - for use with languages + that don't support params arrays. + + The Type that will provide data + The name of the method, property or field that will provide data + + + + The name of a the method, property or fiend to be used as a source + + + + + A Type to be used as a source + + + + + TextMessageWriter writes constraint descriptions and messages + in displayable form as a text stream. It tailors the display + of individual message components to form the standard message + format of NUnit assertion failure messages. + + + + + Prefix used for the expected value line of a message + + + + + Prefix used for the actual value line of a message + + + + + Length of a message prefix + + + + + Construct a TextMessageWriter + + + + + Construct a TextMessageWriter, specifying a user message + and optional formatting arguments. + + + + + + + Method to write single line message with optional args, usually + written to precede the general failure message, at a givel + indentation level. + + The indentation level of the message + The message to be written + Any arguments used in formatting the message + + + + Display Expected and Actual lines for a constraint. This + is called by MessageWriter's default implementation of + WriteMessageTo and provides the generic two-line display. + + The constraint that failed + + + + Display Expected and Actual lines for given values. This + method may be called by constraints that need more control over + the display of actual and expected values than is provided + by the default implementation. + + The expected value + The actual value causing the failure + + + + Display Expected and Actual lines for given values, including + a tolerance value on the expected line. + + The expected value + The actual value causing the failure + The tolerance within which the test was made + + + + Display the expected and actual string values on separate lines. + If the mismatch parameter is >=0, an additional line is displayed + line containing a caret that points to the mismatch point. + + The expected string value + The actual string value + The point at which the strings don't match or -1 + If true, case is ignored in string comparisons + If true, clip the strings to fit the max line length + + + + Writes the text for a connector. + + The connector. + + + + Writes the text for a predicate. + + The predicate. + + + + Write the text for a modifier. + + The modifier. + + + + Writes the text for an expected value. + + The expected value. + + + + Writes the text for an actual value. + + The actual value. + + + + Writes the text for a generalized value. + + The value. + + + + Writes the text for a collection value, + starting at a particular point, to a max length + + The collection containing elements to write. + The starting point of the elements to write + The maximum number of elements to write + + + + Write the generic 'Expected' line for a constraint + + The constraint that failed + + + + Write the generic 'Expected' line for a given value + + The expected value + + + + Write the generic 'Expected' line for a given value + and tolerance. + + The expected value + The tolerance within which the test was made + + + + Write the generic 'Actual' line for a constraint + + The constraint for which the actual value is to be written + + + + Write the generic 'Actual' line for a given value + + The actual value causing a failure + + + + Gets or sets the maximum line length for this writer + + + + + Basic Asserts on strings. + + + + + The Equals method throws an AssertionException. This is done + to make sure there is no mistake by calling this function. + + + + + + + override the default ReferenceEquals to throw an AssertionException. This + implementation makes sure there is no mistake in calling this function + as part of Assert. + + + + + + + Asserts that a string is found within another string. + + The expected string + The string to be examined + The message to display in case of failure + Arguments used in formatting the message + + + + Asserts that a string is found within another string. + + The expected string + The string to be examined + The message to display in case of failure + + + + Asserts that a string is found within another string. + + The expected string + The string to be examined + + + + Asserts that a string is not found within another string. + + The expected string + The string to be examined + The message to display in case of failure + Arguments used in formatting the message + + + + Asserts that a string is found within another string. + + The expected string + The string to be examined + The message to display in case of failure + + + + Asserts that a string is found within another string. + + The expected string + The string to be examined + + + + Asserts that a string starts with another string. + + The expected string + The string to be examined + The message to display in case of failure + Arguments used in formatting the message + + + + Asserts that a string starts with another string. + + The expected string + The string to be examined + The message to display in case of failure + + + + Asserts that a string starts with another string. + + The expected string + The string to be examined + + + + Asserts that a string does not start with another string. + + The expected string + The string to be examined + The message to display in case of failure + Arguments used in formatting the message + + + + Asserts that a string does not start with another string. + + The expected string + The string to be examined + The message to display in case of failure + + + + Asserts that a string does not start with another string. + + The expected string + The string to be examined + + + + Asserts that a string ends with another string. + + The expected string + The string to be examined + The message to display in case of failure + Arguments used in formatting the message + + + + Asserts that a string ends with another string. + + The expected string + The string to be examined + The message to display in case of failure + + + + Asserts that a string ends with another string. + + The expected string + The string to be examined + + + + Asserts that a string does not end with another string. + + The expected string + The string to be examined + The message to display in case of failure + Arguments used in formatting the message + + + + Asserts that a string does not end with another string. + + The expected string + The string to be examined + The message to display in case of failure + + + + Asserts that a string does not end with another string. + + The expected string + The string to be examined + + + + Asserts that two strings are equal, without regard to case. + + The expected string + The actual string + The message to display in case of failure + Arguments used in formatting the message + + + + Asserts that two strings are equal, without regard to case. + + The expected string + The actual string + The message to display in case of failure + + + + Asserts that two strings are equal, without regard to case. + + The expected string + The actual string + + + + Asserts that two strings are not equal, without regard to case. + + The expected string + The actual string + The message to display in case of failure + Arguments used in formatting the message + + + + Asserts that two strings are Notequal, without regard to case. + + The expected string + The actual string + The message to display in case of failure + + + + Asserts that two strings are not equal, without regard to case. + + The expected string + The actual string + + + + Asserts that a string matches an expected regular expression pattern. + + The regex pattern to be matched + The actual string + The message to display in case of failure + Arguments used in formatting the message + + + + Asserts that a string matches an expected regular expression pattern. + + The regex pattern to be matched + The actual string + The message to display in case of failure + + + + Asserts that a string matches an expected regular expression pattern. + + The regex pattern to be matched + The actual string + + + + Asserts that a string does not match an expected regular expression pattern. + + The regex pattern to be used + The actual string + The message to display in case of failure + Arguments used in formatting the message + + + + Asserts that a string does not match an expected regular expression pattern. + + The regex pattern to be used + The actual string + The message to display in case of failure + + + + Asserts that a string does not match an expected regular expression pattern. + + The regex pattern to be used + The actual string + + + + Attribute used to mark a class that contains one-time SetUp + and/or TearDown methods that apply to all the tests in a + namespace or an assembly. + + + + + SetUpFixtureAttribute is used to identify a SetUpFixture + + + + + RepeatAttribute may be applied to test case in order + to run it multiple times. + + + + + Construct a RepeatAttribute + + The number of times to run the test + + + + Attribute used to mark a static (shared in VB) property + that returns a list of tests. + + + + + Attribute used to identify a method that is called + immediately after each test is run. The method is + guaranteed to be called, even if an exception is thrown. + + + + + The Iz class is a synonym for Is intended for use in VB, + which regards Is as a keyword. + + + + + Helper class with properties and methods that supply + constraints that operate on exceptions. + + + + + Creates a constraint specifying the exact type of exception expected + + + + + Creates a constraint specifying the exact type of exception expected + + + + + Creates a constraint specifying the type of exception expected + + + + + Creates a constraint specifying the type of exception expected + + + + + Creates a constraint specifying an expected exception + + + + + Creates a constraint specifying an exception with a given InnerException + + + + + Creates a constraint specifying an expected TargetInvocationException + + + + + Creates a constraint specifying an expected TargetInvocationException + + + + + Creates a constraint specifying an expected TargetInvocationException + + + + + Creates a constraint specifying that no exception is thrown + + + + + ListMapper is used to transform a collection used as an actual argument + producing another collection to be used in the assertion. + + + + + Construct a ListMapper based on a collection + + The collection to be transformed + + + + Produces a collection containing all the values of a property + + The collection of property values + + + + + Attribute used to identify a method that is + called before any tests in a fixture are run. + + + + + Attribute used to identify a method that is called after + all the tests in a fixture have run. The method is + guaranteed to be called, even if an exception is thrown. + + + + + Helper class with static methods used to supply constraints + that operate on strings. + + + + + Returns a constraint that succeeds if the actual + value contains the substring supplied as an argument. + + + + + Returns a constraint that fails if the actual + value contains the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value starts with the substring supplied as an argument. + + + + + Returns a constraint that fails if the actual + value starts with the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value ends with the substring supplied as an argument. + + + + + Returns a constraint that fails if the actual + value ends with the substring supplied as an argument. + + + + + Returns a constraint that succeeds if the actual + value matches the Regex pattern supplied as an argument. + + + + + Returns a constraint that fails if the actual + value matches the pattern supplied as an argument. + + + + + Returns a ConstraintExpression, which will apply + the following constraint to all members of a collection, + succeeding if all of them succeed. + + + + + Summary description for MaxTimeAttribute. + + + + + Construct a MaxTimeAttribute, given a time in milliseconds. + + The maximum elapsed time in milliseconds + + + + Summary description for SetUICultureAttribute. + + + + + Construct given the name of a culture + + + + + + Summary description for DirectoryAssert + + + + + The Equals method throws an AssertionException. This is done + to make sure there is no mistake by calling this function. + + + + + + + override the default ReferenceEquals to throw an AssertionException. This + implementation makes sure there is no mistake in calling this function + as part of Assert. + + + + + + + We don't actually want any instances of this object, but some people + like to inherit from it to add other static methods. Hence, the + protected constructor disallows any instances of this object. + + + + + Verifies that two directories are equal. Two directories are considered + equal if both are null, or if both have the same value byte for byte. + If they are not equal an is thrown. + + A directory containing the value that is expected + A directory containing the actual value + The message to display if directories are not equal + Arguments to be used in formatting the message + + + + Verifies that two directories are equal. Two directories are considered + equal if both are null, or if both have the same value byte for byte. + If they are not equal an is thrown. + + A directory containing the value that is expected + A directory containing the actual value + The message to display if directories are not equal + + + + Verifies that two directories are equal. Two directories are considered + equal if both are null, or if both have the same value byte for byte. + If they are not equal an is thrown. + + A directory containing the value that is expected + A directory containing the actual value + + + + Verifies that two directories are equal. Two directories are considered + equal if both are null, or if both have the same value byte for byte. + If they are not equal an is thrown. + + A directory path string containing the value that is expected + A directory path string containing the actual value + The message to display if directories are not equal + Arguments to be used in formatting the message + + + + Verifies that two directories are equal. Two directories are considered + equal if both are null, or if both have the same value byte for byte. + If they are not equal an is thrown. + + A directory path string containing the value that is expected + A directory path string containing the actual value + The message to display if directories are not equal + + + + Verifies that two directories are equal. Two directories are considered + equal if both are null, or if both have the same value byte for byte. + If they are not equal an is thrown. + + A directory path string containing the value that is expected + A directory path string containing the actual value + + + + Asserts that two directories are not equal. If they are equal + an is thrown. + + A directory containing the value that is expected + A directory containing the actual value + The message to display if directories are not equal + Arguments to be used in formatting the message + + + + Asserts that two directories are not equal. If they are equal + an is thrown. + + A directory containing the value that is expected + A directory containing the actual value + The message to display if directories are not equal + + + + Asserts that two directories are not equal. If they are equal + an is thrown. + + A directory containing the value that is expected + A directory containing the actual value + + + + Asserts that two directories are not equal. If they are equal + an is thrown. + + A directory path string containing the value that is expected + A directory path string containing the actual value + The message to display if directories are equal + Arguments to be used in formatting the message + + + + Asserts that two directories are not equal. If they are equal + an is thrown. + + A directory path string containing the value that is expected + A directory path string containing the actual value + The message to display if directories are equal + + + + Asserts that two directories are not equal. If they are equal + an is thrown. + + A directory path string containing the value that is expected + A directory path string containing the actual value + + + + Asserts that the directory is empty. If it is not empty + an is thrown. + + A directory to search + The message to display if directories are not equal + Arguments to be used in formatting the message + + + + Asserts that the directory is empty. If it is not empty + an is thrown. + + A directory to search + The message to display if directories are not equal + + + + Asserts that the directory is empty. If it is not empty + an is thrown. + + A directory to search + + + + Asserts that the directory is empty. If it is not empty + an is thrown. + + A directory to search + The message to display if directories are not equal + Arguments to be used in formatting the message + + + + Asserts that the directory is empty. If it is not empty + an is thrown. + + A directory to search + The message to display if directories are not equal + + + + Asserts that the directory is empty. If it is not empty + an is thrown. + + A directory to search + + + + Asserts that the directory is not empty. If it is empty + an is thrown. + + A directory to search + The message to display if directories are not equal + Arguments to be used in formatting the message + + + + Asserts that the directory is not empty. If it is empty + an is thrown. + + A directory to search + The message to display if directories are not equal + + + + Asserts that the directory is not empty. If it is empty + an is thrown. + + A directory to search + + + + Asserts that the directory is not empty. If it is empty + an is thrown. + + A directory to search + The message to display if directories are not equal + Arguments to be used in formatting the message + + + + Asserts that the directory is not empty. If it is empty + an is thrown. + + A directory to search + The message to display if directories are not equal + + + + Asserts that the directory is not empty. If it is empty + an is thrown. + + A directory to search + + + + Asserts that path contains actual as a subdirectory or + an is thrown. + + A directory to search + sub-directory asserted to exist under directory + The message to display if directory is not within the path + Arguments to be used in formatting the message + + + + Asserts that path contains actual as a subdirectory or + an is thrown. + + A directory to search + sub-directory asserted to exist under directory + The message to display if directory is not within the path + + + + Asserts that path contains actual as a subdirectory or + an is thrown. + + A directory to search + sub-directory asserted to exist under directory + + + + Asserts that path contains actual as a subdirectory or + an is thrown. + + A directory to search + sub-directory asserted to exist under directory + The message to display if directory is not within the path + Arguments to be used in formatting the message + + + + Asserts that path contains actual as a subdirectory or + an is thrown. + + A directory to search + sub-directory asserted to exist under directory + The message to display if directory is not within the path + + + + Asserts that path contains actual as a subdirectory or + an is thrown. + + A directory to search + sub-directory asserted to exist under directory + + + + Asserts that path does not contain actual as a subdirectory or + an is thrown. + + A directory to search + sub-directory asserted to exist under directory + The message to display if directory is not within the path + Arguments to be used in formatting the message + + + + Asserts that path does not contain actual as a subdirectory or + an is thrown. + + A directory to search + sub-directory asserted to exist under directory + The message to display if directory is not within the path + + + + Asserts that path does not contain actual as a subdirectory or + an is thrown. + + A directory to search + sub-directory asserted to exist under directory + + + + Asserts that path does not contain actual as a subdirectory or + an is thrown. + + A directory to search + sub-directory asserted to exist under directory + The message to display if directory is not within the path + Arguments to be used in formatting the message + + + + Asserts that path does not contain actual as a subdirectory or + an is thrown. + + A directory to search + sub-directory asserted to exist under directory + The message to display if directory is not within the path + + + + Asserts that path does not contain actual as a subdirectory or + an is thrown. + + A directory to search + sub-directory asserted to exist under directory + + + + The SpecialValue enum is used to represent TestCase arguments + that cannot be used as arguments to an Attribute. + + + + + Null represents a null value, which cannot be used as an + argument to an attriute under .NET 1.x + + + + + Summary description for FileAssert. + + + + + The Equals method throws an AssertionException. This is done + to make sure there is no mistake by calling this function. + + + + + + + override the default ReferenceEquals to throw an AssertionException. This + implementation makes sure there is no mistake in calling this function + as part of Assert. + + + + + + + We don't actually want any instances of this object, but some people + like to inherit from it to add other static methods. Hence, the + protected constructor disallows any instances of this object. + + + + + Verifies that two Streams are equal. Two Streams are considered + equal if both are null, or if both have the same value byte for byte. + If they are not equal an is thrown. + + The expected Stream + The actual Stream + The message to display if Streams are not equal + Arguments to be used in formatting the message + + + + Verifies that two Streams are equal. Two Streams are considered + equal if both are null, or if both have the same value byte for byte. + If they are not equal an is thrown. + + The expected Stream + The actual Stream + The message to display if objects are not equal + + + + Verifies that two Streams are equal. Two Streams are considered + equal if both are null, or if both have the same value byte for byte. + If they are not equal an is thrown. + + The expected Stream + The actual Stream + + + + Verifies that two files are equal. Two files are considered + equal if both are null, or if both have the same value byte for byte. + If they are not equal an is thrown. + + A file containing the value that is expected + A file containing the actual value + The message to display if Streams are not equal + Arguments to be used in formatting the message + + + + Verifies that two files are equal. Two files are considered + equal if both are null, or if both have the same value byte for byte. + If they are not equal an is thrown. + + A file containing the value that is expected + A file containing the actual value + The message to display if objects are not equal + + + + Verifies that two files are equal. Two files are considered + equal if both are null, or if both have the same value byte for byte. + If they are not equal an is thrown. + + A file containing the value that is expected + A file containing the actual value + + + + Verifies that two files are equal. Two files are considered + equal if both are null, or if both have the same value byte for byte. + If they are not equal an is thrown. + + The path to a file containing the value that is expected + The path to a file containing the actual value + The message to display if Streams are not equal + Arguments to be used in formatting the message + + + + Verifies that two files are equal. Two files are considered + equal if both are null, or if both have the same value byte for byte. + If they are not equal an is thrown. + + The path to a file containing the value that is expected + The path to a file containing the actual value + The message to display if objects are not equal + + + + Verifies that two files are equal. Two files are considered + equal if both are null, or if both have the same value byte for byte. + If they are not equal an is thrown. + + The path to a file containing the value that is expected + The path to a file containing the actual value + + + + Asserts that two Streams are not equal. If they are equal + an is thrown. + + The expected Stream + The actual Stream + The message to be displayed when the two Stream are the same. + Arguments to be used in formatting the message + + + + Asserts that two Streams are not equal. If they are equal + an is thrown. + + The expected Stream + The actual Stream + The message to be displayed when the Streams are the same. + + + + Asserts that two Streams are not equal. If they are equal + an is thrown. + + The expected Stream + The actual Stream + + + + Asserts that two files are not equal. If they are equal + an is thrown. + + A file containing the value that is expected + A file containing the actual value + The message to display if Streams are not equal + Arguments to be used in formatting the message + + + + Asserts that two files are not equal. If they are equal + an is thrown. + + A file containing the value that is expected + A file containing the actual value + The message to display if objects are not equal + + + + Asserts that two files are not equal. If they are equal + an is thrown. + + A file containing the value that is expected + A file containing the actual value + + + + Asserts that two files are not equal. If they are equal + an is thrown. + + The path to a file containing the value that is expected + The path to a file containing the actual value + The message to display if Streams are not equal + Arguments to be used in formatting the message + + + + Asserts that two files are not equal. If they are equal + an is thrown. + + The path to a file containing the value that is expected + The path to a file containing the actual value + The message to display if objects are not equal + + + + Asserts that two files are not equal. If they are equal + an is thrown. + + The path to a file containing the value that is expected + The path to a file containing the actual value + + + + Adding this attribute to a method within a + class makes the method callable from the NUnit test runner. There is a property + called Description which is optional which you can provide a more detailed test + description. This class cannot be inherited. + + + + [TestFixture] + public class Fixture + { + [Test] + public void MethodToTest() + {} + + [Test(Description = "more detailed description")] + publc void TestDescriptionMethod() + {} + } + + + + + + Randomizer returns a set of random values in a repeatable + way, to allow re-running of tests if necessary. + + + + + Get a randomizer for a particular member, returning + one that has already been created if it exists. + This ensures that the same values are generated + each time the tests are reloaded. + + + + + Get a randomizer for a particular parameter, returning + one that has already been created if it exists. + This ensures that the same values are generated + each time the tests are reloaded. + + + + + Construct a randomizer using a random seed + + + + + Construct a randomizer using a specified seed + + + + + Return an array of random doubles between 0.0 and 1.0. + + + + + + + Return an array of random doubles with values in a specified range. + + + + + Return an array of random ints with values in a specified range. + + + + + Get a random seed for use in creating a randomizer. + + + + + Used to mark a field for use as a datapoint when executing a theory + within the same fixture that requires an argument of the field's Type. + + + + + Used to mark an array as containing a set of datapoints to be used + executing a theory within the same fixture that requires an argument + of the Type of the array elements. + + + + diff --git a/References/Tools/NUnit/framework/nunit.mocks.dll b/References/Tools/NUnit/framework/nunit.mocks.dll new file mode 100644 index 0000000..558f475 Binary files /dev/null and b/References/Tools/NUnit/framework/nunit.mocks.dll differ diff --git a/References/Tools/NUnit/framework/pnunit.framework.dll b/References/Tools/NUnit/framework/pnunit.framework.dll new file mode 100644 index 0000000..494dc72 Binary files /dev/null and b/References/Tools/NUnit/framework/pnunit.framework.dll differ diff --git a/References/Tools/NUnit/launcher.log.conf b/References/Tools/NUnit/launcher.log.conf new file mode 100644 index 0000000..d340cad --- /dev/null +++ b/References/Tools/NUnit/launcher.log.conf @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/References/Tools/NUnit/lib/failure.png b/References/Tools/NUnit/lib/failure.png new file mode 100644 index 0000000..2e400b2 Binary files /dev/null and b/References/Tools/NUnit/lib/failure.png differ diff --git a/References/Tools/NUnit/lib/fit.dll b/References/Tools/NUnit/lib/fit.dll new file mode 100644 index 0000000..40bbef0 Binary files /dev/null and b/References/Tools/NUnit/lib/fit.dll differ diff --git a/References/Tools/NUnit/lib/ignored.png b/References/Tools/NUnit/lib/ignored.png new file mode 100644 index 0000000..478efbf Binary files /dev/null and b/References/Tools/NUnit/lib/ignored.png differ diff --git a/References/Tools/NUnit/lib/inconclusive.png b/References/Tools/NUnit/lib/inconclusive.png new file mode 100644 index 0000000..4807b7c Binary files /dev/null and b/References/Tools/NUnit/lib/inconclusive.png differ diff --git a/References/Tools/NUnit/lib/log4net.dll b/References/Tools/NUnit/lib/log4net.dll new file mode 100644 index 0000000..20a2e1c Binary files /dev/null and b/References/Tools/NUnit/lib/log4net.dll differ diff --git a/References/Tools/NUnit/lib/nunit-console-runner.dll b/References/Tools/NUnit/lib/nunit-console-runner.dll new file mode 100644 index 0000000..3f9c0e9 Binary files /dev/null and b/References/Tools/NUnit/lib/nunit-console-runner.dll differ diff --git a/References/Tools/NUnit/lib/nunit-gui-runner.dll b/References/Tools/NUnit/lib/nunit-gui-runner.dll new file mode 100644 index 0000000..894c338 Binary files /dev/null and b/References/Tools/NUnit/lib/nunit-gui-runner.dll differ diff --git a/References/Tools/NUnit/lib/nunit.core.dll b/References/Tools/NUnit/lib/nunit.core.dll new file mode 100644 index 0000000..6fd79cf Binary files /dev/null and b/References/Tools/NUnit/lib/nunit.core.dll differ diff --git a/References/Tools/NUnit/lib/nunit.core.interfaces.dll b/References/Tools/NUnit/lib/nunit.core.interfaces.dll new file mode 100644 index 0000000..2794827 Binary files /dev/null and b/References/Tools/NUnit/lib/nunit.core.interfaces.dll differ diff --git a/References/Tools/NUnit/lib/nunit.fixtures.dll b/References/Tools/NUnit/lib/nunit.fixtures.dll new file mode 100644 index 0000000..1658b37 Binary files /dev/null and b/References/Tools/NUnit/lib/nunit.fixtures.dll differ diff --git a/References/Tools/NUnit/lib/nunit.uiexception.dll b/References/Tools/NUnit/lib/nunit.uiexception.dll new file mode 100644 index 0000000..3f05866 Binary files /dev/null and b/References/Tools/NUnit/lib/nunit.uiexception.dll differ diff --git a/References/Tools/NUnit/lib/nunit.uikit.dll b/References/Tools/NUnit/lib/nunit.uikit.dll new file mode 100644 index 0000000..cb3cfdf Binary files /dev/null and b/References/Tools/NUnit/lib/nunit.uikit.dll differ diff --git a/References/Tools/NUnit/lib/nunit.util.dll b/References/Tools/NUnit/lib/nunit.util.dll new file mode 100644 index 0000000..71ca49b Binary files /dev/null and b/References/Tools/NUnit/lib/nunit.util.dll differ diff --git a/References/Tools/NUnit/lib/skipped.png b/References/Tools/NUnit/lib/skipped.png new file mode 100644 index 0000000..7c9fc64 Binary files /dev/null and b/References/Tools/NUnit/lib/skipped.png differ diff --git a/References/Tools/NUnit/lib/success.png b/References/Tools/NUnit/lib/success.png new file mode 100644 index 0000000..2a30150 Binary files /dev/null and b/References/Tools/NUnit/lib/success.png differ diff --git a/References/Tools/NUnit/nunit-agent.exe b/References/Tools/NUnit/nunit-agent.exe new file mode 100644 index 0000000..9b189c1 Binary files /dev/null and b/References/Tools/NUnit/nunit-agent.exe differ diff --git a/References/Tools/NUnit/nunit-agent.exe.config b/References/Tools/NUnit/nunit-agent.exe.config new file mode 100644 index 0000000..c31edbc --- /dev/null +++ b/References/Tools/NUnit/nunit-agent.exe.config @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/References/Tools/NUnit/nunit-console-x86.exe b/References/Tools/NUnit/nunit-console-x86.exe new file mode 100644 index 0000000..83849f3 Binary files /dev/null and b/References/Tools/NUnit/nunit-console-x86.exe differ diff --git a/References/Tools/NUnit/nunit-console-x86.exe.config b/References/Tools/NUnit/nunit-console-x86.exe.config new file mode 100644 index 0000000..2636e47 --- /dev/null +++ b/References/Tools/NUnit/nunit-console-x86.exe.config @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/References/Tools/NUnit/nunit-console.exe b/References/Tools/NUnit/nunit-console.exe new file mode 100644 index 0000000..9f61905 Binary files /dev/null and b/References/Tools/NUnit/nunit-console.exe differ diff --git a/References/Tools/NUnit/nunit-console.exe.config b/References/Tools/NUnit/nunit-console.exe.config new file mode 100644 index 0000000..2636e47 --- /dev/null +++ b/References/Tools/NUnit/nunit-console.exe.config @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/References/Tools/NUnit/nunit-x86.exe b/References/Tools/NUnit/nunit-x86.exe new file mode 100644 index 0000000..1383237 Binary files /dev/null and b/References/Tools/NUnit/nunit-x86.exe differ diff --git a/References/Tools/NUnit/nunit-x86.exe.config b/References/Tools/NUnit/nunit-x86.exe.config new file mode 100644 index 0000000..9dae8a1 --- /dev/null +++ b/References/Tools/NUnit/nunit-x86.exe.config @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/References/Tools/NUnit/nunit.exe b/References/Tools/NUnit/nunit.exe new file mode 100644 index 0000000..e3a0d28 Binary files /dev/null and b/References/Tools/NUnit/nunit.exe differ diff --git a/References/Tools/NUnit/nunit.exe.config b/References/Tools/NUnit/nunit.exe.config new file mode 100644 index 0000000..9dae8a1 --- /dev/null +++ b/References/Tools/NUnit/nunit.exe.config @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/References/Tools/NUnit/nunit.framework.dll b/References/Tools/NUnit/nunit.framework.dll new file mode 100644 index 0000000..2729ddf Binary files /dev/null and b/References/Tools/NUnit/nunit.framework.dll differ diff --git a/References/Tools/NUnit/pnunit-agent.exe b/References/Tools/NUnit/pnunit-agent.exe new file mode 100644 index 0000000..7a559eb Binary files /dev/null and b/References/Tools/NUnit/pnunit-agent.exe differ diff --git a/References/Tools/NUnit/pnunit-agent.exe.config b/References/Tools/NUnit/pnunit-agent.exe.config new file mode 100644 index 0000000..15d4cdb --- /dev/null +++ b/References/Tools/NUnit/pnunit-agent.exe.config @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/References/Tools/NUnit/pnunit-launcher.exe b/References/Tools/NUnit/pnunit-launcher.exe new file mode 100644 index 0000000..5251378 Binary files /dev/null and b/References/Tools/NUnit/pnunit-launcher.exe differ diff --git a/References/Tools/NUnit/pnunit-launcher.exe.config b/References/Tools/NUnit/pnunit-launcher.exe.config new file mode 100644 index 0000000..b037b91 --- /dev/null +++ b/References/Tools/NUnit/pnunit-launcher.exe.config @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/References/Tools/NUnit/pnunit.framework.dll b/References/Tools/NUnit/pnunit.framework.dll new file mode 100644 index 0000000..494dc72 Binary files /dev/null and b/References/Tools/NUnit/pnunit.framework.dll differ diff --git a/References/Tools/NUnit/pnunit.tests.dll b/References/Tools/NUnit/pnunit.tests.dll new file mode 100644 index 0000000..ac67a2b Binary files /dev/null and b/References/Tools/NUnit/pnunit.tests.dll differ diff --git a/References/Tools/NUnit/runFile.exe b/References/Tools/NUnit/runFile.exe new file mode 100644 index 0000000..a794458 Binary files /dev/null and b/References/Tools/NUnit/runFile.exe differ diff --git a/References/Tools/NUnit/runFile.exe.config b/References/Tools/NUnit/runFile.exe.config new file mode 100644 index 0000000..35909b4 --- /dev/null +++ b/References/Tools/NUnit/runFile.exe.config @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/References/Tools/NUnit/runpnunit.bat b/References/Tools/NUnit/runpnunit.bat new file mode 100644 index 0000000..6efc8b4 --- /dev/null +++ b/References/Tools/NUnit/runpnunit.bat @@ -0,0 +1,2 @@ +start pnunit-agent agent.conf +pnunit-launcher test.conf \ No newline at end of file diff --git a/References/Tools/NUnit/test.conf b/References/Tools/NUnit/test.conf new file mode 100644 index 0000000..a35e718 --- /dev/null +++ b/References/Tools/NUnit/test.conf @@ -0,0 +1,24 @@ + + + + + Testing + + + Testing + pnunit.tests.dll + TestLibraries.Testing.EqualTo19 + localhost:8080 + + ..\server + + + + + + + + + + + \ No newline at end of file diff --git a/References/Tools/ResxSync/resxsync.exe b/References/Tools/ResxSync/resxsync.exe new file mode 100644 index 0000000..7f477f0 Binary files /dev/null and b/References/Tools/ResxSync/resxsync.exe differ diff --git a/References/Tools/Rhino.Mocks/Rhino.Mocks.dll b/References/Tools/Rhino.Mocks/Rhino.Mocks.dll new file mode 100644 index 0000000..4b6904b Binary files /dev/null and b/References/Tools/Rhino.Mocks/Rhino.Mocks.dll differ diff --git a/References/Tools/Rhino.Mocks/Rhino.Mocks.xml b/References/Tools/Rhino.Mocks/Rhino.Mocks.xml new file mode 100644 index 0000000..7518b3e --- /dev/null +++ b/References/Tools/Rhino.Mocks/Rhino.Mocks.xml @@ -0,0 +1,5226 @@ + + + + Rhino.Mocks.Partial + + + + + Interface for constraints + + + + + determains if the object pass the constraints + + + + + And operator for constraints + + + + + Not operator for constraints + + + + + Or operator for constraints + + + + + Allow overriding of || or && + + + + + + + Allow overriding of || or && + + + + + + + Gets the message for this constraint + + + + + + Initializes a new constraint object. + + The expected object, The actual object is passed in as a parameter to the method + + + + Evaluate this constraint. + + The actual object that was passed in the method call to the mock. + True when the constraint is met, else false. + + + + Checks if the properties of the object + are the same as the properies of the object. + + The expected object + The actual object + True when both objects have the same values, else False. + + + + + + + + + This is the real heart of the beast. + + + + Used by CheckReferenceType to check all properties of the reference type. + + The expected object + The actual object + True when both objects have the same values, else False. + + + + Used by CheckReferenceType to check all fields of the reference type. + + The expected object + The actual object + True when both objects have the same values, else False. + + + + Checks the items of both collections + + The expected collection + + True if both collections contain the same items in the same order. + + + + Builds a propertyname from the Stack _properties like 'Order.Product.Price' + to be used in the error message. + + A nested property name. + + + + Rhino.Mocks uses this property to generate an error message. + + + A message telling the tester why the constraint failed. + + + + + Constrain that the public field has a specified value + + + + + Constrain that the public field matches another constraint. + + + + + Creates a new instance. + + Name of the public field. + Constraint to place on the public field value. + + + + Creates a new instance, specifying a disambiguating + for the public field. + + The type that declares the public field, used to disambiguate between public fields. + Name of the public field. + Constraint to place on the public field value. + + + + Determines if the object passes the constraint. + + + + + Gets the message for this constraint + + + + + + Creates a new instance. + + Name of the public field. + Expected value. + + + + Creates a new instance, specifying a disambiguating + for the public field. + + The type that declares the public field, used to disambiguate between public fields. + Name of the public field. + Expected value. + + + + Constrain that the property has a specified value + + + + + Constrain that the property matches another constraint. + + + + + Creates a new instance. + + Name of the property. + Constraint to place on the property value. + + + + Creates a new instance, specifying a disambiguating + for the property. + + The type that declares the property, used to disambiguate between properties. + Name of the property. + Constraint to place on the property value. + + + + Determines if the object passes the constraint. + + + + + Gets the message for this constraint + + + + + + Creates a new instance. + + Name of the property. + Expected value. + + + + Creates a new instance, specifying a disambiguating + for the property. + + The type that declares the property, used to disambiguate between properties. + Name of the property. + Expected value. + + + + Constrain that the parameter must be of the specified type + + + + + Creates a new instance. + + Type. + + + + determains if the object pass the constraints + + + + + Gets the message for this constraint + + + + + + Constraint that determines whether an object is the same object as another. + + + + + Creates a new instance. + + Obj. + + + + Determines if the object passes the constraints. + + + + + Gets the message for this constraint. + + + + + Evaluate a parameter using constraints + + + + + Create new instance + + + + + + determains if the object pass the constraints + + + + + Gets the message for this constraint + + + + + + A constraint based on lambda expression, we are using Expression{T} + because we want to be able to get good error reporting on that. + + + + + Initializes a new instance of the class. + + The expr. + + + + determains if the object pass the constraints + + + + + + + Gets the message for this constraint + + + + + + Constrain that the list contains the same items as the parameter list + + + + + Creates a new instance. + + In list. + + + + determains if the object pass the constraints + + + + + Gets the message for this constraint + + + + + + Constrain that the parameter is one of the items in the list + + + + + Creates a new instance. + + In list. + + + + determains if the object pass the constraints + + + + + Gets the message for this constraint + + + + + + Constrain that the object is inside the parameter list + + + + + Creates a new instance. + + In list. + + + + determains if the object pass the constraints + + + + + Gets the message for this constraint + + + + + + Applies another AbstractConstraint to the collection count. + + + + + Creates a new instance. + + The constraint that should be applied to the collection count. + + + + Determines if the parameter conforms to this constraint. + + + + + Gets the message for this constraint. + + + + + Applies another AbstractConstraint to a specific list element. + + + + + Creates a new instance. + + The zero-based index of the list element. + The constraint that should be applied to the list element. + + + + Determines if the parameter conforms to this constraint. + + + + + Gets the message for this constraint + + + + + + Applies another AbstractConstraint to a specific generic keyed list element. + + + + + Creates a new instance. + + The key of the list element. + The constraint that should be applied to the list element. + + + + Determines if the parameter conforms to this constraint. + + + + + Gets the message for this constraint + + + + + + Constrains that all elements are in the parameter list + + + + + Initializes a new instance of the class. + + The these. + + + + determains if the object pass the constraints + + + + + + + Gets the message for this constraint + + + + + + Combines two constraints, constraint pass if either is fine. + + + + + Creates a new instance. + + C1. + C2. + + + + determains if the object pass the constraints + + + + + Gets the message for this constraint + + + + + + Negate a constraint + + + + + Creates a new instance. + + C1. + + + + determains if the object pass the constraints + + + + + Gets the message for this constraint + + + + + + Combines two constraints + + + + + + Creates a new instance. + + C1. + C2. + + + + determains if the object pass the constraints + + + + + Gets the message for this constraint + + + + + + Constrain the argument to validate according to regex pattern + + + + + Creates a new instance. + + Pattern. + + + + determains if the object pass the constraints + + + + + Gets the message for this constraint + + + + + + Constraint that evaluate whatever an argument contains the specified string. + + + + + Creates a new instance. + + Inner string. + + + + determains if the object pass the constraints + + + + + Gets the message for this constraint + + + + + + Constraint that evaluate whatever an argument ends with the specified string + + + + + Creates a new instance. + + End. + + + + determains if the object pass the constraints + + + + + Gets the message for this constraint + + + + + + Constraint that evaluate whatever an argument start with the specified string + + + + + Creates a new instance. + + Start. + + + + determains if the object pass the constraints + + + + + Gets the message for this constraint + + + + + + Constraint that evaluate whatever an object equals another + + + + + Creates a new instance. + + Obj. + + + + determains if the object pass the constraints + + + + + Gets the message for this constraint + + + + + + Constraint that always returns true + + + + + determains if the object pass the constraints + + + + + Gets the message for this constraint + + + + + + Constraint that evaluate whatever a comparable is greater than another + + + + + Creates a new instance. + + + + + determains if the object pass the constraints + + + + + Gets the message for this constraint + + + + + + Central location for constraints + + + + + Evaluate a greater than constraint for . + + The object the parameter should be greater than + + + + Evaluate a less than constraint for . + + The object the parameter should be less than + + + + Evaluate a less than or equal constraint for . + + The object the parameter should be less than or equal to + + + + Evaluate a greater than or equal constraint for . + + The object the parameter should be greater than or equal to + + + + Evaluate an equal constraint for . + + The object the parameter should equal to + + + + Evaluate a not equal constraint for . + + The object the parameter should not equal to + + + + Evaluate a same as constraint. + + The object the parameter should the same as. + + + + Evaluate a not same as constraint. + + The object the parameter should not be the same as. + + + + A constraints that accept anything + + + + + + A constraint that accept only nulls + + + + + + A constraint that accept only non null values + + + + + + A constraint that accept only value of the specified type + + + + + A constraint that accept only value of the specified type + + + + + Evaluate a parameter using a predicate + + The predicate to use + + + + Provides access to the constraintes defined in the class to be used in context + with the syntax. + + The type of the argument + + + + Evaluate a greater than constraint for . + + The object the parameter should be greater than + + + + Evaluate a less than constraint for . + + The object the parameter should be less than + + + + Evaluate a less than or equal constraint for . + + The object the parameter should be less than or equal to + + + + Evaluate a greater than or equal constraint for . + + The object the parameter should be greater than or equal to + + + + Evaluate an equal constraint for . + + The object the parameter should equal to + + + + Evaluate a not equal constraint for . + + The object the parameter should not equal to + + + + Evaluate a same as constraint. + + The object the parameter should the same as. + + + + Evaluate a not same as constraint. + + The object the parameter should not be the same as. + + + + Throws NotSupportedException. Don't use Equals to define constraints. Use Equal instead. + + + + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + + + + A constraints that accept anything + + + + + + A constraint that accept only nulls + + + + + + A constraint that accept only non null values + + + + + + A constraint that accept only value of the specified type. + The check is performed on the type that has been defined + as the argument type. + + + + + Central location for constraints about lists and collections + + + + + Determines whether the specified obj is in the paramter. + The parameter must be IEnumerable. + + Obj. + + + + + Determains whatever the parameter is in the collection. + + + + + Determains that the parameter collection is identical to the specified collection + + + + + Determines that the parameter collection has the specified number of elements. + + The constraint that should be applied to the collection count. + + + + Determines that an element of the parameter collections conforms to another AbstractConstraint. + + The zero-based index of the list element. + The constraint which should be applied to the list element. + + + + Determines that an element of the parameter collections conforms to another AbstractConstraint. + + The key of the element. + The constraint which should be applied to the element. + + + + Determines that all elements of the specified collection are in the the parameter collection + + The collection to compare against + The constraint which should be applied to the list parameter. + + + + Provides access to the constraintes defined in the class to be used in context + with the syntax. + + + + + Determines whether the specified object is in the paramter. + The parameter must be IEnumerable. + + Obj. + + + + + Determains whatever the parameter is in the collection. + + + + + Determains that the parameter collection is identical to the specified collection + + + + + Determines that the parameter collection has the specified number of elements. + + The constraint that should be applied to the collection count. + + + + Determines that an element of the parameter collections conforms to another AbstractConstraint. + + The zero-based index of the list element. + The constraint which should be applied to the list element. + + + + Determines that all elements of the specified collection are in the the parameter collection + + The collection to compare against + The constraint which should be applied to the list parameter. + + + + Throws NotSupportedException. Don't use Equals to define constraints. Use Equal instead. + + + + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + + + + Provides a dummy field to pass as out or ref argument. + + + + + + Dummy field to satisfy the compiler. Used for out and ref arguments. + + + + + Central location for constraints for object's properties + + + + + Constrains the parameter to have property with the specified value + + Name of the property. + Expected value. + + + + + Constrains the parameter to have property with the specified value. + + The type that declares the property, used to disambiguate between properties. + Name of the property. + Expected value. + + + + + Constrains the parameter to have a property satisfying a specified constraint. + + Name of the property. + Constraint for the property. + + + + Constrains the parameter to have a property satisfying a specified constraint. + + The type that declares the property, used to disambiguate between properties. + Name of the property. + Constraint for the property. + + + + Determines whether the parameter has the specified property and that it is null. + + Name of the property. + + + + + Determines whether the parameter has the specified property and that it is null. + + The type that declares the property, used to disambiguate between properties. + Name of the property. + + + + + Determines whether the parameter has the specified property and that it is not null. + + Name of the property. + + + + + Determines whether the parameter has the specified property and that it is not null. + + The type that declares the property, used to disambiguate between properties. + Name of the property. + + + + + constraints the parameter to have the exact same property values as the expected object. + + An object, of the same type as the parameter, whose properties are set with the expected values. + An instance of the constraint that will do the actual check. + + The parameter's public property values and public field values will be matched against the expected object's + public property values and public field values. The first mismatch will be reported and no further matching is done. + The matching is recursive for any property or field that has properties or fields of it's own. + Collections are supported through IEnumerable, which means the constraint will check if the actual and expected + collection contain the same values in the same order, where the values contained by the collection can have properties + and fields of their own that will be checked as well because of the recursive nature of this constraint. + + + + + Central location for constraints for object's public fields + + + + + Constrains the parameter to have a public field with the specified value + + Name of the public field. + Expected value. + + + + + Constrains the parameter to have a public field with the specified value. + + The type that declares the public field, used to disambiguate between public fields. + Name of the public field. + Expected value. + + + + + Constrains the parameter to have a public field satisfying a specified constraint. + + Name of the public field. + Constraint for the public field. + + + + Constrains the parameter to have a public field satisfying a specified constraint. + + The type that declares the public field, used to disambiguate between public fields. + Name of the public field. + Constraint for the public field. + + + + Determines whether the parameter has the specified public field and that it is null. + + Name of the public field. + + + + + Determines whether the parameter has the specified public field and that it is null. + + The type that declares the public field, used to disambiguate between public fields. + Name of the public field. + + + + + Determines whether the parameter has the specified public field and that it is not null. + + Name of the public field. + + + + + Determines whether the parameter has the specified public field and that it is not null. + + The type that declares the public field, used to disambiguate between public fields. + Name of the public field. + + + + + Central location for all text related constraints + + + + + Constrain the argument to starts with the specified string + + + + + Constrain the argument to end with the specified string + + + + + Constrain the argument to contain the specified string + + + + + Constrain the argument to validate according to regex pattern + + + + + Provides access to the constraintes defined in the class to be used in context + with the syntax. + + + + + Constrain the argument to starts with the specified string + + + + + + Constrain the argument to end with the specified string + + + + + Constrain the argument to contain the specified string + + + + + Constrain the argument to validate according to regex pattern + + + + + Throws NotSupportedException. Don't use Equals to define constraints. Use Equal instead. + + + + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + + + + An expectaton violation was detected. + + + + + Creates a new instance. + + Message. + + + + Serialization constructor + + + + + Signals that an object was call on a mock repostiroy which doesn't + belong to this mock repository or not a mock + + + + + Creates a new instance. + + Message. + + + + Serialization constructor + + + + + Abstract class that holds common information for + expectations. + + + + + Interface to validate that a method call is correct. + + + + + Validate the arguments for the method. + This method can be called numerous times, so be careful about side effects + + The arguments with which the method was called + + + + Add an actual method call to this expectation + + + + + Returns the return value or throw the exception and setup any output / ref parameters + that has been set. + + + + + Allow to set the return value in the future, if it was already set. + + + + + Builds the verification failure message. + + + + + + Gets the error message. + + + + + + Range of expected calls + + + + + Number of call actually made for this method + + + + + If this expectation is still waiting for calls. + + + + + The return value for a method matching this expectation + + + + + Gets or sets the exception to throw on a method matching this expectation. + + + + + Gets a value indicating whether this instance's action is staisfied. + A staisfied instance means that there are no more requirements from + this method. A method with non void return value must register either + a return value or an exception to throw. + + + + + Gets the method this expectation is for. + + + + + Gets or sets what special condtions there are for this method + repeating. + + + + + Gets a value indicating whether this expectation was satisfied + + + + + Specify whatever this expectation has a return value set + You can't check ReturnValue for this because a valid return value include null. + + + + + An action to execute when the method is matched. + + + + + Set the out / ref parameters for the method call. + The indexing is zero based and ignores any non out/ref parameter. + It is possible not to pass all the parameters. This method can be called only once. + + + + + Documentation Message + + + + + Gets the invocation for this expectation + + The invocation. + + + + Occurs when the exceptation is match on a method call + + + + + Number of actuall calls made that passed this expectation + + + + + Range of expected calls that should pass this expectation. + + + + + The return value for a method matching this expectation + + + + + The exception to throw on a method matching this expectation. + + + + + The method this expectation is for. + + + + + The return value for this method was set + + + + + Whether this method will repeat + unlimited number of times. + + + + + A delegate that will be run when the + expectation is matched. + + + + + The arguments that matched this expectation. + + + + + Documentation message + + + + + The method originalInvocation + + + + + Get the hash code + + + + + Add an actual actualMethodCall call to this expectation + + + + + Builds the verification failure message. + + + + + + Allow to set the return value in the future, if it was already set. + + + + + Returns the return value or throw the exception and setup output / ref parameters + + + + + Validate the arguments for the method on the child methods + + The arguments with which the method was called + + + + Creates a new instance. + + The originalInvocation for this method, required because it contains the generic type infromation + + + + Creates a new instance. + + Expectation. + + + + Validate the arguments for the method on the child methods + + The arguments with which the method was called + + + + Determines if this object equal to obj + + + + + The error message for these arguments + + + + + Asserts that the delegate has the same parameters as the expectation's method call + + + + + Setter for the outpur / ref parameters for this expecataion. + Can only be set once. + + + + + Specify whatever this expectation has a return value set + You can't check ReturnValue for this because a valid return value include null. + + + + + Gets the method this expectation is for. + + + + + Gets the originalInvocation for this expectation + + The originalInvocation. + + + + Gets or sets what special condtions there are for this method + + + + + Range of expected calls + + + + + Number of call actually made for this method + + + + + If this expectation is still waiting for calls. + + + + + Gets a value indicating whether this expectation was satisfied + + + + + The return value for a method matching this expectation + + + + + An action to execute when the method is matched. + + + + + Gets or sets the exception to throw on a method matching this expectation. + + + + + Gets a value indicating whether this instance's action is staisfied. + A staisfied instance means that there are no more requirements from + this method. A method with non void return value must register either + a return value or an exception to throw or an action to execute. + + + + + Documentation message + + + + + Occurs when the exceptation is match on a method call + + + + + Gets the error message. + + + + + + Expectation that matchs any arguments for the method. + + + + + Creates a new instance. + + Invocation for this expectation + + + + Creates a new instance. + + Expectation. + + + + Validate the arguments for the method. + + The arguments with which the method was called + + + + Determines if the object equal to expectation + + + + + Get the hash code + + + + + Gets the error message. + + + + + + Summary description for ArgsEqualExpectation. + + + + + Creates a new instance. + + Expected args. + The invocation for this expectation + + + + Validate the arguments for the method. + + The arguments with which the method was called + + + + Determines if the object equal to expectation + + + + + Get the hash code + + + + + Gets the error message. + + + + + + Get the expected args. + + + + + Call a specified callback to verify the expectation + + + + + Creates a new instance. + + Expectation. + Callback. + + + + Creates a new instance. + + Invocation for this expectation + Callback. + + + + Validate the arguments for the method on the child methods + + The arguments with which the method was called + + + + Determines if the object equal to expectation + + + + + Get the hash code + + + + + Gets the error message. + + + + + + Expect the method's arguments to match the contraints + + + + + Creates a new instance. + + Invocation for this expectation + Constraints. + + + + Creates a new instance. + + Expectation. + Constraints. + + + + Validate the arguments for the method. + + The arguments with which the method was called + + + + Determines if the object equal to expectation + + + + + Get the hash code + + + + + Gets the error message. + + + + + + ExpectationsList + + + + + Dictionary + + + + + Dictionary class + + + + + Create a new instance of ProxyStateDictionary + + + + + Operation on a remoting proxy + + + It is not possible to directly communicate to a real proxy via transparent proxy. + Transparent proxy impersonates a user type and only methods of that user type are callable. + The only methods that are guaranteed to exist on any transparent proxy are methods defined + in Object: namely ToString(), GetHashCode(), and Equals()). + + These three methods are the only way to tell the real proxy to do something. + Equals() is the most suitable of all, since it accepts an arbitrary object parameter. + The RemotingProxy code is built so that if it is compared to an IRemotingProxyOperation, + transparentProxy.Equals(operation) will call operation.Process(realProxy). + This way we can retrieve a real proxy from transparent proxy and perform + arbitrary operation on it. + + + + + Implementation of IInvocation based on remoting proxy + + Some methods are marked NotSupported since they either don't make sense + for remoting proxies, or they are never called by Rhino Mocks + + + + Generates remoting proxies and provides utility functions + + + + + Create the proxy using remoting + + + + + Check whether an object is a transparent proxy with a RemotingProxy behind it + + Object to check + true if the object is a transparent proxy with a RemotingProxy instance behind it, false otherwise + We use Equals() method to communicate with the real proxy behind the object. + See IRemotingProxyOperation for more details + + + + Retrieve a mocked object from a transparent proxy + + Transparent proxy with a RemotingProxy instance behind it + Mocked object associated with the proxy + We use Equals() method to communicate with the real proxy behind the object. + See IRemotingProxyOperation for more details + + + + Allows to call a method and immediatly get it's options. + + + + + Interface to allows to call a method and immediatly get it's options. + + + + + Get the method options for the call + + The method call should go here, the return value is ignored + + + + Creates a new instance. + + + + + Get the method options for the call + + The method call should go here, the return value is ignored + + + + Allows to call a method and immediatly get it's options. + Set the expected number for the call to Any() + + + + + Creates a new instance. + + Proxy. + Mocked instance. + + + + Get the method options for the call + + The method call should go here, the return value is ignored + + + + This class is reponsible for taking a delegate and creating a wrapper + interface around it, so it can be mocked. + + + + + The scope for all the delegate interfaces create by this mock repositroy. + + + + + Gets a type with an "Invoke" method suitable for use as a target of the + specified delegate type. + + + + + + + Raise events for all subscribers for an event + + + + + Raise events for all subscribers for an event + + + + + Raise the event + + + + + The most common form for the event handler signature + + + + + Create an event raise for the specified event on this instance. + + + + + Creates a new instance of EventRaiser + + + + + Raise the event + + + + + The most common signature for events + Here to allow intellisense to make better guesses about how + it should suggest parameters. + + + + + Allows to define what would happen when a method + is called. + + + + + Allows to define what would happen when a method + is called. + + + + + Set the return value for the method. + + The object the method will return + IRepeat that defines how many times the method will return this value + + + + Allow to override this return value in the future + + IRepeat that defines how many times the method will return this value + + + + Throws the specified exception when the method is called. + + Exception to throw + + + + Ignores the arguments for this method. Any argument will be matched + againt this method. + + + + + Add constraints for the method's arguments. + + + + + Set a callback method for the last call + + + + + Set a delegate to be called when the expectation is matched. + The delegate return value will be returned from the expectation. + + + + + Set a delegate to be called when the expectation is matched. + The delegate return value will be returned from the expectation. + + + + + Set a delegate to be called when the expectation is matched. + The delegate return value will be returned from the expectation. + + + + + Set a delegate to be called when the expectation is matched. + The delegate return value will be returned from the expectation. + + + + + Set a delegate to be called when the expectation is matched. + The delegate return value will be returned from the expectation. + + + + + Set a delegate to be called when the expectation is matched. + The delegate return value will be returned from the expectation. + + + + + Set a delegate to be called when the expectation is matched. + The delegate return value will be returned from the expectation. + + + + + Set a delegate to be called when the expectation is matched. + The delegate return value will be returned from the expectation. + + + + + Set a delegate to be called when the expectation is matched. + The delegate return value will be returned from the expectation. + + + + + Set a delegate to be called when the expectation is matched. + The delegate return value will be returned from the expectation. + + + + + Set a delegate to be called when the expectation is matched. + The delegate return value will be returned from the expectation. + + + + + Set a delegate to be called when the expectation is matched. + The delegate return value will be returned from the expectation. + + + + + Set a delegate to be called when the expectation is matched + and allow to optionally modify the invocation as needed + + + + + Call the original method on the class, bypassing the mocking layers. + + + + + + Call the original method on the class, optionally bypassing the mocking layers. + + + + + + Use the property as a simple property, getting/setting the values without + causing mock expectations. + + + + + Expect last (property) call as property setting, ignore the argument given + + + + + + Expect last (property) call as property setting with a given argument. + + + + + + + Get an event raiser for the last subscribed event. + + + + + Set the parameter values for out and ref parameters. + This is done using zero based indexing, and _ignoring_ any non out/ref parameter. + + + + + Documentation message for the expectation + + Message + + + + Better syntax to define repeats. + + + + + Allows to specify the number of time for method calls + + + + + Repeat the method twice. + + + + + Repeat the method once. + + + + + Repeat the method at least once, then repeat as many time as it would like. + + + + + Repeat the method any number of times. + This has special affects in that this method would now ignore orderring. + + + + + Set the range to repeat an action. + + Min. + Max. + + + + Set the amount of times to repeat an action. + + + + + This method must not appear in the replay state. + This has special affects in that this method would now ignore orderring. + + + + + Creates a new instance. + + the repository for this expectation + the recorder for this proxy + the proxy for this expectation + Expectation. + + + + Add constraints for the method's arguments. + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a delegate to be called when the expectation is matched. + The delegate return value will be returned from the expectation. + + + + + Set a delegate to be called when the expectation is matched. + The delegate return value will be returned from the expectation. + + + + + Set the return value for the method. + + The object the method will return + IRepeat that defines how many times the method will return this value + + + + Set the return value for the method, but allow to override this return value in the future + + IRepeat that defines how many times the method will return this value + + + + Throws the specified exception when the method is called. + + Exception to throw + + + + Ignores the arguments for this method. Any argument will be matched + againt this method. + + + + + Call the original method on the class, bypassing the mocking layers. + + + + + + Call the original method on the class, optionally bypassing the mocking layers + + + + + + Use the property as a simple property, getting/setting the values without + causing mock expectations. + + + + + Expect last (property) call as property setting, ignore the argument given + + + + + + Expect last (property) call as property setting with a given argument. + + + + + + + Gets the event raiser for the last event + + + + + Set the parameter values for out and ref parameters. + This is done using zero based indexing, and _ignoring_ any non out/ref parameter. + + + + + Repeat the method twice. + + + + + Repeat the method once. + + + + + Repeat the method at least once, then repeat as many time as it would like. + + + + + This method must not appear in the replay state. + + + + + Documentation message for the expectation + + Message + + + + Repeat the method any number of times. + + + + + Set the range to repeat an action. + + Min. + Max. + + + + Set the amount of times to repeat an action. + + + + + Better syntax to define repeats. + + + + + This class will provide hash code for hashtables without needing + to call the GetHashCode() on the object, which may very well be mocked. + This class has no state so it is a singelton to avoid creating a lot of objects + that does the exact same thing. See flyweight patterns. + + + + + Get the hash code for a proxy object without calling GetHashCode() + on the object. + + + + + Compares two instances of mocked objects + + + + + Compare two mocked objects + + + + + The next hash code value for a mock object. + This is safe for multi threading. + + + + + The sole instance of + + + + + Doesn't log anything, just makes happy noises + + + + + Log expectations - allows to see what is going on inside Rhino Mocks + + + + + Logs the expectation as is was recorded + + The invocation. + The expectation. + + + + Logs the expectation as it was recorded + + The invocation. + The expectation. + + + + Logs the unexpected method call. + + The invocation. + The message. + + + + Logs the expectation as is was recorded + + The invocation. + The expectation. + + + + Logs the expectation as it was recorded + + The invocation. + The expectation. + + + + Logs the unexpected method call. + + The invocation. + The message. + + + + This is a dummy type that is used merely to give DynamicProxy the proxy instance that + it needs to create IProxy's types. + + + + + Interface to find the repository of a mocked object + + + + + Return true if it should call the original method on the object + instead of pass it to the message chain. + + The method to call + + + + Register a method to be called on the object directly + + + + + Register a property on the object that will behave as a simple property + + + + + Check if the method was registered as a property method. + + + + + Do get/set on the property, according to need. + + + + + Do add/remove on the event + + + + + Get the subscribers of a spesific event + + + + + Gets the declaring type of the method, taking into acccount the possible generic + parameters that it was created with. + + + + + Clears the state of the object, remove original calls, property behavior, subscribed events, etc. + + + + + Get all the method calls arguments that were made against this object with the specificed + method. + + + Only method calls in replay mode are counted + + + + + Records the method call + + + + + The unique hash code of this mock, which is not related + to the value of the GetHashCode() call on the object. + + + + + Gets the repository. + + + + + Gets the implemented types by this mocked object + + The implemented. + + + + Gets or sets the constructor arguments. + + The constructor arguments. + + + + Create a new instance of + + + + + Return true if it should call the original method on the object + instead of pass it to the message chain. + + The method to call + + + + Register a method to be called on the object directly + + + + + Register a property on the object that will behave as a simple property + Return true if there is already a value for the property + + + + + Check if the method was registered as a property method. + + + + + Do get/set on the property, according to need. + + + + + Do add/remove on the event + + + + + Get the subscribers of a spesific event + + + + + Gets the declaring type of the method, taking into acccount the possible generic + parameters that it was created with. + + + + + Get all the method calls arguments that were made against this object with the specificed + method. + + + + + Only method calls in replay mode are counted + + + + + Records the method call + + + + + + + Clears the state of the object, remove original calls, property behavior, subscribed events, etc. + + + + + The unique hash code of this proxy, which is not related + to the value of the GetHashCode() call on the object. + + + + + Gets the repository. + + + + + Gets or sets the constructor arguments. + + The constructor arguments. + + + + Gets the implemented types by this mocked object + + The implemented. + + + + Range for expected method calls + + + + + Creates a new instance. + + Min. + Max. + + + + Return the string representation of this range. + + + + + Gets or sets the min. + + + + + + Gets or sets the max. + + + + + + Records all the expectations for a mock and + return a ReplayDynamicMockState when Replay() + is called. + + + + + Records all the expectations for a mock + + + + + Different actions on this mock + + + + + Add a method call for this state' mock. + + The invocation for this method + The method that was called + The arguments this method was called with + + + + Verify that this mock expectations have passed. + + + + + Verify that we can move to replay state and move + to the reply state. + + + + + Gets a mock state that match the original mock state of the object. + + + + + Get the options for the last method call + + + + + Set the exception to throw when Verify is called. + This is used to report exception that may have happened but where caught in the code. + This way, they are reported anyway when Verify() is called. + + + + + This method is called to indicate that a property behavior call. + This is done so we generate good error message in the common case of people using + Stubbed properties with Return(). + + + + + Gets the matching verify state for this state + + + + + Get the options for the last method call + + + + + Get the options for the last method call + + + + + Set the exception to throw when Verify is called. + This is used to report exception that may have happened but where caught in the code. + This way, they are reported anyway when Verify() is called. + + + + + This method is called to indicate that a property behavior call. + This is done so we generate good error message in the common case of people using + Stubbed properties with Return(). + + + + + Creates a new instance. + + Repository. + The proxy that generates the method calls + + + + Add a method call for this state' mock. + + The invocation for this method + The method that was called + The arguments this method was called with + + + + Verify that we can move to replay state and move + to the reply state. + + + + + Verify that we can move to replay state and move + to the reply state. + + + + + Verify that this mock expectations have passed. + + + + + Gets a mock state that match the original mock state of the object. + + + + + Asserts the previous method is closed (had an expectation set on it so we can replay it correctly) + + + + + Gets the last expectation. + + + + + Gets the total method calls count. + + + + + Get the options for the last method call + + + + + Gets the matching verify state for this state + + + + + Creates a new instance. + + Repository. + The proxy that generates the method calls + + + + Verify that we can move to replay state and move + to the reply state. + + + + + Gets a mock state that match the original mock state of the object. + + + + + Records all the expectations for a mock and + return a ReplayPartialMockState when Replay() + is called. + + + + + Creates a new instance. + + Repository. + The proxy that generates the method calls + + + + Verify that we can move to replay state and move + to the reply state. + + + + + Gets a mock state that match the original mock state of the object. + + + + + Options for special repeat option + + + + + This method can be called only as many times as the IMethodOptions.Expect allows. + + + + + This method should never be called + + + + + This method can be call any number of times + + + + + This method will call the original method + + + + + This method will call the original method, bypassing the mocking layer + + + + + This method will simulate simple property behavior + + + + + Validate all expectations on a mock and ignores calls to + any method that was not setup properly. + + + + + Validate all expectations on a mock + + + + + The repository for this state + + + + + The proxy object for this state + + + + + Get the options for the last method call + + + + + Creates a new instance. + + The previous state for this method + + + + Add a method call for this state' mock. + + The invocation for this method + The method that was called + The arguments this method was called with + + + + Add a method call for this state' mock. + This allows derived method to cleanly get a the setupresult behavior while adding + their own. + + The invocation for this method + The method that was called + The arguments this method was called with + + + + Set the exception to throw when Verify is called. + This is used to report exception that may have happened but where caught in the code. + This way, they are reported anyway when Verify() is called. + + + + + not relevant + + + + + Verify that this mock expectations have passed. + + + + + Verify that we can move to replay state and move + to the reply state. + + + + + Gets a mock state that match the original mock state of the object. + + + + + Get the options for the last method call + + + + + Gets the matching verify state for this state + + + + + Creates a new instance. + + The previous state for this method + + + + Add a method call for this state' mock. + + The invocation for this method + The method that was called + The arguments this method was called with + + + + Gets a mock state that match the original mock state of the object. + + + + + Validate all expectations on a mock and ignores calls to + any method that was not setup properly. + + + + + Creates a new instance. + + The previous state for this method + + + + Add a method call for this state' mock. + + The invocation for this method + The method that was called + The arguments this method was called with + + + + Gets a mock state that match the original mock state of the object. + + + + + Summary description for RhinoInterceptor. + + + + + Creates a new instance. + + + + + Intercept a method call and direct it to the repository. + + + + + Behave like a stub, all properties and events acts normally, methods calls + return default values by default (but can use expectations to set them up), etc. + + + + + Initializes a new instance of the class. + + The proxy that generates the method calls + Repository. + + + + We don't care much about expectations here, so we will remove the exepctation if + it is not closed. + + + + + Verify that we can move to replay state and move + to the reply state. + + + + + + Validate expectations on recorded methods, but in general completely ignoring them. + Similar to except that it would return a + when BackToRecord is called. + + + + + Initializes a new instance of the class. + + The previous state for this method + + + + Add a method call for this state' mock. + + The invocation for this method + The method that was called + The arguments this method was called with + + + + Gets a mock state that match the original mock state of the object. + + + + + Rudimetry implementation that simply logs methods calls as text. + + + + + Initializes a new instance of the class. + + The writer. + + + + Logs the expectation as is was recorded + + The invocation. + The expectation. + + + + Logs the expectation as it was recorded + + The invocation. + The expectation. + + + + Logs the unexpected method call. + + The invocation. + The message. + + + + Write rhino mocks log info to the trace + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class. + + if set to true [log recorded]. + if set to true [log replayed]. + if set to true [log unexpected]. + + + + Logs the expectation as is was recorded + + The invocation. + The expectation. + + + + Logs the expectation as it was recorded + + The invocation. + The expectation. + + + + Logs the unexpected method call. + + The invocation. + The message. + + + + Writes log information as stack traces about rhino mocks activity + + + + + Allows to redirect output to a different location. + + + + + Logs the expectation as is was recorded + + The invocation. + The expectation. + + + + Logs the expectation as it was recorded + + The invocation. + The expectation. + + + + Logs the unexpected method call. + + The invocation. + The message. + + + + Validate arguments for methods + + + + + Validate that the passed argument is not null. + + The object to validate + The name of the argument + + If the obj is null, an ArgumentNullException with the passed name + is thrown. + + + + + Validate that the arguments are equal. + + Expected args. + Actual Args. + + + + Validate that the two argument are equals, including validation for + when the arguments are collections, in which case it will validate their values. + + + + + This method is safe for use even if any of the objects is a mocked object + that override equals. + + + + + Throw an object already verified when accessed + + + + + Create a new instance of VerifiedMockState + + The previous mock state, used to get the initial record state + + + + Add a method call for this state' mock. + + The invocation for this method + The method that was called + The arguments this method was called with + + + + Verify that this mock expectations have passed. + + + + + Verify that we can move to replay state and move + to the reply state. + + + + + Gets a mock state that match the original mock state of the object. + + + + + Get the options for the last method call + + + + + Set the exception to throw when Verify is called. + This is used to report exception that may have happened but where caught in the code. + This way, they are reported anyway when Verify() is called. + + + + + not relevant + + + + + Gets the matching verify state for this state + + + + + Get the options for the last method call + + + + + Records the actions on all the mocks created by a repository. + + + + + Records the specified call with the specified args on the mocked object. + + + + + Get the expectation for this method on this object with this arguments + + + + + This check the methods that were setup using the SetupResult.For() + or LastCall.Repeat.Any() and that bypass the whole expectation model. + + + + + Gets the all expectations for a mocked object and method combination, + regardless of the expected arguments / callbacks / contraints. + + Mocked object. + Method. + List of all relevant expectation + + + + Gets the all expectations for proxy. + + Mocked object. + List of all relevant expectation + + + + Removes all the repeatable expectations for proxy. + + Mocked object. + + + + Replaces the old expectation with the new expectation for the specified proxy/method pair. + This replace ALL expectations that equal to old expectations. + + Proxy. + Method. + Old expectation. + New expectation. + + + + Adds the recorder and turn it into the active recorder. + + Recorder. + + + + Moves to previous recorder. + + + + + Gets the recorded expectation or null. + + + + + Gets the next expected calls string. + + + + + Moves to parent recorder. + + + + + Set the expectation so it can repeat any number of times. + + + + + Removes the expectation from the recorder + + + + + Clear the replayer to call (and all its chain of replayers) + This also removes it from the list of expectations, so it will never be considered again + + + + + Get the expectation for this method on this object with this arguments + + + + + Gets a value indicating whether this instance has expectations that weren't satisfied yet. + + + true if this instance has expectations; otherwise, false. + + + + + Marker interface used to indicate that this is a partial mock. + + + + + Options for CallOriginalMethod + + + + + No expectation is created, the method will be called directly + + + + + Normal expectation is created, but when the method is later called, it will also call the original method + + + + + Base class for method recorders, handle delegating to inner recorder if needed. + + + + + List of the expected actions on for this recorder + The legal values are: + * Expectations + * Method Recorders + + + + + The current recorder. + + + + + The current replayer; + + + + + The parent recorder of this one, may be null. + + + + + This contains a list of all the replayers that should be ignored + for a spesific method call. A replayer gets into this list by calling + ClearReplayerToCall() on its parent. This list is Clear()ed on each new invocation. + + + + + All the repeatable methods calls. + + + + + Counts the recursion depth of the current expectation search stack + + + + + Creates a new instance. + + + + + Creates a new instance. + + Parent recorder. + Repeatable methods + + + + Records the specified call with the specified args on the mocked object. + + + + + Get the expectation for this method on this object with this arguments + + + + + Gets the all expectations for a mocked object and method combination, + regardless of the expected arguments / callbacks / contraints. + + Mocked object. + Method. + List of all relevant expectation + + + + Gets the all expectations for proxy. + + Mocked object. + List of all relevant expectation + + + + Replaces the old expectation with the new expectation for the specified proxy/method pair. + This replace ALL expectations that equal to old expectations. + + Proxy. + Method. + Old expectation. + New expectation. + + + + Remove the all repeatable expectations for proxy. + + Mocked object. + + + + Set the expectation so it can repeat any number of times. + + + + + Removes the expectation from the recorder + + + + + Adds the recorder and turn it into the active recorder. + + Recorder. + + + + Moves to previous recorder. + + + + + Moves to parent recorder. + + + + + Gets the recorded expectation or null. + + + + + Clear the replayer to call (and all its chain of replayers). + This also removes it from the list of expectations, so it will never be considered again + + + + + Get the expectation for this method on this object with this arguments + + + + + Gets the next expected calls string. + + + + + Handles the real getting of the recorded expectation or null. + + + + + Handle the real execution of this method for the derived class + + + + + Handle the real execution of this method for the derived class + + + + + Handle the real execution of this method for the derived class + + + + + Handle the real execution of this method for the derived class + + + + + Handle the real execution of this method for the derived class + + + + + Handle the real execution of this method for the derived class + + + + + Should this replayer be considered valid for this call? + + + + + This check the methods that were setup using the SetupResult.For() + or LastCall.Repeat.Any() and that bypass the whole expectation model. + + + + + Gets a value indicating whether this instance has expectations that weren't satisfied yet. + + + true if this instance has expectations; otherwise, false. + + + + + Handle the real execution of this method for the derived class + + + + + Ordered collection of methods, methods must arrive in specified order + in order to pass. + + + + + Unordered collection of method records, any expectation that exist + will be matched. + + + + + The parent recorder we have redirected to. + Useful for certain edge cases in orderring. + See: FieldProblem_Entropy for the details. + + + + + Creates a new instance. + + Parent recorder. + Repeatable methods + + + + Creates a new instance. + + + + + Records the specified call with the specified args on the mocked object. + + Mocked object. + Method. + Expectation. + + + + Get the expectation for this method on this object with this arguments + + Invocation for this method + Mocked object. + Method. + Args. + True is the call was recorded, false otherwise + + + + Gets the all expectations for a mocked object and method combination, + regardless of the expected arguments / callbacks / contraints. + + Mocked object. + Method. + List of all relevant expectation + + + + Gets the all expectations for proxy. + + Mocked object. + List of all relevant expectation + + + + Replaces the old expectation with the new expectation for the specified proxy/method pair. + This replace ALL expectations that equal to old expectations. + + Proxy. + Method. + Old expectation. + New expectation. + + + + Handle the real execution of this method for the derived class + + + + + Handles the real getting of the recorded expectation or null. + + + + + Handle the real execution of this method for the derived class + + + + + Gets the next expected calls string. + + + + + Create an exception for an unexpected method call. + + + + + Gets a value indicating whether this instance has expectations that weren't satisfied yet. + + + true if this instance has expectations; otherwise, false. + + + + + Creates a new instance. + + Parent recorder. + Repetable methods + + + + Creates a new instance. + + + + + Handles the real getting of the recorded expectation or null. + + + + + Get the expectation for this method on this object with this arguments + + + + + Gets the next expected calls string. + + + + + Hold an expectation for a method call on an object + + + + + Creates a new instance. + + Proxy. + Method. + Expectation. + + + + Determains if the object equal to this instance + + Obj. + + + + + Gets the hash code. + + + + + + Gets the proxy. + + + + + + Gets the method. + + + + + + Gets the expectation. + + + + + + Holds a pair of mocked object and a method + and allows to compare them against each other. + This allows us to have a distinction between mockOne.MyMethod() and + mockTwo.MyMethod()... + + + + + Creates a new instance. + + Proxy. + Method. + + + + Determains whatever obj equals to this instance. + ProxyMethodPairs are equals when they point to the same /instance/ of + an object, and to the same method. + + Obj. + + + + + Gets the hash code. + + + + + + Gets the proxy. + + + + + + Gets the method. + + + + + + Change the recorder from ordered to unordered and vice versa + + + + + Creates a new instance. + + + + + Disposes this instance. + + + + + Utility class for dealing with messing generics scenarios. + + + + + There are issues with trying to get this to work correctly with open generic types, since this is an edge case, + I am letting the runtime handle it. + + + + + Gets the real type, including de-constructing and constructing the type of generic + methods parameters. + + The type. + The invocation. + + + + + Because we need to support complex types here (simple generics were handled above) we + need to be aware of the following scenarios: + List[T] and List[Foo[T]] + + + + + Utility class for working with method calls. + + + + + Return the string representation of a method call and its arguments. + + The method + The method arguments + Invocation of the method, used to get the generics arguments + Delegate to format the parameter + The string representation of this method call + + + + Return the string representation of a method call and its arguments. + + The invocation of the method, used to get the generic parameters + The method + The method arguments + The string representation of this method call + + + + Delegate to format the argument for the string representation of + the method call. + + + + + Utility to get the default value for a type + + + + + The default value for a type. + Null for reference types and void + 0 for value types. + First element for enums + Note that we need to get the value even for opened generic types, such as those from + generic methods. + + Type. + The invocation. + the default value + + + + Defines constraints and return values for arguments of a mock. + Only use Arg inside a method call on a mock that is recording. + Example: + ExpectCall( + mock.foo( + Arg<int>.Is.GreaterThan(2), + Arg<string>.Is.Anything + )); + Use Arg.Text for string specific constraints + Use Arg<ListClass>.List for list specific constraints + + + + + + Register the predicate as a constraint for the current call. + + The predicate. + default(T) + + Allow you to use code to create constraints + + demo.AssertWasCalled(x => x.Bar(Arg{string}.Matches(a => a.StartsWith("b") && a.Contains("ba")))); + + + + + + Define a complex constraint for this argument by passing several constraints + combined with operators. (Use Is in simple cases.) + Example: Arg<string>.Matches(Is.Equal("Hello") || Text.EndsWith("u")); + + Constraints using Is, Text and List + Dummy to satisfy the compiler + + + + Define a Ref argument. + + Constraints for this argument + value returned by the mock + + + + + Define a out parameter. Use it together with the keyword out and use the + Dummy field available by the return value. + Example: mock.foo( out Arg<string>.Out("hello").Dummy ); + + + + + + + Define a simple constraint for this argument. (Use Matches in simple cases.) + Example: + Arg<int>.Is.Anthing + Arg<string>.Is.Equal("hello") + + + + + Define Constraints on list arguments. + + + + + Use the Arg class (without generic) to define Text constraints + + + + + Evaluate an equal constraint for . + + The object the parameter should equal to + + + + Define constraints on text arguments. + + + + + Used to manage the static state of the Arg<T> class"/> + + + + + Resets the static state + + + + + Returns return values for the out and ref parameters + Note: the array returned has the size of the number of out and ref + argument definitions + + + + + + Returns the constraints for all arguments. + Out arguments have an Is.Anything constraint and are also in the list. + + + + + + What should BackToRecord clear + + + + + Retain all expectations and behaviors and return to mock + + + + + All expectations + + + + + Event subscribers for this instance + + + + + Methods that should be forwarded to the base class implementation + + + + + Properties that should behave like properties + + + + + Remove all the behavior of the object + + + + + This delegate is compatible with the System.Func{T,R} signature + We have to define our own to get compatability with 2.0 + + + + + This class defines a lot of method signatures, which we will use + to allow compatability on net-2.0 + + + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + + + dummy + + + + + Allows expectations to be set on methods that should never be called. + For methods with void return value, you need to use LastCall or + DoNotExpect.Call() with a delegate. + + + + + Sets LastCall.Repeat.Never() on /any/ proxy on /any/ repository on the current thread. + This method if not safe for multi threading scenarios. + + + + + Accepts a delegate that will execute inside the method which + LastCall.Repeat.Never() will be applied to. + It is expected to be used with anonymous delegates / lambda expressions and only one + method should be called. + + + IService mockSrv = mocks.CreateMock(typeof(IService)) as IService; + DoNotExpect.Call(delegate{ mockSrv.Stop(); }); + ... + + + + + Allows to set expectation on methods that has return values. + For methods with void return value, you need to use LastCall + + + + + The method options for the last call on /any/ proxy on /any/ repository on the current thread. + This method if not safe for multi threading scenarios, use . + + + + + Accepts a delegate that will execute inside the method, and then return the resulting + instance. + It is expected to be used with anonymous delegates / lambda expressions and only one + method should be called. + + + IService mockSrv = mocks.CreateMock(typeof(IService)) as IService; + Expect.Call(delegate{ mockSrv.Start(); }).Throw(new NetworkException()); + ... + + + + + Get the method options for the last method call on the mockInstance. + + + + + A delegate that can be used to get better syntax on Expect.Call(delegate { foo.DoSomething(); }); + + + + + Allows to set various options for the last method call on + a specified object. + If the method has a return value, it's recommended to use Expect + + + + + Allows to get an interface to work on the last call. + + The mocked object + Interface that allows to set options for the last method call on this object + + + + Set the return value for the method. + + The object the method will return + IRepeat that defines how many times the method will return this value + + + + Set the return value for the method. This overload is needed for LastCall.Return(null) + + The object the method will return + IRepeat that defines how many times the method will return this value + + + + Throws the specified exception when the method is called. + + Exception to throw + + + + Ignores the arguments for this method. Any argument will be matched + againt this method. + + + + + Add constraints for the method's arguments. + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Set a callback method for the last call + + + + + Call the original method on the class, bypassing the mocking layers, for the last call. + + + + + Call the original method on the class, optionally bypassing the mocking layers, for the last call. + + + + + Set a delegate to be called when the expectation is matched. + The delegate return value will be returned from the expectation. + + + + + Gets an interface that will raise the last event when called. + + + + + Set the parameter values for out and ref parameters. + This is done using zero based indexing, and _ignoring_ any non out/ref parameter. + + + + + Documentation message for the expectation + + Message + + + + Use the property as a simple property, getting/setting the values without + causing mock expectations. + + + + + Better syntax to define repeats. + + + + + This is a data structure that is used by + to pass + the current method to the relevant delegate + + + + + Initializes a new instance of the class. + + The invocation. + + + + Gets the args for this method invocation + + + + + Gets or sets the return value for this method invocation + + The return value. + + + + Accessor for the current mocker + + + + + The current mocker + + + + + Creates proxied instances of types. + + + Adds optional new usage: + using(mockRepository.Record()) { + Expect.Call(mock.Method()).Return(retVal); + } + using(mockRepository.Playback()) { + // Execute code + } + N.B. mockRepository.ReplayAll() and mockRepository.VerifyAll() + calls are taken care of by Record/Playback + + + + + This is a map of types to ProxyGenerators. + + + + + This is used to record the last repository that has a method called on it. + + + + + this is used to get to the last proxy on this repository. + + + + + For mock delegates, maps the proxy instance from intercepted invocations + back to the delegate that was originally returned to client code, if any. + + + + + All the proxies in the mock repositories + + + + + This is here because we can't put it in any of the recorders, since repeatable methods + have no orderring, and if we try to handle them using the usual manner, we would get into + wierd situations where repeatable method that was defined in an orderring block doesn't + exists until we enter this block. + + + + + Creates a new instance. + + + + + Move the repository to ordered mode + + + + + Move the repository to un-ordered mode + + + + + Creates a mock for the specified type. + + Type. + Arguments for the class' constructor, if mocking a concrete class + + + + Creates a strict mock for the specified type. + + Type. + Arguments for the class' constructor, if mocking a concrete class + + + + Creates a remoting mock for the specified type. + + Type. + Arguments for the class' constructor, if mocking a concrete class + + + + Creates a strict remoting mock for the specified type. + + Type. + Arguments for the class' constructor, if mocking a concrete class + + + + Creates a remoting mock for the specified type. + + + Arguments for the class' constructor, if mocking a concrete class + + + + + Creates a strict remoting mock for the specified type. + + + Arguments for the class' constructor, if mocking a concrete class + + + + + Creates a mock from several types, with strict semantics. + Only may be a class. + + + + + Creates a strict mock from several types, with strict semantics. + Only may be a class. + + + + + Creates a mock from several types, with strict semantics. + Only may be a class. + + The main type to mock. + Extra interface types to mock. + Arguments for the class' constructor, if mocking a concrete class. + + + + Creates a strict mock from several types, with strict semantics. + Only may be a class. + + The main type to mock. + Extra interface types to mock. + Arguments for the class' constructor, if mocking a concrete class. + + + + Creates a mock from several types, with dynamic semantics. + Only may be a class. + + The main type to mock. + Extra interface types to mock. + + + + Creates a mock from several types, with dynamic semantics. + Only may be a class. + + The main type to mock. + Extra interface types to mock. + Arguments for the class' constructor, if mocking a concrete class. + + + + Creates a dynamic mock for the specified type. + + Type. + Arguments for the class' constructor, if mocking a concrete class + + + + Creates a dynamic mock for the specified type. + + Type. + Arguments for the class' constructor, if mocking a concrete class + + + + Creates a dynamic mock for the specified type. + + + Arguments for the class' constructor, if mocking a concrete class + + + + + Creates a mock object that defaults to calling the class methods. + + Type. + Arguments for the class' constructor. + + + + Creates a mock object that defaults to calling the class methods. + + Type. + Extra interface types to mock. + + + + Creates a mock object that defaults to calling the class methods. + + Type. + Extra interface types to mock. + Arguments for the class' constructor. + + + + Creates a mock object using remoting proxies + + Type to mock - must be MarshalByRefObject + Mock object + Proxy mock can mock non-virtual methods, but not static methods + Creates the mock state for this proxy + + + + Cause the mock state to change to replay, any further call is compared to the + ones that were called in the record state. + + the object to move to replay state + + + + Cause the mock state to change to replay, any further call is compared to the + ones that were called in the record state. + + the object to move to replay state + + + + + Move the mocked object back to record state. + Will delete all current expectations! + + + + + Move the mocked object back to record state. + Optionally, can delete all current expectations, but allows more granularity about how + it would behave with regard to the object state. + + + + + Verify that all the expectations for this object were fulfilled. + + the object to verify the expectations for + + + + Get the method options for the last call on + mockedInstance. + + The mock object + Method options for the last call + + + + Maps an invocation proxy back to the mock object instance that was originally + returned to client code which might have been a delegate to this proxy. + + The mock object proxy from the intercepted invocation + The mock object + + + + This is provided to allow advance extention functionality, where Rhino Mocks standard + functionality is not enough. + + The type to mock + Delegate that create the first state of the mocked object (usualy the record state). + Additional types to be implemented, this can be only interfaces + optional arguments for the constructor + + + + + Method: GetMockedObject + Get an IProxy from a mocked object instance, or throws if the + object is not a mock object. + + + + + Method: GetMockedObjectOrNull + Get an IProxy from a mocked object instance, or null if the + object is not a mock object. + + + + + Pops the recorder. + + + + + Pushes the recorder. + + New recorder. + + + + All the mock objects in this repository will be moved + to record state. + + + + + All the mock objects in this repository will be moved + to record state. + + + + + Replay all the mocks from this repository + + + + + Verify all the mocks from this repository + + + + + Gets the proxy generator for a specific type. Having a single ProxyGenerator + with multiple types linearly degrades the performance so this implementation + keeps one ProxyGenerator per type. + + + + + Set the exception to be thrown when verified is called. + + + + + Creates a mock for the spesified type. + + Arguments for the class' constructor, if mocking a concrete class + + + + Creates a strict mock for the spesified type. + + Arguments for the class' constructor, if mocking a concrete class + + + + Creates a dynamic mock for the specified type. + + Arguments for the class' constructor, if mocking a concrete class + + + + Creates a mock object from several types. + + + + + Creates a strict mock object from several types. + + + + + Create a mock object from several types with dynamic semantics. + + + + + Create a mock object from several types with partial semantics. + + + + + Create a mock object from several types with strict semantics. + + Extra interface types to mock. + Arguments for the class' constructor, if mocking a concrete class + + + + Create a strict mock object from several types with strict semantics. + + Extra interface types to mock. + Arguments for the class' constructor, if mocking a concrete class + + + + Create a mock object from several types with dynamic semantics. + + Extra interface types to mock. + Arguments for the class' constructor, if mocking a concrete class + + + + Create a mock object from several types with partial semantics. + + Extra interface types to mock. + Arguments for the class' constructor, if mocking a concrete class + + + + Create a mock object with from a class that defaults to calling the class methods + + Arguments for the class' constructor, if mocking a concrete class + + + + Create a stub object, one that has properties and events ready for use, and + can have methods called on it. It requires an explicit step in order to create + an expectation for a stub. + + The arguments for constructor. + + + + Create a stub object, one that has properties and events ready for use, and + can have methods called on it. It requires an explicit step in order to create + an expectation for a stub. + + The type. + The arguments for constructor. + + + + + Generates a stub without mock repository + + The arguments for constructor. + + + + + Generates the stub without mock repository + + The type. + The arguments for constructor. + + + + Returns true if the passed mock is currently in replay mode. + + The mock to test. + True if the mock is in replay mode, false otherwise. + + + + Generate a mock object without needing the mock repository + + + + + Determines whether the specified proxy is a stub. + + The proxy. + + + + Register a call on a prperty behavior + + + + + + + + + + + + + + + + Gets the recorder. + + + + + + Gets the replayer for this repository. + + + + + + Gets the last proxy which had a method call. + + + + + Delegate: CreateMockState + This is used internally to cleanly handle the creation of different + RecordMockStates. + + + + + Used for [assembly: InternalsVisibleTo(RhinoMocks.StrongName)] + Used for [assembly: InternalsVisibleTo(RhinoMocks.NormalName)] + + + + + Strong name for the Dynamic Proxy assemblies. Used for InternalsVisibleTo specification. + + + + + Normal name for dynamic proxy assemblies. Used for InternalsVisibleTo specification. + + + + + Logs all method calls for methods + + + + + A set of extension methods that adds Arrange Act Assert mode to Rhino Mocks + + + + + Create an expectation on this mock for this action to occur + + + The mock. + The action. + + + + + Reset all expectations on this mock object + + + The mock. + + + + Reset the selected expectation on this mock object + + + The mock. + The options to reset the expectations on this mock. + + + + Cause the mock state to change to replay, any further call is compared to the + ones that were called in the record state. + + the mocked object to move to replay state + + + + Gets the mock repository for this specificied mock object + + + The mock. + + + + + Create an expectation on this mock for this action to occur + + + + The mock. + The action. + + + + + Tell the mock object to perform a certain action when a matching + method is called. + Does not create an expectation for this method. + + + The mock. + The action. + + + + + Tell the mock object to perform a certain action when a matching + method is called. + Does not create an expectation for this method. + + + + The mock. + The action. + + + + + Gets the arguments for calls made on this mock object and the method that was called + in the action. + + + The mock. + The action. + + + Here we will get all the arguments for all the calls made to DoSomething(int) + + var argsForCalls = foo54.GetArgumentsForCallsMadeOn(x => x.DoSomething(0)) + + + + + + Gets the arguments for calls made on this mock object and the method that was called + in the action and matches the given constraints + + + The mock. + The action. + The setup constraints. + + + Here we will get all the arguments for all the calls made to DoSomething(int) + + var argsForCalls = foo54.GetArgumentsForCallsMadeOn(x => x.DoSomething(0)) + + + + + + Asserts that a particular method was called on this mock object + + + The mock. + The action. + + + + Asserts that a particular method was called on this mock object that match + a particular constraint set. + + + The mock. + The action. + The setup constraints. + + + + Asserts that a particular method was NOT called on this mock object + + + The mock. + The action. + + + + Asserts that a particular method was NOT called on this mock object that match + a particular constraint set. + + + The mock. + The action. + The setup constraints. + + + + Finds the approprite implementation type of this item. + This is the class or an interface outside of the rhino mocks. + + The mocked obj. + + + + + Verifies all expectations on this mock object + + The mock object. + + + + Gets the event raiser for the event that was called in the action passed + + The type of the event source. + The mock object. + The event subscription. + + + + + Raise the specified event using the passed arguments. + The even is extracted from the passed labmda + + The type of the event source. + The mock object. + The event subscription. + The sender. + The instance containing the event data. + + + + Raise the specified event using the passed arguments. + The even is extracted from the passed labmda + + The type of the event source. + The mock object. + The event subscription. + The args. + + + + Fake type that disallow creating it. + Should have been System.Type, but we can't use it. + + + + + Setup method calls to repeat any number of times. + + + + + Get the method options and set the last method call to repeat + any number of times. + This also means that the method would transcend ordering + + + + + Get the method options for the last method call on the mockInstance and set it + to repeat any number of times. + This also means that the method would transcend ordering + + + + + Allows easier access to MockRepository, works closely with Mocker.Current to + allow access to a context where the mock repository is automatially verified at + the end of the code block. + + + + + Initialize a code block where Mocker.Current is initialized. + At the end of the code block, all the expectation will be verified. + This overload will create a new MockRepository. + + The code that will be executed under the mock context + + + + Initialize a code block where Mocker.Current is initialized. + At the end of the code block, all the expectation will be verified. + This overload will create a new MockRepository. + + The mock repository to use, at the end of the code block, VerifyAll() will be called on the repository. + The code that will be executed under the mock context + + + + Create a FluentMocker + + The mock repository to use. + + + + A method with no arguments and no return value that will be called under the mock context. + + + + + FluentMocker implements some kind of fluent interface attempt + for saying "With the Mocks [mocks], Expecting (in same order) [things] verify [that]." + + + + + Interface to verify previously defined expectations + + + + + Verifies if a piece of code + + + + + Defines unordered expectations + + A delegate describing the expectations + an IMockVerifier + + + + Defines ordered expectations + + A delegate describing the expectations + an IMockVerifier + + + + Verifies previously defined expectations + + + + diff --git a/References/Tools/Rhino.Mocks/acknowledgements.txt b/References/Tools/Rhino.Mocks/acknowledgements.txt new file mode 100644 index 0000000..834b0b0 --- /dev/null +++ b/References/Tools/Rhino.Mocks/acknowledgements.txt @@ -0,0 +1,2 @@ +Rhino Mocks is using Castle Dynamic Proxy (http://www.castleproject.org/dynamicproxy/index.html) to handle proxying the types it needs to mock. +The Dynamic Proxy project has been invaluable resource and made creating Rhino Mocks possible. \ No newline at end of file diff --git a/References/Tools/Rhino.Mocks/license.txt b/References/Tools/Rhino.Mocks/license.txt new file mode 100644 index 0000000..33edd59 --- /dev/null +++ b/References/Tools/Rhino.Mocks/license.txt @@ -0,0 +1,25 @@ +Copyright (c) 2005 - 2008 Ayende Rahien (ayende@ayende.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of Ayende Rahien nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/ResxSync.bat b/ResxSync.bat new file mode 100644 index 0000000..dc05649 --- /dev/null +++ b/ResxSync.bat @@ -0,0 +1,46 @@ + +@echo off + +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AccessDenied.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AclActionsSelector.ascx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\Admin.master.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AdminCategories.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AdminConfig.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AdminContent.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AdminGroups.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AdminHome.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AdminLog.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AdminNamespaces.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AdminNavPaths.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AdminPages.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AdminProviders.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AdminSnippets.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AdminUsers.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AllPages.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AttachmentManager.ascx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\AttachmentViewer.ascx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\Category.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\Default.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\Edit.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\Editor.ascx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\Error.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\FileManager.ascx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\History.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\ImageEditor.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\Login.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\MasterPageSA.Master.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\NavPath.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\Operation.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\PageNotFound.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\PageListBuilder.ascx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\PermissionsManager.ascx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\Popup.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\PopupWYSIWYG.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\Post.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\Profile.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\Register.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\Search.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\Upload.aspx.resx * +References\Tools\ResxSync\resxsync.exe WebApplication\App_LocalResources\User.aspx.resx * + +References\Tools\ResxSync\resxsync.exe WebApplication\Properties\Messages.resx * diff --git a/ScrewTurnWiki.sln b/ScrewTurnWiki.sln new file mode 100644 index 0000000..99b9850 --- /dev/null +++ b/ScrewTurnWiki.sln @@ -0,0 +1,218 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginFramework", "PluginFramework\PluginFramework.csproj", "{531A83D6-76F9-4014-91C5-295818E2D948}" + ProjectSection(ProjectDependencies) = postProject + {2DF980A6-4742-49B1-A090-DE79314644D0} = {2DF980A6-4742-49B1-A090-DE79314644D0} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{C353A35C-86D0-4154-9500-4F88CAAB29C3}" + ProjectSection(ProjectDependencies) = postProject + {2DF980A6-4742-49B1-A090-DE79314644D0} = {2DF980A6-4742-49B1-A090-DE79314644D0} + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81} = {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApplication", "WebApplication\WebApplication.csproj", "{1DB1E325-A12E-4415-990C-A5DB669BE835}" + ProjectSection(ProjectDependencies) = postProject + {2DF980A6-4742-49B1-A090-DE79314644D0} = {2DF980A6-4742-49B1-A090-DE79314644D0} + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81} = {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core-Tests", "Core-Tests\Core-Tests.csproj", "{013B5DA5-76F9-4D7F-A174-4926BF51E24B}" + ProjectSection(ProjectDependencies) = postProject + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81} = {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81} + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Misc", "Misc", "{ADF6EC2E-6C55-4771-AD8C-025CD9CA3A70}" + ProjectSection(SolutionItems) = preProject + Documentation.shfb = Documentation.shfb + Tests.nunit = Tests.nunit + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SearchEngine", "SearchEngine\SearchEngine.csproj", "{2DF980A6-4742-49B1-A090-DE79314644D0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SearchEngine-Tests", "SearchEngine-Tests\SearchEngine-Tests.csproj", "{07625628-842E-4CAA-A029-4D6852C7CA20}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestScaffolding", "TestScaffolding\TestScaffolding.csproj", "{F865670A-DEDE-41B5-B426-48D73C3B5B1C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AclEngine", "AclEngine\AclEngine.csproj", "{44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AclEngine-Tests", "AclEngine-Tests\AclEngine-Tests.csproj", "{9F22D0A6-115B-4EB1-8506-65263674CEA3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlProvidersCommon", "SqlProvidersCommon\SqlProvidersCommon.csproj", "{617D5D30-97F9-48B2-903D-29D4524492E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlProvidersCommon-Tests", "SqlProvidersCommon-Tests\SqlProvidersCommon-Tests.csproj", "{67590C3A-1A7C-4608-90CA-1C1632D2F643}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlServerProviders", "SqlServerProviders\SqlServerProviders.csproj", "{ECB488D9-C8E9-41E0-BE27-27F41F76F8A0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlServerProviders-Tests", "SqlServerProviders-Tests\SqlServerProviders-Tests.csproj", "{F75DFFE1-A8CF-4CC2-B15F-3EC7EAADDCFC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginPack", "PluginPack\PluginPack.csproj", "{88212C14-10A0-4D46-8203-D48534465181}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginPack-Tests", "PluginPack-Tests\PluginPack-Tests.csproj", "{C657F6C0-05E5-4873-97A4-B91ED1F51D85}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|.NET = Debug|.NET + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Release|.NET = Release|.NET + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {531A83D6-76F9-4014-91C5-295818E2D948}.Debug|.NET.ActiveCfg = Debug|Any CPU + {531A83D6-76F9-4014-91C5-295818E2D948}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {531A83D6-76F9-4014-91C5-295818E2D948}.Debug|Any CPU.Build.0 = Debug|Any CPU + {531A83D6-76F9-4014-91C5-295818E2D948}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {531A83D6-76F9-4014-91C5-295818E2D948}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {531A83D6-76F9-4014-91C5-295818E2D948}.Release|.NET.ActiveCfg = Release|Any CPU + {531A83D6-76F9-4014-91C5-295818E2D948}.Release|Any CPU.ActiveCfg = Release|Any CPU + {531A83D6-76F9-4014-91C5-295818E2D948}.Release|Any CPU.Build.0 = Release|Any CPU + {531A83D6-76F9-4014-91C5-295818E2D948}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {531A83D6-76F9-4014-91C5-295818E2D948}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {C353A35C-86D0-4154-9500-4F88CAAB29C3}.Debug|.NET.ActiveCfg = Debug|Any CPU + {C353A35C-86D0-4154-9500-4F88CAAB29C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C353A35C-86D0-4154-9500-4F88CAAB29C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C353A35C-86D0-4154-9500-4F88CAAB29C3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {C353A35C-86D0-4154-9500-4F88CAAB29C3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {C353A35C-86D0-4154-9500-4F88CAAB29C3}.Release|.NET.ActiveCfg = Release|Any CPU + {C353A35C-86D0-4154-9500-4F88CAAB29C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C353A35C-86D0-4154-9500-4F88CAAB29C3}.Release|Any CPU.Build.0 = Release|Any CPU + {C353A35C-86D0-4154-9500-4F88CAAB29C3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {C353A35C-86D0-4154-9500-4F88CAAB29C3}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {1DB1E325-A12E-4415-990C-A5DB669BE835}.Debug|.NET.ActiveCfg = Debug|Any CPU + {1DB1E325-A12E-4415-990C-A5DB669BE835}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DB1E325-A12E-4415-990C-A5DB669BE835}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DB1E325-A12E-4415-990C-A5DB669BE835}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {1DB1E325-A12E-4415-990C-A5DB669BE835}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {1DB1E325-A12E-4415-990C-A5DB669BE835}.Release|.NET.ActiveCfg = Release|Any CPU + {1DB1E325-A12E-4415-990C-A5DB669BE835}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DB1E325-A12E-4415-990C-A5DB669BE835}.Release|Any CPU.Build.0 = Release|Any CPU + {1DB1E325-A12E-4415-990C-A5DB669BE835}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {1DB1E325-A12E-4415-990C-A5DB669BE835}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {013B5DA5-76F9-4D7F-A174-4926BF51E24B}.Debug|.NET.ActiveCfg = Debug|Any CPU + {013B5DA5-76F9-4D7F-A174-4926BF51E24B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {013B5DA5-76F9-4D7F-A174-4926BF51E24B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {013B5DA5-76F9-4D7F-A174-4926BF51E24B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {013B5DA5-76F9-4D7F-A174-4926BF51E24B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {013B5DA5-76F9-4D7F-A174-4926BF51E24B}.Release|.NET.ActiveCfg = Release|Any CPU + {013B5DA5-76F9-4D7F-A174-4926BF51E24B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {013B5DA5-76F9-4D7F-A174-4926BF51E24B}.Release|Any CPU.Build.0 = Release|Any CPU + {013B5DA5-76F9-4D7F-A174-4926BF51E24B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {013B5DA5-76F9-4D7F-A174-4926BF51E24B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2DF980A6-4742-49B1-A090-DE79314644D0}.Debug|.NET.ActiveCfg = Debug|Any CPU + {2DF980A6-4742-49B1-A090-DE79314644D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DF980A6-4742-49B1-A090-DE79314644D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DF980A6-4742-49B1-A090-DE79314644D0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2DF980A6-4742-49B1-A090-DE79314644D0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2DF980A6-4742-49B1-A090-DE79314644D0}.Release|.NET.ActiveCfg = Release|Any CPU + {2DF980A6-4742-49B1-A090-DE79314644D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DF980A6-4742-49B1-A090-DE79314644D0}.Release|Any CPU.Build.0 = Release|Any CPU + {2DF980A6-4742-49B1-A090-DE79314644D0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2DF980A6-4742-49B1-A090-DE79314644D0}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {07625628-842E-4CAA-A029-4D6852C7CA20}.Debug|.NET.ActiveCfg = Debug|Any CPU + {07625628-842E-4CAA-A029-4D6852C7CA20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07625628-842E-4CAA-A029-4D6852C7CA20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07625628-842E-4CAA-A029-4D6852C7CA20}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {07625628-842E-4CAA-A029-4D6852C7CA20}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {07625628-842E-4CAA-A029-4D6852C7CA20}.Release|.NET.ActiveCfg = Release|Any CPU + {07625628-842E-4CAA-A029-4D6852C7CA20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07625628-842E-4CAA-A029-4D6852C7CA20}.Release|Any CPU.Build.0 = Release|Any CPU + {07625628-842E-4CAA-A029-4D6852C7CA20}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {07625628-842E-4CAA-A029-4D6852C7CA20}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F865670A-DEDE-41B5-B426-48D73C3B5B1C}.Debug|.NET.ActiveCfg = Debug|Any CPU + {F865670A-DEDE-41B5-B426-48D73C3B5B1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F865670A-DEDE-41B5-B426-48D73C3B5B1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F865670A-DEDE-41B5-B426-48D73C3B5B1C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F865670A-DEDE-41B5-B426-48D73C3B5B1C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F865670A-DEDE-41B5-B426-48D73C3B5B1C}.Release|.NET.ActiveCfg = Release|Any CPU + {F865670A-DEDE-41B5-B426-48D73C3B5B1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F865670A-DEDE-41B5-B426-48D73C3B5B1C}.Release|Any CPU.Build.0 = Release|Any CPU + {F865670A-DEDE-41B5-B426-48D73C3B5B1C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F865670A-DEDE-41B5-B426-48D73C3B5B1C}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81}.Debug|.NET.ActiveCfg = Debug|Any CPU + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81}.Release|.NET.ActiveCfg = Release|Any CPU + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81}.Release|Any CPU.Build.0 = Release|Any CPU + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {9F22D0A6-115B-4EB1-8506-65263674CEA3}.Debug|.NET.ActiveCfg = Debug|Any CPU + {9F22D0A6-115B-4EB1-8506-65263674CEA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F22D0A6-115B-4EB1-8506-65263674CEA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F22D0A6-115B-4EB1-8506-65263674CEA3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {9F22D0A6-115B-4EB1-8506-65263674CEA3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {9F22D0A6-115B-4EB1-8506-65263674CEA3}.Release|.NET.ActiveCfg = Release|Any CPU + {9F22D0A6-115B-4EB1-8506-65263674CEA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F22D0A6-115B-4EB1-8506-65263674CEA3}.Release|Any CPU.Build.0 = Release|Any CPU + {9F22D0A6-115B-4EB1-8506-65263674CEA3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {9F22D0A6-115B-4EB1-8506-65263674CEA3}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {617D5D30-97F9-48B2-903D-29D4524492E8}.Debug|.NET.ActiveCfg = Debug|Any CPU + {617D5D30-97F9-48B2-903D-29D4524492E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {617D5D30-97F9-48B2-903D-29D4524492E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {617D5D30-97F9-48B2-903D-29D4524492E8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {617D5D30-97F9-48B2-903D-29D4524492E8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {617D5D30-97F9-48B2-903D-29D4524492E8}.Release|.NET.ActiveCfg = Release|Any CPU + {617D5D30-97F9-48B2-903D-29D4524492E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {617D5D30-97F9-48B2-903D-29D4524492E8}.Release|Any CPU.Build.0 = Release|Any CPU + {617D5D30-97F9-48B2-903D-29D4524492E8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {617D5D30-97F9-48B2-903D-29D4524492E8}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {67590C3A-1A7C-4608-90CA-1C1632D2F643}.Debug|.NET.ActiveCfg = Debug|Any CPU + {67590C3A-1A7C-4608-90CA-1C1632D2F643}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67590C3A-1A7C-4608-90CA-1C1632D2F643}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67590C3A-1A7C-4608-90CA-1C1632D2F643}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {67590C3A-1A7C-4608-90CA-1C1632D2F643}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {67590C3A-1A7C-4608-90CA-1C1632D2F643}.Release|.NET.ActiveCfg = Release|Any CPU + {67590C3A-1A7C-4608-90CA-1C1632D2F643}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67590C3A-1A7C-4608-90CA-1C1632D2F643}.Release|Any CPU.Build.0 = Release|Any CPU + {67590C3A-1A7C-4608-90CA-1C1632D2F643}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {67590C3A-1A7C-4608-90CA-1C1632D2F643}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {ECB488D9-C8E9-41E0-BE27-27F41F76F8A0}.Debug|.NET.ActiveCfg = Debug|Any CPU + {ECB488D9-C8E9-41E0-BE27-27F41F76F8A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ECB488D9-C8E9-41E0-BE27-27F41F76F8A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ECB488D9-C8E9-41E0-BE27-27F41F76F8A0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {ECB488D9-C8E9-41E0-BE27-27F41F76F8A0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {ECB488D9-C8E9-41E0-BE27-27F41F76F8A0}.Release|.NET.ActiveCfg = Release|Any CPU + {ECB488D9-C8E9-41E0-BE27-27F41F76F8A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ECB488D9-C8E9-41E0-BE27-27F41F76F8A0}.Release|Any CPU.Build.0 = Release|Any CPU + {ECB488D9-C8E9-41E0-BE27-27F41F76F8A0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {ECB488D9-C8E9-41E0-BE27-27F41F76F8A0}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F75DFFE1-A8CF-4CC2-B15F-3EC7EAADDCFC}.Debug|.NET.ActiveCfg = Debug|Any CPU + {F75DFFE1-A8CF-4CC2-B15F-3EC7EAADDCFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F75DFFE1-A8CF-4CC2-B15F-3EC7EAADDCFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F75DFFE1-A8CF-4CC2-B15F-3EC7EAADDCFC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F75DFFE1-A8CF-4CC2-B15F-3EC7EAADDCFC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F75DFFE1-A8CF-4CC2-B15F-3EC7EAADDCFC}.Release|.NET.ActiveCfg = Release|Any CPU + {F75DFFE1-A8CF-4CC2-B15F-3EC7EAADDCFC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F75DFFE1-A8CF-4CC2-B15F-3EC7EAADDCFC}.Release|Any CPU.Build.0 = Release|Any CPU + {F75DFFE1-A8CF-4CC2-B15F-3EC7EAADDCFC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F75DFFE1-A8CF-4CC2-B15F-3EC7EAADDCFC}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {88212C14-10A0-4D46-8203-D48534465181}.Debug|.NET.ActiveCfg = Debug|Any CPU + {88212C14-10A0-4D46-8203-D48534465181}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88212C14-10A0-4D46-8203-D48534465181}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88212C14-10A0-4D46-8203-D48534465181}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {88212C14-10A0-4D46-8203-D48534465181}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {88212C14-10A0-4D46-8203-D48534465181}.Release|.NET.ActiveCfg = Release|Any CPU + {88212C14-10A0-4D46-8203-D48534465181}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88212C14-10A0-4D46-8203-D48534465181}.Release|Any CPU.Build.0 = Release|Any CPU + {88212C14-10A0-4D46-8203-D48534465181}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {88212C14-10A0-4D46-8203-D48534465181}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {C657F6C0-05E5-4873-97A4-B91ED1F51D85}.Debug|.NET.ActiveCfg = Debug|Any CPU + {C657F6C0-05E5-4873-97A4-B91ED1F51D85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C657F6C0-05E5-4873-97A4-B91ED1F51D85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C657F6C0-05E5-4873-97A4-B91ED1F51D85}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {C657F6C0-05E5-4873-97A4-B91ED1F51D85}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {C657F6C0-05E5-4873-97A4-B91ED1F51D85}.Release|.NET.ActiveCfg = Release|Any CPU + {C657F6C0-05E5-4873-97A4-B91ED1F51D85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C657F6C0-05E5-4873-97A4-B91ED1F51D85}.Release|Any CPU.Build.0 = Release|Any CPU + {C657F6C0-05E5-4873-97A4-B91ED1F51D85}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {C657F6C0-05E5-4873-97A4-B91ED1F51D85}.Release|Mixed Platforms.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/SearchEngine-Tests/BasicWordInfoTests.cs b/SearchEngine-Tests/BasicWordInfoTests.cs new file mode 100644 index 0000000..434452e --- /dev/null +++ b/SearchEngine-Tests/BasicWordInfoTests.cs @@ -0,0 +1,75 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class BasicWordInfoTests { + + [Test] + public void Constructor() { + BasicWordInfo info = new BasicWordInfo(2, 0, WordLocation.Content); + Assert.AreEqual(2, info.FirstCharIndex, "Wrong start index"); + Assert.AreEqual(0, info.WordIndex, "Wrong word index"); + } + + [Test] + public void Equals() { + BasicWordInfo info1 = new BasicWordInfo(0, 0, WordLocation.Content); + BasicWordInfo info2 = new BasicWordInfo(0, 0, WordLocation.Content); + BasicWordInfo info3 = new BasicWordInfo(10, 1, WordLocation.Content); + BasicWordInfo info4 = new BasicWordInfo(10, 1, WordLocation.Title); + + Assert.IsTrue(info1.Equals(info2), "info1 should equal info2"); + Assert.IsFalse(info1.Equals(info3), "info1 should not equal info3"); + Assert.IsFalse(info3.Equals(info4), "info3 should not equal info4"); + Assert.IsTrue(info1.Equals(info1), "info1 should equal itself"); + Assert.IsFalse(info1.Equals(null), "info1 should not equal null"); + Assert.IsFalse(info1.Equals("hello"), "info1 should not equal a string"); + } + + [Test] + public void EqualityOperator() { + BasicWordInfo info1 = new BasicWordInfo(0, 0, WordLocation.Content); + BasicWordInfo info2 = new BasicWordInfo(0, 0, WordLocation.Content); + BasicWordInfo info3 = new BasicWordInfo(10, 1, WordLocation.Content); + BasicWordInfo info4 = new BasicWordInfo(10, 1, WordLocation.Title); + + Assert.IsTrue(info1 == info2, "info1 should equal info2"); + Assert.IsFalse(info1 == info3, "info1 should not equal info3"); + Assert.IsFalse(info3 == info4, "info3 should not equal info4"); + Assert.IsFalse(info1 == null, "info1 should not equal null"); + } + + [Test] + public void InequalityOperator() { + BasicWordInfo info1 = new BasicWordInfo(0, 0, WordLocation.Content); + BasicWordInfo info2 = new BasicWordInfo(0, 0, WordLocation.Content); + BasicWordInfo info3 = new BasicWordInfo(10, 1, WordLocation.Content); + BasicWordInfo info4 = new BasicWordInfo(10, 1, WordLocation.Title); + + Assert.IsFalse(info1 != info2, "info1 should equal info2"); + Assert.IsTrue(info1 != info3, "info1 should not equal info3"); + Assert.IsTrue(info3 != info4, "info3 should not equal info4"); + Assert.IsTrue(info1 != null, "info1 should not equal null"); + } + + [Test] + public void CompareTo() { + BasicWordInfo info1 = new BasicWordInfo(0, 0, WordLocation.Content); + BasicWordInfo info2 = new BasicWordInfo(0, 0, WordLocation.Content); + BasicWordInfo info3 = new BasicWordInfo(10, 1, WordLocation.Content); + BasicWordInfo info4 = new BasicWordInfo(10, 1, WordLocation.Title); + + Assert.AreEqual(0, info1.CompareTo(info2), "info1 should equal info2"); + Assert.AreEqual(-1, info1.CompareTo(info3), "info1 should be smaller than info3"); + Assert.AreEqual(2, info3.CompareTo(info4), "info3 should be greater than info4"); + Assert.AreEqual(1, info1.CompareTo(null), "info1 should be greater than null"); + } + + } + +} diff --git a/SearchEngine-Tests/DumpedChangeTests.cs b/SearchEngine-Tests/DumpedChangeTests.cs new file mode 100644 index 0000000..cdf79c7 --- /dev/null +++ b/SearchEngine-Tests/DumpedChangeTests.cs @@ -0,0 +1,73 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class DumpedChangeTests : TestsBase { + + [Test] + public void Constructor() { + DumpedChange change = new DumpedChange( + new DumpedDocument(MockDocument("doc", "Docum", "d", DateTime.Now)), + new List(new DumpedWord[] { new DumpedWord(1, "word") }), + new List(new DumpedWordMapping[] { new DumpedWordMapping(1, 1, 4, 1, WordLocation.Content.Location) })); + + Assert.AreEqual("doc", change.Document.Name, "Wrong name"); + Assert.AreEqual(1, change.Words.Count, "Wrong words count"); + Assert.AreEqual("word", change.Words[0].Text, "Wrong word"); + Assert.AreEqual(1, change.Mappings.Count, "Wrong mappings count"); + Assert.AreEqual(1, change.Mappings[0].WordID, "Wrong word index"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_NullDocument() { + DumpedChange change = new DumpedChange( + null, + new List(new DumpedWord[] { new DumpedWord(1, "word") }), + new List(new DumpedWordMapping[] { new DumpedWordMapping(1, 1, 4, 1, WordLocation.Content.Location) })); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_NullWords() { + DumpedChange change = new DumpedChange( + new DumpedDocument(MockDocument("doc", "Docum", "d", DateTime.Now)), + null, + new List(new DumpedWordMapping[] { new DumpedWordMapping(1, 1, 4, 1, WordLocation.Content.Location) })); + } + + [Test] + public void Constructor_EmptyWords() { + // Words can be empty + DumpedChange change = new DumpedChange( + new DumpedDocument(MockDocument("doc", "Docum", "d", DateTime.Now)), + new List(), + new List(new DumpedWordMapping[] { new DumpedWordMapping(1, 1, 4, 1, WordLocation.Content.Location) })); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_NullMappings() { + DumpedChange change = new DumpedChange( + new DumpedDocument(MockDocument("doc", "Docum", "d", DateTime.Now)), + null, + new List(new DumpedWordMapping[] { new DumpedWordMapping(1, 1, 4, 1, WordLocation.Content.Location) })); + } + + [Test] + public void Constructor_EmptyMappings() { + // Mappings can be empty + DumpedChange change = new DumpedChange( + new DumpedDocument(MockDocument("doc", "Docum", "d", DateTime.Now)), + new List(new DumpedWord[] { new DumpedWord(1, "word") }), + new List()); + } + + } + +} diff --git a/SearchEngine-Tests/DumpedDocumentTests.cs b/SearchEngine-Tests/DumpedDocumentTests.cs new file mode 100644 index 0000000..d487f8d --- /dev/null +++ b/SearchEngine-Tests/DumpedDocumentTests.cs @@ -0,0 +1,60 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class DumpedDocumentTests : TestsBase { + + [Test] + public void Constructor_WithDocument() { + IDocument doc = MockDocument("name", "Title", "doc", DateTime.Now); + DumpedDocument ddoc = new DumpedDocument(doc); + + Assert.AreEqual(doc.ID, ddoc.ID, "Wrong ID"); + Assert.AreEqual("name", ddoc.Name, "Wrong name"); + Assert.AreEqual("Title", ddoc.Title, "Wrong title"); + Assert.AreEqual(doc.DateTime, ddoc.DateTime, "Wrong date/time"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_WithDocument_NullDocument() { + DumpedDocument ddoc = new DumpedDocument(null); + } + + [Test] + public void Constructor_WithParameters() { + IDocument doc = MockDocument("name", "Title", "doc", DateTime.Now); + DumpedDocument ddoc = new DumpedDocument(doc.ID, doc.Name, doc.Title, doc.TypeTag, doc.DateTime); + + Assert.AreEqual(doc.ID, ddoc.ID, "Wrong ID"); + Assert.AreEqual("name", ddoc.Name, "Wrong name"); + Assert.AreEqual("Title", ddoc.Title, "Wrong title"); + Assert.AreEqual(doc.DateTime, ddoc.DateTime, "Wrong date/time"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_WithParameters_InvalidName(string name) { + DumpedDocument ddoc = new DumpedDocument(10, name, "Title", "doc", DateTime.Now); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_WithParameters_InvalidTitle(string title) { + DumpedDocument ddoc = new DumpedDocument(1, "name", title, "doc", DateTime.Now); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_WithParameters_InvalidTypeTag(string typeTag) { + DumpedDocument ddoc = new DumpedDocument(1, "name", "Title", typeTag, DateTime.Now); + } + + } + +} diff --git a/SearchEngine-Tests/DumpedWordMappingTests.cs b/SearchEngine-Tests/DumpedWordMappingTests.cs new file mode 100644 index 0000000..871dc9f --- /dev/null +++ b/SearchEngine-Tests/DumpedWordMappingTests.cs @@ -0,0 +1,40 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class DumpedWordMappingTests : TestsBase { + + [Test] + public void Constructor_Integers() { + DumpedWordMapping map = new DumpedWordMapping(5, 2, 3, 4, WordLocation.Keywords.Location); + Assert.AreEqual(5, map.WordID, "Wrong word ID"); + Assert.AreEqual(2, map.DocumentID, "Wrong document ID"); + Assert.AreEqual(3, map.FirstCharIndex, "Wrong first char index"); + Assert.AreEqual(4, map.WordIndex, "Wrong word index"); + Assert.AreEqual(WordLocation.Keywords.Location, map.Location, "Wrong word location"); + } + + [Test] + public void Constructor_WithBasicWordInfo() { + DumpedWordMapping map = new DumpedWordMapping(5, 2, new BasicWordInfo(3, 4, WordLocation.Keywords)); + Assert.AreEqual(5, map.WordID, "Wrong word ID"); + Assert.AreEqual(2, map.DocumentID, "Wrong document ID"); + Assert.AreEqual(3, map.FirstCharIndex, "Wrong first char index"); + Assert.AreEqual(4, map.WordIndex, "Wrong word index"); + Assert.AreEqual(WordLocation.Keywords.Location, map.Location, "Wrong word location"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_WithBasicWordInfo_NullInfo() { + DumpedWordMapping map = new DumpedWordMapping(10, 12, null); + } + + } + +} diff --git a/SearchEngine-Tests/DumpedWordTests.cs b/SearchEngine-Tests/DumpedWordTests.cs new file mode 100644 index 0000000..5a93e75 --- /dev/null +++ b/SearchEngine-Tests/DumpedWordTests.cs @@ -0,0 +1,40 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class DumpedWordTests { + + [Test] + public void Constructor_WithParameters() { + DumpedWord w = new DumpedWord(12, "word"); + Assert.AreEqual(12, w.ID, "Wrong ID"); + Assert.AreEqual("word", w.Text, "Wrong text"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_WithParameters_InvalidText(string text) { + DumpedWord w = new DumpedWord(5, text); + } + + [Test] + public void Constructor_Word() { + DumpedWord w = new DumpedWord(new Word(23, "text")); + Assert.AreEqual(23, w.ID, "Wrong ID"); + Assert.AreEqual("text", w.Text, "Wrong text"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_Word_NullWord() { + DumpedWord w = new DumpedWord(null); + } + + } + +} diff --git a/SearchEngine-Tests/InMemoryIndexBaseTests.cs b/SearchEngine-Tests/InMemoryIndexBaseTests.cs new file mode 100644 index 0000000..af6da07 --- /dev/null +++ b/SearchEngine-Tests/InMemoryIndexBaseTests.cs @@ -0,0 +1,158 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using Rhino.Mocks.Constraints; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class InMemoryIndexBaseTests : IndexBaseTests { + + /// + /// Gets the instance of the index to test. + /// + /// The instance of the index. + protected override IIndex GetIndex() { + return MockInMemoryIndex(); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetBuildDocumentDelegate_NullDelegate() { + IInMemoryIndex index = (IInMemoryIndex)GetIndex(); + index.SetBuildDocumentDelegate(null); + } + + [Test] + public void InitializeData() { + IInMemoryIndex index = (IInMemoryIndex)GetIndex(); + + IDocument d = MockDocument("doc", "Document", "doc", DateTime.Now); + DumpedDocument[] documents = new DumpedDocument[] { new DumpedDocument(d) }; + + DumpedWord[] words = new DumpedWord[] { + new DumpedWord(new Word(1, "document")), + new DumpedWord(new Word(2, "this")), + new DumpedWord(new Word(3, "is")), + new DumpedWord(new Word(4, "some")), + new DumpedWord(new Word(5, "content")) }; + + DumpedWordMapping[] mappings = new DumpedWordMapping[] { + new DumpedWordMapping(words[0].ID, documents[0].ID, new BasicWordInfo(0, 0, WordLocation.Title)), + new DumpedWordMapping(words[1].ID, documents[0].ID, new BasicWordInfo(0, 0, WordLocation.Content)), + new DumpedWordMapping(words[2].ID, documents[0].ID, new BasicWordInfo(5, 1, WordLocation.Content)), + new DumpedWordMapping(words[3].ID, documents[0].ID, new BasicWordInfo(8, 2, WordLocation.Content)), + new DumpedWordMapping(words[4].ID, documents[0].ID, new BasicWordInfo(13, 3, WordLocation.Content)) }; + + index.SetBuildDocumentDelegate(delegate(DumpedDocument doc) { return d; }); + + index.InitializeData(documents, words, mappings); + + Assert.AreEqual(1, index.TotalDocuments, "Wrong document count"); + Assert.AreEqual(5, index.TotalWords, "Wrong word count"); + Assert.AreEqual(5, index.TotalOccurrences, "Wrong occurrence count"); + + SearchResultCollection res = index.Search(new SearchParameters("document content")); + Assert.AreEqual(1, res.Count, "Wrong result count"); + Assert.AreEqual(2, res[0].Matches.Count, "Wrong matches count"); + + Assert.AreEqual("document", res[0].Matches[0].Text, "Wrong match text"); + Assert.AreEqual(0, res[0].Matches[0].FirstCharIndex, "Wrong match first char index"); + Assert.AreEqual(0, res[0].Matches[0].WordIndex, "Wrong match word index"); + Assert.AreEqual(WordLocation.Title, res[0].Matches[0].Location, "Wrong match location"); + + Assert.AreEqual("content", res[0].Matches[1].Text, "Wrong match text"); + Assert.AreEqual(13, res[0].Matches[1].FirstCharIndex, "Wrong match first char index"); + Assert.AreEqual(3, res[0].Matches[1].WordIndex, "Wrong match word index"); + Assert.AreEqual(WordLocation.Content, res[0].Matches[1].Location, "Wrong match location"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void InitializeData_NullDocuments() { + IInMemoryIndex index = (IInMemoryIndex)GetIndex(); + index.SetBuildDocumentDelegate(delegate(DumpedDocument doc) { return null; }); + index.InitializeData(null, new DumpedWord[0], new DumpedWordMapping[0]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void InitializeData_NullWords() { + IInMemoryIndex index = (IInMemoryIndex)GetIndex(); + index.SetBuildDocumentDelegate(delegate(DumpedDocument doc) { return null; }); + index.InitializeData(new DumpedDocument[0], null, new DumpedWordMapping[0]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void InitializeData_NullMappings() { + IInMemoryIndex index = (IInMemoryIndex)GetIndex(); + index.SetBuildDocumentDelegate(delegate(DumpedDocument doc) { return null; }); + index.InitializeData(new DumpedDocument[0], new DumpedWord[0], null); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void InitializeData_NoBuildDocumentDelegate() { + IInMemoryIndex index = (IInMemoryIndex)GetIndex(); + index.InitializeData(new DumpedDocument[0], new DumpedWord[0], new DumpedWordMapping[0]); + } + + [Test] + public void InitializeData_DocumentNotAvailable() { + IInMemoryIndex index = (IInMemoryIndex)GetIndex(); + + IDocument doc = MockDocument("doc", "Document", "doc", DateTime.Now); + IDocument inexistent = MockDocument2("inexistent", "Inexistent", "doc", DateTime.Now); + + DumpedDocument[] documents = new DumpedDocument[] { + new DumpedDocument(doc), + new DumpedDocument(inexistent) }; + + DumpedWord[] words = new DumpedWord[] { + new DumpedWord(new Word(1, "document")), + new DumpedWord(new Word(2, "this")), + new DumpedWord(new Word(3, "is")), + new DumpedWord(new Word(4, "some")), + new DumpedWord(new Word(5, "content")), + + new DumpedWord(new Word(6, "inexistent")), + new DumpedWord(new Word(7, "dummy")), + new DumpedWord(new Word(8, "text")), + new DumpedWord(new Word(9, "used")), + new DumpedWord(new Word(10, "for")), + new DumpedWord(new Word(11, "testing")), + new DumpedWord(new Word(12, "purposes")) }; + + DumpedWordMapping[] mappings = new DumpedWordMapping[] { + new DumpedWordMapping(words[0].ID, documents[0].ID, new BasicWordInfo(0, 0, WordLocation.Title)), + new DumpedWordMapping(words[1].ID, documents[0].ID, new BasicWordInfo(0, 0, WordLocation.Content)), + new DumpedWordMapping(words[2].ID, documents[0].ID, new BasicWordInfo(5, 1, WordLocation.Content)), + new DumpedWordMapping(words[3].ID, documents[0].ID, new BasicWordInfo(8, 2, WordLocation.Content)), + new DumpedWordMapping(words[4].ID, documents[0].ID, new BasicWordInfo(13, 3, WordLocation.Content)), + + new DumpedWordMapping(words[5].ID, documents[1].ID, new BasicWordInfo(0, 0, WordLocation.Title)), + new DumpedWordMapping(words[6].ID, documents[1].ID, new BasicWordInfo(0, 0, WordLocation.Content)), + new DumpedWordMapping(words[7].ID, documents[1].ID, new BasicWordInfo(6, 1, WordLocation.Content)), + new DumpedWordMapping(words[8].ID, documents[1].ID, new BasicWordInfo(11, 2, WordLocation.Content)), + new DumpedWordMapping(words[9].ID, documents[1].ID, new BasicWordInfo(16, 3, WordLocation.Content)), + new DumpedWordMapping(words[10].ID, documents[1].ID, new BasicWordInfo(20, 4, WordLocation.Content)), + new DumpedWordMapping(words[11].ID, documents[1].ID, new BasicWordInfo(28, 5, WordLocation.Content)) }; + + index.SetBuildDocumentDelegate(delegate(DumpedDocument d) { + if(d.Name == "doc") return doc; + else return null; + }); + + index.InitializeData(documents, words, mappings); + + Assert.AreEqual(1, index.Search(new SearchParameters("this")).Count, "Wrong result count"); + Assert.AreEqual(0, index.Search(new SearchParameters("dummy")).Count, "Wrong result count"); + } + + } + +} diff --git a/SearchEngine-Tests/IndexBaseTests.cs b/SearchEngine-Tests/IndexBaseTests.cs new file mode 100644 index 0000000..e6cb74e --- /dev/null +++ b/SearchEngine-Tests/IndexBaseTests.cs @@ -0,0 +1,529 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + public abstract class IndexBaseTests : TestsBase { + + // These tests treat instances of IInMemoryIndex as special cases + // They are tested correctly, properly handling the IndexChanged event + + /// + /// Gets the instance of the index to test. + /// + /// The instance of the index. + protected abstract IIndex GetIndex(); + + [Test] + public void StopWordsProperty() { + IIndex index = GetIndex(); + Assert.AreEqual(0, index.StopWords.Length, "Wrong stop words count"); + + index.StopWords = new string[] { "the", "those" }; + Assert.AreEqual(2, index.StopWords.Length, "Wrong stop words count"); + Assert.AreEqual("the", index.StopWords[0], "Wrong stop word at index 0"); + Assert.AreEqual("those", index.StopWords[1], "Wrong stop word at index 1"); + } + + [Test] + public void Statistics() { + IIndex index = GetIndex(); + + Assert.AreEqual(0, index.TotalWords, "Wrong total words count"); + Assert.AreEqual(0, index.TotalDocuments, "Wrong total documents count"); + Assert.AreEqual(0, index.TotalOccurrences, "Wrong total occurrences count"); + } + + [Test] + public void Clear() { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + IDocument doc = MockDocument("Doc", "Document", "ptdoc", DateTime.Now); + + if(imIndex != null) imIndex.IndexChanged += AutoHandlerForDocumentStorage; + index.StoreDocument(doc, null, PlainTextDocumentContent, null); + + bool eventFired = false; + if(imIndex != null) { + imIndex.IndexChanged += delegate(object sender, IndexChangedEventArgs e) { + eventFired = true; + }; + } + + index.Clear(null); + + if(imIndex != null) Assert.IsTrue(eventFired, "IndexChanged event not fired"); + Assert.AreEqual(0, index.TotalDocuments, "Wrong document count"); + Assert.AreEqual(0, index.TotalWords, "Wrong word count"); + Assert.AreEqual(0, index.TotalOccurrences, "Wrong occurrence count"); + Assert.AreEqual(0, index.Search(new SearchParameters("document")).Count, "Wrong result count"); + } + + [Test] + public void StoreDocument() { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + IDocument doc = MockDocument("Doc", "Document", "ptdoc", DateTime.Now); + + bool eventFired = false; + if(imIndex != null) { + imIndex.IndexChanged += delegate(object sender, IndexChangedEventArgs e) { + eventFired = e.Document == doc && e.Change == IndexChangeType.DocumentAdded; + if(eventFired) { + Assert.AreEqual("Doc", e.ChangeData.Document.Name, "Wrong document"); + Assert.AreEqual(5, e.ChangeData.Words.Count, "Wrong count"); + Assert.AreEqual(5, e.ChangeData.Mappings.Count, "Wrong count"); + Assert.IsTrue(e.ChangeData.Mappings.Find(delegate(DumpedWordMapping m) { return m.WordIndex == 0 && m.Location == WordLocation.Content.Location; }) != null, "Mappings does not contain a word"); + Assert.IsTrue(e.ChangeData.Mappings.Find(delegate(DumpedWordMapping m) { return m.WordIndex == 1 && m.Location == WordLocation.Content.Location; }) != null, "Mappings does not contain a word"); + Assert.IsTrue(e.ChangeData.Mappings.Find(delegate(DumpedWordMapping m) { return m.WordIndex == 2 && m.Location == WordLocation.Content.Location; }) != null, "Mappings does not contain a word"); + Assert.IsTrue(e.ChangeData.Mappings.Find(delegate(DumpedWordMapping m) { return m.WordIndex == 3 && m.Location == WordLocation.Content.Location; }) != null, "Mappings does not contain a word"); + Assert.IsTrue(e.ChangeData.Mappings.Find(delegate(DumpedWordMapping m) { return m.WordIndex == 0 && m.Location == WordLocation.Title.Location; }) != null, "Mappings does not contain a word"); + } + }; + + imIndex.IndexChanged += AutoHandlerForDocumentStorage; + } + + Assert.AreEqual(5, index.StoreDocument(doc, null, PlainTextDocumentContent, null), "Wrong number of indexed words"); + Assert.AreEqual(5, index.TotalWords, "Wrong total words count"); + Assert.AreEqual(5, index.TotalOccurrences, "Wrong total occurrences count"); + Assert.AreEqual(1, index.TotalDocuments, "Wrong total documents count"); + if(imIndex != null) Assert.IsTrue(eventFired, "Event not fired"); + } + + [Test] + public void StoreDocument_ExistingDocument() { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + if(imIndex != null) imIndex.IndexChanged += AutoHandlerForDocumentStorage; + + IDocument doc = MockDocument("Doc", "Document", "ptdoc", DateTime.Now); + + Assert.AreEqual(5, index.StoreDocument(doc, null, PlainTextDocumentContent, null), "Wrong number of indexed words"); + Assert.AreEqual(5, index.TotalWords, "Wrong total words count"); + Assert.AreEqual(5, index.TotalOccurrences, "Wrong total occurrences count"); + Assert.AreEqual(1, index.TotalDocuments, "Wrong total documents count"); + + doc = MockDocument2("Doc", "Document", "ptdoc", DateTime.Now); + + Assert.AreEqual(7, index.StoreDocument(doc, null, PlainTextDocumentContent2, null), "Wrong number of indexed words"); + Assert.AreEqual(7, index.TotalWords, "Wrong total words count"); + Assert.AreEqual(7, index.TotalOccurrences, "Wrong total occurrences count"); + Assert.AreEqual(1, index.TotalDocuments, "Wrong total documents count"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void StoreDocument_NullDocument() { + IIndex index = GetIndex(); + index.StoreDocument(null, null, "blah", null); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void StoreDocument_NullContent() { + IIndex index = GetIndex(); + index.StoreDocument(MockDocument("Doc", "Document", "ptdoc", DateTime.Now), null, null, null); + } + + [Test] + public void RemoveDocument() { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + IDocument doc1 = MockDocument("Doc1", "Document 1", "ptdoc", DateTime.Now); + IDocument doc2 = MockDocument("Doc2", "Document 2", "ptdoc", DateTime.Now); + + if(imIndex != null) imIndex.IndexChanged += AutoHandlerForDocumentStorage; + + index.StoreDocument(doc1, null, "", null); + index.StoreDocument(doc2, null, "", null); + + bool eventFired = false; + + if(imIndex != null) { + imIndex.IndexChanged += delegate(object sender, IndexChangedEventArgs e) { + eventFired = e.Document == doc1 && e.Change == IndexChangeType.DocumentRemoved; + if(eventFired) { + Assert.AreEqual("Doc1", e.ChangeData.Document.Name, "Wrong document"); + Assert.AreEqual(1, e.ChangeData.Words.Count, "Wrong count"); + Assert.AreEqual(6, e.ChangeData.Mappings.Count, "Wrong count"); + Assert.IsTrue(e.ChangeData.Mappings.Find(delegate(DumpedWordMapping m) { return m.WordIndex == 0 && m.Location == WordLocation.Content.Location; }) != null, "Mappings does not contain a word"); + Assert.IsTrue(e.ChangeData.Mappings.Find(delegate(DumpedWordMapping m) { return m.WordIndex == 1 && m.Location == WordLocation.Content.Location; }) != null, "Mappings does not contain a word"); + Assert.IsTrue(e.ChangeData.Mappings.Find(delegate(DumpedWordMapping m) { return m.WordIndex == 2 && m.Location == WordLocation.Content.Location; }) != null, "Mappings does not contain a word"); + Assert.IsTrue(e.ChangeData.Mappings.Find(delegate(DumpedWordMapping m) { return m.WordIndex == 3 && m.Location == WordLocation.Content.Location; }) != null, "Mappings does not contain a word"); + Assert.IsTrue(e.ChangeData.Mappings.Find(delegate(DumpedWordMapping m) { return m.WordIndex == 0 && m.Location == WordLocation.Title.Location; }) != null, "Mappings does not contain a word"); + Assert.IsTrue(e.ChangeData.Mappings.Find(delegate(DumpedWordMapping m) { return m.WordIndex == 1 && m.Location == WordLocation.Title.Location; }) != null, "Mappings does not contain a word"); + } + }; + } + + Assert.AreEqual(2, index.TotalDocuments, "Wrong document count"); + + index.RemoveDocument(doc1, null); + Assert.AreEqual(1, index.TotalDocuments, "Wrong document count"); + + IDocument doc3 = MockDocument("Doc1", "Document 1", "ptdoc", DateTime.Now); + index.StoreDocument(doc3, null, "", null); + Assert.AreEqual(2, index.TotalDocuments, "Wrong document count"); + + index.RemoveDocument(doc1, null); + Assert.AreEqual(1, index.TotalDocuments, "Wrong document count"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveDocument_NullDocument() { + IIndex index = GetIndex(); + index.RemoveDocument(null, null); + } + + private static bool AreDocumentsEqual(IDocument doc1, IDocument doc2) { + return + //doc1.ID == doc2.ID && // ID can be different (new instance / loaded from storage) + doc1.Name == doc2.Name && + doc1.Title == doc2.Title && + doc1.TypeTag == doc2.TypeTag && + doc1.DateTime.ToString("yyMMddHHmmss") == doc2.DateTime.ToString("yyMMddHHmmss"); + } + + [Test] + public void Search_Basic() { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + // The mocked document has a default content + IDocument doc1 = MockDocument("Doc1", "Document 1", "ptdoc", DateTime.Now); + IDocument doc2 = MockDocument("Doc2", "Document 2", "ptdoc", DateTime.Now); + IDocument doc3 = MockDocument("Doc3", "Document 3", "ptdoc", DateTime.Now); + + if(imIndex != null) imIndex.IndexChanged += AutoHandlerForDocumentStorage; + + index.StoreDocument(doc1, null, "", null); + index.StoreDocument(doc2, null, "", null); + index.StoreDocument(doc3, null, "", null); + + SearchResultCollection res1 = index.Search(new SearchParameters("specifications")); + SearchResultCollection res2 = index.Search(new SearchParameters("this")); + + Assert.AreEqual(0, res1.Count, "Wrong result count"); + Assert.AreEqual(3, res2.Count, "Wrong result count"); + + // Matches are in unpredictable order + bool doc1Found = false, doc2Found = false, doc3Found = false; + foreach(SearchResult r in res2) { + if(AreDocumentsEqual(r.Document, doc1)) doc1Found = true; + else if(AreDocumentsEqual(r.Document, doc2)) doc2Found = true; + else if(AreDocumentsEqual(r.Document, doc3)) doc3Found = true; + + Assert.AreEqual(1, r.Matches.Count, "Wrong match count"); + Assert.AreEqual(0, r.Matches[0].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(4, r.Matches[0].Text.Length, "Wrong length"); + Assert.AreEqual(33.3333F, r.Relevance.Value, 0.01F, "Wrong relevance value"); + } + + Assert.IsTrue(doc1Found, "Doc1 not found in results"); + Assert.IsTrue(doc2Found, "Doc2 not found in results"); + Assert.IsTrue(doc3Found, "Doc3 not found in results"); + } + + [Test] + public void Search_Basic_SingleResultWord() { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + IDocument doc = MockDocument3("Doc", "Document", "ptdoc", DateTime.Now); + + if(imIndex != null) imIndex.IndexChanged += AutoHandlerForDocumentStorage; + + index.StoreDocument(doc, null, "", null); + + SearchResultCollection res = index.Search(new SearchParameters("todo")); + + Assert.AreEqual(1, res.Count, "Wrong result count"); + Assert.IsTrue(AreDocumentsEqual(doc, res[0].Document), "Wrong document"); + Assert.AreEqual(100, res[0].Relevance.Value, "Wrong relevance"); + Assert.AreEqual(1, res[0].Matches.Count, "Wrong match count"); + Assert.AreEqual(0, res[0].Matches[0].FirstCharIndex, "Wrong first char index"); + Assert.AreEqual(0, res[0].Matches[0].WordIndex, "Wrong word index"); + Assert.AreEqual("todo", res[0].Matches[0].Text, "Wrong word text"); + } + + [Test] + public void Search_Basic_MultipleWords() { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + // The mocked document has a default content + IDocument doc1 = MockDocument("Doc1", "Document 1", "ptdoc", DateTime.Now); + + if(imIndex != null) imIndex.IndexChanged += AutoHandlerForDocumentStorage; + + index.StoreDocument(doc1, null, "", null); + + SearchResultCollection res = index.Search(new SearchParameters("this content")); + + Assert.AreEqual(1, res.Count, "Wrong result count"); + Assert.AreEqual(100F, res[0].Relevance.Value, 0.01F, "Wrong relevance value"); + Assert.IsTrue(AreDocumentsEqual(doc1, res[0].Document), "Wrong document"); + Assert.AreEqual(2, res[0].Matches.Count, "Wrong matches count"); + Assert.AreEqual(0, res[0].Matches[0].FirstCharIndex, "Wrong start index for match at position 0"); + Assert.AreEqual(4, res[0].Matches[0].Text.Length, "Wrong length for match at position 0"); + Assert.AreEqual(13, res[0].Matches[1].FirstCharIndex, "Wrong start index for match at position 1"); + Assert.AreEqual(7, res[0].Matches[1].Text.Length, "Wrong length for match at position 1"); + } + + [Test] + public void Search_Filtered() { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + // The mocked document has a default content + IDocument doc1 = MockDocument("Doc1", "Document 1", "ptdoc", DateTime.Now); + IDocument doc2 = MockDocument("Doc2", "Document 2", "htmldoc", DateTime.Now); + IDocument doc3 = MockDocument("Doc3", "Document 3", "odoc", DateTime.Now); + + if(imIndex != null) imIndex.IndexChanged += AutoHandlerForDocumentStorage; + + index.StoreDocument(doc1, null, "", null); + index.StoreDocument(doc2, null, "", null); + index.StoreDocument(doc3, null, "", null); + + SearchResultCollection res = index.Search(new SearchParameters("this", "ptdoc", "htmldoc")); + + Assert.AreEqual(2, res.Count, "Wrong result count"); + + // Matches are in unpredictable order + bool doc1Found = false, doc2Found = false, doc3Found = false; + foreach(SearchResult r in res) { + if(AreDocumentsEqual(r.Document, doc1)) doc1Found = true; + else if(AreDocumentsEqual(r.Document, doc2)) doc2Found = true; + else if(AreDocumentsEqual(r.Document, doc3)) doc3Found = true; + + Assert.AreEqual(1, r.Matches.Count, "Wrong match count"); + Assert.AreEqual(0, r.Matches[0].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(4, r.Matches[0].Text.Length, "Wrong length"); + Assert.AreEqual(50F, r.Relevance.Value, 0.01F, "Wrong relevance value"); + } + + Assert.IsTrue(doc1Found, "Doc1 not found in results"); + Assert.IsTrue(doc2Found, "Doc2 not found in results"); + Assert.IsFalse(doc3Found, "Doc3 found in results"); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void Search_Filtered_EmptyDocumentTags() { + IIndex index = GetIndex(); + index.Search(new SearchParameters("hello", new string[0])); + } + + [Test] + public void Search_WithOptions_AtLeastOneWord() { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + IDocument doc1 = MockDocument("Doc1", "Document 1", "ptdoc", DateTime.Now); + + if(imIndex != null) imIndex.IndexChanged += AutoHandlerForDocumentStorage; + + index.StoreDocument(doc1, null, "", null); + + SearchResultCollection res = index.Search(new SearchParameters("this content", SearchOptions.AllWords)); + + Assert.AreEqual(1, res.Count, "Wrong result count"); + Assert.AreEqual(0, res[0].Matches[0].FirstCharIndex, "Wrong start index for 'this'"); + Assert.AreEqual(4, res[0].Matches[0].Text.Length, "Wrong length for 'index'"); + Assert.AreEqual(13, res[0].Matches[1].FirstCharIndex, "Wrong start index for 'content'"); + Assert.AreEqual(7, res[0].Matches[1].Text.Length, "Wrong length for 'content'"); + + res = index.Search(new SearchParameters("this stuff", SearchOptions.AtLeastOneWord)); + + Assert.AreEqual(1, res.Count, "Wrong result count"); + } + + [TestCase("content", 1)] + [TestCase("this content", 1)] + [TestCase("this stuff", 0)] + [TestCase("blah", 0)] + public void Search_WithOptions_AllWords(string query, int results) { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + IDocument doc1 = MockDocument("Doc1", "Document 1", "ptdoc", DateTime.Now); + + if(imIndex != null) imIndex.IndexChanged += AutoHandlerForDocumentStorage; + + index.StoreDocument(doc1, null, "", null); + + SearchResultCollection res = index.Search(new SearchParameters(query, SearchOptions.AllWords)); + + Assert.AreEqual(results, res.Count, "Wrong result count"); + } + + [TestCase("content", 1)] + [TestCase("this is some content", 1)] + [TestCase("THIS SOME content is", 0)] + [TestCase("this is test content", 0)] + [TestCase("blah", 0)] + public void Search_WithOptions_ExactPhrase(string query, int results) { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + IDocument doc1 = MockDocument("Doc1", "Document 1", "ptdoc", DateTime.Now); + + if(imIndex != null) imIndex.IndexChanged += AutoHandlerForDocumentStorage; + + index.StoreDocument(doc1, null, "", null); + + SearchResultCollection res = index.Search(new SearchParameters(query, SearchOptions.ExactPhrase)); + + Assert.AreEqual(results, res.Count, "Wrong result count"); + } + + [Test] + public void Search_ExactPhrase() { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + IDocument doc = MockDocument4("Doc", "Document", "ptdoc", DateTime.Now); + + if(imIndex != null) imIndex.IndexChanged += AutoHandlerForDocumentStorage; + + index.StoreDocument(doc, null, "", null); + + SearchResultCollection res = index.Search(new SearchParameters("content repeated content blah blah", SearchOptions.ExactPhrase)); + Assert.AreEqual(0, res.Count, "Wrong result count"); + + res = index.Search(new SearchParameters("repeated content", SearchOptions.ExactPhrase)); + Assert.AreEqual(1, res.Count, "Wrong result count"); + } + + [Test] + public void Search_Basic_Location() { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + IDocument doc1 = MockDocument("Doc1", "Document 1", "ptdoc", DateTime.Now); + + if(imIndex != null) imIndex.IndexChanged += AutoHandlerForDocumentStorage; + + index.StoreDocument(doc1, new string[] { "development" }, "", null); + + SearchResultCollection res = index.Search(new SearchParameters("document")); + + Assert.AreEqual(1, res.Count, "Wrong result count"); + Assert.AreEqual(1, res[0].Matches.Count, "Wrong matches count"); + Assert.AreEqual(WordLocation.Title, res[0].Matches[0].Location, "Wrong location"); + + res = index.Search(new SearchParameters("content")); + + Assert.AreEqual(1, res.Count, "Wrong result count"); + Assert.AreEqual(1, res[0].Matches.Count, "Wrong matches count"); + Assert.AreEqual(WordLocation.Content, res[0].Matches[0].Location, "Wrong location"); + + res = index.Search(new SearchParameters("development")); + + Assert.AreEqual(1, res.Count, "Wrong result count"); + Assert.AreEqual(1, res[0].Matches.Count, "Wrong matches count"); + Assert.AreEqual(WordLocation.Keywords, res[0].Matches[0].Location, "Wrong location"); + + IDocument doc2 = MockDocument2("Doc2", "Text 2", "ptdoc", DateTime.Now); + index.StoreDocument(doc2, new string[] { "document" }, "", null); + + res = index.Search(new SearchParameters("document")); + + Assert.AreEqual(2, res.Count, "Wrong result count"); + Assert.AreEqual(1, res[0].Matches.Count, "Wrong matches count"); + Assert.AreEqual(1, res[1].Matches.Count, "Wrong matches count"); + } + + [Test] + public void Search_Basic_LocationRelevance_1() { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + IDocument doc1 = MockDocument("Doc1", "Document 1", "ptdoc", DateTime.Now); + IDocument doc2 = MockDocument2("Doc2", "Text 2", "ptdoc", DateTime.Now); + + if(imIndex != null) imIndex.IndexChanged += AutoHandlerForDocumentStorage; + + index.StoreDocument(doc1, null, "", null); + index.StoreDocument(doc2, null, "", null); + + // "dummy" is only present in the content of doc2 + // "document" is only present in the title of doc1 + SearchResultCollection res = index.Search(new SearchParameters("dummy document")); + + Assert.AreEqual(2, res.Count, "Wrong result count"); + Assert.AreEqual(1, res[0].Matches.Count, "Wrong matches count"); + Assert.AreEqual(1, res[1].Matches.Count, "Wrong matches count"); + foreach(SearchResult r in res) { + if(r.Matches[0].Location == WordLocation.Content) Assert.AreEqual(33.3, r.Relevance.Value, 0.1, "Wrong relevance for content"); + else if(r.Matches[0].Location == WordLocation.Title) Assert.AreEqual(66.6, r.Relevance.Value, 0.1, "Wrong relevance for title"); + } + } + + [Test] + public void Search_Basic_LocationRelevance_2() { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + IDocument doc1 = MockDocument("Doc1", "Document 1", "ptdoc", DateTime.Now); + IDocument doc2 = MockDocument2("Doc2", "Text 2", "ptdoc", DateTime.Now); + + if(imIndex != null) imIndex.IndexChanged += AutoHandlerForDocumentStorage; + + index.StoreDocument(doc1, null, "", null); + index.StoreDocument(doc2, new string[] { "blah" }, "", null); + + // "blah" is only present in the keywords of doc2 + // "content" is only present in the content of doc1 + SearchResultCollection res = index.Search(new SearchParameters("content blah")); + + Assert.AreEqual(2, res.Count, "Wrong result count"); + Assert.AreEqual(1, res[0].Matches.Count, "Wrong matches count"); + Assert.AreEqual(1, res[1].Matches.Count, "Wrong matches count"); + foreach(SearchResult r in res) { + if(r.Matches[0].Location == WordLocation.Content) Assert.AreEqual(40.0, r.Relevance.Value, 0.1, "Wrong relevance for content"); + else if(r.Matches[0].Location == WordLocation.Keywords) Assert.AreEqual(60.0, r.Relevance.Value, 0.1, "Wrong relevance for keywords"); + } + } + + [Test] + public void Search_Basic_LocationRelevance_3() { + IIndex index = GetIndex(); + IInMemoryIndex imIndex = index as IInMemoryIndex; + + IDocument doc1 = MockDocument("Doc1", "Document 1", "ptdoc", DateTime.Now); + IDocument doc2 = MockDocument2("Doc2", "Text 2", "ptdoc", DateTime.Now); + + if(imIndex != null) imIndex.IndexChanged += AutoHandlerForDocumentStorage; + + index.StoreDocument(doc1, null, "", null); + index.StoreDocument(doc2, new string[] { "blah" }, "", null); + + // "blah" is only present in the keywords of doc2 + // "document" is only present in the title of doc1 + SearchResultCollection res = index.Search(new SearchParameters("document blah")); + + Assert.AreEqual(2, res.Count, "Wrong result count"); + Assert.AreEqual(1, res[0].Matches.Count, "Wrong matches count"); + Assert.AreEqual(1, res[1].Matches.Count, "Wrong matches count"); + foreach(SearchResult r in res) { + if(r.Matches[0].Location == WordLocation.Keywords) Assert.AreEqual(42.8, r.Relevance.Value, 0.1, "Wrong relevance for content"); + else if(r.Matches[0].Location == WordLocation.Title) Assert.AreEqual(57.1, r.Relevance.Value, 0.1, "Wrong relevance for keywords"); + } + } + + } + +} diff --git a/SearchEngine-Tests/IndexChangedEventArgsTests.cs b/SearchEngine-Tests/IndexChangedEventArgsTests.cs new file mode 100644 index 0000000..0e9c68d --- /dev/null +++ b/SearchEngine-Tests/IndexChangedEventArgsTests.cs @@ -0,0 +1,48 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class IndexChangedEventArgsTests : TestsBase { + + [Test] + public void Constructor() { + IDocument doc = MockDocument("Doc", "Document", "ptdoc", DateTime.Now); + DumpedChange change = new DumpedChange(new DumpedDocument(doc), new List(), + new List(new DumpedWordMapping[] { new DumpedWordMapping(1, 1, 1, 1, 1) })); + + IndexChangedEventArgs args = new IndexChangedEventArgs(doc, IndexChangeType.DocumentAdded, change, null); + + Assert.AreSame(doc, args.Document, "Invalid document instance"); + Assert.AreEqual(IndexChangeType.DocumentAdded, args.Change, "Wrong change"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_NullDocument() { + IDocument doc = MockDocument("Doc", "Document", "ptdoc", DateTime.Now); + DumpedChange change = new DumpedChange(new DumpedDocument(doc), new List(), + new List(new DumpedWordMapping[] { new DumpedWordMapping(1, 1, 1, 1, 1) })); + + IndexChangedEventArgs args = new IndexChangedEventArgs(null, IndexChangeType.DocumentAdded, change, null); + } + + [Test] + public void Constructor_IndexCleared() { + IndexChangedEventArgs args = new IndexChangedEventArgs(null, IndexChangeType.IndexCleared, null, null); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_NullChangeData() { + IndexChangedEventArgs args = new IndexChangedEventArgs( + MockDocument("Doc", "Document", "ptdoc", DateTime.Now), IndexChangeType.DocumentAdded, null, null); + } + + } + +} diff --git a/SearchEngine-Tests/OccurrenceDictionaryTests.cs b/SearchEngine-Tests/OccurrenceDictionaryTests.cs new file mode 100644 index 0000000..c38f0b1 --- /dev/null +++ b/SearchEngine-Tests/OccurrenceDictionaryTests.cs @@ -0,0 +1,394 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class OccurrenceDictionaryTests : TestsBase { + + [Test] + public void Constructor_NoCapacity() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + Assert.AreEqual(0, dic.Count, "Wrong count (Dictionary should be empty)"); + } + + [Test] + public void Constructor_WithCapacity() { + OccurrenceDictionary dic = new OccurrenceDictionary(10); + Assert.AreEqual(0, dic.Count, "Wrong count (Dictionary should be empty)"); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void Constructor_InvalidCapacity() { + OccurrenceDictionary dic = new OccurrenceDictionary(-1); + } + + [Test] + public void Add_KV() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.Add(MockDocument("Doc1", "Doc 1", "d", DateTime.Now), new SortedBasicWordInfoSet()); + Assert.AreEqual(1, dic.Count, "Wrong count"); + dic.Add(MockDocument("Doc2", "Doc 2", "d", DateTime.Now), new SortedBasicWordInfoSet()); + Assert.AreEqual(2, dic.Count, "Wrong count"); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void Add_KV_ExistingKey() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.Add(MockDocument("Doc1", "Doc 1", "d", DateTime.Now), new SortedBasicWordInfoSet()); + dic.Add(MockDocument("Doc1", "Doc 2", "d2", DateTime.Now.AddHours(1)), new SortedBasicWordInfoSet()); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Add_KV_NullKey() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.Add(null, new SortedBasicWordInfoSet()); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Add_KV_NullValue() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.Add(MockDocument("Doc1", "Doc 1", "d", DateTime.Now), null); + } + + [Test] + public void Add_Pair() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.Add(new KeyValuePair( + MockDocument("Doc1", "Doc 1", "d", DateTime.Now), new SortedBasicWordInfoSet())); + Assert.AreEqual(1, dic.Count, "Wrong count"); + dic.Add(MockDocument("Doc2", "Doc 2", "d", DateTime.Now), new SortedBasicWordInfoSet()); + Assert.AreEqual(2, dic.Count, "Wrong count"); + } + + [Test] + public void ContainsKey() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + IDocument doc = MockDocument("Doc", "Doc", "d", DateTime.Now); + Assert.IsFalse(dic.ContainsKey(doc), "ContainsKey should return false"); + dic.Add(doc, new SortedBasicWordInfoSet()); + Assert.IsTrue(dic.ContainsKey(doc), "ContainsKey should return true"); + Assert.IsFalse(dic.ContainsKey(MockDocument("Doc2", "Doc 2", "d", DateTime.Now)), "ContainsKey should return false"); + + IDocument doc2 = MockDocument("Doc", "Doc", "d", DateTime.Now); + Assert.IsTrue(dic.ContainsKey(doc2), "ContainsKey should return true"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ContainsKey_NullKey() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.ContainsKey(null); + } + + [Test] + public void Keys() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + IDocument doc1 = MockDocument("Doc1", "Doc1", "d", DateTime.Now); + IDocument doc2 = MockDocument("Doc2", "Doc2", "d", DateTime.Now); + dic.Add(doc1, new SortedBasicWordInfoSet()); + dic.Add(doc2, new SortedBasicWordInfoSet()); + + Assert.AreEqual(2, dic.Keys.Count, "Wrong key count"); + + bool doc1Found = false, doc2Found = false; + foreach(IDocument d in dic.Keys) { + if(d.Name == "Doc1") doc1Found = true; + if(d.Name == "Doc2") doc2Found = true; + } + + Assert.IsTrue(doc1Found, "Doc1 not found"); + Assert.IsTrue(doc2Found, "Doc2 not found"); + } + + [Test] + public void Remove_KV() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + set.Add(new BasicWordInfo(5, 0, WordLocation.Content)); + dic.Add(MockDocument("Doc1", "Doc1", "d", DateTime.Now), set); + dic.Add(MockDocument("Doc2", "Doc2", "d", DateTime.Now), new SortedBasicWordInfoSet()); + Assert.AreEqual(2, dic.Count, "Wrong initial count"); + Assert.IsFalse(dic.Remove(MockDocument("Doc3", "Doc3", "d", DateTime.Now)), "Remove should return false"); + Assert.IsTrue(dic.Remove(MockDocument("Doc1", "Doc1", "d", DateTime.Now)), "Remove should return true"); + Assert.AreEqual(1, dic.Count, "Wrong count"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Remove_KV_NullKey() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.Remove(null as IDocument); + } + + [Test] + public void Remove_Pair() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.Add(MockDocument("Doc1", "Doc1", "d", DateTime.Now), new SortedBasicWordInfoSet()); + dic.Add(MockDocument("Doc2", "Doc2", "d", DateTime.Now), new SortedBasicWordInfoSet()); + Assert.AreEqual(2, dic.Count, "Wrong initial count"); + Assert.IsFalse(dic.Remove( + new KeyValuePair(MockDocument("Doc3", "Doc3", "d", DateTime.Now), new SortedBasicWordInfoSet())), + "Remove should return false"); + Assert.IsTrue(dic.Remove( + new KeyValuePair(MockDocument("Doc2", "Doc2", "d", DateTime.Now), new SortedBasicWordInfoSet())), + "Remove should return true"); + Assert.AreEqual(1, dic.Count, "Wrong count"); + } + + [Test] + public void RemoveExtended() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + SortedBasicWordInfoSet set1 = new SortedBasicWordInfoSet(); + set1.Add(new BasicWordInfo(5, 0, WordLocation.Content)); + set1.Add(new BasicWordInfo(12, 1, WordLocation.Keywords)); + SortedBasicWordInfoSet set2 = new SortedBasicWordInfoSet(); + set2.Add(new BasicWordInfo(1, 0, WordLocation.Content)); + set2.Add(new BasicWordInfo(4, 1, WordLocation.Title)); + dic.Add(MockDocument("Doc1", "Doc", "doc", DateTime.Now), set1); + dic.Add(MockDocument("Doc2", "Doc", "doc", DateTime.Now), set2); + + List dm = dic.RemoveExtended(MockDocument("Doc1", "Doc", "doc", DateTime.Now), 1); + Assert.AreEqual(2, dm.Count, "Wrong count"); + + Assert.IsTrue(dm.Find(delegate(DumpedWordMapping m) { + return m.WordID == 1 && m.DocumentID == 1 && + m.FirstCharIndex == 5 && m.WordIndex == 0 && + m.Location == WordLocation.Content.Location; + }) != null, "Mapping not found"); + + Assert.IsTrue(dm.Find(delegate(DumpedWordMapping m) { + return m.WordID == 1 && m.DocumentID == 1 && + m.FirstCharIndex == 12 && m.WordIndex == 1 && + m.Location == WordLocation.Keywords.Location; + }) != null, "Mapping not found"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveExtended_NullDocument() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.RemoveExtended(null, 1); + } + + [Test] + public void TryGetValue() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + IDocument doc1 = MockDocument("Doc1", "Doc1", "d", DateTime.Now); + IDocument doc2 = MockDocument("Doc2", "Doc2", "d", DateTime.Now); + + SortedBasicWordInfoSet set = null; + + Assert.IsFalse(dic.TryGetValue(doc1, out set), "TryGetValue should return false"); + Assert.IsNull(set, "Set should be null"); + + dic.Add(doc1, new SortedBasicWordInfoSet()); + Assert.IsTrue(dic.TryGetValue(MockDocument("Doc1", "Doc1", "d", DateTime.Now), out set), "TryGetValue should return true"); + Assert.IsNotNull(set, "Set should not be null"); + + Assert.IsFalse(dic.TryGetValue(doc2, out set), "TryGetValue should return false"); + Assert.IsNull(set, "Set should have been set to null"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void TryGetValue_NullKey() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + SortedBasicWordInfoSet set = null; + dic.TryGetValue(null, out set); + } + + [Test] + public void Values() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + IDocument doc1 = MockDocument("Doc1", "Doc1", "d", DateTime.Now); + IDocument doc2 = MockDocument("Doc2", "Doc2", "d", DateTime.Now); + SortedBasicWordInfoSet set1 = new SortedBasicWordInfoSet(); + set1.Add(new BasicWordInfo(0, 0, WordLocation.Content)); + SortedBasicWordInfoSet set2 = new SortedBasicWordInfoSet(); + set2.Add(new BasicWordInfo(1, 1, WordLocation.Title)); + dic.Add(doc1, set1); + dic.Add(doc2, set2); + + Assert.AreEqual(2, dic.Values.Count, "Wrong value count"); + + bool set1Found = false, set2Found = false; + foreach(SortedBasicWordInfoSet set in dic.Values) { + if(set[0].FirstCharIndex == 0) set1Found = true; + if(set[0].FirstCharIndex == 1) set2Found = true; + } + + Assert.IsTrue(set1Found, "Set1 not found"); + Assert.IsTrue(set2Found, "Set2 not found"); + } + + [Test] + public void Indexer_Get() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + IDocument doc1 = MockDocument("Doc1", "Doc1", "d", DateTime.Now); + SortedBasicWordInfoSet set1 = new SortedBasicWordInfoSet(); + set1.Add(new BasicWordInfo(1, 1, WordLocation.Content)); + + dic.Add(doc1, set1); + + SortedBasicWordInfoSet output = dic[MockDocument("Doc1", "Doc1", "d", DateTime.Now)]; + Assert.IsNotNull(output, "Output should not be null"); + Assert.AreEqual(1, set1.Count, "Wrong count"); + Assert.AreEqual(1, set1[0].FirstCharIndex, "Wrong first char index"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Indexer_Get_NullIndex() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + SortedBasicWordInfoSet set = dic[null]; + } + + [Test] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void Indexer_Get_InexistentIndex() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + SortedBasicWordInfoSet set = dic[MockDocument("Doc", "Doc", "d", DateTime.Now)]; + } + + [Test] + public void Indexer_Set() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.Add(MockDocument("Doc1", "Doc1", "d", DateTime.Now), new SortedBasicWordInfoSet()); + + SortedBasicWordInfoSet set1 = new SortedBasicWordInfoSet(); + set1.Add(new BasicWordInfo(1, 1, WordLocation.Content)); + + dic[MockDocument("Doc1", "Doc1", "d", DateTime.Now)] = set1; + + SortedBasicWordInfoSet output = dic[MockDocument("Doc1", "Doc1", "d", DateTime.Now)]; + Assert.AreEqual(1, output.Count, "Wrong count"); + Assert.AreEqual(1, output[0].FirstCharIndex, "Wrong first char index"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Indexer_Set_NullIndex() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic[null] = new SortedBasicWordInfoSet(); + } + + [Test] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void Indexer_Set_InexistentIndex() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic[MockDocument("Doc", "Doc", "d", DateTime.Now)] = new SortedBasicWordInfoSet(); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Indexer_Set_NullValue() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.Add(MockDocument("Doc1", "Doc1", "d", DateTime.Now), new SortedBasicWordInfoSet()); + dic[MockDocument("Doc1", "Doc1", "d", DateTime.Now)] = null; + } + + [Test] + public void Clear() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.Add(MockDocument("Doc1", "Doc1", "d", DateTime.Now), new SortedBasicWordInfoSet()); + dic.Clear(); + Assert.AreEqual(0, dic.Count, "Wrong count"); + } + + [Test] + public void Contains() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + IDocument doc = MockDocument("Doc", "Doc", "d", DateTime.Now); + Assert.IsFalse(dic.Contains(new KeyValuePair(doc, new SortedBasicWordInfoSet())), "Contains should return false"); + dic.Add(doc, new SortedBasicWordInfoSet()); + Assert.IsTrue(dic.Contains(new KeyValuePair(doc, new SortedBasicWordInfoSet())), "Contains should return true"); + Assert.IsFalse(dic.Contains(new KeyValuePair(MockDocument("Doc2", "Doc 2", "d", DateTime.Now), new SortedBasicWordInfoSet())), "Contains should return false"); + + IDocument doc2 = MockDocument("Doc", "Doc", "d", DateTime.Now); + Assert.IsTrue(dic.Contains(new KeyValuePair(doc, new SortedBasicWordInfoSet())), "Contains should return true"); + } + + [Test] + public void IsReadOnly() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + Assert.IsFalse(dic.IsReadOnly, "IsReadOnly should always return false"); + } + + [Test] + public void CopyTo() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + set.Add(new BasicWordInfo(1, 1, WordLocation.Title)); + dic.Add(MockDocument("Doc", "Doc", "d", DateTime.Now), set); + KeyValuePair[] array = new KeyValuePair[1]; + dic.CopyTo(array, 0); + + Assert.IsNotNull(array[0], "Array[0] should not be null"); + Assert.AreEqual("Doc", array[0].Key.Name, "Wrong array item"); + Assert.AreEqual(1, array[0].Value.Count, "Wrong count"); + Assert.AreEqual(1, array[0].Value[0].FirstCharIndex, "Wrong first char index"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void CopyTo_NullArray() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.CopyTo(null, 0); + } + + [Test] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void CopyTo_ShortArray() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.Add(MockDocument("Doc", "Doc", "d", DateTime.Now), new SortedBasicWordInfoSet()); + KeyValuePair[] array = new KeyValuePair[0]; + dic.CopyTo(array, 0); + } + + [Test] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void CopyTo_InvalidIndex_Negative() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.Add(MockDocument("Doc", "Doc", "d", DateTime.Now), new SortedBasicWordInfoSet()); + KeyValuePair[] array = new KeyValuePair[1]; + dic.CopyTo(array, -1); + } + + [Test] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void CopyTo_InvalidIndex_TooBig() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + dic.Add(MockDocument("Doc", "Doc", "d", DateTime.Now), new SortedBasicWordInfoSet()); + KeyValuePair[] array = new KeyValuePair[1]; + dic.CopyTo(array, 1); + } + + [Test] + public void GetEnumerator() { + OccurrenceDictionary dic = new OccurrenceDictionary(); + IDocument doc1 = MockDocument("Doc1", "Doc1", "d", DateTime.Now); + IDocument doc2 = MockDocument("Doc2", "Doc2", "d", DateTime.Now); + dic.Add(doc1, new SortedBasicWordInfoSet()); + dic.Add(doc2, new SortedBasicWordInfoSet()); + + Assert.IsNotNull(dic.GetEnumerator(), "GetEnumerator should not return null"); + + int count = 0; + foreach(KeyValuePair pair in dic) { + count++; + } + + Assert.AreEqual(2, count, "Wrong count"); + } + + } + +} diff --git a/SearchEngine-Tests/Properties/AssemblyInfo.cs b/SearchEngine-Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6df285b --- /dev/null +++ b/SearchEngine-Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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("ScrewTurn Wiki Search Engine Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// 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("092d54d9-f657-4a6f-a28d-e7e0fd55f2b3")] diff --git a/SearchEngine-Tests/RelevanceTests.cs b/SearchEngine-Tests/RelevanceTests.cs new file mode 100644 index 0000000..050fdf0 --- /dev/null +++ b/SearchEngine-Tests/RelevanceTests.cs @@ -0,0 +1,119 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class RelevanceTests { + + [Test] + public void Constructor() { + Relevance rel = new Relevance(); + Assert.AreEqual(0, rel.Value, "Wrong value"); + Assert.IsFalse(rel.IsFinalized, "Value should not be finalized"); + } + + [Test] + public void Constructor_WithValue() { + Relevance rel = new Relevance(5); + Assert.AreEqual(5, rel.Value, "Wrong value"); + Assert.IsFalse(rel.IsFinalized, "Value should not be finalized"); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void Constructor_InvalidValue() { + Relevance rel = new Relevance(-1); + } + + [Test] + public void SetValue() { + Relevance rel = new Relevance(); + rel.SetValue(8); + Assert.AreEqual(8, rel.Value, "Wrong value"); + Assert.IsFalse(rel.IsFinalized, "Value should not be finalized"); + rel.SetValue(14); + Assert.AreEqual(14, rel.Value, "Wrong value"); + Assert.IsFalse(rel.IsFinalized, "Value should not be finalized"); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void SetValue_InvalidValue() { + Relevance rel = new Relevance(); + rel.SetValue(-1); + } + + [Test] + // Underscore to avoid interference with Destructor + public void Finalize_() { + Relevance rel = new Relevance(); + rel.SetValue(12); + Assert.AreEqual(12, rel.Value, "Wrong value"); + rel.Finalize(24); + Assert.AreEqual(50, rel.Value, "Wrong finalized value"); + Assert.IsTrue(rel.IsFinalized, "Value should be finalized"); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void Finalize_InvalidFactor() { + Relevance rel = new Relevance(); + rel.SetValue(8); + rel.Finalize(-1); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void Finalize_AlreadyFinalized() { + Relevance rel = new Relevance(); + rel.SetValue(8); + rel.Finalize(0.5F); + rel.Finalize(1); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void Finalize_InvalidTotal() { + Relevance rel = new Relevance(2); + rel.Finalize(-1); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void SetValue_AfterFinalize() { + Relevance rel = new Relevance(); + rel.SetValue(5); + rel.Finalize(12); + rel.SetValue(8); + } + + [Test] + public void NormalizeAfterFinalization() { + Relevance rel = new Relevance(8); + rel.Finalize(16); + rel.NormalizeAfterFinalization(0.5F); + Assert.AreEqual(25, rel.Value, 0.1, "Wrong value"); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void NormalizeAfterFinalization_InvalidFactor() { + Relevance rel = new Relevance(8); + rel.Finalize(16); + rel.NormalizeAfterFinalization(-0.5F); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void NormalizeAfterFinalization_BeforeFinalize() { + Relevance rel = new Relevance(8); + rel.NormalizeAfterFinalization(0.5F); + } + + } + +} diff --git a/SearchEngine-Tests/SearchEngine-Tests.csproj b/SearchEngine-Tests/SearchEngine-Tests.csproj new file mode 100644 index 0000000..ccdbace --- /dev/null +++ b/SearchEngine-Tests/SearchEngine-Tests.csproj @@ -0,0 +1,94 @@ + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {07625628-842E-4CAA-A029-4D6852C7CA20} + Library + Properties + ScrewTurn.Wiki.SearchEngine.Tests + ScrewTurn.Wiki.SearchEngine.Tests + + + 2.0 + + + v3.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + none + true + bin\Release\ + TRACE + prompt + 4 + true + + + + False + ..\References\Tools\NUnit\framework\nunit.framework.dll + + + False + ..\References\Tools\Rhino.Mocks\Rhino.Mocks.dll + + + + 3.5 + + + + + + + AssemblyVersion.cs + + + + + + + + + + + + + + + + + + + + + + + + + + {2DF980A6-4742-49B1-A090-DE79314644D0} + SearchEngine + + + + + \ No newline at end of file diff --git a/SearchEngine-Tests/SearchParametersTests.cs b/SearchEngine-Tests/SearchParametersTests.cs new file mode 100644 index 0000000..0260131 --- /dev/null +++ b/SearchEngine-Tests/SearchParametersTests.cs @@ -0,0 +1,114 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class SearchParametersTests { + + [Test] + public void Constructor_QueryOnly() { + SearchParameters par = new SearchParameters("query"); + Assert.AreEqual("query", par.Query, "Wrong query"); + Assert.IsNull(par.DocumentTypeTags, "DocumentTypeTags should be null"); + Assert.AreEqual(SearchOptions.AtLeastOneWord, par.Options); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_QueryOnly_InvalidQuery(string q) { + SearchParameters par = new SearchParameters(q); + } + + [Test] + public void Constructor_QueryDocumentTypeTags() { + SearchParameters par = new SearchParameters("query", "blah", "doc"); + Assert.AreEqual("query", par.Query, "Wrong query"); + Assert.AreEqual(2, par.DocumentTypeTags.Length, "Wrong DocumentTypeTag count"); + Assert.AreEqual("blah", par.DocumentTypeTags[0], "Wrong type tag"); + Assert.AreEqual("doc", par.DocumentTypeTags[1], "Wrong type tag"); + Assert.AreEqual(SearchOptions.AtLeastOneWord, par.Options); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_QueryDocumentTypeTags_InvalidQuery(string q) { + SearchParameters par = new SearchParameters(q, "blah", "doc"); + } + + [Test] + public void Constructor_QueryDocumentTypeTags_NullDocumentTypeTags() { + SearchParameters par = new SearchParameters("query", null); + Assert.AreEqual("query", par.Query, "Wrong query"); + Assert.IsNull(par.DocumentTypeTags, "DocumentTypeTags should be null"); + Assert.AreEqual(SearchOptions.AtLeastOneWord, par.Options); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void Constructor_QueryDocumentTypeTags_EmptyDocumentTypeTags() { + SearchParameters par = new SearchParameters("query", new string[0]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_QueryDocumentTypeTags_InvalidDocumentTypeTagsElement(string e) { + SearchParameters par = new SearchParameters("query", new string[] { "blah", e }); + } + + [Test] + public void Constructor_QueryOptions() { + SearchParameters par = new SearchParameters("query", SearchOptions.ExactPhrase); + Assert.AreEqual("query", par.Query, "Wrong query"); + Assert.IsNull(par.DocumentTypeTags, "DocumentTypeTags should be null"); + Assert.AreEqual(SearchOptions.ExactPhrase, par.Options); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_QueryOptions_InvalidQuery(string q) { + SearchParameters par = new SearchParameters(q, SearchOptions.ExactPhrase); + } + + [Test] + public void Constructor_Full() { + SearchParameters par = new SearchParameters("query", new string[] { "blah", "doc" }, SearchOptions.AllWords); + Assert.AreEqual("query", par.Query, "Wrong query"); + Assert.AreEqual(2, par.DocumentTypeTags.Length, "Wrong DocumentTypeTag count"); + Assert.AreEqual("blah", par.DocumentTypeTags[0], "Wrong type tag"); + Assert.AreEqual("doc", par.DocumentTypeTags[1], "Wrong type tag"); + Assert.AreEqual(SearchOptions.AllWords, par.Options); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_Full_InvalidQuery(string q) { + SearchParameters par = new SearchParameters(q, new string[] { "blah", "doc" }, SearchOptions.AllWords); + } + + [Test] + public void Constructor_Full_NullDocumentTypeTags() { + SearchParameters par = new SearchParameters("query", null, SearchOptions.AllWords); + Assert.AreEqual("query", par.Query, "Wrong query"); + Assert.IsNull(par.DocumentTypeTags, "DocumentTypeTags should be null"); + Assert.AreEqual(SearchOptions.AllWords, par.Options); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void Constructor_Full_EmptyDocumentTypeTags() { + SearchParameters par = new SearchParameters("query", new string[0], SearchOptions.AllWords); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void Constructor_Full_InvalidDocumentTypeTagsElement(string e) { + SearchParameters par = new SearchParameters("query", new string[] { "blah", e }, SearchOptions.ExactPhrase); + } + + } + +} diff --git a/SearchEngine-Tests/SearchResultCollectionTests.cs b/SearchEngine-Tests/SearchResultCollectionTests.cs new file mode 100644 index 0000000..c65d6ee --- /dev/null +++ b/SearchEngine-Tests/SearchResultCollectionTests.cs @@ -0,0 +1,287 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class SearchResultCollectionTests : TestsBase { + + [Test] + public void Constructor_NoCapacity() { + SearchResultCollection collection = new SearchResultCollection(); + Assert.AreEqual(0, collection.Count, "Wrong count (collection should be empty)"); + } + + [Test] + public void Constructor_WithCapacity() { + SearchResultCollection collection = new SearchResultCollection(15); + Assert.AreEqual(0, collection.Count, "Wrong count (collection should be empty)"); + Assert.AreEqual(15, collection.Capacity, "Wrong capacity"); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void Constructor_InvalidCapacity() { + SearchResultCollection collection = new SearchResultCollection(0); + } + + [Test] + public void AddAndCount() { + SearchResultCollection collection = new SearchResultCollection(); + + Assert.AreEqual(0, collection.Count); + + SearchResult res = new SearchResult(MockDocument("d", "d", "d", DateTime.Now)); + SearchResult res2 = new SearchResult(MockDocument("d2", "d", "d", DateTime.Now)); + + collection.Add(res); + collection.Add(res2); + Assert.AreEqual(2, collection.Count, "Wrong count (collection should contain 2 items)"); + Assert.AreEqual(res, collection[0], "Wrong item at index 0"); + Assert.AreEqual(res2, collection[1], "Wrong item at index 1"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Add_NullItem() { + SearchResultCollection collection = new SearchResultCollection(); + collection.Add(null); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void Add_DuplicateItem() { + SearchResultCollection collection = new SearchResultCollection(); + + SearchResult res = new SearchResult(MockDocument("d", "d", "d", DateTime.Now)); + SearchResult res2 = new SearchResult(MockDocument("d2", "d", "d", DateTime.Now)); + + collection.Add(res); + collection.Add(res2); + collection.Add(res); + } + + [Test] + public void Clear() { + SearchResultCollection collection = new SearchResultCollection(); + + SearchResult res = new SearchResult(MockDocument("d", "d", "d", DateTime.Now)); + + collection.Add(res); + Assert.AreEqual(1, collection.Count, "Wrong count (collection should contain 1 item)"); + + collection.Clear(); + Assert.AreEqual(0, collection.Count, "Wrong count (collection should be empty)"); + } + + [Test] + public void Contains() { + SearchResultCollection collection = new SearchResultCollection(); + + SearchResult res = new SearchResult(MockDocument("d", "d", "d", DateTime.Now)); + SearchResult res2 = new SearchResult(MockDocument("d2", "d", "d", DateTime.Now)); + + collection.Add(res); + Assert.IsTrue(collection.Contains(res), "Collection should contain item"); + Assert.IsFalse(collection.Contains(res2), "Collection should not contain item"); + + Assert.IsFalse(collection.Contains(null), "Contains should return false"); + } + + [Test] + public void GetSearchResult() { + SearchResultCollection collection = new SearchResultCollection(); + + IDocument doc1 = MockDocument("d", "d", "d", DateTime.Now); + IDocument doc2 = MockDocument("d2", "d", "d", DateTime.Now); + IDocument doc3 = MockDocument("d3", "d", "d", DateTime.Now); + SearchResult res = new SearchResult(doc1); + SearchResult res2 = new SearchResult(doc2); + + collection.Add(res); + collection.Add(res2); + + Assert.AreEqual(res, collection.GetSearchResult(doc1), "Wrong search result object"); + Assert.IsNull(collection.GetSearchResult(doc3), "GetSearchResult should return null"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void GetSearchResult_NullDocument() { + SearchResultCollection collection = new SearchResultCollection(); + collection.GetSearchResult(null); + } + + [Test] + public void CopyTo() { + SearchResultCollection collection = new SearchResultCollection(); + + SearchResult res = new SearchResult(MockDocument("d", "d", "d", DateTime.Now)); + SearchResult res2 = new SearchResult(MockDocument("d2", "d", "d", DateTime.Now)); + + collection.Add(res); + collection.Add(res2); + + SearchResult[] results = new SearchResult[2]; + collection.CopyTo(results, 0); + + Assert.AreEqual(res, results[0], "Wrong result item"); + Assert.AreEqual(res2, results[1], "Wrong result item"); + + results = new SearchResult[3]; + collection.CopyTo(results, 0); + + Assert.AreEqual(res, results[0], "Wrong result item"); + Assert.AreEqual(res2, results[1], "Wrong result item"); + Assert.IsNull(results[2], "Non-null item"); + + results = new SearchResult[3]; + collection.CopyTo(results, 1); + + Assert.IsNull(results[0], "Non-null item"); + Assert.AreEqual(res, results[1], "Wrong result item"); + Assert.AreEqual(res2, results[2], "Wrong result item"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void CopyTo_NullArray() { + SearchResultCollection collection = new SearchResultCollection(); + + collection.CopyTo(null, 0); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void CopyTo_InvalidIndex_Negative() { + SearchResultCollection collection = new SearchResultCollection(); + + SearchResult[] results = new SearchResult[10]; + + collection.CopyTo(results, -1); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void CopyTo_InvalidIndex_TooBig() { + SearchResultCollection collection = new SearchResultCollection(); + + SearchResult[] results = new SearchResult[10]; + + collection.CopyTo(results, 10); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void CopyTo_ArrayTooSmall() { + SearchResultCollection collection = new SearchResultCollection(); + + SearchResult res = new SearchResult(MockDocument("d", "d", "d", DateTime.Now)); + SearchResult res2 = new SearchResult(MockDocument("d2", "d", "d", DateTime.Now)); + + collection.Add(res); + collection.Add(res2); + + SearchResult[] results = new SearchResult[1]; + + collection.CopyTo(results, 0); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void CopyTo_NoSpaceAtIndex() { + SearchResultCollection collection = new SearchResultCollection(); + + SearchResult res = new SearchResult(MockDocument("d", "d", "d", DateTime.Now)); + SearchResult res2 = new SearchResult(MockDocument("d2", "d", "d", DateTime.Now)); + + collection.Add(res); + collection.Add(res2); + + SearchResult[] results = new SearchResult[2]; + + collection.CopyTo(results, 1); + } + + [Test] + public void ReadOnly() { + SearchResultCollection collection = new SearchResultCollection(); + Assert.IsFalse(collection.IsReadOnly); + } + + [Test] + public void Remove() { + SearchResultCollection collection = new SearchResultCollection(); + + SearchResult res = new SearchResult(MockDocument("d", "d", "d", DateTime.Now)); + SearchResult res2 = new SearchResult(MockDocument("d2", "d", "d", DateTime.Now)); + SearchResult res3 = new SearchResult(MockDocument("d3", "d", "d", DateTime.Now)); + + collection.Add(res); + collection.Add(res2); + + Assert.IsTrue(collection.Remove(res), "Remove should return true"); + Assert.IsFalse(collection.Remove(res3), "Remove should return false"); + Assert.AreEqual(1, collection.Count, "Wrong count"); + Assert.AreEqual(res2, collection[0], "Wrong item at index 0"); + + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Remove_NullItem() { + SearchResultCollection collection = new SearchResultCollection(); + collection.Remove(null); + } + + [Test] + public void GetEnumerator() { + SearchResultCollection collection = new SearchResultCollection(); + + SearchResult res = new SearchResult(MockDocument("d", "d", "d", DateTime.Now)); + SearchResult res2 = new SearchResult(MockDocument("d2", "d", "d", DateTime.Now)); + + collection.Add(res); + collection.Add(res2); + + int count = 0; + foreach(SearchResult r in collection) { + count++; + } + Assert.AreEqual(2, count, "Wrong count - enumerator does not work"); + } + + [Test] + public void Indexer() { + SearchResultCollection collection = new SearchResultCollection(); + + SearchResult res = new SearchResult(MockDocument("d", "d", "d", DateTime.Now)); + SearchResult res2 = new SearchResult(MockDocument("d2", "d", "d", DateTime.Now)); + + collection.Add(res); + collection.Add(res2); + + Assert.AreEqual(res, collection[0], "Wrong item"); + Assert.AreEqual(res2, collection[1], "Wrong item"); + } + + [Test] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void Indexer_InvalidIndex_Negative() { + SearchResultCollection collection = new SearchResultCollection(); + SearchResult i = collection[-1]; + } + + [Test] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void Indexer_InvalidIndex_TooBig() { + SearchResultCollection collection = new SearchResultCollection(); + SearchResult i = collection[1]; + } + + } + +} diff --git a/SearchEngine-Tests/SearchResultTests.cs b/SearchEngine-Tests/SearchResultTests.cs new file mode 100644 index 0000000..ffd682b --- /dev/null +++ b/SearchEngine-Tests/SearchResultTests.cs @@ -0,0 +1,30 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class SearchResultTests : TestsBase { + + [Test] + public void Constructor() { + IDocument doc = MockDocument("Doc", "Document", "ptdoc", DateTime.Now); + SearchResult res = new SearchResult(doc); + + Assert.AreEqual(doc, res.Document, "Wrong document"); + Assert.AreEqual(0, res.Relevance.Value, "Wrong initial relevance value"); + Assert.IsFalse(res.Relevance.IsFinalized, "Initial relevance value should not be finalized"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_NullDocument() { + SearchResult res = new SearchResult(null); + } + + } + +} diff --git a/SearchEngine-Tests/SortedBasicWordInfoSetTests.cs b/SearchEngine-Tests/SortedBasicWordInfoSetTests.cs new file mode 100644 index 0000000..f2904b5 --- /dev/null +++ b/SearchEngine-Tests/SortedBasicWordInfoSetTests.cs @@ -0,0 +1,129 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class SortedBasicWordInfoSetTests : TestsBase { + + [Test] + public void Constructor_Default() { + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + Assert.AreEqual(0, set.Count, "Wrong count (set should be empty)"); + } + + [Test] + public void Constructor_WithCapacity() { + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(10); + Assert.AreEqual(0, set.Count, "Wrong count (set should be empty)"); + Assert.AreEqual(10, set.Capacity, "Wrong capacity (capacity should be ensured)"); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void Constructor_InvalidCapacity() { + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(0); + } + + [Test] + public void Add_NewItem() { + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + Assert.IsTrue(set.Add(new BasicWordInfo(10, 1, WordLocation.Content)), "Add should return true (adding new item)"); + Assert.AreEqual(1, set.Count, "Wrong count (set should contain 1 item)"); + } + + [Test] + public void Add_ExistingItem() { + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + Assert.IsTrue(set.Add(new BasicWordInfo(2, 0, WordLocation.Content)), "Add should return true (adding new item)"); + Assert.AreEqual(1, set.Count, "Wrong count (set should contain 1 item)"); + Assert.IsFalse(set.Add(new BasicWordInfo(2, 0, WordLocation.Content)), "Add should return false (adding existing item)"); + Assert.AreEqual(1, set.Count, "Wrong count (set should contain 1 item)"); + } + + [Test] + public void Contains() { + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + Assert.IsFalse(set.Contains(new BasicWordInfo(1, 0, WordLocation.Content)), "Contains should return false (inexistent item)"); + Assert.IsTrue(set.Add(new BasicWordInfo(1, 0, WordLocation.Content)), "Add should return true (adding new item)"); + Assert.IsTrue(set.Contains(new BasicWordInfo(1, 0, WordLocation.Content)), "Contains should return true (item exists)"); + Assert.AreEqual(1, set.Count, "Wrong count (set should contain 1 item"); + Assert.IsFalse(set.Contains(new BasicWordInfo(10, 2, WordLocation.Content)), "Contains should return false (inexistent item)"); + } + + [Test] + public void Remove() { + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + Assert.IsFalse(set.Remove(new BasicWordInfo(1, 0, WordLocation.Content)), "Remove should return false (removing inexistent item"); + Assert.IsTrue(set.Add(new BasicWordInfo(1, 0, WordLocation.Content)), "Add should return true (adding new item)"); + Assert.AreEqual(1, set.Count, "Wrong count (set should contain 1 item)"); + Assert.IsTrue(set.Contains(new BasicWordInfo(1, 0, WordLocation.Content)), "Contains should return true (item exists)"); + Assert.IsTrue(set.Remove(new BasicWordInfo(1, 0, WordLocation.Content)), "Remove should return true (removing existing item)"); + Assert.IsFalse(set.Contains(new BasicWordInfo(1, 0, WordLocation.Content)), "Contains should return false (inexistent item)"); + Assert.AreEqual(0, set.Count, "Wrong count (set should be empty)"); + } + + [Test] + public void Clear() { + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + Assert.IsTrue(set.Add(new BasicWordInfo(10, 2, WordLocation.Content)), "Add should return true (adding new item)"); + Assert.IsTrue(set.Add(new BasicWordInfo(2, 1, WordLocation.Content)), "Add should return true (adding new item)"); + Assert.AreEqual(2, set.Count, "Wrong count (set should contain 2 items)"); + set.Clear(); + Assert.AreEqual(0, set.Count, "Wrong count (set should be empty)"); + Assert.IsFalse(set.Contains(new BasicWordInfo(10, 2, WordLocation.Content)), "Contains should return false (empty set)"); + Assert.IsFalse(set.Contains(new BasicWordInfo(2, 1, WordLocation.Content)), "Contains should return false (empty set)"); + } + + [Test] + public void GetEnumerator() { + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + Assert.IsTrue(set.Add(new BasicWordInfo(1, 0, WordLocation.Content)), "Add should return true (adding new item)"); + Assert.IsTrue(set.Add(new BasicWordInfo(3, 1, WordLocation.Content)), "Add should return true (adding new item)"); + Assert.AreEqual(2, set.Count); + int count = 0; + foreach(BasicWordInfo item in set) { + if(count == 0) Assert.AreEqual(1, item.FirstCharIndex, "Wrong start index for current item"); + if(count == 0) Assert.AreEqual(0, item.WordIndex, "Wrong word index for current item"); + if(count == 1) Assert.AreEqual(3, item.FirstCharIndex, "Wrong start index for current item"); + if(count == 1) Assert.AreEqual(1, item.WordIndex, "Wrong word index for current item"); + count++; + } + Assert.AreEqual(2, count); + } + + [Test] + public void Indexer() { + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + Assert.IsTrue(set.Add(new BasicWordInfo(1, 0, WordLocation.Content)), "Add should return true (adding new item)"); + Assert.IsTrue(set.Add(new BasicWordInfo(10, 1, WordLocation.Content)), "Add should return true (adding new item)"); + Assert.IsTrue(set.Add(new BasicWordInfo(3, 2, WordLocation.Content)), "Add should return true (adding new item)"); + Assert.AreEqual(3, set.Count); + Assert.AreEqual(1, set[0].FirstCharIndex, "Wrong start index at index 0"); + Assert.AreEqual(0, set[0].WordIndex, "Wrong word index at index 0"); + Assert.AreEqual(10, set[1].FirstCharIndex, "Wrong start index at index 1"); + Assert.AreEqual(1, set[1].WordIndex, "Wrong word index at index 1"); + Assert.AreEqual(3, set[2].FirstCharIndex, "Wrong start index at index 2"); + Assert.AreEqual(2, set[2].WordIndex, "Wrong word index at index 2"); + } + + [Test] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void Indexer_InvalidIndex_Negative() { + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + BasicWordInfo i = set[-1]; + } + + [Test] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void Indexer_InvalidIndex_TooBig() { + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + BasicWordInfo i = set[1]; + } + + } + +} diff --git a/SearchEngine-Tests/TestsBase.cs b/SearchEngine-Tests/TestsBase.cs new file mode 100644 index 0000000..2501d84 --- /dev/null +++ b/SearchEngine-Tests/TestsBase.cs @@ -0,0 +1,207 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + public class TestsBase { + + /// + /// A general purpose mock repository, initalized dusing test fixture setup. + /// + private MockRepository looseMocks; + + /// + /// Demo content for a plain-text document. + /// + protected const string PlainTextDocumentContent = "This is some content."; + + /// + /// Demo content for a plain-text document. + /// + protected const string PlainTextDocumentContent2 = "Dummy text used for testing purposes."; + + /// + /// Demo content for a plain-text document. + /// + protected const string PlainTextDocumentContent3 = "Todo."; + + /// + /// Demo content for a plain-text document. + /// + protected const string PlainTextDocumentContent4 = "Content with repeated content."; + + /// + /// The words contained in the demo content (PlainTextDocumentContent). + /// + protected WordInfo[] PlainTextDocumentWords = new WordInfo[] { + new WordInfo("This", 0, 0, WordLocation.Content), + new WordInfo("is", 5, 1, WordLocation.Content), + new WordInfo("some", 8, 2, WordLocation.Content), + new WordInfo("content", 13, 3, WordLocation.Content) + }; + + /// + /// The words contained in the demo content (PlainTextDocumentContent2). + /// + protected WordInfo[] PlainTextDocumentWords2 = new WordInfo[] { + new WordInfo("Dummy", 0, 0, WordLocation.Content), + new WordInfo("text", 6, 1, WordLocation.Content), + new WordInfo("used", 11, 2, WordLocation.Content), + new WordInfo("for", 16, 3, WordLocation.Content), + new WordInfo("testing", 20, 4, WordLocation.Content), + new WordInfo("purposes", 28, 5, WordLocation.Content) + }; + + /// + /// The words contained in the demo content (PlainTextDocumentContent3). + /// + protected WordInfo[] PlainTextDocumentWords3 = new WordInfo[] { + new WordInfo("Todo", 0, 0, WordLocation.Content) + }; + + /// + /// The words contained in the demo content (PlainTextDocumentContent4). + /// + protected WordInfo[] PlainTextDocumentWords4 = new WordInfo[] { + new WordInfo("Content", 0, 0, WordLocation.Content), + new WordInfo("with", 8, 1, WordLocation.Content), + new WordInfo("repeated", 13, 2, WordLocation.Content), + new WordInfo("content", 22, 3, WordLocation.Content) + }; + + [SetUp] + public void SetUp() { + looseMocks = new MockRepository(); + } + + /// + /// Mocks an index, inheriting from IndexBase. + /// + /// The index. + public IInMemoryIndex MockInMemoryIndex() { + InMemoryIndexBase index = looseMocks.DynamicMock(); + + looseMocks.Replay(index); + + return index; + } + + /// + /// Mocks a document with a fixed content. + /// + /// The name. + /// The title. + /// The type tag. + /// The date/time. + /// The mocked document. + public IDocument MockDocument(string name, string title, string typeTag, DateTime dateTime) { + IDocument doc = looseMocks.DynamicMock(); + Expect.Call(doc.Name).Return(name).Repeat.Any(); + Expect.Call(doc.ID).Return(1).Repeat.Any(); + Expect.Call(doc.Title).Return(title).Repeat.Any(); + Expect.Call(doc.TypeTag).Return(typeTag).Repeat.Any(); + Expect.Call(doc.DateTime).Return(dateTime).Repeat.Any(); + + Expect.Call(doc.Tokenize(title)).Return(Tools.Tokenize(title, WordLocation.Title)).Repeat.Any(); + Expect.Call(doc.Tokenize(null)).IgnoreArguments().Return(PlainTextDocumentWords).Repeat.Any(); + + looseMocks.Replay(doc); + + return doc; + } + + /// + /// Mocks a document with a fixed content. + /// + /// The name. + /// The title. + /// The type tag. + /// The date/time. + /// The mocked document. + public IDocument MockDocument2(string name, string title, string typeTag, DateTime dateTime) { + IDocument doc = looseMocks.DynamicMock(); + Expect.Call(doc.Name).Return(name).Repeat.Any(); + Expect.Call(doc.ID).Return(2).Repeat.Any(); + Expect.Call(doc.Title).Return(title).Repeat.Any(); + Expect.Call(doc.TypeTag).Return(typeTag).Repeat.Any(); + Expect.Call(doc.DateTime).Return(dateTime).Repeat.Any(); + + Expect.Call(doc.Tokenize(title)).Return(Tools.Tokenize(title, WordLocation.Title)).Repeat.Any(); + Expect.Call(doc.Tokenize(null)).IgnoreArguments().Return(PlainTextDocumentWords2).Repeat.Any(); + + looseMocks.Replay(doc); + + return doc; + } + + /// + /// Mocks a document with a fixed content. + /// + /// The name. + /// The title. + /// The type tag. + /// The date/time. + /// The mocked document. + public IDocument MockDocument3(string name, string title, string typeTag, DateTime dateTime) { + IDocument doc = looseMocks.DynamicMock(); + Expect.Call(doc.Name).Return(name).Repeat.Any(); + Expect.Call(doc.ID).Return(3).Repeat.Any(); + Expect.Call(doc.Title).Return(title).Repeat.Any(); + Expect.Call(doc.TypeTag).Return(typeTag).Repeat.Any(); + Expect.Call(doc.DateTime).Return(dateTime).Repeat.Any(); + + Expect.Call(doc.Tokenize(title)).Return(Tools.Tokenize(title, WordLocation.Title)).Repeat.Any(); + Expect.Call(doc.Tokenize(null)).IgnoreArguments().Return(PlainTextDocumentWords3).Repeat.Any(); + + looseMocks.Replay(doc); + + return doc; + } + + /// + /// Mocks a document with a fixed content. + /// + /// The name. + /// The title. + /// The type tag. + /// The date/time. + /// The mocked document. + public IDocument MockDocument4(string name, string title, string typeTag, DateTime dateTime) { + IDocument doc = looseMocks.DynamicMock(); + Expect.Call(doc.Name).Return(name).Repeat.Any(); + Expect.Call(doc.ID).Return(4).Repeat.Any(); + Expect.Call(doc.Title).Return(title).Repeat.Any(); + Expect.Call(doc.TypeTag).Return(typeTag).Repeat.Any(); + Expect.Call(doc.DateTime).Return(dateTime).Repeat.Any(); + + Expect.Call(doc.Tokenize(title)).Return(Tools.Tokenize(title, WordLocation.Title)).Repeat.Any(); + Expect.Call(doc.Tokenize(null)).IgnoreArguments().Return(PlainTextDocumentWords4).Repeat.Any(); + + looseMocks.Replay(doc); + + return doc; + } + + public uint FreeDocumentId = 1; + public uint FreeWordId = 1; + + public void AutoHandlerForDocumentStorage(object sender, IndexChangedEventArgs e) { + List ids = new List(); + if(e.ChangeData != null && e.ChangeData.Words != null) { + foreach(DumpedWord w in e.ChangeData.Words) { + ids.Add(new WordId(w.Text, FreeWordId)); + FreeWordId++; + } + } + + e.Result = new IndexStorerResult(FreeDocumentId, ids); + FreeDocumentId++; + } + + } + +} diff --git a/SearchEngine-Tests/ToolsTests.cs b/SearchEngine-Tests/ToolsTests.cs new file mode 100644 index 0000000..454513c --- /dev/null +++ b/SearchEngine-Tests/ToolsTests.cs @@ -0,0 +1,123 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class ToolsTests : TestsBase { + + [Test] + public void RemoveDiacriticsAndPunctuation() { + string testPhrase = "Wow, ths thing sems really cool!"; + string testWord = "Wrd"; + + Assert.AreEqual("wow this thing seems really cool", Tools.RemoveDiacriticsAndPunctuation(testPhrase, false), "Wrong normalized phrase"); + Assert.AreEqual("word", Tools.RemoveDiacriticsAndPunctuation(testWord, true), "Wrong normalized word"); + } + + [Test] + public void IsSplitChar() { + foreach(char c in ",.;:-\"'!?^=()<>\\|/[]{}*%&#@~") { + Assert.IsTrue(Tools.IsSplitChar(c), "Char is a split char"); + } + foreach(char c in "abcdefghijklmnopqrstuvwxyz0123456789$") { + Assert.IsFalse(Tools.IsSplitChar(c), "Char is not a split char"); + } + } + + [Test] + public void SkipSplitChars() { + Assert.AreEqual(0, Tools.SkipSplitChars(0, "hello")); + Assert.AreEqual(1, Tools.SkipSplitChars(0, " hello")); + Assert.AreEqual(7, Tools.SkipSplitChars(6, "Hello! How are you?")); + } + + [Test] + public void Tokenize() { + string input = "Hello, there!"; + WordInfo[] expectedOutput = new WordInfo[] { new WordInfo("Hello", 0, 0, WordLocation.Content), new WordInfo("there", 7, 1, WordLocation.Content) }; + + WordInfo[] output = Tools.Tokenize(input, WordLocation.Content); + + Assert.AreEqual(expectedOutput.Length, output.Length, "Wrong output length"); + + for(int i = 0; i < output.Length; i++) { + Assert.AreEqual(expectedOutput[i].Text, output[i].Text, "Wrong word text at index " + i.ToString()); + Assert.AreEqual(expectedOutput[i].FirstCharIndex, output[i].FirstCharIndex, "Wrong first char index at " + i.ToString()); + Assert.AreEqual(expectedOutput[i].WordIndex, output[i].WordIndex, "Wrong word index at " + i.ToString()); + } + } + + [Test] + public void Tokenize_OneWord() { + string input = "todo"; + WordInfo[] expectedOutput = new WordInfo[] { new WordInfo("todo", 0, 0, WordLocation.Content) }; + + WordInfo[] output = Tools.Tokenize(input, WordLocation.Content); + + Assert.AreEqual(expectedOutput.Length, output.Length, "Wrong output length"); + + for(int i = 0; i < output.Length; i++) { + Assert.AreEqual(expectedOutput[i].Text, output[i].Text, "Wrong word text at index " + i.ToString()); + Assert.AreEqual(expectedOutput[i].FirstCharIndex, output[i].FirstCharIndex, "Wrong first char index at " + i.ToString()); + Assert.AreEqual(expectedOutput[i].WordIndex, output[i].WordIndex, "Wrong word index at " + i.ToString()); + } + } + + [Test] + public void Tokenize_OneWordWithSplitChar() { + string input = "todo."; + WordInfo[] expectedOutput = new WordInfo[] { new WordInfo("todo", 0, 0, WordLocation.Content) }; + + WordInfo[] output = Tools.Tokenize(input, WordLocation.Content); + + Assert.AreEqual(expectedOutput.Length, output.Length, "Wrong output length"); + + for(int i = 0; i < output.Length; i++) { + Assert.AreEqual(expectedOutput[i].Text, output[i].Text, "Wrong word text at index " + i.ToString()); + Assert.AreEqual(expectedOutput[i].FirstCharIndex, output[i].FirstCharIndex, "Wrong first char index at " + i.ToString()); + Assert.AreEqual(expectedOutput[i].WordIndex, output[i].WordIndex, "Wrong word index at " + i.ToString()); + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Tokenize_NullText() { + Tools.Tokenize(null, WordLocation.Content); + } + + [Test] + public void RemoveStopWords() { + WordInfo[] input = new WordInfo[] { new WordInfo("I", 0, 0, WordLocation.Content), new WordInfo("like", 7, 1, WordLocation.Content), + new WordInfo("the", 15, 2, WordLocation.Content), new WordInfo("cookies", 22, 3, WordLocation.Content) }; + WordInfo[] expectedOutput = new WordInfo[] { new WordInfo("I", 0, 0, WordLocation.Content), new WordInfo("like", 7, 1, WordLocation.Content), + new WordInfo("cookies", 22, 3, WordLocation.Content) }; + + WordInfo[] output = Tools.RemoveStopWords(input, new string[] { "the", "in", "of" }); + + Assert.AreEqual(expectedOutput.Length, output.Length, "Wrong output length"); + + for(int i = 0; i < output.Length; i++) { + Assert.AreEqual(expectedOutput[i].Text, output[i].Text, "Wrong word text at index " + i.ToString()); + Assert.AreEqual(expectedOutput[i].FirstCharIndex, output[i].FirstCharIndex, "Wrong word position at index " + i.ToString()); + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveStopWords_NullInputWords() { + Tools.RemoveStopWords(null, new string[0]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveStopWords_NullStopWords() { + Tools.RemoveStopWords(new WordInfo[0], null); + } + + } + +} diff --git a/SearchEngine-Tests/WordInfoCollectionTests.cs b/SearchEngine-Tests/WordInfoCollectionTests.cs new file mode 100644 index 0000000..a3e829c --- /dev/null +++ b/SearchEngine-Tests/WordInfoCollectionTests.cs @@ -0,0 +1,265 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class WordInfoCollectionTests { + + [Test] + public void Constructor_NoCapacity() { + WordInfoCollection collection = new WordInfoCollection(); + Assert.AreEqual(0, collection.Count, "Wrong count (collection should be empty)"); + } + + [Test] + public void Constructor_WithCapacity() { + WordInfoCollection collection = new WordInfoCollection(15); + Assert.AreEqual(0, collection.Count, "Wrong count (collection should be empty)"); + Assert.AreEqual(15, collection.Capacity, "Wrong capacity"); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void Constructor_InvalidCapacity() { + WordInfoCollection collection = new WordInfoCollection(0); + } + + [Test] + public void AddAndCount() { + WordInfoCollection collection = new WordInfoCollection(); + + WordInfo mi1 = new WordInfo("continuous", 0, 0, WordLocation.Content); + WordInfo mi2 = new WordInfo("taskbar", 21, 1, WordLocation.Content); + + Assert.AreEqual(0, collection.Count, "Wrong count (collection should be empty)"); + collection.Add(mi2); + collection.Add(mi1); + Assert.AreEqual(2, collection.Count, "Wrong count (collection should contain 2 items)"); + Assert.AreEqual(mi1, collection[0], "Wrong item at index 0"); + Assert.AreEqual(mi2, collection[1], "Wrong item at index 1"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Add_NullItem() { + WordInfoCollection collection = new WordInfoCollection(); + collection.Add(null); + } + + [Test] + public void Clear() { + WordInfoCollection collection = new WordInfoCollection(); + + WordInfo mi1 = new WordInfo("continuous", 0, 0, WordLocation.Content); + WordInfo mi2 = new WordInfo("taskbar", 21, 1, WordLocation.Content); + + collection.Add(mi1); + collection.Add(mi2); + Assert.AreEqual(2, collection.Count, "Wrong count (collection should contain 2 items)"); + + collection.Clear(); + Assert.AreEqual(0, collection.Count, "Wrong count (collection should be empty)"); + } + + [Test] + public void Contains_WordInfo() { + WordInfoCollection collection = new WordInfoCollection(); + + WordInfo mi1 = new WordInfo("continuous", 0, 0, WordLocation.Content); + WordInfo mi2 = new WordInfo("taskbar", 21, 0, WordLocation.Content); + + collection.Add(mi1); + + Assert.IsTrue(collection.Contains(mi1), "Collection should contain item"); + Assert.IsFalse(collection.Contains(mi2), "Collection should not contain item"); + + Assert.IsFalse(collection.Contains(null as WordInfo), "Contains should return false"); + } + + [Test] + public void Contains_String() { + WordInfoCollection collection = new WordInfoCollection(); + + WordInfo mi1 = new WordInfo("continuous", 0, 0, WordLocation.Content); + + collection.Add(mi1); + + Assert.IsTrue(collection.Contains("continuous"), "Collection should contain string"); + Assert.IsFalse(collection.Contains("taskbar"), "Collection should not contain string"); + + Assert.IsFalse(collection.Contains(null as string), "Contains should return false"); + Assert.IsFalse(collection.Contains(""), "Contains should return false"); + } + + [Test] + public void CopyTo() { + WordInfoCollection collection = new WordInfoCollection(); + + WordInfo mi1 = new WordInfo("continuous", 0, 0, WordLocation.Content); + WordInfo mi2 = new WordInfo("goose", 34, 0, WordLocation.Content); + + collection.Add(mi1); + collection.Add(mi2); + + WordInfo[] matches = new WordInfo[2]; + collection.CopyTo(matches, 0); + + Assert.AreEqual(mi1, matches[0], "Wrong match item"); + Assert.AreEqual(mi2, matches[1], "Wrong match item"); + + matches = new WordInfo[3]; + collection.CopyTo(matches, 0); + + Assert.AreEqual(mi1, matches[0], "Wrong match item"); + Assert.AreEqual(mi2, matches[1], "Wrong match item"); + Assert.IsNull(matches[2], "Non-null item"); + + matches = new WordInfo[3]; + collection.CopyTo(matches, 1); + + Assert.IsNull(matches[0], "Non-null item"); + Assert.AreEqual(mi1, matches[1], "Wrong match item"); + Assert.AreEqual(mi2, matches[2], "Wrong match item"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void CopyTo_NullArray() { + WordInfoCollection collection = new WordInfoCollection(); + + collection.CopyTo(null, 0); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void CopyTo_InvalidIndex_Negative() { + WordInfoCollection collection = new WordInfoCollection(); + + WordInfo[] results = new WordInfo[10]; + + collection.CopyTo(results, -1); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void CopyTo_InvalidIndex_TooBig() { + WordInfoCollection collection = new WordInfoCollection(); + + WordInfo[] results = new WordInfo[10]; + + collection.CopyTo(results, 10); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void CopyTo_ArrayTooSmall() { + WordInfoCollection collection = new WordInfoCollection(); + + WordInfo mi1 = new WordInfo("home", 0, 0, WordLocation.Content); + WordInfo mi2 = new WordInfo("taskbar", 100, 0, WordLocation.Content); + + collection.Add(mi1); + collection.Add(mi2); + + WordInfo[] matches = new WordInfo[1]; + + collection.CopyTo(matches, 0); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void CopyTo_NoSpaceAtIndex() { + WordInfoCollection collection = new WordInfoCollection(); + + WordInfo mi1 = new WordInfo("home", 0, 0, WordLocation.Content); + WordInfo mi2 = new WordInfo("taskbar", 100, 0, WordLocation.Content); + + collection.Add(mi1); + collection.Add(mi2); + + WordInfo[] matches = new WordInfo[2]; + + collection.CopyTo(matches, 1); + } + + [Test] + public void ReadOnly() { + WordInfoCollection collection = new WordInfoCollection(); + Assert.IsFalse(collection.IsReadOnly, "Collection should not be read-only"); + } + + [Test] + public void Remove() { + WordInfoCollection collection = new WordInfoCollection(); + + WordInfo mi1 = new WordInfo("goose", 1, 0, WordLocation.Content); + WordInfo mi2 = new WordInfo("hello", 12, 0, WordLocation.Content); + + collection.Add(mi1); + Assert.IsTrue(collection.Contains(mi1), "Collection should contain item"); + Assert.IsFalse(collection.Contains(mi2), "Collection should not contain item"); + + Assert.IsFalse(collection.Contains(null as WordInfo), "Contains should return false"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Remove_NullItem() { + WordInfoCollection collection = new WordInfoCollection(); + collection.Remove(null); + } + + [Test] + public void GetEnumerator() { + WordInfoCollection collection = new WordInfoCollection(); + + WordInfo mi1 = new WordInfo("goose", 1, 0, WordLocation.Content); + WordInfo mi2 = new WordInfo("hello", 12, 0, WordLocation.Content); + + collection.Add(mi2); + collection.Add(mi1); + + int count = 0; + foreach(WordInfo r in collection) { + if(count == 0) Assert.AreEqual(mi1, r, "Wrong item at position 0"); + if(count == 1) Assert.AreEqual(mi2, r, "Wrong item at position 1"); + count++; + } + Assert.AreEqual(2, count, "Wrong count - enumerator does not work"); + } + + [Test] + public void Indexer() { + WordInfoCollection collection = new WordInfoCollection(); + + WordInfo mi1 = new WordInfo("taskbar", 1, 0, WordLocation.Content); + WordInfo mi2 = new WordInfo("goose", 12, 0, WordLocation.Content); + + collection.Add(mi2); + collection.Add(mi1); + + Assert.AreEqual(mi1, collection[0], "Wrong item at position 0"); + Assert.AreEqual(mi2, collection[1], "Wrong item at position 0"); + } + + [Test] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void Indexer_InvalidIndex_Negative() { + WordInfoCollection collection = new WordInfoCollection(); + WordInfo mi = collection[-1]; + } + + [Test] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void Indexer_InvalidIndex_TooBig() { + WordInfoCollection collection = new WordInfoCollection(); + WordInfo mi = collection[0]; + } + + } + +} diff --git a/SearchEngine-Tests/WordInfoTests.cs b/SearchEngine-Tests/WordInfoTests.cs new file mode 100644 index 0000000..b107c91 --- /dev/null +++ b/SearchEngine-Tests/WordInfoTests.cs @@ -0,0 +1,95 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class WordInfoTests : TestsBase { + + [Test] + public void Constructor() { + WordInfo info = new WordInfo("continuous", 2, 0, WordLocation.Content); + Assert.AreEqual(2, info.FirstCharIndex, "Wrong start index"); + Assert.AreEqual(10, info.Text.Length, "Wrong length"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_NullText() { + WordInfo info = new WordInfo(null, 0, 0, WordLocation.Content); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void Constructor_EmptyText() { + WordInfo info = new WordInfo("", 0, 0, WordLocation.Content); + } + + [Test] + public void Equals() { + WordInfo info1 = new WordInfo("word", 0, 0, WordLocation.Content); + WordInfo info2 = new WordInfo("word", 0, 0, WordLocation.Content); + WordInfo info3 = new WordInfo("word", 10, 1, WordLocation.Content); + WordInfo info4 = new WordInfo("word", 10, 1, WordLocation.Title); + WordInfo info5 = new WordInfo("word2", 0, 0, WordLocation.Content); + + Assert.IsTrue(info1.Equals(info2), "info1 should equal info2"); + Assert.IsFalse(info1.Equals(info3), "info1 should not equal info3"); + Assert.IsFalse(info3.Equals(info4), "info3 should not equal info4"); + Assert.IsTrue(info1.Equals(info1), "info1 should equal itself"); + Assert.IsFalse(info1.Equals(null), "info1 should not equal null"); + Assert.IsFalse(info1.Equals("hello"), "info1 should not equal a string"); + Assert.IsFalse(info1.Equals(info5), "info1 should not equal info5"); + } + + [Test] + public void EqualityOperator() { + WordInfo info1 = new WordInfo("word", 0, 0, WordLocation.Content); + WordInfo info2 = new WordInfo("word", 0, 0, WordLocation.Content); + WordInfo info3 = new WordInfo("word", 10, 1, WordLocation.Content); + WordInfo info4 = new WordInfo("word", 10, 1, WordLocation.Title); + WordInfo info5 = new WordInfo("word2", 0, 0, WordLocation.Content); + + Assert.IsTrue(info1 == info2, "info1 should equal info2"); + Assert.IsFalse(info1 == info3, "info1 should not equal info3"); + Assert.IsFalse(info3 == info4, "info3 should not equal info4"); + Assert.IsFalse(info1 == null, "info1 should not equal null"); + Assert.IsFalse(info1 == info5, "info1 should not equal info5"); + } + + [Test] + public void InequalityOperator() { + WordInfo info1 = new WordInfo("word", 0, 0, WordLocation.Content); + WordInfo info2 = new WordInfo("word", 0, 0, WordLocation.Content); + WordInfo info3 = new WordInfo("word", 10, 1, WordLocation.Content); + WordInfo info4 = new WordInfo("word", 10, 1, WordLocation.Title); + WordInfo info5 = new WordInfo("word2", 0, 0, WordLocation.Content); + + Assert.IsFalse(info1 != info2, "info1 should equal info2"); + Assert.IsTrue(info1 != info3, "info1 should not equal info3"); + Assert.IsTrue(info3 != info4, "info3 should not equal info4"); + Assert.IsTrue(info1 != null, "info1 should not equal null"); + Assert.IsTrue(info1 != info5, "info1 should not equal info5"); + } + + [Test] + public void CompareTo() { + WordInfo info1 = new WordInfo("word", 0, 0, WordLocation.Content); + WordInfo info2 = new WordInfo("word", 0, 0, WordLocation.Content); + WordInfo info3 = new WordInfo("word", 10, 1, WordLocation.Content); + WordInfo info4 = new WordInfo("word", 10, 1, WordLocation.Title); + WordInfo info5 = new WordInfo("word2", 0, 0, WordLocation.Content); + + Assert.AreEqual(0, info1.CompareTo(info2), "info1 should equal info2"); + Assert.AreEqual(-3, info1.CompareTo(info3), "info1 should be smaller than info3"); + Assert.AreEqual(2, info3.CompareTo(info4), "info3 should be greater than info4"); + Assert.AreEqual(1, info1.CompareTo(null), "info1 should be greater than null"); + Assert.AreEqual(-1, info1.CompareTo(info5), "info1 should be smaller than info5"); + } + + } + +} diff --git a/SearchEngine-Tests/WordLocationTests.cs b/SearchEngine-Tests/WordLocationTests.cs new file mode 100644 index 0000000..45cb84a --- /dev/null +++ b/SearchEngine-Tests/WordLocationTests.cs @@ -0,0 +1,79 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class WordLocationTests { + + [Test] + public void StaticInstances_Title() { + WordLocation loc1 = WordLocation.Title; + WordLocation loc2 = WordLocation.Title; + + Assert.AreEqual("Title", loc1.ToString(), "Invalid string representation"); + Assert.AreEqual("Title", loc2.ToString(), "Invalid string representation"); + + Assert.IsTrue(loc1 == loc2, "loc1 should equal loc2"); + Assert.IsTrue(loc1.Equals(loc2), "loc1 should equal loc2"); + Assert.AreNotSame(loc2, loc1, "loc1 should not be the same object as loc2"); + } + + [Test] + public void StaticInstances_Content() { + WordLocation loc1 = WordLocation.Content; + WordLocation loc2 = WordLocation.Content; + + Assert.AreEqual("Content", loc1.ToString(), "Invalid string representation"); + Assert.AreEqual("Content", loc2.ToString(), "Invalid string representation"); + + Assert.IsTrue(loc1 == loc2, "loc1 should equal loc2"); + Assert.IsTrue(loc1.Equals(loc2), "loc1 should equal loc2"); + Assert.AreNotSame(loc2, loc1, "loc1 should not be the same object as loc2"); + } + + [Test] + public void StaticInstances_Keywords() { + WordLocation loc1 = WordLocation.Keywords; + WordLocation loc2 = WordLocation.Keywords; + + Assert.AreEqual("Keywords", loc1.ToString(), "Invalid string representation"); + Assert.AreEqual("Keywords", loc2.ToString(), "Invalid string representation"); + + Assert.IsTrue(loc1 == loc2, "loc1 should equal loc2"); + Assert.IsTrue(loc1.Equals(loc2), "loc1 should equal loc2"); + Assert.AreNotSame(loc2, loc1, "loc1 should not be the same object as loc2"); + } + + [Test] + public void StaticInstances_CompareTo() { + Assert.AreEqual(0, WordLocation.Title.CompareTo(WordLocation.Title), "Title should equal Title"); + Assert.AreEqual(1, WordLocation.Content.CompareTo(WordLocation.Title), "Content should be greater than Title"); + Assert.AreEqual(-1, WordLocation.Title.CompareTo(WordLocation.Content), "Title should be smaller than Content"); + } + + [Test] + public void StaticInstances_RelativeRelevance() { + Assert.IsTrue(WordLocation.Title.RelativeRelevance > WordLocation.Keywords.RelativeRelevance, "Wrong relevance relationship"); + Assert.IsTrue(WordLocation.Keywords.RelativeRelevance > WordLocation.Content.RelativeRelevance, "Wrong relevance relationship"); + } + + [Test] + public void StaticMethods_GetInstance() { + Assert.AreEqual(WordLocation.Title, WordLocation.GetInstance(1), "Wrong instance"); + Assert.AreEqual(WordLocation.Keywords, WordLocation.GetInstance(2), "Wrong instance"); + Assert.AreEqual(WordLocation.Content, WordLocation.GetInstance(3), "Wrong instance"); + } + + [TestCase(0, ExpectedException = typeof(ArgumentException))] + [TestCase(4, ExpectedException = typeof(ArgumentException))] + public void StaticMethods_GetInstance_InvalidLocation(byte location) { + WordLocation.GetInstance(location); + } + + } + +} diff --git a/SearchEngine-Tests/WordTests.cs b/SearchEngine-Tests/WordTests.cs new file mode 100644 index 0000000..23e701b --- /dev/null +++ b/SearchEngine-Tests/WordTests.cs @@ -0,0 +1,237 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; + +namespace ScrewTurn.Wiki.SearchEngine.Tests { + + [TestFixture] + public class WordTests : TestsBase { + + [Test] + public void Constructor_2Params() { + Word word = new Word(1, "Hello"); + Assert.AreEqual("hello", word.Text, "Wrong word text"); + Assert.AreEqual(0, word.Occurrences.Count, "Wrong occurrences count"); + Assert.AreEqual(0, word.TotalOccurrences, "Wrong total occurrences count"); + } + + [Test] + public void Constructor_3Params_NoOccurrences() { + Word word = new Word(1, "Hello1", new OccurrenceDictionary()); + Assert.AreEqual("hello1", word.Text, "Wrong word text"); + Assert.AreEqual(0, word.Occurrences.Count, "Wrong occurrences count"); + Assert.AreEqual(0, word.TotalOccurrences, "Wrong total occurrences count"); + } + + [Test] + public void Constructor_3Params_1Occurrence() { + OccurrenceDictionary occ = new OccurrenceDictionary(); + + IDocument doc = MockDocument("Doc", "Doc", "d", DateTime.Now); + + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + set.Add(new BasicWordInfo(0, 0, WordLocation.Content)); + occ.Add(doc, set); + + Word word = new Word(12, "Hello", occ); + Assert.AreEqual("hello", word.Text, "Wrong word text"); + Assert.AreEqual(1, word.Occurrences.Count, "Wrong occurrences count"); + Assert.AreEqual(1, word.TotalOccurrences, "Wrong total occurrences count"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_NullText() { + Word word = new Word(1, null); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void Constructor_InvalidText() { + Word word = new Word(1, ""); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_NullOccurrences() { + Word word = new Word(1, "hello", null); + } + + [Test] + public void Add1Occurrence() { + Word word = new Word(1, "hello"); + + IDocument doc = MockDocument("Doc", "Doc", "d", DateTime.Now); + + word.AddOccurrence(doc, 0, 0, WordLocation.Content); + Assert.AreEqual(1, word.Occurrences.Count, "Wrong occurrences count"); + Assert.AreEqual(1, word.TotalOccurrences, "Wrong total occurences count"); + Assert.AreEqual(0, word.Occurrences[doc][0].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(0, word.Occurrences[doc][0].WordIndex, "Wrong word index"); + } + + [Test] + public void Add2Occurrences_DifferentDocuments() { + Word word = new Word(1, "hello"); + + IDocument doc1 = MockDocument("Doc1", "Doc1", "d", DateTime.Now); + IDocument doc2 = MockDocument("Doc2", "Doc2", "d", DateTime.Now); + word.AddOccurrence(doc1, 0, 0, WordLocation.Content); + word.AddOccurrence(doc2, 10, 1, WordLocation.Content); + Assert.AreEqual(2, word.Occurrences.Count, "Wrong occurrences count"); + Assert.AreEqual(2, word.TotalOccurrences, "Wrong total occurences count"); + Assert.AreEqual(0, word.Occurrences[doc1][0].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(0, word.Occurrences[doc1][0].WordIndex, "Wrong word index"); + Assert.AreEqual(10, word.Occurrences[doc2][0].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(1, word.Occurrences[doc2][0].WordIndex, "Wrong word index"); + } + + [Test] + public void Add2Occurrences_SameDocument() { + Word word = new Word(1, "hello"); + + IDocument doc = MockDocument("Doc", "Doc", "d", DateTime.Now); + word.AddOccurrence(doc, 0, 0, WordLocation.Content); + word.AddOccurrence(doc, 10, 1, WordLocation.Content); + Assert.AreEqual(1, word.Occurrences.Count, "Wrong occurrences count"); + Assert.AreEqual(2, word.TotalOccurrences, "Wrong total occurences count"); + Assert.AreEqual(0, word.Occurrences[doc][0].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(0, word.Occurrences[doc][0].WordIndex, "Wrong word index"); + Assert.AreEqual(10, word.Occurrences[doc][1].FirstCharIndex, "Wrong occurrence"); + Assert.AreEqual(1, word.Occurrences[doc][1].WordIndex, "Wrong word index"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void AddOccurrence_NullDocument() { + Word word = new Word(1, "dummy"); + word.AddOccurrence(null, 0, 0, WordLocation.Content); + } + + [Test] + public void RemoveOccurrences() { + Word word = new Word(1, "hello"); + + IDocument doc1 = MockDocument("Doc1", "Doc1", "d", DateTime.Now); + IDocument doc2 = MockDocument("Doc2", "Doc2", "d", DateTime.Now); + word.AddOccurrence(doc1, 0, 0, WordLocation.Content); + word.AddOccurrence(doc1, 10, 1, WordLocation.Content); + word.AddOccurrence(doc2, 5, 0, WordLocation.Content); + Assert.AreEqual(2, word.Occurrences.Count, "Wrong occurrences count"); + Assert.AreEqual(3, word.TotalOccurrences, "Wrong total occurrences count"); + + word.RemoveOccurrences(doc1); + + Assert.AreEqual(1, word.Occurrences.Count, "Wrong occurrences count"); + Assert.AreEqual(1, word.TotalOccurrences, "Wrong total occurrences count"); + Assert.AreEqual(5, word.Occurrences[doc2][0].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(0, word.Occurrences[doc2][0].WordIndex, "Wrong word index"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveOccurrences_NullDocument() { + Word word = new Word(1, "hey"); + word.RemoveOccurrences(null); + } + + [Test] + public void BulkAddOccurrences_NewDocument() { + Word word = new Word(1, "hello"); + IDocument doc0 = MockDocument("Doc0", "Doc0", "d", DateTime.Now); + word.AddOccurrence(doc0, 10, 0, WordLocation.Content); + Assert.AreEqual(10, word.Occurrences[doc0][0].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(0, word.Occurrences[doc0][0].WordIndex, "Wrong word index"); + + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + set.Add(new BasicWordInfo(10, 0, WordLocation.Content)); + set.Add(new BasicWordInfo(25, 1, WordLocation.Content)); + set.Add(new BasicWordInfo(102, 2, WordLocation.Content)); + IDocument doc = MockDocument("Doc", "Doc", "d", DateTime.Now); + word.BulkAddOccurrences(doc, set); + + Assert.AreEqual(2, word.Occurrences.Count, "Wrong occurrences count"); + Assert.AreEqual(4, word.TotalOccurrences, "Wrong total occurrences count"); + Assert.AreEqual(10, word.Occurrences[doc0][0].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(0, word.Occurrences[doc0][0].WordIndex, "Wrong word index"); + Assert.AreEqual(10, word.Occurrences[doc][0].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(0, word.Occurrences[doc][0].WordIndex, "Wrong word index"); + Assert.AreEqual(25, word.Occurrences[doc][1].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(1, word.Occurrences[doc][1].WordIndex, "Wrong word index"); + Assert.AreEqual(102, word.Occurrences[doc][2].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(2, word.Occurrences[doc][2].WordIndex, "Wrong word index"); + } + + [Test] + public void BulkAddOccurrences_ExistingDocument() { + Word word = new Word(1, "hello"); + IDocument doc0 = MockDocument("Doc0", "Doc0", "d", DateTime.Now); + word.AddOccurrence(doc0, 10, 0, WordLocation.Content); + Assert.AreEqual(10, word.Occurrences[doc0][0].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(0, word.Occurrences[doc0][0].WordIndex, "Wrong word index"); + + IDocument doc = MockDocument("Doc", "Doc", "d", DateTime.Now); + word.AddOccurrence(doc, 0, 0, WordLocation.Content); + Assert.AreEqual(2, word.Occurrences.Count, "Wrong occurrences count"); + Assert.AreEqual(2, word.TotalOccurrences, "Wrong total occurrences count"); + + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + set.Add(new BasicWordInfo(10, 0, WordLocation.Content)); + set.Add(new BasicWordInfo(25, 1, WordLocation.Content)); + set.Add(new BasicWordInfo(102, 2, WordLocation.Content)); + word.BulkAddOccurrences(doc, set); + + Assert.AreEqual(2, word.Occurrences.Count, "Wrong occurrences count"); + Assert.AreEqual(4, word.TotalOccurrences, "Wrong total occurrences count"); + Assert.AreEqual(10, word.Occurrences[doc0][0].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(0, word.Occurrences[doc0][0].WordIndex, "Wrong word index"); + Assert.AreEqual(10, word.Occurrences[doc][0].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(0, word.Occurrences[doc][0].WordIndex, "Wrong word index"); + Assert.AreEqual(25, word.Occurrences[doc][1].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(1, word.Occurrences[doc][1].WordIndex, "Wrong word index"); + Assert.AreEqual(102, word.Occurrences[doc][2].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(2, word.Occurrences[doc][2].WordIndex, "Wrong word index"); + } + + [Test] + public void BulkAddOccurrences_ExistingDocument_EmptyPositionsSet() { + Word word = new Word(1, "hello"); + IDocument doc0 = MockDocument("Doc0", "Doc0", "d", DateTime.Now); + word.AddOccurrence(doc0, 10, 0, WordLocation.Content); + Assert.AreEqual(10, word.Occurrences[doc0][0].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(0, word.Occurrences[doc0][0].WordIndex, "Wrong word index"); + + IDocument doc = MockDocument("Doc", "Doc", "d", DateTime.Now); + word.AddOccurrence(doc, 0, 0, WordLocation.Content); + Assert.AreEqual(2, word.Occurrences.Count, "Wrong occurrences count"); + Assert.AreEqual(2, word.TotalOccurrences, "Wrong total occurrences count"); + + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + word.BulkAddOccurrences(doc, set); + + Assert.AreEqual(1, word.Occurrences.Count, "Wrong occurrences count"); + Assert.AreEqual(1, word.TotalOccurrences, "Wrong total occurrences count"); + Assert.AreEqual(10, word.Occurrences[doc0][0].FirstCharIndex, "Wrong start index"); + Assert.AreEqual(0, word.Occurrences[doc0][0].WordIndex, "Wrong word index"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void BulkAddOccurrences_NullDocument() { + Word word = new Word(1, "john"); + word.BulkAddOccurrences(null, new SortedBasicWordInfoSet()); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void BulkAddOccurrences_NullPositions() { + Word word = new Word(1, "john"); + word.BulkAddOccurrences(MockDocument("Doc", "Doc", "d", DateTime.Now), null); + } + + } + +} diff --git a/SearchEngine/BasicWordInfo.cs b/SearchEngine/BasicWordInfo.cs new file mode 100644 index 0000000..534d91d --- /dev/null +++ b/SearchEngine/BasicWordInfo.cs @@ -0,0 +1,133 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Contains basic information about a word in a document. + /// + public class BasicWordInfo : IEquatable, IComparable { + + /// + /// The index in the original document the word starts at. + /// + protected ushort firstCharIndex; + /// + /// The index of the word in the document. + /// + protected ushort wordIndex; + /// + /// The location of the word in the document. + /// + protected WordLocation location; + + /// + /// Initializes a new instance of the class. + /// + /// The index of the first character of the word in the document. + /// The index of the word in the document. + /// The location of the word in the document. + /// If or are less than zero. + public BasicWordInfo(ushort firstCharIndex, ushort wordIndex, WordLocation location) { + if(firstCharIndex < 0) throw new ArgumentOutOfRangeException("firstCharIndex", "Invalid first char index: must be greater than or equal to zero"); + if(wordIndex < 0) throw new ArgumentOutOfRangeException("wordIndex", "Invalid word index: must be greater than or equal to zero"); + + this.firstCharIndex = firstCharIndex; + this.wordIndex = wordIndex; + this.location = location; + } + + /// + /// Gets the index of the first character of the word in the document. + /// + public ushort FirstCharIndex { + get { return firstCharIndex; } + } + + /// + /// Gets the index of the word in the document. + /// + public ushort WordIndex { + get { return wordIndex; } + } + + /// + /// Gets the location of the word in the document. + /// + public WordLocation Location { + get { return location; } + } + + /// + /// Determinates whether the current instance is equal to an instance of an object. + /// + /// The instance of the object. + /// true if the instances are value-equal, false otherwise. + public override bool Equals(object other) { + if(other is BasicWordInfo) return Equals((BasicWordInfo)other); + else return false; + } + + /// + /// Determinates whether the current instance is value-equal to another. + /// + /// The other instance. + /// true if the instances are value-equal, false otherwise. + public bool Equals(BasicWordInfo other) { + if(object.ReferenceEquals(other, null)) return false; + + return other.FirstCharIndex == firstCharIndex && other.WordIndex == wordIndex && + other.Location == location; + } + + /// + /// Applies the value-equality operator to two objects. + /// + /// The first object. + /// The second object. + /// true if the objects are value-equal, false otherwise. + public static bool operator ==(BasicWordInfo x, BasicWordInfo y) { + if(object.ReferenceEquals(x, null) && !object.ReferenceEquals(y, null) || + !object.ReferenceEquals(x, null) && object.ReferenceEquals(y, null)) return false; + + if(object.ReferenceEquals(x, null) && object.ReferenceEquals(y, null)) return true; + + return x.Equals(y); + } + + /// + /// Applies the value-inequality operator to two objects. + /// + /// The first object. + /// The second object. + /// true if the objects are not value-equal, false otherwise. + public static bool operator !=(BasicWordInfo x, BasicWordInfo y) { + return !(x == y); + } + + /// + /// Gets the hash code of the current instance. + /// + /// The hash code. + public override int GetHashCode() { + return location.GetHashCode() + firstCharIndex * 10 + wordIndex * 100000; + } + + /// + /// Compares the current instance with another instance. + /// + /// The other instance. + /// The comparison result. + /// The First Char Index does not partecipate to the comparison. + public int CompareTo(BasicWordInfo other) { + if(other == null) return 1; + + int res = location.CompareTo(other.Location) * 2; + return res + wordIndex.CompareTo(other.WordIndex); + } + + } + +} diff --git a/SearchEngine/DumpedChange.cs b/SearchEngine/DumpedChange.cs new file mode 100644 index 0000000..ffbc731 --- /dev/null +++ b/SearchEngine/DumpedChange.cs @@ -0,0 +1,70 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Represents a change occurred to the index, structured for easy dumping to disk or database. + /// + /// The class is not thread-safe. + public class DumpedChange { + + /// + /// The dumped document data. + /// + protected DumpedDocument document; + /// + /// The list of dumped words data. + /// + protected List words; + /// + /// The list of dumped mappings data. + /// + protected List mappings; + + /// + /// Initializes a new instance of the class. + /// + /// The dumped document data. + /// The list of dumped words data. + /// The list of dumped mappings data. + /// If , or are null. + public DumpedChange(DumpedDocument document, List words, List mappings) { + if(document == null) throw new ArgumentNullException("document"); + if(words == null) throw new ArgumentNullException("words"); + if(mappings == null) throw new ArgumentNullException("mappings"); + + // mappings can be empty if the document did not have any indexable content + //if(mappings.Count == 0) throw new ArgumentException("Mappings cannot be empty", "mappings"); + + this.document = document; + this.words = words; + this.mappings = mappings; + } + + /// + /// Gets the dumped document data. + /// + public DumpedDocument Document { + get { return document; } + } + + /// + /// Gets the list of dumped words data. + /// + public List Words { + get { return words; } + } + + /// + /// Gets the list of dumped mappings data. + /// + public List Mappings { + get { return mappings; } + } + + } + +} diff --git a/SearchEngine/DumpedDocument.cs b/SearchEngine/DumpedDocument.cs new file mode 100644 index 0000000..0960972 --- /dev/null +++ b/SearchEngine/DumpedDocument.cs @@ -0,0 +1,114 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Represents a document structured for easy dumped on disk or database. + /// + /// The class is not thread-safe. + public class DumpedDocument { + + /// + /// The document unique ID. + /// + protected uint id; + /// + /// The document unique name. + /// + protected string name; + /// + /// The document title. + /// + protected string title; + /// + /// The document type tag. + /// + protected string typeTag; + /// + /// The document date/time. + /// + protected DateTime dateTime; + + /// + /// Initializes a new instance of the class. + /// + /// The document unique ID. + /// The document unique name. + /// The document title. + /// The document type tag. + /// The document date/time. + /// If , or are null. + /// If , or are empty. + public DumpedDocument(uint id, string name, string title, string typeTag, DateTime dateTime) { + if(name == null) throw new ArgumentNullException("name"); + if(title == null) throw new ArgumentNullException("title"); + if(typeTag == null) throw new ArgumentNullException("typeTag"); + + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(title.Length == 0) throw new ArgumentException("Title cannot be empty", "title"); + if(typeTag.Length == 0) throw new ArgumentException("Type Tag cannot be empty", "typeTag"); + + this.id = id; + this.name = name; + this.title = title; + this.typeTag = typeTag; + this.dateTime = dateTime; + } + + /// + /// Initializes a new instance of the class. + /// + /// The document do wrap for dumping. + /// If is null. + public DumpedDocument(IDocument document) { + if(document == null) throw new ArgumentNullException("document"); + + id = document.ID; + name = document.Name; + title = document.Title; + typeTag = document.TypeTag; + dateTime = document.DateTime; + } + + /// + /// Gets or sets the document unique ID. + /// + public uint ID { + get { return id; } + set { id = value; } + } + + /// + /// Gets the document unique name. + /// + public string Name { + get { return name; } + } + + /// + /// Gets the title of the document. + /// + public string Title { + get { return title; } + } + + /// + /// Gets the document type tag. + /// + public string TypeTag { + get { return typeTag; } + } + + /// + /// Gets the document date/time. + /// + public DateTime DateTime { + get { return dateTime; } + } + + } + +} diff --git a/SearchEngine/DumpedWord.cs b/SearchEngine/DumpedWord.cs new file mode 100644 index 0000000..6aea03c --- /dev/null +++ b/SearchEngine/DumpedWord.cs @@ -0,0 +1,67 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Represents a word structured for easy dumping to disk or database. + /// + /// The class is not thread-safe. + public class DumpedWord { + + /// + /// The word unique ID. + /// + protected uint id; + /// + /// The word culture-invariant lowercase text. + /// + protected string text; + + /// + /// Initializes a new instance of the class. + /// + /// The unique word ID. + /// The word culture-invariant lowercase text. + /// If is null. + /// If is empty. + public DumpedWord(uint id, string text) { + if(text == null) throw new ArgumentNullException("text"); + if(text.Length == 0) throw new ArgumentException("Text cannot be empty", "text"); + + this.id = id; + this.text = text; + } + + /// + /// Initializes a new instance of the class. + /// + /// The word to extract the information from. + /// If is null. + public DumpedWord(Word word) { + if(word == null) throw new ArgumentNullException("word"); + + this.id = word.ID; + this.text = word.Text; + } + + /// + /// Gets or sets the word unique ID. + /// + public uint ID { + get { return id; } + set { id = value; } + } + + /// + /// Gets the word culture-invariant lowercase text. + /// + public string Text { + get { return text; } + } + + } + +} diff --git a/SearchEngine/DumpedWordMapping.cs b/SearchEngine/DumpedWordMapping.cs new file mode 100644 index 0000000..6a28256 --- /dev/null +++ b/SearchEngine/DumpedWordMapping.cs @@ -0,0 +1,106 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Contains a word mapping data, structured for easy dumping on disk or database. + /// + /// The class is not thread-safe. + public class DumpedWordMapping { + + /// + /// The word unique ID. + /// + protected uint wordId; + /// + /// The document unique ID. + /// + protected uint documentId; + /// + /// The index of the character of the word. + /// + protected ushort firstCharIndex; + /// + /// The index of the word in the original document. + /// + protected ushort wordIndex; + /// + /// The location identifier. + /// + protected byte location; + + /// + /// Initializes a new instance of the class. + /// + /// The word unique ID. + /// The document unique ID. + /// The index of the first character the word. + /// The index of the word in the original index. + /// The location identifier. + public DumpedWordMapping(uint wordId, uint documentId, ushort firstCharIndex, ushort wordIndex, byte location) { + this.wordId = wordId; + this.documentId = documentId; + this.firstCharIndex = firstCharIndex; + this.wordIndex = wordIndex; + this.location = location; + } + + /// + /// Initializes a new instance of the class. + /// + /// The word unique ID. + /// The document unique ID. + /// The . + /// If is null. + public DumpedWordMapping(uint wordId, uint documentId, BasicWordInfo info) { + if(info == null) throw new ArgumentNullException("info"); + + this.wordId = wordId; + this.documentId = documentId; + this.firstCharIndex = info.FirstCharIndex; + this.wordIndex = info.WordIndex; + this.location = info.Location.Location; + } + + /// + /// Gets or sets the word unique ID. + /// + public uint WordID { + get { return wordId; } + set { wordId = value; } + } + + /// + /// Gets the document unique ID. + /// + public uint DocumentID { + get { return documentId; } + } + + /// + /// Gets the index of the first character of the word. + /// + public ushort FirstCharIndex { + get { return firstCharIndex; } + } + + /// + /// Gets the index of the word in the original document. + /// + public ushort WordIndex { + get { return wordIndex; } + } + + /// + /// Gets the location identifier. + /// + public byte Location { + get { return location; } + } + + } + +} diff --git a/SearchEngine/IDocument.cs b/SearchEngine/IDocument.cs new file mode 100644 index 0000000..22d4514 --- /dev/null +++ b/SearchEngine/IDocument.cs @@ -0,0 +1,47 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Defines the interface a generic document. + /// + public interface IDocument { + + /// + /// Gets or sets the globally unique ID of the document. + /// + uint ID { get; set; } + + /// + /// Gets the globally-unique name of the document. + /// + string Name { get; } + + /// + /// Gets the title of the document, if any. + /// + string Title { get; } + + /// + /// Gets the tag for the document type. + /// + string TypeTag { get; } + + /// + /// Gets the document date/time. + /// + DateTime DateTime { get; } + + /// + /// Performs the tokenization of the document content. + /// + /// The content to tokenize. + /// The extracted words and their positions. + WordInfo[] Tokenize(string content); + + } + +} diff --git a/SearchEngine/IIndex.cs b/SearchEngine/IIndex.cs new file mode 100644 index 0000000..d85b72b --- /dev/null +++ b/SearchEngine/IIndex.cs @@ -0,0 +1,131 @@ + +using System; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// A delegate that is used for converting a to an instance of a class implementing , + /// while reading index data from a permanent storage. + /// + /// The to convert. + /// The converted document implementing or null if no document is found. + public delegate IDocument BuildDocument(DumpedDocument document); + + /// + /// Defines an interface for a search engine index. + /// + public interface IIndex { + + /// + /// Gets or sets the stop words to be used while indexing new content. + /// + string[] StopWords { get; set; } + + /// + /// Gets the total count of unique words. + /// + /// Computing the result is O(1). + int TotalWords { get; } + + /// + /// Gets the total count of documents. + /// + /// Computing the result is O(n*m), where n is the number of + /// words in the index and m is the number of documents. + int TotalDocuments { get; } + + /// + /// Gets the total number of occurrences (count of words in each document). + /// + /// Computing the result is O(n), + /// where n is the number of words in the index. + int TotalOccurrences { get; } + + /// + /// Completely clears the index (stop words are not affected). + /// + /// A state object that is passed to the IndexStorer SaveDate/DeleteData function. + void Clear(object state); + + /// + /// Stores a document in the index. + /// + /// The document. + /// The document keywords, if any, an empty array or null otherwise. + /// The content of the document. + /// A state object that is passed to the IndexStorer SaveDate/DeleteData function. + /// The number of indexed words (including duplicates). + /// Indexing the content of the document is O(n), + /// where n is the total number of words in the document. + /// If or are null. + int StoreDocument(IDocument document, string[] keywords, string content, object state); + + /// + /// Removes a document from the index. + /// + /// The document to remove. + /// A state object that is passed to the IndexStorer SaveDate/DeleteData function. + /// If is null. + void RemoveDocument(IDocument document, object state); + + /// + /// Performs a search in the index. + /// + /// The search parameters. + /// The results. + /// If is null. + SearchResultCollection Search(SearchParameters parameters); + + } + + /// + /// Defines an interface for an in-memory index. + /// + public interface IInMemoryIndex : IIndex { + + /// + /// An event fired when the index is changed. + /// + event EventHandler IndexChanged; + + /// + /// Sets the delegate used for converting a to an instance of a class implementing , + /// while reading index data from a permanent storage. + /// + /// The delegate (cannot be null). + /// This method must be called before invoking . + /// If is null. + void SetBuildDocumentDelegate(BuildDocument buildDocument); + + /// + /// Initializes index data by completely emptying the index catalog and storing the specified data. + /// + /// The documents. + /// The words. + /// The mappings. + /// The method does not check the consistency of the data passed as arguments. + /// If , or are null. + /// If was not called. + void InitializeData(DumpedDocument[] documents, DumpedWord[] words, DumpedWordMapping[] mappings); + + } + + /// + /// Lists legal search options. + /// + public enum SearchOptions { + /// + /// Search for at least one word of the search query. + /// + AtLeastOneWord, + /// + /// Search for all the words of the search query, in any order. + /// + AllWords, + /// + /// Search for an exact phrase. + /// + ExactPhrase + } + +} diff --git a/SearchEngine/InMemoryIndexBase.cs b/SearchEngine/InMemoryIndexBase.cs new file mode 100644 index 0000000..8951e55 --- /dev/null +++ b/SearchEngine/InMemoryIndexBase.cs @@ -0,0 +1,514 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Globalization; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Implements a base class for the search index. + /// + /// All instance and static members are thread-safe. + public abstract class InMemoryIndexBase : IInMemoryIndex { + + /// + /// The stop words to be used while indexing new content. + /// + protected string[] stopWords = null; + /// + /// Contains the index catalog. + /// + protected Dictionary catalog = null; + /// + /// The delegate. + /// + protected BuildDocument buildDocument = null; + + /// + /// An event fired when the index is changed. + /// + public event EventHandler IndexChanged; + + /// + /// Sets the delegate used for converting a to an instance of a class implementing , + /// while reading index data from a permanent storage. + /// + /// The delegate (cannot be null). + /// This method must be called before invoking . + /// If is null. + public void SetBuildDocumentDelegate(BuildDocument buildDocument) { + if(buildDocument == null) throw new ArgumentNullException("buildDocument"); + lock(this) { + this.buildDocument = buildDocument; + } + } + + /// + /// Takes care of firing the event. + /// + /// The affected document. + /// The change performed. + /// The dumped change data. + /// A state object that is passed to the IndexStorer SaveDate/DeleteData function. + /// The storage result or null. + protected IndexStorerResult OnIndexChange(IDocument document, IndexChangeType change, DumpedChange changeData, object state) { + if(IndexChanged != null) { + IndexChangedEventArgs args = new IndexChangedEventArgs(document, change, changeData, state); + IndexChanged(this, args); + return args.Result; + } + else return null; + } + + /// + /// Initializes a new instance of the class. + /// + public InMemoryIndexBase() { + this.stopWords = new string[0]; + this.catalog = new Dictionary(5000); + } + + /// + /// Gets or sets the stop words to be used while indexing new content. + /// + public string[] StopWords { + get { + lock(this) { + return stopWords; + } + } + set { + if(value == null) throw new ArgumentNullException("value", "Stop words cannot be null"); + lock(this) { + stopWords = value; + } + } + } + + /// + /// Gets the total count of unique words. + /// + /// Computing the result is O(1). + public int TotalWords { + get { + lock(this) { + return catalog.Count; + } + } + } + + /// + /// Gets the total count of documents. + /// + /// Computing the result is O(n*m), where n is the number of + /// words in the index and m is the number of documents. + public int TotalDocuments { + get { + List docs = new List(100); + lock(this) { + foreach(KeyValuePair pair in catalog) { + foreach(KeyValuePair pair2 in pair.Value.Occurrences) { + if(!docs.Contains(pair2.Key)) docs.Add(pair2.Key); + } + } + } + return docs.Count; + } + } + + /// + /// Gets the total number of occurrences (count of words in each document). + /// + /// Computing the result is O(n), + /// where n is the number of words in the index. + public int TotalOccurrences { + get { + int count = 0; + lock(this) { + foreach(KeyValuePair pair in catalog) { + count += pair.Value.TotalOccurrences; + } + } + return count; + } + } + + /// + /// Completely clears the index (stop words are not affected). + /// + /// A state object that is passed to the IndexStorer SaveDate/DeleteData function. + public void Clear(object state) { + lock(this) { + catalog.Clear(); + OnIndexChange(null, IndexChangeType.IndexCleared, null, state); + } + } + + /// + /// Initializes index data by completely emptying the index catalog and storing the specified data. + /// + /// The documents. + /// The words. + /// The mappings. + /// The method does not check the consistency of the data passed as arguments. + /// If , or are null. + /// If was not called. + public void InitializeData(DumpedDocument[] documents, DumpedWord[] words, DumpedWordMapping[] mappings) { + if(documents == null) throw new ArgumentNullException("documents"); + if(words == null) throw new ArgumentNullException("words"); + if(mappings == null) throw new ArgumentNullException("mappings"); + + if(buildDocument == null) throw new InvalidOperationException("InitializeData can be invoked only when the BuildDocument delegate is set"); + + lock(this) { + catalog.Clear(); + catalog = new Dictionary(words.Length); + + // Contains the IDs of documents that are missing + List missingDocuments = new List(50); + + // 1. Prepare a dictionary with all documents for use in the last step + Dictionary tempDocuments = new Dictionary(documents.Length); + foreach(DumpedDocument doc in documents) { + IDocument builtDoc = buildDocument(doc); + // Null means that the document no longer exists - silently skip it + if(builtDoc != null) { + tempDocuments.Add(doc.ID, builtDoc); + } + else { + missingDocuments.Add(doc.ID); + } + } + + // 2. Load words into the catalog, keeping track of them by ID in a dictionary for the next step + Dictionary tempWords = new Dictionary(words.Length); + + // Test for hashing algorithm -- no more used since sequential IDs + //if(words.Length > 0 && words[0].ID != Tools.HashString(words[0].Text)) { + // throw new InvalidOperationException("The search engine index seems to use an outdated hashing algorithm"); + //} + + foreach(DumpedWord w in words) { + Word word = new Word(w.ID, w.Text); + /*if(tempWords.ContainsKey(w.ID)) { + string t = string.Format("CURRENT: {0}, {1} --- EXISTING: {2}", w.ID, word, tempWords[w.ID]); + Console.WriteLine(t); + }*/ + tempWords.Add(w.ID, word); + /*if(catalog.ContainsKey(w.Text)) { + string t = string.Format("CURRENT: {0}, {1} --- EXISTING: {2}", w.ID, word, catalog[w.Text]); + Console.WriteLine(t); + }*/ + catalog.Add(w.Text, word); + } + + // 3. Add mappings and documents + foreach(DumpedWordMapping map in mappings) { + // HACK: Skip mappings that refer to missing documents and gracefully skip unknown words + if(!missingDocuments.Contains(map.DocumentID)) { + try { + tempWords[map.WordID].AddOccurrence(tempDocuments[map.DocumentID], + map.FirstCharIndex, map.WordIndex, WordLocation.GetInstance(map.Location)); + } + catch(KeyNotFoundException) { } + } + } + } + } + + /// + /// Stores a document in the index. + /// + /// The document. + /// The document keywords, if any, an empty array or null otherwise. + /// The content of the document. + /// A state object that is passed to the IndexStorer SaveDate/DeleteData function. + /// The number of indexed words (including duplicates) in the document title and content. + /// Indexing the content of the document is O(n), + /// where n is the total number of words in the document. + /// If the specified document was already in the index, all the old occurrences + /// are deleted from the index. + /// If or are null. + public int StoreDocument(IDocument document, string[] keywords, string content, object state) { + if(document == null) throw new ArgumentNullException("document"); + if(keywords == null) keywords = new string[0]; + if(content == null) throw new ArgumentNullException("content"); + + lock(this) { + DumpedChange removeChange = RemoveDocumentInternal(document); + + if(removeChange != null) { + OnIndexChange(document, IndexChangeType.DocumentRemoved, removeChange, state); + } + } + + keywords = Tools.CleanupKeywords(keywords); + + // When the IndexStorer handles the IndexChanged event and a document is added, the storer generates a new ID and returns it + // via the event handler, then the in-memory index is updated (the document instance is shared across all words) - the final ID + // is generated by the actual IndexStorer implementation (SaveData properly populates the Result field in the args) + + List dw = new List(content.Length / 5); + List dm = new List(content.Length / 5); + Word tempWord = null; + List newWords = new List(50); + DumpedWord tempDumpedWord = null; + + int count = 0; + uint sequentialWordId = uint.MaxValue; + + // Store content words + WordInfo[] words = document.Tokenize(content); + words = Tools.RemoveStopWords(words, stopWords); + + foreach(WordInfo info in words) { + dm.Add(StoreWord(info.Text, document, info.FirstCharIndex, info.WordIndex, WordLocation.Content, out tempWord, out tempDumpedWord)); + if(tempDumpedWord != null && tempWord != null) { + dm[dm.Count - 1].WordID = sequentialWordId; + tempDumpedWord.ID = sequentialWordId; + dw.Add(tempDumpedWord); + tempWord.ID = sequentialWordId; + newWords.Add(tempWord); + sequentialWordId--; + } + } + count += words.Length; + + // Store title words + words = document.Tokenize(document.Title); + words = Tools.RemoveStopWords(words, stopWords); + + foreach(WordInfo info in words) { + dm.Add(StoreWord(info.Text, document, info.FirstCharIndex, info.WordIndex, WordLocation.Title, out tempWord, out tempDumpedWord)); + if(tempDumpedWord != null && tempWord != null) { + dm[dm.Count - 1].WordID = sequentialWordId; + tempDumpedWord.ID = sequentialWordId; + dw.Add(tempDumpedWord); + tempWord.ID = sequentialWordId; + newWords.Add(tempWord); + sequentialWordId--; + } + } + count += words.Length; + + ushort tempCount = 0; + + // Store keywords + for(ushort i = 0; i < (ushort)keywords.Length; i++) { + dm.Add(StoreWord(keywords[i], document, tempCount, i, WordLocation.Keywords, out tempWord, out tempDumpedWord)); + if(tempDumpedWord != null && tempWord != null) { + dm[dm.Count - 1].WordID = sequentialWordId; + tempDumpedWord.ID = sequentialWordId; + dw.Add(tempDumpedWord); + tempWord.ID = sequentialWordId; + newWords.Add(tempWord); + sequentialWordId--; + } + tempCount += (ushort)(1 + keywords[i].Length); + } + count += keywords.Length; + + IndexStorerResult result = OnIndexChange(document, IndexChangeType.DocumentAdded, + new DumpedChange(new DumpedDocument(document), dw, dm), state); + + // Update document ID + if(result != null && result.DocumentID.HasValue) { + document.ID = result.DocumentID.Value; + } + else { + // HACK: result is null -> index is corrupted, silently return + return 0; + } + + // Update word IDs in newWords + bool wordIdUpdated = false; + foreach(Word word in newWords) { + wordIdUpdated = false; + foreach(WordId id in result.WordIDs) { + if(id.Text == word.Text) { + word.ID = id.ID; + wordIdUpdated = true; + break; + } + } + if(!wordIdUpdated) throw new InvalidOperationException("No ID for new word"); + } + + return count; + } + + /// + /// Stores a word in the catalog. + /// + /// The word to store. + /// The document the word occurs in. + /// The index of the first character of the word in the document the word occurs at. + /// The index of the word in the document. + /// The location of the word. + /// The new word, or null. + /// The dumped word data, or null. + /// The dumped word mapping data. + /// Storing a word in the index is O(n log n), + /// where n is the number of words already in the index. + protected DumpedWordMapping StoreWord(string wordText, IDocument document, ushort firstCharIndex, ushort wordIndex, + WordLocation location, out Word newWord, out DumpedWord dumpedWord) { + + wordText = wordText.ToLower(CultureInfo.InvariantCulture); + + lock(this) { + Word word = null; + + if(!catalog.TryGetValue(wordText, out word)) { + // Use ZERO as initial ID, update when IndexStorer has stored the word + // A reference to this newly-created word must be passed outside this method + word = new Word(0, wordText); + catalog.Add(wordText, word); + newWord = word; + dumpedWord = new DumpedWord(word); + } + else { + newWord = null; + dumpedWord = null; + } + + word.AddOccurrence(document, firstCharIndex, wordIndex, location); + return new DumpedWordMapping(word.ID, document.ID, firstCharIndex, wordIndex, location.Location); + } + } + + /// + /// Removes a document from the index. + /// + /// The document to remove. + /// A state object that is passed to the IndexStorer SaveDate/DeleteData function. + /// If is null. + public void RemoveDocument(IDocument document, object state) { + if(document == null) throw new ArgumentNullException("document"); + + DumpedChange dc = RemoveDocumentInternal(document); + + if(dc != null) { + OnIndexChange(document, IndexChangeType.DocumentRemoved, dc, state); + } + // else nothing to do + } + + /// + /// Finds a document with a specified name. + /// + /// The name of the document. + /// The document or null. + private IDocument FindDocument(string name) { + foreach(KeyValuePair pair in catalog) { + foreach(KeyValuePair pair2 in pair.Value.Occurrences) { + if(StringComparer.OrdinalIgnoreCase.Compare(pair2.Key.Name, name) == 0) { + return pair2.Key; + } + } + } + return null; + } + + /// + /// Removes a document from the index and generates the dumped change data. + /// + /// The document to remove. + /// The dumped change data, if any, null otherwise. + protected DumpedChange RemoveDocumentInternal(IDocument document) { + if(document == null) throw new ArgumentNullException("document"); + + // Find real document to remove by name + document = FindDocument(document.Name); + if(document == null) { + return null; + } + + List dw = null; + List dm = new List(1500); + + foreach(string w in catalog.Keys) { + dm.AddRange(catalog[w].RemoveOccurrences(document)); + } + + // Remove all words that have no occurrences left + List toRemove = new List(50); + foreach(string w in catalog.Keys) { + if(catalog[w].TotalOccurrences == 0) toRemove.Add(w); + } + dw = new List(toRemove.Count); + foreach(string w in toRemove) { + dw.Add(new DumpedWord(catalog[w])); + catalog.Remove(w); + } + + if(dm.Count > 0 || dw.Count > 0 || document != null) { + return new DumpedChange(new DumpedDocument(document), dw, dm); + } + else return null; + } + + /// + /// Performs a search in the index. + /// + /// The search parameters. + /// The results. + /// If is null. + public SearchResultCollection Search(SearchParameters parameters) { + if(parameters == null) throw new ArgumentNullException("parameters"); + + using(IWordFetcher fetcher = new InMemoryIndexWordFetcher(catalog)) { + if(parameters.DocumentTypeTags == null) { + return Tools.SearchInternal(parameters.Query, null, false, parameters.Options, fetcher); + } + else { + return Tools.SearchInternal(parameters.Query, parameters.DocumentTypeTags, true, parameters.Options, fetcher); + } + } + } + + } + + /// + /// Implements a word fetcher for use with the in-memory index. + /// + public class InMemoryIndexWordFetcher : IWordFetcher { + + private Dictionary catalog; + + /// + /// Initializes a new instance of the class. + /// + /// The index catalog. + public InMemoryIndexWordFetcher(Dictionary catalog) { + if(catalog == null) throw new ArgumentNullException("catalog"); + + this.catalog = catalog; + } + + /// + /// Tries to get a word. + /// + /// The text of the word. + /// The found word, if any, null otherwise. + /// true if the word is found, false otherwise. + public bool TryGetWord(string text, out Word word) { + lock(catalog) { + return catalog.TryGetValue(text, out word); + } + } + + #region IDisposable Members + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() { + // Nothing to do + } + + #endregion + + } + +} diff --git a/SearchEngine/IndexChangedEventArgs.cs b/SearchEngine/IndexChangedEventArgs.cs new file mode 100644 index 0000000..002df1f --- /dev/null +++ b/SearchEngine/IndexChangedEventArgs.cs @@ -0,0 +1,111 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Contains arguments for the IndexChanged event of the interface. + /// + public class IndexChangedEventArgs : EventArgs { + + private IDocument document; + private IndexChangeType change; + private DumpedChange changeData; + private object state; + + private IndexStorerResult result = null; + + /// + /// Initializes a new instance of the class. + /// + /// The affected document. + /// The change performed. + /// The dumped change data. + /// A state object that is passed to the IndexStorer SaveDate/DeleteData function. + /// If is not and or are null. + public IndexChangedEventArgs(IDocument document, IndexChangeType change, DumpedChange changeData, object state) { + if(change != IndexChangeType.IndexCleared) { + if(document == null) throw new ArgumentNullException("document"); + if(changeData == null) throw new ArgumentNullException("changeData"); + } + + this.document = document; + this.change = change; + this.changeData = changeData; + this.state = state; + } + + /// + /// Initializes a new instance of the class. + /// + /// The affected document. + /// The change performed. + /// The dumped change data. + /// A state object that is passed to the IndexStorer SaveDate/DeleteData function. + /// The storer result, if any. + /// If is not and or are null. + public IndexChangedEventArgs(IDocument document, IndexChangeType change, DumpedChange changeData, object state, IndexStorerResult result) + : this(document, change, changeData, state) { + + this.result = result; + } + + /// + /// Gets the affected document. + /// + public IDocument Document { + get { return document; } + } + + /// + /// Gets the change performed. + /// + public IndexChangeType Change { + get { return change; } + } + + /// + /// Gets the dumped change data. + /// + public DumpedChange ChangeData { + get { return changeData; } + } + + /// + /// Gets the state object that is passed to the IndexStorer SaveDate/DeleteData function. + /// + public object State { + get { return state; } + } + + /// + /// Gets or sets the index storer result, if any. + /// + public IndexStorerResult Result { + get { return result; } + set { result = value; } + } + + } + + /// + /// Lists valid index changes. + /// + public enum IndexChangeType { + /// + /// A document is added. + /// + DocumentAdded, + /// + /// A document is removed. + /// + DocumentRemoved, + /// + /// The index is cleared. + /// + IndexCleared + } + +} diff --git a/SearchEngine/IndexStorerBase.cs b/SearchEngine/IndexStorerBase.cs new file mode 100644 index 0000000..85fcc9e --- /dev/null +++ b/SearchEngine/IndexStorerBase.cs @@ -0,0 +1,222 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Implements a base class for an index storer. + /// + /// Instance and static members are thread-safe. + public abstract class IndexStorerBase : IDisposable { + + /// + /// Indicates whether the object was disposed. + /// + protected bool disposed = false; + + /// + /// The index bound to this storer. + /// + protected IInMemoryIndex index = null; + + /// + /// true if the index data seems corrupted. + /// + protected bool dataCorrupted = false; + + /// + /// Contains the exception occurred during index setup. + /// + protected Exception reasonForDataCorruption = null; + + /// + /// The event handler for the of the bound index. + /// + protected EventHandler indexChangedHandler; + + /// + /// Initializes a new instance of the class. + /// + /// The index to manage. + /// If is null. + public IndexStorerBase(IInMemoryIndex index) { + if(index == null) throw new ArgumentNullException("index"); + + this.index = index; + indexChangedHandler = new EventHandler(IndexChangedHandler); + this.index.IndexChanged += indexChangedHandler; + } + + /// + /// Gets the index. + /// + public IInMemoryIndex Index { + get { + lock(this) { + return index; + } + } + } + + /// + /// Loads the index from the data store the first time. + /// + /// The dumped documents. + /// The dumped words. + /// The dumped word mappings. + protected abstract void LoadIndexInternal(out DumpedDocument[] documents, out DumpedWord[] words, out DumpedWordMapping[] mappings); + + /// + /// Gets the approximate size, in bytes, of the search engine index. + /// + public abstract long Size { get; } + + /// + /// Gets a value indicating whether the index data seems corrupted and cannot be used. + /// + public bool DataCorrupted { + get { + lock(this) { + return dataCorrupted; + } + } + } + + /// + /// Gets the exception that caused data corruption. + /// + public Exception ReasonForDataCorruption { + get { return reasonForDataCorruption; } + } + + /// + /// Loads the index from the data store the first time. + /// + public void LoadIndex() { + lock(this) { + DumpedDocument[] documents = null; + DumpedWord[] words = null; + DumpedWordMapping[] mappings = null; + + dataCorrupted = false; + + try { + LoadIndexInternal(out documents, out words, out mappings); + } + catch(Exception ex) { + reasonForDataCorruption = ex; + dataCorrupted = true; + } + + if(!dataCorrupted) { + try { + index.InitializeData(documents, words, mappings); + } + catch(Exception ex) { + reasonForDataCorruption = ex; + dataCorrupted = true; + } + } + } + } + + /// + /// Handles the events. + /// + /// The sender. + /// The event arguments. + protected void IndexChangedHandler(object sender, IndexChangedEventArgs e) { + lock(this) { + if(disposed) return; + + switch(e.Change) { + case IndexChangeType.IndexCleared: + InitDataStore(e.State); + break; + case IndexChangeType.DocumentAdded: + if(!dataCorrupted) { + IndexStorerResult result = SaveData(e.ChangeData, e.State); + e.Result = result; + } + break; + case IndexChangeType.DocumentRemoved: + if(!dataCorrupted) DeleteData(e.ChangeData, e.State); + break; + default: + throw new NotSupportedException("Invalid Change Type"); + } + } + } + + /// + /// Initializes the data storage. + /// + /// A state object passed from the index. + protected abstract void InitDataStore(object state); + + /// + /// Deletes data from the data storage. + /// + /// The data to delete. + /// A state object passed from the index. + protected abstract void DeleteData(DumpedChange data, object state); + + /// + /// Stores new data into the data storage. + /// + /// The data to store. + /// A state object passed by the index. + /// The storer result, if any. + /// When saving a new document, the document ID in data.Mappings must be + /// replaced with the currect document ID, generated by the concrete implementation of + /// this method. data.Words should have IDs numbered from uint.MaxValue downwards. + /// The method re-numbers the words appropriately. + protected abstract IndexStorerResult SaveData(DumpedChange data, object state); + + /// + /// Disposes the current object. + /// + public void Dispose() { + lock(this) { + if(!disposed) { + disposed = true; + index.IndexChanged -= indexChangedHandler; + } + } + } + + /// + /// Determines whether a is contained in a list. + /// + /// The mapping. + /// The list. + /// true if the mapping is contained in the list, false otherwise. + protected static bool Find(DumpedWordMapping mapping, IEnumerable list) { + foreach(DumpedWordMapping m in list) { + if(m.WordID == mapping.WordID && + m.DocumentID == mapping.DocumentID && + m.FirstCharIndex == mapping.FirstCharIndex && + m.WordIndex == mapping.WordIndex && + m.Location == mapping.Location) return true; + } + return false; + } + + /// + /// Determines whether a is contained in a list. + /// + /// The word. + /// The list. + /// true if the word is contained in the list, false orherwise. + protected static bool Find(DumpedWord word, IEnumerable list) { + foreach(DumpedWord w in list) { + if(w.ID == word.ID && w.Text == word.Text) return true; + } + return false; + } + + } + +} diff --git a/SearchEngine/IndexStorerResult.cs b/SearchEngine/IndexStorerResult.cs new file mode 100644 index 0000000..5cc0de1 --- /dev/null +++ b/SearchEngine/IndexStorerResult.cs @@ -0,0 +1,78 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Contains the results of an index storer operation. + /// + public class IndexStorerResult { + + private uint? documentId; + private List wordIds; + + /// + /// Initializes a new instance of the class. + /// + /// The ID of the document just stored, if any. + /// The IDs of the words just stored, if any. + public IndexStorerResult(uint? documentId, List wordIds) { + this.documentId = documentId; + this.wordIds = wordIds; + } + + /// + /// Gets or sets the ID of the document just stored, if any. + /// + public uint? DocumentID { + get { return documentId; } + set { documentId = value; } + } + + /// + /// Gets or sets the IDs of the words + /// + public List WordIDs { + get { return wordIds; } + set { wordIds = value; } + } + + } + + /// + /// Describes the ID of a word. + /// + public class WordId { + + private string text; + private uint id; + + /// + /// Initializes a new instance of the class. + /// + /// The word text, lowercase. + /// The word ID. + public WordId(string text, uint id) { + this.text = text; + this.id = id; + } + + /// + /// Gets the word text. + /// + public string Text { + get { return text; } + } + + /// + /// Gets the word ID. + /// + public uint ID { + get { return id; } + } + + } + +} diff --git a/SearchEngine/OccurrenceDictionary.cs b/SearchEngine/OccurrenceDictionary.cs new file mode 100644 index 0000000..0f6e06c --- /dev/null +++ b/SearchEngine/OccurrenceDictionary.cs @@ -0,0 +1,271 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Implements a IDocument-SortedBasicWordInfo dictionary which treats IDocuments as value type. + /// + /// All instance members are not thread-safe. + public class OccurrenceDictionary : IDictionary { + + private Dictionary dictionary; + private Dictionary mappings; // Used for performance purposes + + /// + /// Initializes a new instance of the class. + /// + /// The initial capacity of the dictionary. + /// If is less than or equal to zero. + public OccurrenceDictionary(int capacity) { + if(capacity < 0) throw new ArgumentOutOfRangeException("capacity", "Capacity must be greater than zero"); + + mappings = new Dictionary(capacity); + dictionary = new Dictionary(capacity); + } + + /// + /// Initializes a new instance of the class. + /// + public OccurrenceDictionary() + : this(10) { } + + /// + /// Gets the number of elements in the dictionary. + /// + public int Count { + get { return dictionary.Count; } + } + + /// + /// Adds the specified key and value to the dictionary. + /// + /// The key of the element to add. + /// The value of the element to add. + /// If or are null. + /// If is already present in the dictionary. + public void Add(IDocument key, SortedBasicWordInfoSet value) { + if(key == null) throw new ArgumentNullException("key"); + if(value == null) throw new ArgumentNullException("value"); + + if(ContainsKey(key)) throw new ArgumentException("The specified key is already contained in the dictionary", "key"); + + mappings.Add(key.Name, key); + dictionary.Add(key, value); + } + + /// + /// Adds the specified key-value pair to the dictionary. + /// + /// The key-value pair to add. + public void Add(KeyValuePair item) { + Add(item.Key, item.Value); + } + + /// + /// Determines whether the dictionary contains the specified key. + /// + /// The key to locate. + /// true if the dictionary contains the specified key, false otherwise. + /// If is null. + public bool ContainsKey(IDocument key) { + if(key == null) throw new ArgumentNullException("key"); + + return FindKey(key) != null; + } + + /// + /// Gets the keys of the dictionary. + /// + public ICollection Keys { + get { return dictionary.Keys; } + } + + /// + /// Removes an element from the dictionary. + /// + /// The key of the element to remove. + /// true if the element is removed, false otherwise. + /// If is null. + public bool Remove(IDocument key) { + if(key == null) throw new ArgumentNullException("key"); + + try { + // The removal is considered to be successful even if no word mappings are removed + RemoveExtended(key, uint.MaxValue); + return true; + } + catch(KeyNotFoundException) { + return false; + } + } + + /// + /// Removes an element from the dictionary. + /// + /// The element to remove. + /// true if the element is removed, false otherwise. + public bool Remove(KeyValuePair item) { + return Remove(item.Key); + } + + /// + /// Removes an element from the dictionary. + /// + /// The key of the element to remove. + /// The unique ID of the word that is being removed. + /// The list of removed words mappings, in dumpable format. + /// If key is not found, a is thrown. + /// If is null. + /// If is not present in the dictionary. + public List RemoveExtended(IDocument key, uint wordId) { + if(key == null) throw new ArgumentNullException("key"); + + IDocument target = FindKey(key); + if(target == null) throw new KeyNotFoundException("Specified IDocument was not found"); + else { + IDocument mappedDoc = mappings[key.Name]; + if(mappings.Remove(target.Name)) { + // Prepare the list of DumpedWordMapping objects + SortedBasicWordInfoSet set = dictionary[mappedDoc]; + List dump = new List(set.Count); + foreach(BasicWordInfo w in set) { + dump.Add(new DumpedWordMapping(wordId, key.ID, w)); + } + + if(!dictionary.Remove(target)) throw new InvalidOperationException("Internal data is broken"); + + return dump; + } + else throw new InvalidOperationException("Internal data is broken"); + } + } + + /// + /// Finds an actual key in the mappings dictionary. + /// + /// The document. + /// The key, or null. + /// If is null. + private IDocument FindKey(IDocument key) { + if(key == null) throw new ArgumentNullException("key"); + + IDocument target = null; + if(mappings.TryGetValue(key.Name, out target)) return target; + else return null; + } + + /// + /// Tries to retrieve a value from the dictionary. + /// + /// The key of the value to retrieve. + /// The resulting value, or null. + /// true if the value is retrieved, false otherwise. + /// If is null. + public bool TryGetValue(IDocument key, out SortedBasicWordInfoSet value) { + if(key == null) throw new ArgumentNullException("key"); + + IDocument target = FindKey(key); + if(target == null) { + value = null; + return false; + } + else { + value = dictionary[target]; + return true; + } + } + + /// + /// Gets the values of the dictionary. + /// + public ICollection Values { + get { return dictionary.Values; } + } + + /// + /// Gets or sets a value in the dictionary. + /// + /// The key of the value to ger or set. + /// The value. + /// If is null. + /// If the key is not found. + public SortedBasicWordInfoSet this[IDocument key] { + get { + if(key == null) throw new ArgumentNullException("key"); + + IDocument target = FindKey(key); + if(target == null) throw new IndexOutOfRangeException("The specified key was not found"); + else return dictionary[target]; + } + set { + if(key == null) throw new ArgumentNullException("key"); + if(value == null) throw new ArgumentNullException("value"); + + IDocument target = FindKey(key); + if(target == null) throw new IndexOutOfRangeException("The specified key was not found"); + else dictionary[target] = value; + } + } + + /// + /// Clears the dictionary. + /// + public void Clear() { + mappings.Clear(); + dictionary.Clear(); + } + + /// + /// Determines whether the dictionary contains an element. + /// + /// The key-value pair representing the element. + /// true if the dictionary contains the element, false otherwise. + public bool Contains(KeyValuePair item) { + return ContainsKey(item.Key); + } + + /// + /// Gets a value indicating whether the dictionary is read-only. + /// + public bool IsReadOnly { + get { return false; } + } + + /// + /// Copies the content of the dictionary to a key-value pairs array. + /// + /// The output array. + /// The index at which the copy begins. + /// If is null. + public void CopyTo(KeyValuePair[] array, int arrayIndex) { + if(array == null) throw new ArgumentNullException("array"); + + int i = 0; + foreach(KeyValuePair pair in dictionary) { + array[arrayIndex + i] = pair; + i++; + } + } + + /// + /// Returns an enumerator that iterates through the elements in the dictionary. + /// + /// The iterator. + public IEnumerator> GetEnumerator() { + return dictionary.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through the elements in the dictionary. + /// + /// The iterator. + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return dictionary.GetEnumerator(); + } + + } + +} diff --git a/SearchEngine/Properties/AssemblyInfo.cs b/SearchEngine/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4e84b0b --- /dev/null +++ b/SearchEngine/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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("ScrewTurn Wiki Search Engine")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// 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("8c3cfb7c-c4ff-426f-8c4b-87f84f8a799d")] diff --git a/SearchEngine/Relevance.cs b/SearchEngine/Relevance.cs new file mode 100644 index 0000000..75c236b --- /dev/null +++ b/SearchEngine/Relevance.cs @@ -0,0 +1,95 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Represents the relevance of a search result with two states: non-finalized and finalized. + /// When the state is non-finalized, the value of the relevance has an unknown meaning. When the state + /// is finalized, the value of the relevance is a percentage value representing the relevance of a search result. + /// + /// All members are not thread-safe. + public class Relevance { + + /// + /// The value of the relevance. + /// + protected float value; + /// + /// A value indicating whether the relevance value is finalized. + /// + protected bool isFinalized; + + /// + /// Initializes a new instance of the class. + /// + public Relevance() + : this(0) { } + + /// + /// Initializes a new instance of the class. + /// + /// The initial relevance value, non-finalized. + /// If is less than zero. + public Relevance(float value) { + if(value < 0) throw new ArgumentOutOfRangeException("value", "Value must be greater than or equal to zero"); + this.value = value; + this.isFinalized = false; + } + + /// + /// Sets the non-finalized value of the relevance. + /// + /// The value. + /// If is less than zero. + /// If is true ( was called). + public void SetValue(float value) { + if(value < 0) throw new ArgumentOutOfRangeException("value", "Value must be greater than or equal to zero"); + if(isFinalized) throw new InvalidOperationException("After finalization, the value cannot be changed anymore"); + this.value = value; + } + + /// + /// Finalizes the value of the relevance. + /// + /// The global relevance value. + /// The method sets the finalized value of the relevance to value / total * 100. + /// If is less than zero. + /// If is true ( was called). + public void Finalize(float total) { + if(total < 0) throw new ArgumentOutOfRangeException("total", "Total must be greater than or equal to zero"); + if(isFinalized) throw new InvalidOperationException("Finalization already performed"); + value = value / total * 100; + isFinalized = true; + } + + /// + /// Normalizes the relevance after finalization. + /// + /// The normalization factor. + /// If is false ( was not called). + public void NormalizeAfterFinalization(float factor) { + if(factor < 0) throw new ArgumentOutOfRangeException("factor", "Factor must be greater than or equal to zero"); + if(!isFinalized) throw new InvalidOperationException("Normalization can be performed only after finalization"); + value = value * factor; + } + + /// + /// Gets a value indicating whether the relevance value is finalized. + /// + public bool IsFinalized { + get { return isFinalized; } + } + + /// + /// Gets the value of the relevance. + /// + public float Value { + get { return value; } + } + + } + +} diff --git a/SearchEngine/SearchEngine.csproj b/SearchEngine/SearchEngine.csproj new file mode 100644 index 0000000..b3be4ea --- /dev/null +++ b/SearchEngine/SearchEngine.csproj @@ -0,0 +1,116 @@ + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {2DF980A6-4742-49B1-A090-DE79314644D0} + Library + Properties + ScrewTurn.Wiki.SearchEngine + ScrewTurn.Wiki.SearchEngine + + + 2.0 + + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + v3.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + 512 + bin\Debug\ScrewTurn.Wiki.SearchEngine.XML + + + none + true + bin\Release\ + TRACE + prompt + 4 + true + 512 + bin\Release\ScrewTurn.Wiki.SearchEngine.xml + + + + + 3.5 + + + + + AssemblyVersion.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + .NET Framework 2.0 %28x86%29 + true + + + False + .NET Framework 3.0 %28x86%29 + false + + + False + .NET Framework 3.5 + false + + + + + \ No newline at end of file diff --git a/SearchEngine/SearchParameters.cs b/SearchEngine/SearchParameters.cs new file mode 100644 index 0000000..e80ab08 --- /dev/null +++ b/SearchEngine/SearchParameters.cs @@ -0,0 +1,108 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Contains search parameters. + /// + public class SearchParameters { + + private string query; + private string[] documentTypeTags; + private SearchOptions options; + + /// + /// Initializes a new instance of the class. + /// + /// The search query. + /// The document type tags to include in the search, or null. + /// The search options. + /// If or one of the elements of (when the array is not null) are null. + /// If or one of the elements of (when the array is not null) are empty. + public SearchParameters(string query, string[] documentTypeTags, SearchOptions options) { + if(query == null) throw new ArgumentNullException("query"); + if(query.Length == 0) throw new ArgumentException("Query cannot be empty", "query"); + if(documentTypeTags != null) { + if(documentTypeTags.Length == 0) throw new ArgumentException("DocumentTypeTags cannot be empty", "documentTypeTags"); + foreach(string dtt in documentTypeTags) { + if(dtt == null) throw new ArgumentNullException("documentTypeTags"); + if(dtt.Length == 0) throw new ArgumentException("DocumentTypeTag cannot be empty", "documentTypeTag"); + } + } + + this.query = PrepareQuery(query); + this.documentTypeTags = documentTypeTags; + this.options = options; + } + + /// + /// Initializes a new instance of the class. + /// + /// The search query. + public SearchParameters(string query) + : this(query, null, SearchOptions.AtLeastOneWord) { } + + /// + /// Initializes a new instance of the class. + /// + /// The search query. + /// The document type tags to include in the search, or null. + public SearchParameters(string query, params string[] documentTypeTags) + : this(query, documentTypeTags, SearchOptions.AtLeastOneWord) { } + + /// + /// Initializes a new instance of the class. + /// + /// The search query. + /// The search options. + public SearchParameters(string query, SearchOptions options) + : this(query, null, options) { } + + /// + /// Prepares a query for searching. + /// + /// The query. + /// The prepared query. + private static string PrepareQuery(string query) { + StringBuilder sb = new StringBuilder(query.Length); + + // This behavior is slightly different from RemoveDiacriticsAndPunctuation + foreach(char c in query) { + if(!ScrewTurn.Wiki.SearchEngine.Tools.IsSplitChar(c)) sb.Append(c); + else sb.Append(" "); + } + + string normalized = Tools.RemoveDiacriticsAndPunctuation(sb.ToString(), false); + return normalized; + } + + /// + /// Gets or sets the query. + /// + public string Query { + get { return query; } + set { query = PrepareQuery(value); } + } + + /// + /// Gets or sets the document type tags to include in the search, or null. + /// + public string[] DocumentTypeTags { + get { return documentTypeTags; } + set { documentTypeTags = value; } + } + + /// + /// Gets or sets the search options. + /// + public SearchOptions Options { + get { return options; } + set { options = value; } + } + + } + +} diff --git a/SearchEngine/SearchResult.cs b/SearchEngine/SearchResult.cs new file mode 100644 index 0000000..4bbf43f --- /dev/null +++ b/SearchEngine/SearchResult.cs @@ -0,0 +1,73 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Collections.ObjectModel; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Represents a search result. + /// + /// Instance and static members are not thread-safe. + public class SearchResult { + + /// + /// The document the result refers to. + /// + protected IDocument document; + /// + /// The matches in the document. + /// + protected WordInfoCollection matches; + /// + /// The result relevance. + /// + protected Relevance relevance; + + /// + /// Initializes a new instance of the class. + /// + /// The document the result refers to. + /// The relevance is initially set to 0. + /// If is null. + public SearchResult(IDocument document) { + if(document == null) throw new ArgumentNullException("document"); + + this.document = document; + this.matches = new WordInfoCollection(); + this.relevance = new Relevance(0); + } + + /// + /// Gets the document the result refers to. + /// + public IDocument Document { + get { return document; } + } + + /// + /// Gets the matches in the document. + /// + public WordInfoCollection Matches { + get { return matches; } + } + + /// + /// Gets the relevance of the search result. + /// + public Relevance Relevance { + get { return relevance; } + } + + /// + /// Gets a string representation of the current instance. + /// + /// The string representation. + public override string ToString() { + return document.Name + "(" + matches.Count.ToString() + " matches)"; + } + + } + +} diff --git a/SearchEngine/SearchResultCollection.cs b/SearchEngine/SearchResultCollection.cs new file mode 100644 index 0000000..966fa24 --- /dev/null +++ b/SearchEngine/SearchResultCollection.cs @@ -0,0 +1,167 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Contains a collection of SearchResults, without duplicates. + /// + /// Instance and static members are not thread-safe. + public class SearchResultCollection : ICollection { + + /// + /// Contains the collection items. + /// + protected List items; + + /// + /// Initializes a new instance of the Class. + /// + public SearchResultCollection() + : this(10) { } + + /// + /// Initializes a new instance of the Class. + /// + /// The initial capacity of the collection. + /// If is less than or equal to zero. + public SearchResultCollection(int capacity) { + if(capacity <= 0) throw new ArgumentOutOfRangeException("Invalid capacity", "capacity"); + + items = new List(capacity); + } + + /// + /// Adds an item to the collection. + /// + /// The item to add. + /// If is null. + /// If is laredy present in the collection. + public void Add(SearchResult item) { + if(item == null) throw new ArgumentNullException("item"); + + foreach(SearchResult r in items) { + if(r.Document == item.Document) + throw new ArgumentException("Item is already present in the collection", "item"); + } + + items.Add(item); + } + + /// + /// Clears the collection. + /// + public void Clear() { + items.Clear(); + } + + /// + /// Determines whether or not the collection contains an item. + /// + /// The item to check for. + /// true if the collection contains item, false otherwise. + public bool Contains(SearchResult item) { + if(item == null) return false; + else return items.Contains(item); + } + + /// + /// Retrieves the search result for a document (looking at document names). + /// + /// The document. + /// The SearchResult object, if any, null otherwise. + /// If is null. + public SearchResult GetSearchResult(IDocument document) { + if(document == null) throw new ArgumentNullException("document"); + + foreach(SearchResult r in items) { + if(r.Document.Name == document.Name) return r; + } + return null; + } + + /// + /// Copies the collection to an array. + /// + /// The destination array. + /// The zero-based array index at which the copy begins. + /// If is outside the bounds of . + public void CopyTo(SearchResult[] array, int arrayIndex) { + if(array == null) throw new ArgumentNullException("array"); + if(arrayIndex < 0 || arrayIndex > array.Length - 1) + throw new ArgumentOutOfRangeException("arrayIndex", "Index should be greater than or equal to zero and less than the number of items in the array"); + + if(array.Length - arrayIndex < items.Count) + throw new ArgumentOutOfRangeException("arrayIndex", "Not enough space for copying the items starting at the specified index"); + + items.CopyTo(array, arrayIndex); + } + + /// + /// Gets the total number of items in the collection. + /// + public int Count { + get { return items.Count; } + } + + /// + /// Gets the capacity of the collection. + /// + public int Capacity { + get { return items.Capacity; } + } + + /// + /// Gets a value indicating whether the collection is read-only. + /// + public bool IsReadOnly { + get { return false; } + } + + /// + /// Removes an item from the collection. + /// + /// The item to remove. + /// true if the item is removed, false otherwise. + /// If is null. + public bool Remove(SearchResult item) { + if(item == null) throw new ArgumentNullException("item"); + + return items.Remove(item); + } + + /// + /// Returns an enumerator that iterates through the items in the collection. + /// + /// The enumerator. + public IEnumerator GetEnumerator() { + return items.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through the items in the collection. + /// + /// The enumerator. + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return items.GetEnumerator(); + } + + /// + /// Gets an item from the collection. + /// + /// The index of the item. + /// The item. + /// If is outside the bounds of the collection. + public SearchResult this[int index] { + get { + if(index < 0 || index > items.Count - 1) throw new IndexOutOfRangeException("Index should be greater than or equal to zero and less than the number of items in the collection"); + + return items[index]; + } + } + + } + +} diff --git a/SearchEngine/SortedBasicWordInfoSet.cs b/SearchEngine/SortedBasicWordInfoSet.cs new file mode 100644 index 0000000..6efa67d --- /dev/null +++ b/SearchEngine/SortedBasicWordInfoSet.cs @@ -0,0 +1,143 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Implements a sorted set of integers which can be accessed by index. + /// + /// Instance members are not thread-safe. + public class SortedBasicWordInfoSet : IEnumerable { + + private List items; + + /// + /// Initializes a new instance of the class. + /// + public SortedBasicWordInfoSet() + : this(10) { } + + /// + /// Initializes a new instance of the class. + /// + /// The initial capacity. + /// If is null. + public SortedBasicWordInfoSet(int capacity) { + if(capacity <= 0) throw new ArgumentOutOfRangeException("Invalid capacity", "capacity"); + + items = new List(capacity); + } + + /// + /// Gets the count if the items in the set. + /// + public int Count { + get { return items.Count; } + } + + /// + /// Gets the capacity of the set. + /// + public int Capacity { + get { return items.Capacity; } + } + + /// + /// Adds a new item to the set. + /// + /// The item to add. + /// true if the item is added, false otherwise. + /// Adding an item is O(log n) if the item is already in the set, + /// O(n) otherwise, where n is the number of items in the set. + public bool Add(BasicWordInfo item) { + int idx = items.BinarySearch(item); + + if(idx < 0) { + // Item does not exist, insert it to the right position to avoid explicit sorting + // Sort (quick sort) is O(nlogn) on average, O(n^2) worst, Insert is O(n) + items.Insert(~idx, item); + return true; + } + else return false; + } + + /// + /// Determines whether the set contains an item. + /// + /// The item to look for. + /// true if the set contains the specified item, false otherwise. + /// The operation is O(log n), where n is the number of items in the set. + public bool Contains(BasicWordInfo item) { + if(items.Count == 0) return false; + + return items.BinarySearch(item) >= 0; + } + + /// + /// Removes an item from the set. + /// + /// The item to remove. + /// true if the item is removed, false otherwise. + /// The operation is O(n), where n is the number of items in the set. + public bool Remove(BasicWordInfo item) { + if(items.Count == 0) return false; + + // Remove and RemoveAt are both O(n) + return items.Remove(item); + } + + /// + /// Clears the set. + /// + /// The operation is O(1). + public void Clear() { + items.Clear(); + } + + /// + /// Returns an enumerator that iterates through the set. + /// + /// The enumerator + IEnumerator IEnumerable.GetEnumerator() { + return items.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through the set. + /// + /// The enumerator + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return items.GetEnumerator(); + } + + /// + /// Gets an item from the set at a specific index. + /// + /// The zero-based index of the element to get. + /// The item at the specified index. + /// If is outside the bounds of the collection. + public BasicWordInfo this[int index] { + get { + if(index < 0 || index > items.Count - 1) throw new IndexOutOfRangeException("Index should be greater than or equal to zero and less than the number of items in the set"); + return items[index]; + } + } + + /// + /// Gets a string representation of the current instance. + /// + /// The string representation. + public override string ToString() { + StringBuilder sb = new StringBuilder(50); + for(int i = 0; i < items.Count; i++) { + sb.AppendFormat("{0}", items[i]); + if(i != items.Count - 1) sb.Append(", "); + } + return sb.ToString(); + } + + } + +} diff --git a/SearchEngine/Tools.cs b/SearchEngine/Tools.cs new file mode 100644 index 0000000..7df1190 --- /dev/null +++ b/SearchEngine/Tools.cs @@ -0,0 +1,368 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Globalization; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Implements useful methods. + /// + public static class Tools { + + #region Main Search Algorithms + + /// + /// Performs a search in the index. + /// + /// The search query. + /// The document type tags to include in the search. + /// true to apply the filter on the document type. + /// The search options. + /// An object that is able to fetch words. + /// The results. + /// If or are null. + /// If is empty. + /// If is true and is null. + /// If is true and is empty. + public static SearchResultCollection SearchInternal(string query, string[] documentTypeTags, bool filterDocumentType, SearchOptions options, IWordFetcher fetcher) { + if(query == null) throw new ArgumentNullException("query"); + if(query.Length == 0) throw new ArgumentException("Query cannot be empty", "query"); + + if(filterDocumentType && documentTypeTags == null) throw new ArgumentNullException("documentTypeTags"); + if(filterDocumentType && documentTypeTags.Length == 0) throw new ArgumentException("documentTypeTags cannot be empty", "documentTypeTags"); + + if(fetcher == null) throw new ArgumentNullException("fetcher"); + + SearchResultCollection results = new SearchResultCollection(); + + query = query.ToLowerInvariant(); + string[] queryWords = query.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + float totalRelevance = 0; + + Word word = null; + foreach(string q in queryWords) { + if(fetcher.TryGetWord(q, out word)) { + foreach(IDocument doc in word.Occurrences.Keys) { + // Skip documents with excluded tags + if(filterDocumentType && + !IsDocumentTypeTagIncluded(doc.TypeTag, documentTypeTags)) continue; + foreach(BasicWordInfo info in word.Occurrences[doc]) { + // If a search result is already present, add a new match to it, + // otherwise create a new search result object + WordInfo mi = new WordInfo(q, info.FirstCharIndex, info.WordIndex, info.Location); + SearchResult res = results.GetSearchResult(doc); + if(res == null) { + res = new SearchResult(doc); + res.Relevance.SetValue(info.Location.RelativeRelevance); + res.Matches.Add(mi); + results.Add(res); + } + else { + res.Matches.Add(mi); + res.Relevance.SetValue(res.Relevance.Value + info.Location.RelativeRelevance); + } + totalRelevance += info.Location.RelativeRelevance; + } + } + } + } + + if(options == SearchOptions.AllWords) { + totalRelevance -= PurgeResultsForAllWords(results, queryWords); + } + else if(options == SearchOptions.ExactPhrase) { + totalRelevance -= PurgeResultsForExactPhrase(results, queryWords); + } + else if(options == SearchOptions.AtLeastOneWord) { + // Nothing to do + } + else throw new InvalidOperationException("Unsupported SearchOptions"); + + // Finalize relevance values + for(int i = 0; i < results.Count; i++) { + results[i].Relevance.Finalize(totalRelevance); + } + + return results; + } + + /// + /// Purges the invalid results when SearchOptions is AllWords. + /// + /// The results to purge. + /// The query words. + /// The relevance value of the removed matches. + public static float PurgeResultsForAllWords(SearchResultCollection results, string[] queryWords) { + // Remove results that do not contain all the searched words + float relevanceToRemove = 0; + List toRemove = new List(); + foreach(SearchResult r in results) { + if(r.Matches.Count < queryWords.Length) toRemove.Add(r); + else { + foreach(string w in queryWords) { + if(!r.Matches.Contains(w)) { + toRemove.Add(r); + break; + } + } + } + } + foreach(SearchResult r in toRemove) { + results.Remove(r); + relevanceToRemove += r.Relevance.Value; + } + return relevanceToRemove; + } + + /// + /// Purges the invalid results when SearchOptions is ExactPhrase. + /// + /// The results to purge. + /// The query words. + /// The relevance value of the removed matches. + public static float PurgeResultsForExactPhrase(SearchResultCollection results, string[] queryWords) { + // Remove results that do not contain the exact phrase + float relevanceToRemove = 0; + List toRemove = new List(); + foreach(SearchResult r in results) { + // Shortcut + if(r.Matches.Count < queryWords.Length) toRemove.Add(r); + else { + // Verify that all matches are in the same order as in the query + // and that their indices make up contiguous words, + // re-iterating from every word in the result, for example: + // query = 'repeated content', result = 'content repeated content' + // result must be tested with 'content repeated' (failing) and with 'repeated content' (succeeding) + + int maxTestShift = 0; + if(queryWords.Length < r.Matches.Count) { + maxTestShift = r.Matches.Count - queryWords.Length; + } + + bool sequenceFound = false; + + for(int shift = 0; shift <= maxTestShift; shift++) { + int firstWordIndex = r.Matches[shift].WordIndex; + bool allOk = true; + + for(int i = 0; i < queryWords.Length; i++) { + if(queryWords[i] != r.Matches[i + shift].Text.ToLowerInvariant() || + r.Matches[i + shift].WordIndex != firstWordIndex + i) { + //toRemove.Add(r); + allOk = false; + break; + } + } + + if(allOk) { + sequenceFound = true; + break; + } + } + + if(!sequenceFound) { + toRemove.Add(r); + } + } + } + foreach(SearchResult r in toRemove) { + results.Remove(r); + relevanceToRemove += r.Relevance.Value; + } + return relevanceToRemove; + } + + /// + /// Determines whether a document tag is contained in a tag array. + /// + /// The tag to check for. + /// The tag array. + /// true if currentTag is contained in includedTags, false otherwise. + /// The comparison is case-insensitive. + public static bool IsDocumentTypeTagIncluded(string currentTag, string[] includedTags) { + currentTag = currentTag.ToLowerInvariant(); + foreach(string s in includedTags) { + if(s.ToLowerInvariant() == currentTag) return true; + } + return false; + } + + #endregion + + /// + /// Cleans up keyworks from invalid characters. + /// + /// The keywords to cleanup. + /// The clean keywords. + public static string[] CleanupKeywords(string[] keywords) { + if(keywords == null || keywords.Length == 0) return keywords; + + List result = new List(keywords.Length); + foreach(string k in keywords) { + string temp = RemoveDiacriticsAndPunctuation(k.Replace(" ", ""), true); + if(temp.Length > 0) { + result.Add(temp); + } + } + + return result.ToArray(); + } + + /// + /// Removes "accents" and punctuation from a string, transforming it to lowercase (culture invariant). + /// + /// The input string. + /// A value indicating whether the input string is a single word. + /// The normalized string (lowercase, culture invariant). Can be empty. + public static string RemoveDiacriticsAndPunctuation(string input, bool isSingleWord) { + // Code partially borrowed from: + // http://weblogs.asp.net/fmarguerie/archive/2006/10/30/removing-diacritics-accents-from-strings.aspx + + string normalizedString = input.Normalize(NormalizationForm.FormD); + StringBuilder stringBuilder = new StringBuilder(input.Length); + + for(int i = 0; i < normalizedString.Length; i++) { + char c = normalizedString[i]; + if(char.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark) { + if(char.IsLetterOrDigit(c)) stringBuilder.Append(c); + else if(!isSingleWord) stringBuilder.Append(" "); + } + } + + if(!isSingleWord) { + while(stringBuilder.ToString().Contains(" ")) stringBuilder.Replace(" ", " "); + } + + return stringBuilder.ToString().ToLowerInvariant().Trim(' ', '\'', '"'); + } + + /// + /// Determines whether a char is a split char. + /// + /// The current char. + /// true if the char is a split char, false otherwise. + public static bool IsSplitChar(char current) { + UnicodeCategory cat = char.GetUnicodeCategory(current); + + // http://msdn.microsoft.com/en-us/library/system.globalization.unicodecategory.aspx + // A split char is anything but the following categories + return + cat != UnicodeCategory.UppercaseLetter && + cat != UnicodeCategory.LowercaseLetter && + cat != UnicodeCategory.TitlecaseLetter && + cat != UnicodeCategory.ModifierLetter && + cat != UnicodeCategory.OtherLetter && + cat != UnicodeCategory.NonSpacingMark && + cat != UnicodeCategory.DecimalDigitNumber && + cat != UnicodeCategory.LetterNumber && + cat != UnicodeCategory.OtherNumber && + cat != UnicodeCategory.CurrencySymbol; + } + + /// + /// Computes the index of the first non-split char given a start index. + /// + /// The start index. + /// The content. + /// The index of the first non-split char. + /// If is null. + public static ushort SkipSplitChars(ushort startIndex, string content) { + if(content == null) throw new ArgumentNullException("content"); + + // startIndex < 0 is not actually a problem, so it's possible to set it to zero + if(startIndex < 0) startIndex = 0; + + int currentIndex = startIndex; + while(currentIndex < content.Length && IsSplitChar(content[currentIndex])) currentIndex++; + return (ushort)currentIndex; + } + + /// + /// Tokenizes a string. + /// + /// The text to tokenize. + /// The location of the words that are extracted. + /// The tokens. + /// If is null. + public static WordInfo[] Tokenize(string text, WordLocation location) { + if(text == null) throw new ArgumentNullException("text"); + + List words = new List(text.Length / 5); // Average 5 chars/word + + ushort currentIndex = 0, currentWordStart; + + // Skip all trailing splitChars + currentIndex = SkipSplitChars(0, text); + + currentWordStart = currentIndex; + + while(currentIndex < text.Length && currentIndex < 65500) { + while(currentIndex < text.Length && !Tools.IsSplitChar(text[currentIndex])) currentIndex++; + string w = text.Substring(currentWordStart, currentIndex - currentWordStart); + w = Tools.RemoveDiacriticsAndPunctuation(w, true); + if(!string.IsNullOrEmpty(w)) { + words.Add(new WordInfo(w, currentWordStart, (ushort)words.Count, location)); + } + currentIndex = SkipSplitChars((ushort)(currentIndex + 1), text); + currentWordStart = currentIndex; + } + + return words.ToArray(); + } + + /// + /// Tokenizes a string. + /// + /// The text to tokenize. + public static WordInfo[] Tokenize(string text) { + return Tokenize(text, WordLocation.Content); + } + + /// + /// Removes stop words from a set of words (case insensitive). + /// + /// The input words. + /// The array of stop words. + /// The input words without the stop words. + /// If or are null. + public static WordInfo[] RemoveStopWords(WordInfo[] words, string[] stopWords) { + if(words == null) throw new ArgumentNullException("words"); + if(stopWords == null) throw new ArgumentNullException("stopWords"); + + List result = new List(words.Length); + + foreach(WordInfo current in words) { + bool found = false; + foreach(string sw in stopWords) { + if(string.Compare(current.Text, sw, true, CultureInfo.InvariantCulture) == 0) { + found = true; + break; + } + } + if(!found) result.Add(current); + } + + return result.ToArray(); + } + + } + + /// + /// Defines the interface for a component that fetches words. + /// + public interface IWordFetcher : IDisposable { + + /// + /// Tries to get a word. + /// + /// The text of the word. + /// The found word, if any, null otherwise. + /// true if the word is found, false otherwise. + bool TryGetWord(string text, out Word word); + + } + +} diff --git a/SearchEngine/Word.cs b/SearchEngine/Word.cs new file mode 100644 index 0000000..10b1e81 --- /dev/null +++ b/SearchEngine/Word.cs @@ -0,0 +1,190 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Globalization; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Represents a word in a document. + /// + /// All instance and static members are thread-safe. + public class Word { + + /// + /// The word text, lowercase. + /// + protected string text; + /// + /// The occurrences. + /// + protected OccurrenceDictionary occurrences; + /// + /// The word unique ID. + /// + protected uint id; + + /// + /// Initializes a new instance of the class. + /// + /// The word ID. + /// The text of the word (lowercase). + /// If is null. + /// If is empty. + public Word(uint id, string text) + : this(id, text, new OccurrenceDictionary(10)) { } + + /// + /// Initializes a new instance of the class. + /// + /// The word ID. + /// The text of the word (lowercase). + /// The occurrences. + /// If or are null. + /// If is empty. + public Word(uint id, string text, OccurrenceDictionary occurrences) { + if(text == null) throw new ArgumentNullException("text"); + if(text.Length == 0) throw new ArgumentException("Text must contain at least one character", "text"); + if(occurrences == null) throw new ArgumentNullException("occurrences"); + + this.text = Tools.RemoveDiacriticsAndPunctuation(text, true); + //if(this.text.Length == 0) throw new InvalidOperationException(); + this.id = id; + this.occurrences = occurrences; + } + + /// + /// Gets or sets the unique ID of the word. + /// + public uint ID { + get { + lock(this) { + return id; + } + } + set { + lock(this) { + id = value; + } + } + } + + /// + /// Gets the text of the word (lowercase). + /// + public string Text { + get { + // Read-only: no need to lock + return text; + } + } + + /// + /// Gets the occurrences. + /// + public OccurrenceDictionary Occurrences { + get { + lock(occurrences) { + return occurrences; + } + } + } + + /// + /// Gets the total occurrences. + /// + /// Computing the result is O(n), where n is the number of + /// documents the word occurs in at least one time. + public int TotalOccurrences { + get { + int count = 0; + lock(occurrences) { + foreach(KeyValuePair pair in occurrences) { + count += pair.Value.Count; + } + } + return count; + } + } + + /// + /// Stores an occurrence. + /// + /// The document the occurrence is referred to. + /// The index of the first character of the word in the document. + /// The index of the word in the document. + /// The location of the word. + /// Adding an occurrence is O(n), where n is the number of occurrences + /// of the word already stored for the same document. If there were no occurrences previously stored, + /// the operation is O(1). + /// If is null. + /// If or are less than zero. + public void AddOccurrence(IDocument document, ushort firstCharIndex, ushort wordIndex, WordLocation location) { + if(document == null) throw new ArgumentNullException("document"); + if(firstCharIndex < 0) throw new ArgumentOutOfRangeException("firstCharIndex", "Invalid first char index: must be greater than or equal to zero"); + if(wordIndex < 0) throw new ArgumentOutOfRangeException("wordIndex", "Invalid word index: must be greater than or equal to zero"); + + lock(occurrences) { + if(occurrences.ContainsKey(document)) { + // Existing document + occurrences[document].Add(new BasicWordInfo(firstCharIndex, wordIndex, location)); + } + else { + // New document + SortedBasicWordInfoSet set = new SortedBasicWordInfoSet(); + set.Add(new BasicWordInfo(firstCharIndex, wordIndex, location)); + occurrences.Add(document, set); + } + } + } + + /// + /// Removes all the occurrences for a document. + /// + /// The document to remove the occurrences of. + /// The dumped word mappings for the removed word occurrences. + /// Removing the occurrences for the document is O(1). + /// If is null. + public List RemoveOccurrences(IDocument document) { + if(document == null) throw new ArgumentNullException("document"); + + lock(occurrences) { + if(occurrences.ContainsKey(document)) return occurrences.RemoveExtended(document, ID); + else return new List(); + } + } + + /// + /// Adds a bulk of occurrences of the word in a document, removing all the old positions, if any. + /// + /// The document. + /// The positions. + /// If positions is empty, the effect of the invocation of the method is equal to + /// that of with the same document. + /// Bulk-adding the occurrences is O(1). + /// If or are null. + public void BulkAddOccurrences(IDocument document, SortedBasicWordInfoSet positions) { + if(document == null) throw new ArgumentNullException("document"); + if(positions == null) throw new ArgumentNullException("positions"); + + lock(occurrences) { + if(occurrences.ContainsKey(document)) { + if(positions.Count == 0) RemoveOccurrences(document); + else occurrences[document] = positions; + } + else occurrences.Add(document, positions); + } + } + + /// + /// Gets a string representation of the current instance. + /// + /// The string representation. + public override string ToString() { + return string.Format("{0} [x{1}]", text, TotalOccurrences); + } + + } + +} diff --git a/SearchEngine/WordInfo.cs b/SearchEngine/WordInfo.cs new file mode 100644 index 0000000..3ea7eb9 --- /dev/null +++ b/SearchEngine/WordInfo.cs @@ -0,0 +1,126 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Contains full information about a word in a document. + /// + public class WordInfo : BasicWordInfo, IEquatable, IComparable { + + /// + /// The word text. + /// + protected string text; + + /// + /// Initializes a new instance of the class. + /// + /// The text of the word. + /// The index of the first character of the word in the document. + /// The index of the word in the document. + /// The location of the word in the document. + /// If is null. + /// If is empty. + /// If or are less than zero. + public WordInfo(string text, ushort firstCharIndex, ushort wordIndex, WordLocation location) + : base(firstCharIndex, wordIndex, location) { + if(text == null) throw new ArgumentNullException("text"); + if(text.Length == 0) throw new ArgumentException("Invalid text", "text"); + + this.text = Tools.RemoveDiacriticsAndPunctuation(text, true); + //if(this.text.Length == 0) throw new InvalidOperationException(); + } + + /// + /// Gets the text of the word. + /// + public string Text { + get { return text; } + } + + /// + /// Gets a string representation of the current instance. + /// + /// The string representation. + public override string ToString() { + return string.Format("{0}:{1}", text, firstCharIndex); + } + /// + /// Determinates whether the current instance is equal to an instance of an object. + /// + /// The instance of the object. + /// true if the instances are value-equal, false otherwise. + public override bool Equals(object other) { + if(other is WordInfo) return Equals((WordInfo)other); + else return false; + } + + /// + /// Determinates whether the current instance is value-equal to another. + /// + /// The other instance. + /// true if the instances are value-equal, false otherwise. + public bool Equals(WordInfo other) { + if(object.ReferenceEquals(other, null)) return false; + + return other.FirstCharIndex == firstCharIndex && other.WordIndex == wordIndex && + other.Location == location && other.text == text; + } + + /// + /// Applies the value-equality operator to two objects. + /// + /// The first object. + /// The second object. + /// true if the objects are value-equal, false otherwise. + public static bool operator ==(WordInfo x, WordInfo y) { + if(object.ReferenceEquals(x, null) && !object.ReferenceEquals(y, null) || + !object.ReferenceEquals(x, null) && object.ReferenceEquals(y, null)) return false; + + if(object.ReferenceEquals(x, null) && object.ReferenceEquals(y, null)) return true; + + return x.Equals(y); + } + + /// + /// Applies the value-equality operator to two objects. + /// + /// The first object. + /// The second object. + /// true if the objects are not value-equal, false otherwise. + public static bool operator !=(WordInfo x, WordInfo y) { + return !(x == y); + } + + /// + /// Gets the hash code of the current instance. + /// + /// The hash code. + public override int GetHashCode() { + return base.GetHashCode() ^ text.GetHashCode(); + } + + /// + /// Compares the current instance with another instance. + /// + /// The other instance. + /// The comparison result. + /// The First Char Index does not partecipate to the comparison. + public int CompareTo(WordInfo other) { + if(other == null) return 1; + + // text has a distance module of 1 + // location has a distance module of 2 + // wordIndex has a distance module of 3 + + int res = location.CompareTo(other.Location) * 2; + int res2 = wordIndex.CompareTo(other.WordIndex) * 3; + return res + res2 + text.CompareTo(other.Text); + } + + } + +} diff --git a/SearchEngine/WordInfoCollection.cs b/SearchEngine/WordInfoCollection.cs new file mode 100644 index 0000000..3e421d6 --- /dev/null +++ b/SearchEngine/WordInfoCollection.cs @@ -0,0 +1,172 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Contains a collection of MatchInfo objects, sorted by index. + /// + public class WordInfoCollection : ICollection { + + /// + /// The items of the collection. + /// + protected List items; + + /// + /// Initializes a new instance of the class. + /// + public WordInfoCollection() + : this(10) { } + + /// + /// Initializes a new instance of the class. + /// + /// The initial capacity of the collection. + /// If is less than or equal to zero. + public WordInfoCollection(int capacity) { + if(capacity <= 0) throw new ArgumentOutOfRangeException("capacity", "Invalid capacity"); + + items = new List(capacity); + } + + /// + /// Adds an item to the collection. + /// + /// The item to add. + /// If is null. + public void Add(WordInfo item) { + if(item == null) throw new ArgumentNullException("item"); + + items.Add(item); + + // Sort + items.Sort(delegate(WordInfo mi1, WordInfo mi2) { return mi1.FirstCharIndex.CompareTo(mi2.FirstCharIndex); }); + } + + /// + /// Clears the collection. + /// + public void Clear() { + items.Clear(); + } + + /// + /// Determines whether an item is in the collection. + /// + /// The item to check for. + /// true if the item in the collection, false otherwise. + public bool Contains(WordInfo item) { + if(item == null) return false; + return items.Contains(item); + } + + /// + /// Determines whether a word is in the collection. + /// + /// The word. + /// true if the word is in the collection, false otherwise. + public bool Contains(string word) { + if(string.IsNullOrEmpty(word)) return false; + + foreach(WordInfo w in items) { + if(w.Text == word) return true; + } + + return false; + } + + /// + /// Copies the collection items to an array. + /// + /// The destination array. + /// The zero-based array index at which the copy begins. + /// If is not within the bounds of . + public void CopyTo(WordInfo[] array, int arrayIndex) { + if(array == null) throw new ArgumentNullException("array"); + if(arrayIndex < 0 || arrayIndex > array.Length - 1) + throw new ArgumentOutOfRangeException("arrayIndex", "Index should be greater than or equal to zero and less than the number of items in the array"); + + if(array.Length - arrayIndex < items.Count) + throw new ArgumentOutOfRangeException("arrayIndex", "Not enough space for copying the items starting at the specified index"); + + items.CopyTo(array, arrayIndex); + } + + /// + /// Gets the number of items in the collection. + /// + public int Count { + get { return items.Count; } + } + + /// + /// Gets the capacity of the collection. + /// + public int Capacity { + get { return items.Capacity; } + } + + /// + /// Gets a value indicating whether the collection is read-only. + /// + public bool IsReadOnly { + get { return false; } + } + + /// + /// Removes an item from the collection. + /// + /// The item to remove. + /// true if item is removed, false otherwise. + /// If is null. + public bool Remove(WordInfo item) { + if(item == null) throw new ArgumentNullException("item"); + + return items.Remove(item); + } + + /// + /// Returns an enumerator that iterates through the items of the collection. + /// + /// The enumerator. + public IEnumerator GetEnumerator() { + return items.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through the items of the collection. + /// + /// The enumerator. + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return items.GetEnumerator(); + } + + /// + /// Gets an item of the collection. + /// + /// The index of the item to retrieve. + /// The item. + /// If is outside the bounds of the collection. + public WordInfo this[int index] { + get { + if(index < 0 || index > items.Count - 1) + throw new IndexOutOfRangeException("Index should be greater than or equal to zero and less than the number of items in the collection"); + + return items[index]; + } + } + + /// + /// Returns a string representation of the current instance. + /// + /// The string representation. + public override string ToString() { + return items.Count.ToString() + " matches"; + } + + } + +} diff --git a/SearchEngine/WordLocation.cs b/SearchEngine/WordLocation.cs new file mode 100644 index 0000000..d1a06c5 --- /dev/null +++ b/SearchEngine/WordLocation.cs @@ -0,0 +1,159 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.SearchEngine { + + /// + /// Describes the location of a word in a document. + /// + public class WordLocation : IComparable, IEquatable { + + private byte location; + private string label; + private float relativeRelevance; + + /// + /// Initializes a new instance of the class. + /// + /// A number representing the location. + /// The label of the instance. + /// The relative relevance of the instance. + protected WordLocation(byte location, string label, float relativeRelevance) { + this.location = location; + this.label = label; + this.relativeRelevance = relativeRelevance; + } + + /// + /// Gets the location identifier. + /// + /// This property should only be used for serialization purposes. + public byte Location { + get { return location; } + } + + /// + /// Gets the relative relevance of the word location. + /// + public float RelativeRelevance { + get { return relativeRelevance; } + } + + /// + /// Gets a string representation of the current instance. + /// + /// The string representation. + public override string ToString() { + return label; + } + + /// + /// Represents a word that is in the title of a document. + /// + public static WordLocation Title { + get { return new WordLocation(1, "Title", 2); } + } + + /// + /// Represents a word that is in the keywords of a document. + /// + public static WordLocation Keywords { + get { return new WordLocation(2, "Keywords", 1.5F); } + } + + /// + /// Represents a word that is in the content of a document. + /// + public static WordLocation Content { + get { return new WordLocation(3, "Content", 1); } + } + + /// + /// Gets the correct instance from the location identifier. + /// + /// The location identifier. + /// The correct instance. + /// If is different from 1, 2, 3. + public static WordLocation GetInstance(byte location) { + switch(location) { + case 1: + return Title; + case 2: + return Keywords; + case 3: + return Content; + default: + throw new ArgumentOutOfRangeException("Invalid location", "location"); + } + } + + /// + /// Compares the current instance to another. + /// + /// The other instance. + /// The comparison result. + public int CompareTo(WordLocation other) { + if(object.ReferenceEquals(other, null)) return 1; + + if(location > other.location) return 1; + else if(location < other.location) return -1; + else return 0; + //return location.CompareTo(other.location); + } + + /// + /// Determines whether the current instance equals an object. + /// + /// The object. + /// true if the current instance equals the object, false otherwise. + public override bool Equals(object obj) { + if(obj is WordLocation) return Equals((WordLocation)obj); + else return false; + } + + /// + /// Returns the hash code of the current instance. + /// + /// The hash code. + public override int GetHashCode() { + return location; + } + + /// + /// Determines whether the current instance equals another. + /// + /// The other instance. + /// true if the current instance equals the other, false otherwise. + public bool Equals(WordLocation other) { + if(object.ReferenceEquals(other, null)) return false; + else return location == other.location; + } + + /// + /// Applies the value-equality operator to two objects. + /// + /// The first object. + /// The second object. + /// true if the objects are value-equal, false otherwise. + public static bool operator ==(WordLocation x, WordLocation y) { + if(object.ReferenceEquals(x, null) && !object.ReferenceEquals(y, null)) return false; + if(!object.ReferenceEquals(x, null) && object.ReferenceEquals(y, null)) return false; + if(object.ReferenceEquals(x, null) && object.ReferenceEquals(y, null)) return true; + return x.Equals(y); + } + + /// + /// Applies the value-inequality operator to two objects. + /// + /// The first object. + /// The second object. + /// true if the objects are not value-equal, false otherwise. + public static bool operator !=(WordLocation x, WordLocation y) { + return !(x == y); + } + + } + +} diff --git a/SqlProvidersCommon-Tests/Properties/AssemblyInfo.cs b/SqlProvidersCommon-Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6a7be3f --- /dev/null +++ b/SqlProvidersCommon-Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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("ScrewTurn Wiki SQL Providers Common Types Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// 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("e72d7030-8c71-42c9-b563-f5ef5e27b1c9")] diff --git a/SqlProvidersCommon-Tests/QueryBuilderTests.cs b/SqlProvidersCommon-Tests/QueryBuilderTests.cs new file mode 100644 index 0000000..c584709 --- /dev/null +++ b/SqlProvidersCommon-Tests/QueryBuilderTests.cs @@ -0,0 +1,388 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon.Tests { + + [TestFixture] + public class QueryBuilderTests { + + private MockRepository mocks = new MockRepository(); + + /// + /// Gets a new command builder for testing purposes. + /// + /// The command builder. + private ICommandBuilder MockCommandBuilderWithNamedParameters() { + ICommandBuilder builder = mocks.StrictMock(); + Expect.Call(builder.ObjectNamePrefix).Return("[").Repeat.Any(); + Expect.Call(builder.ObjectNameSuffix).Return("]").Repeat.Any(); + Expect.Call(builder.ParameterNamePrefix).Return("@").Repeat.Any(); + Expect.Call(builder.ParameterNameSuffix).Return("").Repeat.Any(); + Expect.Call(builder.UseNamedParameters).Return(true).Repeat.Any(); + Expect.Call(builder.BatchQuerySeparator).Return("; ").Repeat.Any(); + + mocks.Replay(builder); + + return builder; + } + + /// + /// Gets a new command builder for testing purposes. + /// + /// The command builder. + private ICommandBuilder MockCommandBuilderWithoutNamedParameters() { + ICommandBuilder builder = mocks.StrictMock(); + Expect.Call(builder.ObjectNamePrefix).Return("[").Repeat.Any(); + Expect.Call(builder.ObjectNameSuffix).Return("]").Repeat.Any(); + Expect.Call(builder.ParameterPlaceholder).Return("?").Repeat.Any(); + Expect.Call(builder.UseNamedParameters).Return(false).Repeat.Any(); + Expect.Call(builder.BatchQuerySeparator).Return("; ").Repeat.Any(); + + mocks.Replay(builder); + + return builder; + } + + [Test] + public void SelectFrom_Columns() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table", new string[] { "Col1", "Col2" }); + + Assert.AreEqual("select [Col1], [Col2] from [Table]", query, "Wrong query"); + } + + [Test] + public void SelectFrom_All() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + Assert.AreEqual("select * from [Table]", query, "Wrong query"); + } + + [Test] + public void SelectFrom_ColumnsJoin() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom( + "Table", "Joined", "Col1", "Col2", Join.InnerJoin, + new string[] { "Col1", "Col2" }, new string[] { "Col5", "Col9" }); + + Assert.AreEqual("select [Table].[Col1] as [Table_Col1], [Table].[Col2] as [Table_Col2], [Joined].[Col5] as [Joined_Col5], [Joined].[Col9] as [Joined_Col9] from [Table] inner join [Joined] on [Table].[Col1] = [Joined].[Col2]", query, "Wrong query"); + } + + [Test] + public void SelectFrom_ColumnsJoinMultiple() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom( + "Table", "Joined", new string[] { "Col1", "Col2" }, new string[] { "Col3", "Col4" }, Join.RightJoin, + new string[] { "Col6", "Col7" }, new string[] { "Col8", "Col9" }); + + Assert.AreEqual("select [Table].[Col6] as [Table_Col6], [Table].[Col7] as [Table_Col7], [Joined].[Col8] as [Joined_Col8], [Joined].[Col9] as [Joined_Col9] from [Table] right join [Joined] on [Table].[Col1] = [Joined].[Col3] and [Table].[Col2] = [Joined].[Col4]", query, "Wrong query"); + } + + [Test] + public void SelectFrom_AllJoin() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom( + "Table", "Joined", "Col1", "Col2", Join.LeftJoin); + + Assert.AreEqual("select * from [Table] left join [Joined] on [Table].[Col1] = [Joined].[Col2]", query, "Wrong query"); + } + + [Test] + public void SelectFrom_ColumnsJoin2() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom( + "Table1", "Table2", "Col1", "Col2", Join.Join, + new string[] { "Col1", "Col2" }, new string[] { "Col4", "Col6" }, + "Table3", "Col5", Join.LeftJoin, new string[] { "Col34" }); + + Assert.AreEqual("select [Table1].[Col1] as [Table1_Col1], [Table1].[Col2] as [Table1_Col2], [Table2].[Col4] as [Table2_Col4], [Table2].[Col6] as [Table2_Col6], [Table3].[Col34] as [Table3_Col34] from [Table1] join [Table2] on [Table1].[Col1] = [Table2].[Col2] left join [Table3] on [Table1].[Col1] = [Table3].[Col5]", query, "Wrong query"); + } + + [Test] + public void SelectCountFrom() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectCountFrom("Table"); + + Assert.AreEqual("select count(*) from [Table]", query, "Wrong query"); + } + + [Test] + public void Where_AndWhere_NamedParameter() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).Where(query, "Col1", WhereOperator.LessThanOrEqualTo, "Param1"); + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).AndWhere(query, "Col2", WhereOperator.Equals, "Param2"); + + Assert.AreEqual("select * from [Table] where [Col1] <= @Param1 and [Col2] = @Param2", query, "Wrong query"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).Where(query, "Col1", WhereOperator.LessThanOrEqualTo, "Param1", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).AndWhere(query, "Col2", WhereOperator.Equals, "Param2", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).AndWhere(query, "Col3", WhereOperator.Equals, "Param3", false, true) + ")"; + + Assert.AreEqual("select * from [Table] where ([Col1] <= @Param1 and ([Col2] = @Param2 and [Col3] = @Param3))", query, "Wrong query"); + } + + [Test] + public void Where_AndWhere_UnnamedParameter() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).Where(query, "Col1", WhereOperator.LessThanOrEqualTo, "Param1"); + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).AndWhere(query, "Col2", WhereOperator.Equals, "Param2"); + + Assert.AreEqual("select * from [Table] where [Col1] <= ? and [Col2] = ?", query, "Wrong query"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).Where(query, "Col1", WhereOperator.LessThanOrEqualTo, "Param1", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).AndWhere(query, "Col2", WhereOperator.Equals, "Param2", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).AndWhere(query, "Col3", WhereOperator.Equals, "Param3", false, true) + ")"; + + Assert.AreEqual("select * from [Table] where ([Col1] <= ? and ([Col2] = ? and [Col3] = ?))", query, "Wrong query"); + } + + [Test] + public void Where_OrWhere_NamedParameter() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).Where(query, "Col1", WhereOperator.LessThanOrEqualTo, "Param1"); + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).OrWhere(query, "Col2", WhereOperator.Equals, "Param2"); + + Assert.AreEqual("select * from [Table] where [Col1] <= @Param1 or [Col2] = @Param2", query, "Wrong query"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).Where(query, "Col1", WhereOperator.LessThanOrEqualTo, "Param1", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).OrWhere(query, "Col2", WhereOperator.Equals, "Param2", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).OrWhere(query, "Col3", WhereOperator.Equals, "Param3", false, true) + ")"; + + Assert.AreEqual("select * from [Table] where ([Col1] <= @Param1 or ([Col2] = @Param2 or [Col3] = @Param3))", query, "Wrong query"); + } + + [Test] + public void Where_OrWhere_UnnamedParameter() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).Where(query, "Col1", WhereOperator.LessThanOrEqualTo, "Param1"); + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).OrWhere(query, "Col2", WhereOperator.Equals, "Param2"); + + Assert.AreEqual("select * from [Table] where [Col1] <= ? or [Col2] = ?", query, "Wrong query"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).Where(query, "Col1", WhereOperator.LessThanOrEqualTo, "Param1", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).OrWhere(query, "Col2", WhereOperator.Equals, "Param2", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).OrWhere(query, "Col3", WhereOperator.Equals, "Param3", false, true) + ")"; + + Assert.AreEqual("select * from [Table] where ([Col1] <= ? or ([Col2] = ? or [Col3] = ?))", query, "Wrong query"); + } + + [Test] + public void Where_Table_AndWhere_Table_NamedParameter() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).Where(query, "Table", "Col1", WhereOperator.LessThanOrEqualTo, "Param1"); + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).AndWhere(query, "Table", "Col2", WhereOperator.Equals, "Param2"); + + Assert.AreEqual("select * from [Table] where [Table].[Col1] <= @Param1 and [Table].[Col2] = @Param2", query, "Wrong query"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).Where(query, "Table", "Col1", WhereOperator.LessThanOrEqualTo, "Param1", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).AndWhere(query, "Table", "Col2", WhereOperator.Equals, "Param2", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).AndWhere(query, "Table", "Col3", WhereOperator.Equals, "Param3", false, true) + ")"; + + Assert.AreEqual("select * from [Table] where ([Table].[Col1] <= @Param1 and ([Table].[Col2] = @Param2 and [Table].[Col3] = @Param3))", query, "Wrong query"); + } + + [Test] + public void Where_Table_AndWhere_Table_UnnamedParameter() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).Where(query, "Table", "Col1", WhereOperator.LessThanOrEqualTo, "Param1"); + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).AndWhere(query, "Table", "Col2", WhereOperator.Equals, "Param2"); + + Assert.AreEqual("select * from [Table] where [Table].[Col1] <= ? and [Table].[Col2] = ?", query, "Wrong query"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).Where(query, "Table", "Col1", WhereOperator.LessThanOrEqualTo, "Param1", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).AndWhere(query, "Table", "Col2", WhereOperator.Equals, "Param2", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).AndWhere(query, "Table", "Col3", WhereOperator.Equals, "Param3", false, true) + ")"; + + Assert.AreEqual("select * from [Table] where ([Table].[Col1] <= ? and ([Table].[Col2] = ? and [Table].[Col3] = ?))", query, "Wrong query"); + } + + [Test] + public void Where_Table_OrWhere_Table_NamedParameter() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).Where(query, "Table", "Col1", WhereOperator.LessThanOrEqualTo, "Param1"); + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).OrWhere(query, "Table", "Col2", WhereOperator.Equals, "Param2"); + + Assert.AreEqual("select * from [Table] where [Table].[Col1] <= @Param1 or [Table].[Col2] = @Param2", query, "Wrong query"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).Where(query, "Table", "Col1", WhereOperator.LessThanOrEqualTo, "Param1", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).OrWhere(query, "Table", "Col2", WhereOperator.Equals, "Param2", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).OrWhere(query, "Table", "Col3", WhereOperator.Equals, "Param3", false, true) + ")"; + + Assert.AreEqual("select * from [Table] where ([Table].[Col1] <= @Param1 or ([Table].[Col2] = @Param2 or [Table].[Col3] = @Param3))", query, "Wrong query"); + } + + [Test] + public void Where_Table_OrWhere_Table_UnnamedParameter() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).Where(query, "Table", "Col1", WhereOperator.LessThanOrEqualTo, "Param1"); + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).OrWhere(query, "Table", "Col2", WhereOperator.Equals, "Param2"); + + Assert.AreEqual("select * from [Table] where [Table].[Col1] <= ? or [Table].[Col2] = ?", query, "Wrong query"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).Where(query, "Table", "Col1", WhereOperator.LessThanOrEqualTo, "Param1", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).OrWhere(query, "Table", "Col2", WhereOperator.Equals, "Param2", true, false); + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).OrWhere(query, "Table", "Col3", WhereOperator.Equals, "Param3", false, true) + ")"; + + Assert.AreEqual("select * from [Table] where ([Table].[Col1] <= ? or ([Table].[Col2] = ? or [Table].[Col3] = ?))", query, "Wrong query"); + } + + [Test] + public void WhereIn_NamedParameters() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).WhereIn(query, "Col1", new string[] { "Param1", "Param2" }); + + Assert.AreEqual("select * from [Table] where [Col1] in (@Param1, @Param2)", query, "Wrong query"); + } + + [Test] + public void WhereIn_UnnamedParameters() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).WhereIn(query, "Col1", new string[] { "Param1", "Param2" }); + + Assert.AreEqual("select * from [Table] where [Col1] in (?, ?)", query, "Wrong query"); + } + + [Test] + public void WhereIn_Table_NamedParameters() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).WhereIn(query, "Table", "Col1", new string[] { "Param1", "Param2" }); + + Assert.AreEqual("select * from [Table] where [Table].[Col1] in (@Param1, @Param2)", query, "Wrong query"); + } + + [Test] + public void WhereIn_Table_UnnamedParameters() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).WhereIn(query, "Table", "Col1", new string[] { "Param1", "Param2" }); + + Assert.AreEqual("select * from [Table] where [Table].[Col1] in (?, ?)", query, "Wrong query"); + } + + [Test] + public void WhereNotIn_NamedParameters() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).DeleteFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).WhereNotInSubquery(query, "Table", "Col1", "fake_sub_query"); + + Assert.AreEqual("delete from [Table] where [Table].[Col1] not in (fake_sub_query)", query, "Wrong query"); + } + + [Test] + public void WhereNotIn_UnnamedParameters() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).DeleteFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).WhereNotInSubquery(query, "Table", "Col1", "fake_sub_query"); + + Assert.AreEqual("delete from [Table] where [Table].[Col1] not in (fake_sub_query)", query, "Wrong query"); + } + + [Test] + public void OrderBy() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table"); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).OrderBy(query, new string[] { "Col1", "Col2", "Col3" }, new Ordering[] { Ordering.Asc, Ordering.Desc, Ordering.Asc }); + + Assert.AreEqual("select * from [Table] order by [Col1] asc, [Col2] desc, [Col3] asc", query, "Wrong query"); + } + + [Test] + public void GroupBy() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).SelectFrom("Table", new string[] { "Col1", "Col2" }); + + query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).GroupBy(query, new string[] { "Col2", "Col1" }); + + Assert.AreEqual("select [Col1], [Col2] from [Table] group by [Col2], [Col1]", query, "Wrong query"); + } + + [Test] + public void InsertInto_NamedParameters() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).InsertInto("Table", + new string[] { "Col1", "Col2" }, new string[] { "Param1", "Param2" }); + + Assert.AreEqual("insert into [Table] ([Col1], [Col2]) values (@Param1, @Param2)", query, "Wrong query"); + } + + [Test] + public void InsertInto_UnnamedParameters() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).InsertInto("Table", + new string[] { "Col1", "Col2" }, new string[] { "Param1", "Param2" }); + + Assert.AreEqual("insert into [Table] ([Col1], [Col2]) values (?, ?)", query, "Wrong query"); + } + + [Test] + public void Update_NamedParameters() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).Update("Table", + new string[] { "Col1", "Col2" }, new string[] { "Param1", "Param2" }); + + Assert.AreEqual("update [Table] set [Col1] = @Param1, [Col2] = @Param2", query, "Wrong query"); + } + + [Test] + public void Update_UnnamedParameters() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithoutNamedParameters()).Update("Table", + new string[] { "Col1", "Col2" }, new string[] { "Param1", "Param2" }); + + Assert.AreEqual("update [Table] set [Col1] = ?, [Col2] = ?", query, "Wrong query"); + } + + [Test] + public void UpdateIncrement_Positive() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).UpdateIncrement("Table", "Col1", 3); + + Assert.AreEqual("update [Table] set [Col1] = [Col1] + 3", query, "Wrong query"); + } + + [Test] + public void UpdateIncrement_Negative() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).UpdateIncrement("Table", "Col1", -8); + + Assert.AreEqual("update [Table] set [Col1] = [Col1] - 8", query, "Wrong query"); + } + + [Test] + public void DeleteFrom() { + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).DeleteFrom("Table"); + + Assert.AreEqual("delete from [Table]", query, "Wrong query"); + } + + [Test] + public void AppendForBatch() { + string query1 = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).DeleteFrom("Table"); + string query2 = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).DeleteFrom("Table2"); + + string query = QueryBuilder.NewQuery(MockCommandBuilderWithNamedParameters()).AppendForBatch(query1, query2); + + Assert.AreEqual("delete from [Table]; delete from [Table2]", query, "Wrong query"); + } + + } + +} diff --git a/SqlProvidersCommon-Tests/SqlAclStorerTests.cs b/SqlProvidersCommon-Tests/SqlAclStorerTests.cs new file mode 100644 index 0000000..a80ab76 --- /dev/null +++ b/SqlProvidersCommon-Tests/SqlAclStorerTests.cs @@ -0,0 +1,43 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.AclEngine; +using NUnit.Framework; +using Rhino.Mocks; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon.Tests { + + [TestFixture] + public class SqlAclStorerTests { + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_NullLoadData() { + MockRepository mocks = new MockRepository(); + + SqlAclStorer storer = new SqlAclStorer(mocks.StrictMock(), null, + new DeleteEntries(e => { }), new StoreEntries(e => { })); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_NullDeleteEntries() { + MockRepository mocks = new MockRepository(); + + SqlAclStorer storer = new SqlAclStorer(mocks.StrictMock(), new LoadData(() => { return null; }), + null, new StoreEntries(e => { })); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Constructor_NullStoreEntries() { + MockRepository mocks = new MockRepository(); + + SqlAclStorer storer = new SqlAclStorer(mocks.StrictMock(), new LoadData(() => { return null; }), + new DeleteEntries(e => { }), null); + } + + } + +} diff --git a/SqlProvidersCommon-Tests/SqlIndexTests.cs b/SqlProvidersCommon-Tests/SqlIndexTests.cs new file mode 100644 index 0000000..f0565bf --- /dev/null +++ b/SqlProvidersCommon-Tests/SqlIndexTests.cs @@ -0,0 +1,140 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using RC = Rhino.Mocks.Constraints; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon.Tests { + + [TestFixture] + public class SqlIndexTests { + + [Test] + public void Constructor_NullConnector() { + Assert.Throws(() => { + new SqlIndex(null); + }); + } + + [Test] + public void TotalDocuments_TotalWords_TotalOccurrences() { + MockRepository mocks = new MockRepository(); + IIndexConnector conn = mocks.StrictMock(); + + Expect.Call(conn.GetCount(IndexElementType.Documents)).Return(12); + Expect.Call(conn.GetCount(IndexElementType.Words)).Return(567); + Expect.Call(conn.GetCount(IndexElementType.Occurrences)).Return(3456); + + mocks.ReplayAll(); + + SqlIndex index = new SqlIndex(conn); + + Assert.AreEqual(12, index.TotalDocuments, "Wrong document count"); + Assert.AreEqual(567, index.TotalWords, "Wrong word count"); + Assert.AreEqual(3456, index.TotalOccurrences, "Wrong occurence count"); + + mocks.VerifyAll(); + } + + [Test] + public void Clear() { + MockRepository mocks = new MockRepository(); + IIndexConnector conn = mocks.StrictMock(); + + string dummyState = "state"; + + conn.ClearIndex(dummyState); + LastCall.On(conn); + + mocks.ReplayAll(); + + SqlIndex index = new SqlIndex(conn); + + index.Clear(dummyState); + + mocks.VerifyAll(); + } + + [Test] + public void StoreDocument() { + MockRepository mocks = new MockRepository(); + IIndexConnector conn = mocks.StrictMock(); + ScrewTurn.Wiki.SearchEngine.IDocument doc = mocks.StrictMock(); + + string dummyState = "state"; + + string content = "This is some test content."; + string title = "My Document"; + + Expect.Call(doc.Title).Return(title).Repeat.AtLeastOnce(); + Expect.Call(doc.Tokenize(content)).Return(ScrewTurn.Wiki.SearchEngine.Tools.Tokenize(content, ScrewTurn.Wiki.SearchEngine.WordLocation.Content)); + Expect.Call(doc.Tokenize(title)).Return(ScrewTurn.Wiki.SearchEngine.Tools.Tokenize(title, ScrewTurn.Wiki.SearchEngine.WordLocation.Title)); + + Predicate contentPredicate = (array) => { + return + array.Length == 5 && + array[0].Text == "this" && + array[1].Text == "is" && + array[2].Text == "some" && + array[3].Text == "test" && + array[4].Text == "content"; + }; + Predicate titlePredicate = (array) => { + return + array.Length == 2 && + array[0].Text == "my" && + array[1].Text == "document"; + }; + Predicate keywordsPredicate = (array) => { + return + array.Length == 1 && + array[0].Text == "test"; + }; + + conn.DeleteDataForDocument(doc, dummyState); + LastCall.On(conn); + Expect.Call(conn.SaveDataForDocument(null, null, null, null, null)).IgnoreArguments() + .Constraints(RC.Is.Same(doc), RC.Is.Matching(contentPredicate), RC.Is.Matching(titlePredicate), RC.Is.Matching(keywordsPredicate), RC.Is.Same(dummyState)) + .Return(8); + + mocks.ReplayAll(); + + SqlIndex index = new SqlIndex(conn); + + Assert.AreEqual(8, index.StoreDocument(doc, new string[] { "test" }, content, dummyState), "Wrong occurrence count"); + + mocks.VerifyAll(); + } + + [Test] + public void Search() { + // Basic integration test: search algorithms are already extensively tested with InMemoryIndexBase + + MockRepository mocks = new MockRepository(); + + IIndexConnector conn = mocks.StrictMock(); + ScrewTurn.Wiki.SearchEngine.IWordFetcher fetcher = mocks.StrictMock(); + + ScrewTurn.Wiki.SearchEngine.Word dummy = null; + Expect.Call(fetcher.TryGetWord("test", out dummy)).Return(false); + Expect.Call(fetcher.TryGetWord("query", out dummy)).Return(false); + fetcher.Dispose(); + LastCall.On(fetcher); + + Expect.Call(conn.GetWordFetcher()).Return(fetcher); + + mocks.ReplayAll(); + + SqlIndex index = new SqlIndex(conn); + + Assert.AreEqual(0, index.Search(new ScrewTurn.Wiki.SearchEngine.SearchParameters("test query")).Count, "Wrong search result count"); + + mocks.VerifyAll(); + } + + } + +} diff --git a/SqlProvidersCommon-Tests/SqlProvidersCommon-Tests.csproj b/SqlProvidersCommon-Tests/SqlProvidersCommon-Tests.csproj new file mode 100644 index 0000000..04f2ec1 --- /dev/null +++ b/SqlProvidersCommon-Tests/SqlProvidersCommon-Tests.csproj @@ -0,0 +1,82 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {67590C3A-1A7C-4608-90CA-1C1632D2F643} + Library + Properties + ScrewTurn.Wiki.Plugins.SqlCommon.Tests + ScrewTurn.Wiki.Plugins.SqlCommon.Tests + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + none + true + bin\Release\ + TRACE + prompt + 4 + true + + + + False + ..\References\Tools\NUnit\framework\nunit.framework.dll + + + False + ..\References\Tools\Rhino.Mocks\Rhino.Mocks.dll + + + + 3.5 + + + + + + + AssemblyVersion.cs + + + + + + + + + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81} + AclEngine + + + {2DF980A6-4742-49B1-A090-DE79314644D0} + SearchEngine + + + {617D5D30-97F9-48B2-903D-29D4524492E8} + SqlProvidersCommon + + + + + \ No newline at end of file diff --git a/SqlProvidersCommon/Hash.cs b/SqlProvidersCommon/Hash.cs new file mode 100644 index 0000000..18f6754 --- /dev/null +++ b/SqlProvidersCommon/Hash.cs @@ -0,0 +1,40 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Security.Cryptography; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon { + + /// + /// Computes hashes. + /// + public static class Hash { + + /// + /// Computes the Hash code of a string. + /// + /// The string. + /// The Hash code. + private static byte[] ComputeBytes(string input) { + SHA1 sha1 = SHA1CryptoServiceProvider.Create(); + return sha1.ComputeHash(Encoding.ASCII.GetBytes(input)); + } + + /// + /// Computes the Hash code of a string and converts it into a Hex string. + /// + /// The string. + /// The Hash code, converted into a Hex string. + public static string Compute(string input) { + byte[] bytes = ComputeBytes(input); + string result = ""; + for(int i = 0; i < bytes.Length; i++) { + result += string.Format("{0:X2}", bytes[i]); + } + return result; + } + + } + +} diff --git a/SqlProvidersCommon/ICommandBuilder.cs b/SqlProvidersCommon/ICommandBuilder.cs new file mode 100644 index 0000000..5851250 --- /dev/null +++ b/SqlProvidersCommon/ICommandBuilder.cs @@ -0,0 +1,87 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Data.Common; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon { + + /// + /// Defines an interface for a command builder component. + /// + /// Classes implementing this interface should be thread-safe. + public interface ICommandBuilder { + + /// + /// Gets the table and column name prefix. + /// + string ObjectNamePrefix { get; } + + /// + /// Gets the table and column name suffix. + /// + string ObjectNameSuffix { get; } + + /// + /// Gets the parameter name prefix. + /// + string ParameterNamePrefix { get; } + + /// + /// Gets the parameter name suffix. + /// + string ParameterNameSuffix { get; } + + /// + /// Gets the parameter name placeholder. + /// + string ParameterPlaceholder { get; } + + /// + /// Gets a value indicating whether to use named parameters. If false, + /// parameter placeholders will be equal to . + /// + bool UseNamedParameters { get; } + + /// + /// Gets the string to use in order to separate queries in a batch. + /// + string BatchQuerySeparator { get; } + + /// + /// Gets a new database connection, open. + /// + /// The connection string. + /// The connection. + DbConnection GetConnection(string connString); + + /// + /// Gets a properly built database command, with the underlying connection already open. + /// + /// The connection string. + /// The prepared query. + /// The parameters, if any. + /// The command. + DbCommand GetCommand(string connString, string preparedQuery, List parameters); + + /// + /// Gets a properly built database command, re-using an open connection. + /// + /// The open connection to use. + /// The prepared query. + /// The parameters, if any. + /// The command. + DbCommand GetCommand(DbConnection connection, string preparedQuery, List parameters); + + /// + /// Gets a properly built database command, re-using an open connection and a begun transaction. + /// + /// The transaction. + /// The prepared query. + /// The parameters, if any. + /// The command. + DbCommand GetCommand(DbTransaction transaction, string preparedQuery, List parameters); + + } + +} diff --git a/SqlProvidersCommon/IIndexConnector.cs b/SqlProvidersCommon/IIndexConnector.cs new file mode 100644 index 0000000..c9064b8 --- /dev/null +++ b/SqlProvidersCommon/IIndexConnector.cs @@ -0,0 +1,131 @@ + +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Text; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon { + + /// + /// Defines the interface for a connector between an index to a pages storage provider. + /// + public interface IIndexConnector { + + /// + /// Invokes the GetWordFetcher delegate. + /// + /// The word fetcher. + IWordFetcher GetWordFetcher(); + + /// + /// Defines a delegate for a method that gets the size of the approximate index in bytes. + /// + /// The size of the index, in bytes. + long GetSize(); + + /// + /// Invokes the GetCount delegate. + /// + /// The element type. + /// The element count. + int GetCount(IndexElementType element); + + /// + /// Invokes the ClearIndex delegate. + /// + /// A state object passed from the index. + void ClearIndex(object state); + + /// + /// Invokes the DeleteDataForDocument delegate. + /// + /// The document. + /// A state object passed from the index. + void DeleteDataForDocument(IDocument document, object state); + + /// + /// Invokes the SaveDataForDocument delegate. + /// + /// The document. + /// The content words. + /// The title words. + /// The keywords. + /// A state object passed from the index. + /// The number of stored occurrences. + int SaveDataForDocument(IDocument document, WordInfo[] content, WordInfo[] title, WordInfo[] keywords, object state); + + } + + /// + /// Defines a delegate for a method for getting a word fetcher. + /// + /// The word fetcher. + public delegate IWordFetcher GetWordFetcher(); + + /// + /// Defines a delegate for a method that gets the size of the approximate index in bytes. + /// + /// The size of the index, in bytes. + public delegate long GetSize(); + + /// + /// Defines a delegate for a method that counts elements in the index. + /// + /// The element type. + /// The element count. + public delegate int GetCount(IndexElementType element); + + /// + /// Defines a delegate for method that clears the index. + /// + /// A state object passed from the index. + public delegate void ClearIndex(object state); + + /// + /// Defines a delegate for a method for saving data. The passed data is the whole document data, as it was tokenized. + /// + /// The document. + /// The content words. + /// The title words. + /// The keywords. + /// A state object passed from the index. + /// The number of stored occurrences. + public delegate int SaveDataForDocument(IDocument document, WordInfo[] content, WordInfo[] title, WordInfo[] keywords, object state); + + /// + /// Defines a delegate for a method for deleting a document's data. + /// + /// The document. + /// A state object passed from the index. + public delegate void DeleteDataForDocument(IDocument document, object state); + + /// + /// Defines a delegate for a method for finding a word. + /// + /// The word text. + /// The returned word. + /// An open database connection. + /// true if the word is found, false otherwise. + public delegate bool TryFindWord(string text, out Word word, DbConnection connection); + + /// + /// Defines legal index element types. + /// + public enum IndexElementType { + /// + /// The documents. + /// + Documents, + /// + /// The words. + /// + Words, + /// + /// The occurrences. + /// + Occurrences + } + +} diff --git a/SqlProvidersCommon/IndexConnector.cs b/SqlProvidersCommon/IndexConnector.cs new file mode 100644 index 0000000..75955c1 --- /dev/null +++ b/SqlProvidersCommon/IndexConnector.cs @@ -0,0 +1,111 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ScrewTurn.Wiki.SearchEngine; +using System.Data.Common; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon { + + /// + /// Defines a connector between an index and a pages storage provider. + /// + public class IndexConnector : IIndexConnector { + + private GetWordFetcher getWordFetcher; + private GetSize getSize; + private GetCount getCount; + private ClearIndex clearIndex; + private DeleteDataForDocument deleteData; + private SaveDataForDocument saveData; + private TryFindWord tryFindWord; + + /// + /// Initializes a new instance of the class. + /// + /// The GetWordFetcher delegate. + /// The GetSize delegate. + /// The GetCount delegate. + /// The ClearIndex delegate. + /// The DeleteDataForDocument delegate. + /// The SaveData delegate. + /// The TryFindWord delegate. + public IndexConnector(GetWordFetcher getWordFetcher, GetSize getSize, GetCount getCount, ClearIndex clearIndex, + DeleteDataForDocument deleteData, SaveDataForDocument saveData, TryFindWord tryFindWord) { + + if(getWordFetcher == null) throw new ArgumentNullException("getWordFetcher"); + if(getSize == null) throw new ArgumentNullException("getSize"); + if(getCount == null) throw new ArgumentNullException("getCount"); + if(clearIndex == null) throw new ArgumentNullException("clearIndex"); + if(deleteData == null) throw new ArgumentNullException("deleteData"); + if(saveData == null) throw new ArgumentNullException("saveData"); + if(tryFindWord == null) throw new ArgumentNullException("tryFindWord"); + + this.getWordFetcher = getWordFetcher; + this.getSize = getSize; + this.getCount = getCount; + this.clearIndex = clearIndex; + this.deleteData = deleteData; + this.saveData = saveData; + this.tryFindWord = tryFindWord; + } + + /// + /// Invokes the GetWordFetcher delegate. + /// + /// The word fetcher. + public IWordFetcher GetWordFetcher() { + return getWordFetcher(); + } + + /// + /// Defines a delegate for a method that gets the size of the approximate index in bytes. + /// + /// The size of the index, in bytes. + public long GetSize() { + return getSize(); + } + + /// + /// Invokes the GetCount delegate. + /// + /// The element type. + /// The element count. + public int GetCount(IndexElementType element) { + return getCount(element); + } + + /// + /// Invokes the ClearIndex delegate. + /// + /// A state object passed from the index. + public void ClearIndex(object state) { + clearIndex(state); + } + + /// + /// Invokes the DeleteDataForDocument delegate. + /// + /// The document. + /// A state object passed from the index. + public void DeleteDataForDocument(IDocument document, object state) { + deleteData(document, state); + } + + /// + /// Invokes the SaveDataForDocument delegate. + /// + /// The document. + /// The content words. + /// The title words. + /// The keywords. + /// A state object passed from the index. + /// The number of stored occurrences. + public int SaveDataForDocument(IDocument document, WordInfo[] content, WordInfo[] title, WordInfo[] keywords, object state) { + return saveData(document, content, title, keywords, state); + } + + } + +} diff --git a/SqlProvidersCommon/Parameter.cs b/SqlProvidersCommon/Parameter.cs new file mode 100644 index 0000000..be4bbf3 --- /dev/null +++ b/SqlProvidersCommon/Parameter.cs @@ -0,0 +1,97 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Data.Common; +using System.Data; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon { + + /// + /// Represents a generic database parameter. + /// + public class Parameter { + + private ParameterType type; + private string name; + private object value; + + /// + /// Initializes a new instance of the class. + /// + /// The type of the parameter. + /// The name of the parameter. + /// The value of the parameter. + public Parameter(ParameterType type, string name, object value) { + this.type = type; + this.name = name; + this.value = value; + } + + /// + /// Gets the type of the parameter. + /// + public ParameterType Type { + get { return type; } + } + + /// + /// Gets the name of the parameter. + /// + public string Name { + get { return name; } + } + + /// + /// Gets or sets the value of the parameter. + /// + public object Value { + get { return value; } + set { this.value = value; } + } + + } + + /// + /// Lists parameter types. + /// + public enum ParameterType { + /// + /// An Int16. + /// + Int16, + /// + /// An Int32. + /// + Int32, + /// + /// An Int64. + /// + Int64, + /// + /// A unicode string. + /// + String, + /// + /// A unicode character. + /// + Char, + /// + /// A date/time. + /// + DateTime, + /// + /// A boolean. + /// + Boolean, + /// + /// A byte. + /// + Byte, + /// + /// An array of bytes. + /// + ByteArray + } + +} diff --git a/SqlProvidersCommon/Properties/AssemblyInfo.cs b/SqlProvidersCommon/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..428513d --- /dev/null +++ b/SqlProvidersCommon/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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("ScrewTurn Wiki SQL Providers Common Types")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// 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("ffa1ba27-d10a-4b1a-af49-adad56672de5")] diff --git a/SqlProvidersCommon/QueryBuilder.cs b/SqlProvidersCommon/QueryBuilder.cs new file mode 100644 index 0000000..53133b4 --- /dev/null +++ b/SqlProvidersCommon/QueryBuilder.cs @@ -0,0 +1,1056 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon { + + /// + /// A tool for building queries. + /// + public class QueryBuilder { + + private ICommandBuilder builder; + + /// + /// Initializes a new instance of the class. + /// + /// The command builder. + public QueryBuilder(ICommandBuilder builder) { + this.builder = builder; + } + + /// + /// Initializes a new instance of the class. + /// + /// The command builder. + /// The new instance. + public static QueryBuilder NewQuery(ICommandBuilder builder) { + return new QueryBuilder(builder); + } + + /// + /// Builds a SELECT query. + /// + /// The table. + /// The columns to select. + /// The SELECT query. + public string SelectFrom(string table, string[] columns) { + StringBuilder sb = new StringBuilder(100); + + sb.Append("select "); + for(int i = 0; i < columns.Length; i++) { + sb.Append(builder.ObjectNamePrefix); + sb.Append(columns[i]); + sb.Append(builder.ObjectNameSuffix); + if(i != columns.Length - 1) sb.Append(","); + sb.Append(" "); + } + + sb.Append("from "); + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + + return sb.ToString(); + } + + /// + /// Builds a SELECT query. + /// + /// The table. + /// The SELECT query. + public string SelectFrom(string table) { + StringBuilder sb = new StringBuilder(100); + + sb.Append("select * from "); + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + + return sb.ToString(); + } + + /// + /// Builds a SELECT query with a JOIN clause. + /// + /// The min table. + /// The joined table. + /// The main table column to join. + /// The joined table column to join. + /// The JOIN type. + /// The main table columns to select. + /// The joined table columns to select. + /// The SELECT query (returned columns are named like [Table_Column]). + public string SelectFrom(string table, string joinedTable, string tableColumn, string joinedTableColumn, Join join, + string[] tableColumns, string[] joinedTableColumns) { + + return SelectFrom(table, joinedTable, new string[] { tableColumn }, new string[] { joinedTableColumn }, join, + tableColumns, joinedTableColumns); + } + + /// + /// Builds a SELECT query with a JOIN clause. + /// + /// The min table. + /// The joined table. + /// The main table columns to join. + /// The joined table columns to join. + /// The JOIN type. + /// The main table columns to select. + /// The joined table columns to select. + /// The SELECT query (returned columns are named like [Table_Column]). + public string SelectFrom(string table, string joinedTable, string[] joinTableColumns, string[] joinJoinedTableColumns, Join join, + string[] tableColumns, string[] joinedTableColumns) { + + StringBuilder sb = new StringBuilder(200); + sb.Append("select "); + + for(int i = 0; i < tableColumns.Length; i++) { + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + sb.Append(builder.ObjectNamePrefix); + sb.Append(tableColumns[i]); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" as "); + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append("_"); + sb.Append(tableColumns[i]); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(", "); + } + + for(int i = 0; i < joinedTableColumns.Length; i++) { + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinedTable); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinedTableColumns[i]); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" as "); + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinedTable); + sb.Append("_"); + sb.Append(joinedTableColumns[i]); + sb.Append(builder.ObjectNameSuffix); + + if(i != joinedTableColumns.Length - 1) sb.Append(", "); + } + + sb.Append(" from "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" "); + sb.Append(JoinToString(join)); + sb.Append(" "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinedTable); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" on "); + + for(int i = 0; i < joinTableColumns.Length; i++) { + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinTableColumns[i]); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" = "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinedTable); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinJoinedTableColumns[i]); + sb.Append(builder.ObjectNameSuffix); + + if(i != joinTableColumns.Length - 1) sb.Append(" and "); + } + + return sb.ToString(); + } + + /// + /// Builds a SELECT query with a JOIN clause. + /// + /// The main table. + /// The joined table. + /// The main table column to join. + /// The joined table column to join. + /// The JOIN type. + /// The SELECT query. + public string SelectFrom(string table, string joinedTable, string tableColumn, string joinedTableColumn, Join join) { + StringBuilder sb = new StringBuilder(100); + sb.Append("select * from "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" "); + sb.Append(JoinToString(join)); + sb.Append(" "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinedTable); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" on "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + sb.Append(builder.ObjectNamePrefix); + sb.Append(tableColumn); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" = "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinedTable); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinedTableColumn); + sb.Append(builder.ObjectNameSuffix); + + return sb.ToString(); + } + + /// + /// Builds a SELECT query with two JOIN clauses (both on the main table). + /// + /// The main table. + /// The joined table. + /// The main table column to join. + /// The joined table column to join. + /// The join. + /// The main table columns to select. + /// The joined table columns to select. + /// The other joined table. + /// The other joined table column to join. + /// The join. + /// The other joined table columns to select. + /// The SELECT query (returned columns are named like [Table_Column]). + public string SelectFrom(string table, string joinedTable, string tableColumn, string joinedTableColumn, Join join, + string[] tableColumns, string[] joinedTableColumns, + string otherJoinedTable, string otherJoinedTableColumn, Join otherJoin, string[] otherJoinedTableColumns) { + + StringBuilder sb = new StringBuilder(200); + sb.Append("select "); + + for(int i = 0; i < tableColumns.Length; i++) { + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + sb.Append(builder.ObjectNamePrefix); + sb.Append(tableColumns[i]); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" as "); + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append("_"); + sb.Append(tableColumns[i]); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(", "); + } + + for(int i = 0; i < joinedTableColumns.Length; i++) { + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinedTable); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinedTableColumns[i]); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" as "); + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinedTable); + sb.Append("_"); + sb.Append(joinedTableColumns[i]); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(", "); + } + + for(int i = 0; i < otherJoinedTableColumns.Length; i++) { + sb.Append(builder.ObjectNamePrefix); + sb.Append(otherJoinedTable); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + sb.Append(builder.ObjectNamePrefix); + sb.Append(otherJoinedTableColumns[i]); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" as "); + sb.Append(builder.ObjectNamePrefix); + sb.Append(otherJoinedTable); + sb.Append("_"); + sb.Append(otherJoinedTableColumns[i]); + sb.Append(builder.ObjectNameSuffix); + + if(i != otherJoinedTableColumns.Length - 1) sb.Append(", "); + } + + sb.Append(" from "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" "); + sb.Append(JoinToString(join)); + sb.Append(" "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinedTable); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" on "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + sb.Append(builder.ObjectNamePrefix); + sb.Append(tableColumn); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" = "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinedTable); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + sb.Append(builder.ObjectNamePrefix); + sb.Append(joinedTableColumn); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" "); + sb.Append(JoinToString(otherJoin)); + sb.Append(" "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(otherJoinedTable); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" on "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + sb.Append(builder.ObjectNamePrefix); + sb.Append(tableColumn); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" = "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(otherJoinedTable); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + sb.Append(builder.ObjectNamePrefix); + sb.Append(otherJoinedTableColumn); + sb.Append(builder.ObjectNameSuffix); + + return sb.ToString(); + } + + /// + /// Converts a Join type to its string representation. + /// + /// The join type. + /// The string representation. + private static string JoinToString(Join join) { + switch(join) { + case Join.Join: + return "join"; + case Join.InnerJoin: + return "inner join"; + case Join.LeftJoin: + return "left join"; + case Join.RightJoin: + return "right join"; + default: + throw new NotSupportedException(); + } + } + + /// + /// Builds a SELECT COUNT(*) query. + /// + /// The table. + /// The SELECT query. + public string SelectCountFrom(string table) { + StringBuilder sb = new StringBuilder(100); + + sb.Append("select count(*) from "); + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + + return sb.ToString(); + } + + /// + /// Builds a WHERE clause condition. + /// + /// The table the column belongs to, or null. + /// The column. + /// The operator. + /// The parameter name. + /// The condition. + private string BuildWhereClause(string table, string column, WhereOperator op, string parameter) { + StringBuilder sb = new StringBuilder(80); + + if(table != null) { + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + } + + sb.Append(builder.ObjectNamePrefix); + sb.Append(column); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" "); + sb.Append(WhereOperatorToString(op)); + + if(op != WhereOperator.IsNull && op != WhereOperator.IsNotNull) { + sb.Append(" "); + + if(builder.UseNamedParameters) { + sb.Append(builder.ParameterNamePrefix); + sb.Append(parameter); + sb.Append(builder.ParameterNameSuffix); + } + else sb.Append(builder.ParameterPlaceholder); + } + + return sb.ToString(); + } + + /// + /// Applies a WHERE clause to a query. + /// + /// The query. + /// The column subject of the WHERE clause. + /// The operator. + /// The name of the parameter for the WHERE clause. + /// The resulting query. + public string Where(string query, string column, WhereOperator op, string parameter) { + return Where(query, null, column, op, parameter, false, false); + } + + /// + /// Applies a WHERE clause to a query. + /// + /// The query. + /// The column subject of the WHERE clause. + /// The operator. + /// The name of the parameter for the WHERE clause. + /// A value indicating whether to open a bracket after the WHERE. + /// A value indicating whether to close a bracket after the clause. + /// The resulting query. + public string Where(string query, string column, WhereOperator op, string parameter, bool openBracket, bool closeBracket) { + return Where(query, null, column, op, parameter, openBracket, closeBracket); + } + + /// + /// Applies a WHERE clause to a query. + /// + /// The query. + /// The table the column belongs to. + /// The column subject of the WHERE clause. + /// The operator. + /// The name of the parameter for the WHERE clause. + /// The resulting query. + public string Where(string query, string table, string column, WhereOperator op, string parameter) { + return Where(query, table, column, op, parameter, false, false); + } + + /// + /// Applies a WHERE clause to a query. + /// + /// The query. + /// The table the column belongs to. + /// The column subject of the WHERE clause. + /// The operator. + /// The name of the parameter for the WHERE clause. + /// A value indicating whether to open a bracket after the WHERE. + /// A value indicating whether to close a bracket after the clause. + /// The resulting query. + public string Where(string query, string table, string column, WhereOperator op, string parameter, bool openBracket, bool closeBracket) { + StringBuilder sb = new StringBuilder(150); + sb.Append(query); + sb.Append(" where "); + if(openBracket) sb.Append("("); + + sb.Append(BuildWhereClause(table, column, op, parameter)); + + if(closeBracket) sb.Append(")"); + + return sb.ToString(); + } + + /// + /// Builds a WHERE clause with IN operator. + /// + /// The table the column belongs to, or null. + /// The column subject of the WHERE clause. + /// The names of the parameters in the IN set. + /// The resulting clause. + private string BuildWhereInClause(string table, string column, string[] parameters) { + StringBuilder sb = new StringBuilder(100); + + if(!string.IsNullOrEmpty(table)) { + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + } + + sb.Append(builder.ObjectNamePrefix); + sb.Append(column); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" in ("); + + for(int i = 0; i < parameters.Length; i++) { + if(builder.UseNamedParameters) { + sb.Append(builder.ParameterNamePrefix); + sb.Append(parameters[i]); + sb.Append(builder.ParameterNameSuffix); + } + else { + sb.Append(builder.ParameterPlaceholder); + } + if(i != parameters.Length - 1) sb.Append(", "); + } + + sb.Append(")"); + + return sb.ToString(); + } + + /// + /// Applies a WHERE clause with IN operator to a query. + /// + /// The query. + /// The column subject of the WHERE clause. + /// The names of the parameters in the IN set. + /// The resulting query. + public string WhereIn(string query, string column, string[] parameters) { + return WhereIn(query, null, column, parameters); + } + + /// + /// Applies a WHERE clause with IN operator to a query. + /// + /// The query. + /// The table the column belongs to. + /// The column subject of the WHERE clause. + /// The names of the parameters in the IN set. + /// The resulting query. + public string WhereIn(string query, string table, string column, string[] parameters) { + StringBuilder sb = new StringBuilder(200); + sb.Append(query); + sb.Append(" where "); + + sb.Append(BuildWhereInClause(table, column, parameters)); + + return sb.ToString(); + } + + /// + /// Applies a WHERE NOT IN (subQuery) clause to a query. + /// + /// The query. + /// The table the column belongs to. + /// The column. + /// The subQuery. + /// The resulting query. + public string WhereNotInSubquery(string query, string table, string column, string subQuery) { + StringBuilder sb = new StringBuilder(200); + sb.Append(query); + sb.Append(" where "); + + if(table != null) { + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + sb.Append("."); + } + + sb.Append(builder.ObjectNamePrefix); + sb.Append(column); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" not in ("); + sb.Append(subQuery); + sb.Append(")"); + + return sb.ToString(); + } + + /// + /// Adds another WHERE clause to a query. + /// + /// The query. + /// The column subject of the WHERE clause. + /// The operator. + /// The name of the parameter for the WHERE clause. + /// The resulting query. + public string AndWhere(string query, string column, WhereOperator op, string parameter) { + return AndWhere(query, column, op, parameter, false, false); + } + + /// + /// Adds another WHERE clause to a query. + /// + /// The query. + /// The column subject of the WHERE clause. + /// The operator. + /// The name of the parameter for the WHERE clause. + /// A value indicating whether to open a bracket after the AND. + /// A value indicating whether to close a bracket after the clause. + /// The resulting query. + public string AndWhere(string query, string column, WhereOperator op, string parameter, bool openBracket, bool closeBracket) { + return AndWhere(query, null, column, op, parameter, openBracket, closeBracket); + } + + /// + /// Adds another WHERE clause to a query. + /// + /// The query. + /// The table the column belongs to. + /// The column subject of the WHERE clause. + /// The operator. + /// The name of the parameter for the WHERE clause. + /// The resulting query. + public string AndWhere(string query, string table, string column, WhereOperator op, string parameter) { + return AndWhere(query, table, column, op, parameter, false, false); + } + + /// + /// Adds another WHERE clause to a query. + /// + /// The query. + /// The table the column belongs to. + /// The column subject of the WHERE clause. + /// The operator. + /// The name of the parameter for the WHERE clause. + /// A value indicating whether to open a bracket after the AND. + /// A value indicating whether to close a bracket after the clause. + /// The resulting query. + public string AndWhere(string query, string table, string column, WhereOperator op, string parameter, bool openBracket, bool closeBracket) { + StringBuilder sb = new StringBuilder(200); + sb.Append(query); + sb.Append(" and "); + if(openBracket) sb.Append("("); + + sb.Append(BuildWhereClause(table, column, op, parameter)); + + if(closeBracket) sb.Append(")"); + + return sb.ToString(); + } + + /// + /// Adds another WHERE clause to a query. + /// + /// The query. + /// The column subject of the WHERE clause. + /// The operator. + /// The name of the parameter for the WHERE clause. + /// The resulting query. + public string OrWhere(string query, string column, WhereOperator op, string parameter) { + return OrWhere(query, column, op, parameter, false, false); + } + + /// + /// Adds another WHERE clause to a query. + /// + /// The query. + /// The column subject of the WHERE clause. + /// The operator. + /// The name of the parameter for the WHERE clause. + /// A value indicating whether to open a bracket after the OR. + /// A value indicating whether to close a bracket after the clause. + /// The resulting query. + public string OrWhere(string query, string column, WhereOperator op, string parameter, bool openBracket, bool closeBracket) { + return OrWhere(query, null, column, op, parameter, openBracket, closeBracket); + } + + /// + /// Adds another WHERE clause to a query. + /// + /// The query. + /// The table the column belongs to. + /// The column subject of the WHERE clause. + /// The operator. + /// The name of the parameter for the WHERE clause. + /// The resulting query. + public string OrWhere(string query, string table, string column, WhereOperator op, string parameter) { + return OrWhere(query, table, column, op, parameter, false, false); + } + + /// + /// Adds another WHERE clause to a query. + /// + /// The query. + /// The table the column belongs to. + /// The column subject of the WHERE clause. + /// The operator. + /// The name of the parameter for the WHERE clause. + /// A value indicating whether to open a bracket after the AND. + /// A value indicating whether to close a bracket after the clause. + /// The resulting query. + public string OrWhere(string query, string table, string column, WhereOperator op, string parameter, bool openBracket, bool closeBracket) { + StringBuilder sb = new StringBuilder(200); + sb.Append(query); + sb.Append(" or "); + if(openBracket) sb.Append("("); + + sb.Append(BuildWhereClause(table, column, op, parameter)); + + if(closeBracket) sb.Append(")"); + + return sb.ToString(); + } + + /// + /// Converts a WHERE operator to its corresponding string. + /// + /// The operator. + /// The string. + private static string WhereOperatorToString(WhereOperator op) { + switch(op) { + case WhereOperator.Like: + return "like"; + case WhereOperator.Equals: + return "="; + case WhereOperator.NotEquals: + return "<>"; + case WhereOperator.GreaterThan: + return ">"; + case WhereOperator.LessThan: + return "<"; + case WhereOperator.GreaterThanOrEqualTo: + return ">="; + case WhereOperator.LessThanOrEqualTo: + return "<="; + case WhereOperator.IsNull: + return "is null"; + case WhereOperator.IsNotNull: + return "is not null"; + default: + throw new NotSupportedException(); + } + } + + /// + /// Applies an ORDER BY clause to a query. + /// + /// The query. + /// The columns to order by. + /// The ordering directions for each column. + /// The resulting query. + public string OrderBy(string query, string[] columns, Ordering[] orderings) { + StringBuilder sb = new StringBuilder(200); + sb.Append(query); + + sb.Append(" order by "); + + for(int i = 0; i < columns.Length; i++) { + sb.Append(builder.ObjectNamePrefix); + sb.Append(columns[i]); + sb.Append(builder.ObjectNameSuffix); + sb.Append(" "); + + sb.Append(OrderingToString(orderings[i])); + + if(i != columns.Length - 1) sb.Append(", "); + } + + return sb.ToString(); + } + + /// + /// Converts an ordering to string. + /// + /// The ordering. + /// The string. + private static string OrderingToString(Ordering ordering) { + switch(ordering) { + case Ordering.Asc: + return "asc"; + case Ordering.Desc: + return "desc"; + default: + throw new NotSupportedException(); + } + } + + /// + /// Applies a GROUP BY clause to a query. + /// + /// The query. + /// The columns to group by. + /// The resulting query. + public string GroupBy(string query, string[] columns) { + StringBuilder sb = new StringBuilder(200); + sb.Append(query); + sb.Append(" group by "); + + for(int i = 0; i < columns.Length; i++) { + sb.Append(builder.ObjectNamePrefix); + sb.Append(columns[i]); + sb.Append(builder.ObjectNameSuffix); + + if(i != columns.Length - 1) sb.Append(", "); + } + + return sb.ToString(); + } + + /// + /// Builds an INSERT INTO query. + /// + /// The destination table. + /// The columns names. + /// The parameters names. + /// The INSERT INTO query. + public string InsertInto(string table, string[] columns, string[] parameters) { + StringBuilder sb = new StringBuilder(200); + sb.Append("insert into "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" ("); + for(int i = 0; i < columns.Length; i++) { + sb.Append(builder.ObjectNamePrefix); + sb.Append(columns[i]); + sb.Append(builder.ObjectNameSuffix); + if(i != columns.Length - 1) sb.Append(", "); + } + sb.Append(") values ("); + for(int i = 0; i < parameters.Length; i++) { + if(builder.UseNamedParameters) { + sb.Append(builder.ParameterNamePrefix); + sb.Append(parameters[i]); + sb.Append(builder.ParameterNameSuffix); + } + else { + sb.Append(builder.ParameterPlaceholder); + } + + if(i != parameters.Length - 1) sb.Append(", "); + } + sb.Append(")"); + + return sb.ToString(); + } + + /// + /// Builds an UPDATE query. + /// + /// The table. + /// The columns to update. + /// The parameters. + /// The UPDATE query, without any WHERE clause. + public string Update(string table, string[] columns, string[] parameters) { + StringBuilder sb = new StringBuilder(100); + sb.Append("update "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" set "); + for(int i = 0; i < columns.Length; i++) { + sb.Append(builder.ObjectNamePrefix); + sb.Append(columns[i]); + sb.Append(builder.ObjectNameSuffix); + sb.Append(" = "); + + if(builder.UseNamedParameters) { + sb.Append(builder.ParameterNamePrefix); + sb.Append(parameters[i]); + sb.Append(builder.ParameterNameSuffix); + } + else { + sb.Append(builder.ParameterPlaceholder); + } + + if(i != columns.Length - 1) sb.Append(", "); + } + + return sb.ToString(); + } + + /// + /// Builds an UPDATE query that increments the numerical value of a column by one. + /// + /// The table. + /// The column to update. + /// The increment or decrement value. + /// The UPDATE query, without any WHERE clause. + public string UpdateIncrement(string table, string column, int increment) { + StringBuilder sb = new StringBuilder(100); + sb.Append("update "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" set "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(column); + sb.Append(builder.ObjectNameSuffix); + + sb.Append(" = "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(column); + sb.Append(builder.ObjectNameSuffix); + + if(increment > 0) sb.Append(" + "); + else sb.Append(" - "); + sb.Append(Math.Abs(increment).ToString()); + + return sb.ToString(); + } + + /// + /// Builds a DELETE FROM query. + /// + /// The table. + /// The DELETE FROM query, without any WHERE clause. + public string DeleteFrom(string table) { + StringBuilder sb = new StringBuilder(100); + sb.Append("delete from "); + + sb.Append(builder.ObjectNamePrefix); + sb.Append(table); + sb.Append(builder.ObjectNameSuffix); + + return sb.ToString(); + } + + /// + /// Appends a query to an existing query for batch execution. + /// + /// The query. + /// The second query. + /// The resulting query. + public string AppendForBatch(string query, string secondQuery) { + return query + builder.BatchQuerySeparator + secondQuery; + } + + } + + /// + /// Lists WHERE operators. + /// + public enum WhereOperator { + /// + /// LIKE. + /// + Like, + /// + /// =. + /// + Equals, + /// + /// <>. + /// + NotEquals, + /// + /// >. + /// + GreaterThan, + /// + /// < + /// + LessThan, + /// + /// >=. + /// + GreaterThanOrEqualTo, + /// + /// <=. + /// + LessThanOrEqualTo, + /// + /// IS NULL. + /// + IsNull, + /// + /// IS NOT NULL. + /// + IsNotNull + } + + /// + /// List JOIN types. + /// + public enum Join { + /// + /// JOIN. + /// + Join, + /// + /// INNER JOIN. + /// + InnerJoin, + /// + /// LEFT JOIN. + /// + LeftJoin, + /// + /// RIGHT JOIN. + /// + RightJoin + } + + /// + /// Lists ordering directions. + /// + public enum Ordering { + /// + /// Ascending. + /// + Asc, + /// + /// Descending. + /// + Desc + } + +} diff --git a/SqlProvidersCommon/SqlAclStorer.cs b/SqlProvidersCommon/SqlAclStorer.cs new file mode 100644 index 0000000..ddefbd2 --- /dev/null +++ b/SqlProvidersCommon/SqlAclStorer.cs @@ -0,0 +1,82 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.AclEngine; +using System.Data.Common; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon { + + /// + /// Implements a SQL ACL Storer. + /// + public class SqlAclStorer : AclStorerBase { + + private LoadData loadData; + private DeleteEntries deleteEntries; + private StoreEntries storeEntries; + + /// + /// Initializes a new instance of the class. + /// + /// The instance of the ACL Manager to handle. + /// The delegate. + /// The delegate. + /// The delegate. + public SqlAclStorer(IAclManager aclManager, LoadData loadData, DeleteEntries deleteEntries, StoreEntries storeEntries) + : base(aclManager) { + + if(loadData == null) throw new ArgumentNullException("loadData"); + if(deleteEntries == null) throw new ArgumentNullException("deleteEntries"); + if(storeEntries == null) throw new ArgumentNullException("storeEntries"); + + this.loadData = loadData; + this.deleteEntries = deleteEntries; + this.storeEntries = storeEntries; + } + + /// + /// Loads data from storage. + /// + /// The loaded ACL entries. + protected override AclEntry[] LoadDataInternal() { + return loadData(); + } + + /// + /// Deletes some entries. + /// + /// The entries to delete. + protected override void DeleteEntries(AclEntry[] entries) { + deleteEntries(entries); + } + + /// + /// Stores some entries. + /// + /// The entries to store. + protected override void StoreEntries(AclEntry[] entries) { + storeEntries(entries); + } + + } + + /// + /// Defines a delegate for a method that loads ACL data from storage. + /// + /// The loaded ACL entries. + public delegate AclEntry[] LoadData(); + + /// + /// Defines a delegate for a method that deletes ACL entries in the storage. + /// + /// The entries to delete. + public delegate void DeleteEntries(AclEntry[] entries); + + /// + /// Defines a delegate for a method that stores ACL entries in the storage. + /// + /// The entries to store. + public delegate void StoreEntries(AclEntry[] entries); + +} diff --git a/SqlProvidersCommon/SqlClassBase.cs b/SqlProvidersCommon/SqlClassBase.cs new file mode 100644 index 0000000..a1866b2 --- /dev/null +++ b/SqlProvidersCommon/SqlClassBase.cs @@ -0,0 +1,301 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Data.Common; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon { + + /// + /// Implements a base class for all SQL-based classes. + /// + public abstract class SqlClassBase { + + #region Utility Methods + + /// + /// Gets a value indicating whether a column contains nonexistent or missing value. + /// + /// The . + /// The name of the column. + /// true if the column contains a nonexistent or missing value, false otherwise. + protected bool IsDBNull(DbDataReader reader, string column) { + return reader.IsDBNull(reader.GetOrdinal(column)); + } + + /// + /// Gets the value of a nullable column. + /// + /// The return type. + /// The . + /// The name of the column. + /// The default value to return when the column contains a null value. + /// The value. + protected T GetNullableColumn(DbDataReader reader, string column, T defaultValue) { + if(IsDBNull(reader, column)) return defaultValue; + else return (T)reader[column]; + } + + /// + /// Reads all the contents of a binary column. + /// + /// The . + /// The name of the column. + /// The max size, in bytes, to read. If exceeded, null is returned. + /// The read bytes, or null. + /// This method buffers the data in memory; avoid reading data bigger than a few megabytes. + protected byte[] GetBinaryColumn(DbDataReader reader, string column, int maxSize) { + // 128 KB read buffer + byte[] buffer = new byte[131072]; + byte[] tempResult = new byte[maxSize]; + + int columnOrdinal = reader.GetOrdinal(column); + + long read = 0; + long totalRead = 0; + do { + read = reader.GetBytes(columnOrdinal, totalRead, buffer, 0, buffer.Length); + + if(totalRead + read > maxSize) return null; + + if(read > 0) { + Buffer.BlockCopy(buffer, 0, tempResult, (int)totalRead, (int)read); + } + totalRead += read; + } while(read > 0); + + // Copy tempBuffer in final array + buffer = null; + + byte[] result = new byte[totalRead]; + Buffer.BlockCopy(tempResult, 0, result, 0, result.Length); + + return result; + } + + /// + /// Copies all the contents of a binary column into a . + /// + /// The . + /// The name of the column. + /// The destination . + protected int ReadBinaryColumn(DbDataReader reader, string column, System.IO.Stream stream) { + // 128 KB read buffer + byte[] buffer = new byte[131072]; + + int columnOrdinal = reader.GetOrdinal(column); + + int read = 0; + int totalRead = 0; + do { + read = (int)reader.GetBytes(columnOrdinal, totalRead, buffer, 0, buffer.Length); + + if(read > 0) { + stream.Write(buffer, 0, read); + } + + totalRead += read; + } while(read > 0); + + return totalRead; + } + + /// + /// Logs an exception. + /// + /// The exception. + protected abstract void LogException(Exception ex); + + /// + /// Executes a scalar command, then closes the connection. + /// + /// The return type. + /// The command to execute. + /// The default value of the return value, to use when the command fails. + /// The result. + /// The connection is closed after the execution. + protected T ExecuteScalar(DbCommand command, T defaultValue) { + return ExecuteScalar(command, defaultValue, true); + } + + /// + /// Executes a scalar command, then closes the connection. + /// + /// The return type. + /// The command to execute. + /// The default value of the return value, to use when the command fails. + /// A value indicating whether to close the connection after execution. + /// The result. + protected T ExecuteScalar(DbCommand command, T defaultValue, bool close) { + object temp = null; + + try { + temp = command.ExecuteScalar(); + } + catch(DbException dbex) { + LogException(dbex); + } + finally { + if(close) { + CloseConnection(command.Connection); + } + } + + if(temp != null) { + return (T)temp; + } + else return defaultValue; + } + + /// + /// Executes a non-query command, then closes the connection. + /// + /// The command to execute. + /// The rows affected (-1 if the command failed). + /// The connection is closed after the execution. + protected int ExecuteNonQuery(DbCommand command) { + return ExecuteNonQuery(command, true); + } + + /// + /// Executes a non-query command, then closes the connection if requested. + /// + /// The command to execute. + /// A value indicating whether to close the connection after execution. + /// The rows affected (-1 if the command failed). + protected int ExecuteNonQuery(DbCommand command, bool close) { + return ExecuteNonQuery(command, close, true); + } + + /// + /// Executes a non-query command, then closes the connection if requested. + /// + /// The command to execute. + /// A value indicating whether to close the connection after execution. + /// A value indicating whether to log any error. + /// The rows affected (-1 if the command failed). + protected int ExecuteNonQuery(DbCommand command, bool close, bool logError) { + int rows = -1; + + try { + rows = command.ExecuteNonQuery(); + } + catch(DbException dbex) { + if(logError) LogException(dbex); + } + finally { + if(close) { + CloseConnection(command.Connection); + } + } + + return rows; + } + + /// + /// Executes a reader command, leaving the connection open. + /// + /// The command to execute. + /// A value indicating whether to close the connection on error. + /// The data reader, or null if the command fails. + protected DbDataReader ExecuteReader(DbCommand command, bool closeOnError) { + DbDataReader reader = null; + try { + reader = command.ExecuteReader(); + } + catch(DbException dbex) { + LogException(dbex); + if(closeOnError) CloseConnection(command.Connection); + } + + return reader; + } + + /// + /// Executes a reader command, leaving the connection open. + /// + /// The command to execute. + /// The data reader, or null if the command fails. + /// If the command fails, the connection is closed. + protected DbDataReader ExecuteReader(DbCommand command) { + return ExecuteReader(command, true); + } + + /// + /// Closes a connection, swallowing all exceptions. + /// + /// The connection to close. + protected void CloseConnection(DbConnection connection) { + try { + connection.Close(); + } + catch { } + } + + /// + /// Closes a reader, a command and the associated connection. + /// + /// The command. + /// The reader. + protected void CloseReader(DbCommand command, DbDataReader reader) { + try { + reader.Close(); + } + catch { } + CloseConnection(command.Connection); + } + + /// + /// Closes a reader. + /// + /// The reader. + protected void CloseReader(DbDataReader reader) { + try { + reader.Close(); + } + catch { } + } + + /// + /// Begins a transaction. + /// + /// The connection. + /// The transaction. + protected DbTransaction BeginTransaction(DbConnection connection) { + return connection.BeginTransaction(); + } + + /// + /// Commits a transaction. + /// + /// The transaction. + protected void CommitTransaction(DbTransaction transaction) { + // Commit sets transaction.Connection to null + DbConnection connection = transaction.Connection; + transaction.Commit(); + transaction.Dispose(); + CloseConnection(connection); + } + + /// + /// Rolls back a transaction. + /// + /// The transaction. + protected void RollbackTransaction(DbTransaction transaction) { + // Rollback sets transaction.Connection to null + DbConnection connection = transaction.Connection; + try { + transaction.Rollback(); + } + catch { } + transaction.Dispose(); + CloseConnection(connection); + } + + #endregion + + } + +} diff --git a/SqlProvidersCommon/SqlFilesStorageProviderBase.cs b/SqlProvidersCommon/SqlFilesStorageProviderBase.cs new file mode 100644 index 0000000..be73853 --- /dev/null +++ b/SqlProvidersCommon/SqlFilesStorageProviderBase.cs @@ -0,0 +1,1394 @@ + +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon { + + /// + /// Implements a base class for a SQL files storage provider. + /// + public abstract class SqlFilesStorageProviderBase : SqlStorageProviderBase, IFilesStorageProviderV30 { + + private const int MaxFileSize = 52428800; // 50 MB + + #region IFilesStorageProvider Members + + /// + /// Prepares the directory name. + /// + /// The directory to prepare. + /// The prepared directory, for example "/" or "/my/directory/". + private static string PrepareDirectory(string directory) { + if(string.IsNullOrEmpty(directory)) return "/"; + else { + return (!directory.StartsWith("/") ? "/" : "") + + directory + + (!directory.EndsWith("/") ? "/" : ""); + } + } + + /// + /// Determines whether a directory exists. + /// + /// A database connection. + /// The directory, for example "/my/directory". + /// true if the directory exists, false otherwise. + /// The root directory always exists. + private bool DirectoryExists(DbConnection connection, string directory) { + directory = PrepareDirectory(directory); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectCountFrom("Directory"); + query = queryBuilder.Where(query, "FullPath", WhereOperator.Equals, "FullPath"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "FullPath", directory)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + int count = ExecuteScalar(command, -1, false); + + return count == 1; + } + + /// + /// Determines whether a directory exists. + /// + /// A database transaction. + /// The directory, for example "/my/directory". + /// true if the directory exists, false otherwise. + /// The root directory always exists. + private bool DirectoryExists(DbTransaction transaction, string directory) { + directory = PrepareDirectory(directory); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectCountFrom("Directory"); + query = queryBuilder.Where(query, "FullPath", WhereOperator.Equals, "FullPath"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "FullPath", directory)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int count = ExecuteScalar(command, -1, false); + + return count == 1; + } + + /// + /// Splits a file full name into the directory and file parts. + /// + /// The file full name, for example "/file.txt" or "/directory/file.txt". + /// The resulting directory path, for example "/" or "/directory/". + /// The file name, for example "file.txt". + private static void SplitFileFullName(string fullName, out string directory, out string file) { + directory = fullName.Substring(0, fullName.LastIndexOf("/") + 1); + directory = PrepareDirectory(directory); + file = fullName.Substring(fullName.LastIndexOf("/") + 1); + } + + /// + /// Determines whether a file exists. + /// + /// A database connection. + /// The file full name, for example "/file.txt" or "/directory/file.txt". + /// true if the file exists, false otherwise. + private bool FileExists(DbConnection connection, string fullName) { + string directory, file; + SplitFileFullName(fullName, out directory, out file); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectCountFrom("File"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", file)); + parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + int count = ExecuteScalar(command, -1, false); + + return count == 1; + } + + /// + /// Determines whether a file exists. + /// + /// A database transaction. + /// The file full name, for example "/file.txt" or "/directory/file.txt". + /// true if the file exists, false otherwise. + private bool FileExists(DbTransaction transaction, string fullName) { + string directory, file; + SplitFileFullName(fullName, out directory, out file); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectCountFrom("File"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", file)); + parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int count = ExecuteScalar(command, -1, false); + + return count == 1; + } + + /// + /// Lists the Files in the specified Directory. + /// + /// The full directory name, for example "/my/directory". Null, empty or "/" for the root directory. + /// The list of Files in the directory. + /// If does not exist. + public string[] ListFiles(string directory) { + directory = PrepareDirectory(directory); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + if(!DirectoryExists(connection, directory)) { + CloseConnection(connection); + throw new ArgumentException("Directory does not exist", "directory"); + } + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("File", new string[] { "Name" }); + query = queryBuilder.Where(query, "Directory", WhereOperator.Equals, "Directory"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(20); + + while(reader.Read()) { + result.Add(directory + reader["Name"] as string); + } + + CloseReader(command, reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Lists the Directories in the specified directory. + /// + /// An open connection. + /// The full directory name, for example "/my/directory". Null, empty or "/" for the root directory. + /// The list of Directories in the Directory. + private string[] ListDirectories(DbConnection connection, string directory) { + directory = PrepareDirectory(directory); + + ICommandBuilder builder = GetCommandBuilder(); + + if(!DirectoryExists(connection, directory)) { + CloseConnection(connection); + throw new ArgumentException("Directory does not exist", "directory"); + } + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Directory", new string[] { "FullPath" }); + query = queryBuilder.Where(query, "Parent", WhereOperator.Equals, "Parent"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Parent", directory)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(20); + + while(reader.Read()) { + result.Add(reader["FullPath"] as string); + } + + CloseReader(reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Lists the Directories in the specified directory. + /// + /// A database transaction. + /// The full directory name, for example "/my/directory". Null, empty or "/" for the root directory. + /// The list of Directories in the Directory. + private string[] ListDirectories(DbTransaction transaction, string directory) { + directory = PrepareDirectory(directory); + + ICommandBuilder builder = GetCommandBuilder(); + + if(!DirectoryExists(transaction, directory)) { + RollbackTransaction(transaction); + throw new ArgumentException("Directory does not exist", "directory"); + } + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Directory", new string[] { "FullPath" }); + query = queryBuilder.Where(query, "Parent", WhereOperator.Equals, "Parent"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Parent", directory)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(20); + + while(reader.Read()) { + result.Add(reader["FullPath"] as string); + } + + CloseReader(reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Lists the Directories in the specified directory. + /// + /// The full directory name, for example "/my/directory". Null, empty or "/" for the root directory. + /// The list of Directories in the Directory. + /// If does not exist. + public string[] ListDirectories(string directory) { + directory = PrepareDirectory(directory); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + if(!DirectoryExists(connection, directory)) { + CloseConnection(connection); + throw new ArgumentException("Directory does not exist", "directory"); + } + + string[] result = ListDirectories(connection, directory); + CloseConnection(connection); + + return result; + } + + /// + /// Stores a file. + /// + /// The full name of the file. + /// A Stream object used as source of a byte stream, + /// i.e. the method reads from the Stream and stores the content properly. + /// true to overwrite an existing file. + /// true if the File is stored, false otherwise. + /// If overwrite is false and File already exists, the method returns false. + /// If os are null. + /// If is empty or does not support reading. + public bool StoreFile(string fullName, System.IO.Stream sourceStream, bool overwrite) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + if(sourceStream == null) throw new ArgumentNullException("sourceStream"); + if(!sourceStream.CanRead) throw new ArgumentException("Cannot read from Source Stream", "sourceStream"); + + string directory, filename; + SplitFileFullName(fullName, out directory, out filename); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + bool fileExists = FileExists(transaction, fullName); + + if(fileExists && !overwrite) { + RollbackTransaction(transaction); + return false; + } + + // To achieve decent performance, an UPDATE query is issued if the file exists, + // otherwise an INSERT query is issued + + string query; + List parameters; + + byte[] fileData = null; + int size = Tools.ReadStream(sourceStream, ref fileData, MaxFileSize); + if(size < 0) { + RollbackTransaction(transaction); + throw new ArgumentException("Source Stream contains too much data", "sourceStream"); + } + + if(fileExists) { + query = queryBuilder.Update("File", new string[] { "Size", "LastModified", "Data" }, new string[] { "Size", "LastModified", "Data" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory"); + + parameters = new List(5); + parameters.Add(new Parameter(ParameterType.Int64, "Size", (long)size)); + parameters.Add(new Parameter(ParameterType.DateTime, "LastModified", DateTime.Now)); + parameters.Add(new Parameter(ParameterType.ByteArray, "Data", fileData)); + parameters.Add(new Parameter(ParameterType.String, "Name", filename)); + parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); + } + else { + query = queryBuilder.InsertInto("File", new string[] { "Name", "Directory", "Size", "Downloads", "LastModified", "Data" }, + new string[] { "Name", "Directory", "Size", "Downloads", "LastModified", "Data" }); + + parameters = new List(6); + parameters.Add(new Parameter(ParameterType.String, "Name", filename)); + parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); + parameters.Add(new Parameter(ParameterType.Int64, "Size", (long)size)); + parameters.Add(new Parameter(ParameterType.Int32, "Downloads", 0)); + parameters.Add(new Parameter(ParameterType.DateTime, "LastModified", DateTime.Now)); + parameters.Add(new Parameter(ParameterType.ByteArray, "Data", fileData)); + } + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + if(rows == 1) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows == 1; + } + + /// + /// Retrieves a File. + /// + /// The full name of the File. + /// A Stream object used as destination of a byte stream, + /// i.e. the method writes to the Stream the file content. + /// A value indicating whether or not to count this retrieval in the statistics. + /// true if the file is retrieved, false otherwise. + /// If os are null. + /// If is empty or does not support writing. + public bool RetrieveFile(string fullName, System.IO.Stream destinationStream, bool countHit) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + if(destinationStream == null) throw new ArgumentNullException("destinationStream"); + if(!destinationStream.CanWrite) throw new ArgumentException("Cannot write into Destination Stream", "destinationStream"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(!FileExists(transaction, fullName)) { + RollbackTransaction(transaction); + CloseConnection(connection); + throw new ArgumentException("File does not exist", "fullName"); + } + + string directory, filename; + SplitFileFullName(fullName, out directory, out filename); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("File", new string[] { "Size", "Data" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", filename)); + parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + bool done = false; + + if(reader.Read()) { + int read = ReadBinaryColumn(reader, "Data", destinationStream); + done = (long)read == (long)reader["Size"]; + } + + CloseReader(reader); + + if(!done) { + RollbackTransaction(transaction); + return false; + } + } + else { + RollbackTransaction(transaction); + return false; + } + + if(countHit) { + // Update download count + query = queryBuilder.UpdateIncrement("File", "Downloads", 1); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory"); + + parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", filename)); + parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); + + command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + if(rows != 1) { + RollbackTransaction(transaction); + return false; + } + } + + CommitTransaction(transaction); + + return true; + } + + /// + /// Sets the number of times a file was retrieved. + /// + /// The full name of the file. + /// The count to set. + /// If is null. + /// If is empty. + /// If is less than zero. + public void SetFileRetrievalCount(string fullName, int count) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + if(count < 0) throw new ArgumentOutOfRangeException("count", "Count must be greater than or equal to zero"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string directory, filename; + SplitFileFullName(fullName, out directory, out filename); + + string query = queryBuilder.Update("File", new string[] { "Downloads" }, new string[] { "Downloads" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", filename)); + parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); + parameters.Add(new Parameter(ParameterType.Int32, "Downloads", count)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + ExecuteNonQuery(command); + } + + /// + /// Gets the details of a file. + /// + /// The full name of the file. + /// The details, or null if the file does not exist. + /// If is null. + /// If is empty. + public FileDetails GetFileDetails(string fullName) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string directory, filename; + SplitFileFullName(fullName, out directory, out filename); + + string query = queryBuilder.SelectFrom("File", new string[] { "Size", "Downloads", "LastModified" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", filename)); + parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + FileDetails details = null; + + if(reader.Read()) { + details = new FileDetails((long)reader["Size"], + (DateTime)reader["LastModified"], (int)reader["Downloads"]); + } + + CloseReader(command, reader); + + return details; + } + else return null; + } + + /// + /// Deletes a File. + /// + /// The full name of the File. + /// true if the File is deleted, false otherwise. + /// If is null. + /// If is empty or it does not exist. + public bool DeleteFile(string fullName) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(!FileExists(transaction, fullName)) { + RollbackTransaction(transaction); + throw new ArgumentException("File does not exist", "fullName"); + } + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string directory, filename; + SplitFileFullName(fullName, out directory, out filename); + + string query = queryBuilder.DeleteFrom("File"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "Directory"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", filename)); + parameters.Add(new Parameter(ParameterType.String, "Directory", directory)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + if(rows == 1) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows == 1; + } + + /// + /// Renames or moves a File. + /// + /// The old full name of the File. + /// The new full name of the File. + /// true if the File is renamed, false otherwise. + /// If or are null. + /// If or are empty, or if the old file does not exist, or if the new file already exist. + public bool RenameFile(string oldFullName, string newFullName) { + if(oldFullName == null) throw new ArgumentNullException("oldFullName"); + if(oldFullName.Length == 0) throw new ArgumentException("Old Full Name cannot be empty", "oldFullName"); + if(newFullName == null) throw new ArgumentNullException("newFullName"); + if(newFullName.Length == 0) throw new ArgumentException("New Full Name cannot be empty", "newFullName"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + if(!FileExists(transaction, oldFullName)) { + RollbackTransaction(transaction); + throw new ArgumentException("File does not exist", "oldFullName"); + } + if(FileExists(transaction, newFullName)) { + RollbackTransaction(transaction); + throw new ArgumentException("File already exists", "newFullPath"); + } + + string oldDirectory, newDirectory, oldFilename, newFilename; + SplitFileFullName(oldFullName, out oldDirectory, out oldFilename); + SplitFileFullName(newFullName, out newDirectory, out newFilename); + + string query = queryBuilder.Update("File", new string[] { "Name" }, new string[] { "NewName" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "OldName"); + query = queryBuilder.AndWhere(query, "Directory", WhereOperator.Equals, "OldDirectory"); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "NewName", newFilename)); + parameters.Add(new Parameter(ParameterType.String, "OldName", oldFilename)); + parameters.Add(new Parameter(ParameterType.String, "OldDirectory", oldDirectory)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + if(rows == 1) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows == 1; + } + + /// + /// Creates a new Directory. + /// + /// The path to create the new Directory in. + /// The name of the new Directory. + /// true if the Directory is created, false otherwise. + /// If path is "/my/directory" and name is "newdir", a new directory named "/my/directory/newdir" is created. + /// If or are null. + /// If is empty or if the directory does not exist, or if the new directory already exists. + public bool CreateDirectory(string path, string name) { + if(path == null) throw new ArgumentNullException("path"); + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + path = PrepareDirectory(path); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(!DirectoryExists(transaction, path)) { + RollbackTransaction(transaction); + throw new ArgumentException("Directory does not exist", "path"); + } + + string newDirectoryFullPath = PrepareDirectory(path + name); + + if(DirectoryExists(transaction, newDirectoryFullPath)) { + RollbackTransaction(transaction); + throw new ArgumentException("Directory already exists", "name"); + } + + string query = QueryBuilder.NewQuery(builder).InsertInto("Directory", new string[] { "FullPath", "Parent" }, new string[] { "FullPath", "Parent" }); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "FullPath", newDirectoryFullPath)); + parameters.Add(new Parameter(ParameterType.String, "Parent", path)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + if(rows == 1) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows == 1; + } + + /// + /// Deletes a directory and all its contents. + /// + /// The current transaction to use. + /// The full path of the directory. + /// true if the directory is deleted, false otherwise. + private bool DeleteDirectory(DbTransaction transaction, string fullPath) { + string[] dirs = ListDirectories(transaction, fullPath); + foreach(string dir in dirs) { + if(!DeleteDirectory(transaction, dir)) { + return false; + } + } + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("Directory"); + query = queryBuilder.Where(query, "FullPath", WhereOperator.Equals, "FullPath"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "FullPath", fullPath)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + return rows > 0; + } + + /// + /// Deletes a Directory and all of its content. + /// + /// The full path of the Directory. + /// true if the Directory is deleted, false otherwise. + /// If is null. + /// If is empty or if it equals '/' or it does not exist. + public bool DeleteDirectory(string fullPath) { + if(fullPath == null) throw new ArgumentNullException("fullPath"); + if(fullPath.Length == 0) throw new ArgumentException("Full Path cannot be empty", "fullPath"); + if(fullPath == "/") throw new ArgumentException("Cannot delete the root directory", "fullPath"); + + fullPath = PrepareDirectory(fullPath); + + // /dir/ + // /dir/sub/ + // /dir/sub/blah/ + // /dir/file.txt + // /dir/sub/file.txt + // etc. + + // 1. Delete sub-directories (recursively, depth first) + // 2. Delete directory + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(!DirectoryExists(transaction, fullPath)) { + RollbackTransaction(transaction); + throw new ArgumentException("Directory does not exist"); + } + + bool done = DeleteDirectory(transaction, fullPath); + + if(done) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return done; + } + + /// + /// Renames or moves a Directory. + /// + /// The current transaction to use. + /// The old full path of the Directory. + /// The new full path of the Directory. + /// true if the Directory is renamed, false otherwise. + private bool RenameDirectory(DbTransaction transaction, string oldFullPath, string newFullPath) { + string[] directories = ListDirectories(transaction, oldFullPath); + foreach(string dir in directories) { + string trimmed = dir.Trim('/'); + string name = trimmed.Substring(trimmed.LastIndexOf("/") + 1); + + string newFullPathSub = PrepareDirectory(newFullPath + name); + + RenameDirectory(dir, newFullPathSub); + } + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.Update("Directory", new string[] { "FullPath" }, new string[] { "NewDirectory1" }); + query = queryBuilder.Where(query, "FullPath", WhereOperator.Equals, "OldDirectory1"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "NewDirectory1", newFullPath)); + parameters.Add(new Parameter(ParameterType.String, "OldDirectory1", oldFullPath)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + return rows > 0; + } + + /// + /// Renames or moves a Directory. + /// + /// The old full path of the Directory. + /// The new full path of the Directory. + /// true if the Directory is renamed, false otherwise. + /// If or are null. + /// If or are empty or equal to '/', + /// or if the old directory does not exist or the new directory already exists. + public bool RenameDirectory(string oldFullPath, string newFullPath) { + if(oldFullPath == null) throw new ArgumentNullException("oldFullPath"); + if(oldFullPath.Length == 0) throw new ArgumentException("Old Full Path cannot be empty", "oldFullPath"); + if(oldFullPath == "/") throw new ArgumentException("Cannot rename the root directory", "oldFullPath"); + if(newFullPath == null) throw new ArgumentNullException("newFullPath"); + if(newFullPath.Length == 0) throw new ArgumentException("New Full Path cannot be empty", "newFullPath"); + if(newFullPath == "/") throw new ArgumentException("Cannot rename directory to the root directory", "newFullPath"); + + oldFullPath = PrepareDirectory(oldFullPath); + newFullPath = PrepareDirectory(newFullPath); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(!DirectoryExists(transaction, oldFullPath)) { + RollbackTransaction(transaction); + throw new ArgumentException("Directory does not exist", "oldFullPath"); + } + if(DirectoryExists(transaction, newFullPath)) { + RollbackTransaction(transaction); + throw new ArgumentException("Directory already exists", "newFullPath"); + } + + // /dir/ + // /dir/sub/ + // /dir/sub/blah/ + // /dir/file.txt + // /dir/sub/file.txt + // etc. + + // 1. Rename sub-directories (recursively, depth first) + // 2. Rename directory + + bool done = RenameDirectory(transaction, oldFullPath, newFullPath); + + if(done) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return done; + } + + /// + /// The the names of the pages with attachments. + /// + /// The names of the pages with attachments. + public string[] GetPagesWithAttachments() { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Attachment", new string[] { "Page" }); + query = queryBuilder.GroupBy(query, new string[] { "Page" }); + + DbCommand command = builder.GetCommand(connString, query, new List()); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(100); + + while(reader.Read()) { + result.Add(reader["Page"] as string); + } + + CloseReader(command, reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Returns the names of the Attachments of a Page. + /// + /// A database transaction. + /// The Page Info object that owns the Attachments. + /// The names, or an empty list. + private string[] ListPageAttachments(DbTransaction transaction, PageInfo pageInfo) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Attachment", new string[] { "Name" }); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(10); + + while(reader.Read()) { + result.Add(reader["Name"] as string); + } + + CloseReader(reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Returns the names of the Attachments of a Page. + /// + /// A database connection. + /// The Page Info object that owns the Attachments. + /// The names, or an empty list. + private string[] ListPageAttachments(DbConnection connection, PageInfo pageInfo) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Attachment", new string[] { "Name" }); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(10); + + while(reader.Read()) { + result.Add(reader["Name"] as string); + } + + CloseReader(reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Returns the names of the Attachments of a Page. + /// + /// The Page Info object that owns the Attachments. + /// The names, or an empty list. + /// If is null. + public string[] ListPageAttachments(PageInfo pageInfo) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + string[] result = ListPageAttachments(connection, pageInfo); + CloseConnection(connection); + + return result; + } + + /// + /// Determines whether a page attachment exists. + /// + /// A database connection. + /// The page. + /// The attachment. + /// true if the attachment exists, false otherwise. + private bool AttachmentExists(DbConnection connection, PageInfo page, string name) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectCountFrom("Attachment"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Page", page.FullName)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + int count = ExecuteScalar(command, -1, false); + + return count == 1; + } + + /// + /// Determines whether a page attachment exists. + /// + /// A database transaction. + /// The page. + /// The attachment. + /// true if the attachment exists, false otherwise. + private bool AttachmentExists(DbTransaction transaction, PageInfo page, string name) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectCountFrom("Attachment"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Page", page.FullName)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int count = ExecuteScalar(command, -1, false); + + return count == 1; + } + + /// + /// Stores a Page Attachment. + /// + /// The Page Info that owns the Attachment. + /// The name of the Attachment, for example "myfile.jpg". + /// A Stream object used as source of a byte stream, + /// i.e. the method reads from the Stream and stores the content properly. + /// true to overwrite an existing Attachment. + /// true if the Attachment is stored, false otherwise. + /// If overwrite is false and Attachment already exists, the method returns false. + /// If , or are null. + /// If is empty or if does not support reading. + public bool StorePageAttachment(PageInfo pageInfo, string name, System.IO.Stream sourceStream, bool overwrite) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(sourceStream == null) throw new ArgumentNullException("sourceStream"); + if(!sourceStream.CanRead) throw new ArgumentException("Cannot read from Source Stream", "sourceStream"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + bool attachmentExists = AttachmentExists(transaction, pageInfo, name); + + if(attachmentExists && !overwrite) { + RollbackTransaction(transaction); + return false; + } + + // To achieve decent performance, an UPDATE query is issued if the attachment exists, + // otherwise an INSERT query is issued + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query; + List parameters; + + byte[] attachmentData = null; + int size = Tools.ReadStream(sourceStream, ref attachmentData, MaxFileSize); + if(size < 0) { + RollbackTransaction(transaction); + throw new ArgumentException("Source Stream contains too much data", "sourceStream"); + } + + if(attachmentExists) { + query = queryBuilder.Update("Attachment", new string[] { "Size", "LastModified", "Data" }, new string[] { "Size", "LastModified", "Data" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); + + parameters = new List(5); + parameters.Add(new Parameter(ParameterType.Int64, "Size", (long)size)); + parameters.Add(new Parameter(ParameterType.DateTime, "LastModified", DateTime.Now)); + parameters.Add(new Parameter(ParameterType.ByteArray, "Data", attachmentData)); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); + } + else { + query = queryBuilder.InsertInto("Attachment", new string[] { "Name", "Page", "Size", "Downloads", "LastModified", "Data" }, + new string[] { "Name", "Page", "Size", "Downloads", "LastModified", "Data" }); + + parameters = new List(6); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); + parameters.Add(new Parameter(ParameterType.Int64, "Size", (long)size)); + parameters.Add(new Parameter(ParameterType.Int32, "Downloads", 0)); + parameters.Add(new Parameter(ParameterType.DateTime, "LastModified", DateTime.Now)); + parameters.Add(new Parameter(ParameterType.ByteArray, "Data", attachmentData)); + } + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + if(rows == 1) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows == 1; + } + + /// + /// Retrieves a Page Attachment. + /// + /// The Page Info that owns the Attachment. + /// The name of the Attachment, for example "myfile.jpg". + /// A Stream object used as destination of a byte stream, + /// i.e. the method writes to the Stream the file content. + /// A value indicating whether or not to count this retrieval in the statistics. + /// true if the Attachment is retrieved, false otherwise. + /// If , or are null. + /// If is empty or if does not support writing, + /// or if the page does not have attachments or if the attachment does not exist. + public bool RetrievePageAttachment(PageInfo pageInfo, string name, System.IO.Stream destinationStream, bool countHit) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(destinationStream == null) throw new ArgumentNullException("destinationStream"); + if(!destinationStream.CanWrite) throw new ArgumentException("Cannot write into Destination Stream", "destinationStream"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(!AttachmentExists(transaction, pageInfo, name)) { + RollbackTransaction(transaction); + throw new ArgumentException("Attachment does not exist", "name"); + } + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Attachment", new string[] { "Size", "Data" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + bool done = false; + + if(reader.Read()) { + int read = ReadBinaryColumn(reader, "Data", destinationStream); + done = (long)read == (long)reader["Size"]; + } + + CloseReader(reader); + + if(!done) { + RollbackTransaction(transaction); + return false; + } + } + else { + RollbackTransaction(transaction); + return false; + } + + if(countHit) { + // Update download count + query = queryBuilder.UpdateIncrement("Attachment", "Downloads", 1); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); + + parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); + + command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + if(rows != 1) { + RollbackTransaction(transaction); + return false; + } + } + + CommitTransaction(transaction); + + return true; + } + + /// + /// Sets the number of times a page attachment was retrieved. + /// + /// The page. + /// The name of the attachment. + /// The count to set. + /// If or are null. + /// If is empty. + /// If is less than zero. + public void SetPageAttachmentRetrievalCount(PageInfo pageInfo, string name, int count) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty"); + if(count < 0) throw new ArgumentOutOfRangeException("Count must be greater than or equal to zero", "count"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.Update("Attachment", new string[] { "Downloads" }, new string[] { "Downloads" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); + parameters.Add(new Parameter(ParameterType.Int32, "Downloads", count)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + ExecuteNonQuery(command); + } + + /// + /// Gets the details of a page attachment. + /// + /// The page that owns the attachment. + /// The name of the attachment, for example "myfile.jpg". + /// The details of the attachment, or null if the attachment does not exist. + /// If or are null. + /// If is empty. + public FileDetails GetPageAttachmentDetails(PageInfo pageInfo, string name) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Attachment", new string[] { "Size", "Downloads", "LastModified" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + FileDetails details = null; + + if(reader.Read()) { + details = new FileDetails((long)reader["Size"], + (DateTime)reader["LastModified"], (int)reader["Downloads"]); + } + + CloseReader(command, reader); + + return details; + } + else return null; + } + + /// + /// Deletes a Page Attachment. + /// + /// The Page Info that owns the Attachment. + /// The name of the Attachment, for example "myfile.jpg". + /// true if the Attachment is deleted, false otherwise. + /// If or are null. + /// If is empty or if the page or attachment do not exist. + public bool DeletePageAttachment(PageInfo pageInfo, string name) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(!AttachmentExists(transaction, pageInfo, name)) { + RollbackTransaction(transaction); + throw new ArgumentException("Attachment does not exist", "name"); + } + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("Attachment"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + if(rows == 1) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows == 1; + } + + /// + /// Renames a Page Attachment. + /// + /// The Page Info that owns the Attachment. + /// The old name of the Attachment. + /// The new name of the Attachment. + /// true if the Attachment is renamed, false otherwise. + /// If , or are null. + /// If , or are empty, + /// or if the page or old attachment do not exist, or the new attachment name already exists. + public bool RenamePageAttachment(PageInfo pageInfo, string oldName, string newName) { + if(pageInfo == null) throw new ArgumentNullException("pageInfo"); + if(oldName == null) throw new ArgumentNullException("oldName"); + if(oldName.Length == 0) throw new ArgumentException("Old Name cannot be empty", "oldName"); + if(newName == null) throw new ArgumentNullException("newName"); + if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(!AttachmentExists(transaction, pageInfo, oldName)) { + RollbackTransaction(transaction); + throw new ArgumentException("Attachment does not exist", "name"); + } + if(AttachmentExists(transaction, pageInfo, newName)) { + RollbackTransaction(transaction); + throw new ArgumentException("Attachment already exists", "name"); + } + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.Update("Attachment", new string[] { "Name" }, new string[] { "NewName" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "OldName"); + query = queryBuilder.AndWhere(query, "Page", WhereOperator.Equals, "Page"); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "NewName", newName)); + parameters.Add(new Parameter(ParameterType.String, "OldName", oldName)); + parameters.Add(new Parameter(ParameterType.String, "Page", pageInfo.FullName)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + if(rows == 1) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows == 1; + } + + /// + /// Notifies the Provider that a Page has been renamed. + /// + /// The old Page Info object. + /// The new Page Info object. + /// If or are null. + /// If the new page is already in use. + public void NotifyPageRenaming(PageInfo oldPage, PageInfo newPage) { + if(oldPage == null) throw new ArgumentNullException("oldPage"); + if(newPage == null) throw new ArgumentNullException("newPage"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(ListPageAttachments(transaction, newPage).Length > 0) { + RollbackTransaction(transaction); + throw new ArgumentException("New Page already exists", "newPage"); + } + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.Update("Attachment", new string[] { "Page" }, new string[] { "NewPage" }); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "OldPage"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "NewPage", newPage.FullName)); + parameters.Add(new Parameter(ParameterType.String, "OldPage", oldPage.FullName)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows != -1) CommitTransaction(transaction); + else RollbackTransaction(transaction); + } + + #endregion + + #region IStorageProvider Members + + /// + /// Gets a value specifying whether the provider is read-only, i.e. it can only provide data and not store it. + /// + public bool ReadOnly { + get { return false; } + } + + #endregion + + } + +} diff --git a/SqlProvidersCommon/SqlIndex.cs b/SqlProvidersCommon/SqlIndex.cs new file mode 100644 index 0000000..8b42b93 --- /dev/null +++ b/SqlProvidersCommon/SqlIndex.cs @@ -0,0 +1,211 @@ + +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Text; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon { + + /// + /// Implements a SQL-based search engine index. + /// + public class SqlIndex : IIndex { + + private IIndexConnector connector; + + /// + /// The stop words to be used while indexing new content. + /// + protected string[] stopWords = null; + + /// + /// Initializes a new instance of the class. + /// + /// The connection object. + public SqlIndex(IIndexConnector connector) { + if(connector == null) throw new ArgumentNullException("connector"); + + this.connector = connector; + this.stopWords = new string[0]; + } + + /// + /// Gets or sets the stop words to be used while indexing new content. + /// + /// + public string[] StopWords { + get { + lock(this) { + return stopWords; + } + } + set { + if(value == null) throw new ArgumentNullException("value", "Stop words cannot be null"); + lock(this) { + stopWords = value; + } + } + } + + /// + /// Gets the total count of unique words. + /// + /// Computing the result is O(1). + public int TotalWords { + get { + return connector.GetCount(IndexElementType.Words); + } + } + + /// + /// Gets the total count of documents. + /// + /// Computing the result is O(n*m), where n is the number of + /// words in the index and m is the number of documents. + public int TotalDocuments { + get { + return connector.GetCount(IndexElementType.Documents); + } + } + + /// + /// Gets the total number of occurrences (count of words in each document). + /// + /// Computing the result is O(n), + /// where n is the number of words in the index. + public int TotalOccurrences { + get { + return connector.GetCount(IndexElementType.Occurrences); + } + } + + /// + /// Completely clears the index (stop words are not affected). + /// + /// A state object that is passed to the IndexStorer SaveDate/DeleteData function. + public void Clear(object state) { + connector.ClearIndex(state); + } + + /// + /// Stores a document in the index. + /// + /// The document. + /// The document keywords, if any, an empty array or null otherwise. + /// The content of the document. + /// A state object that is passed to the IndexStorer SaveDate/DeleteData function. + /// The number of indexed words (including duplicates). + /// Indexing the content of the document is O(n), + /// where n is the total number of words in the document. + public int StoreDocument(IDocument document, string[] keywords, string content, object state) { + if(document == null) throw new ArgumentNullException("document"); + if(keywords == null) keywords = new string[0]; + if(content == null) throw new ArgumentNullException("content"); + + RemoveDocument(document, state); + + keywords = ScrewTurn.Wiki.SearchEngine.Tools.CleanupKeywords(keywords); + + // Prepare content words + WordInfo[] contentWords = document.Tokenize(content); + contentWords = ScrewTurn.Wiki.SearchEngine.Tools.RemoveStopWords(contentWords, stopWords); + + // Prepare title words + WordInfo[] titleWords = document.Tokenize(document.Title); + titleWords = ScrewTurn.Wiki.SearchEngine.Tools.RemoveStopWords(titleWords, stopWords); + for(int i = 0; i < titleWords.Length; i++) { + titleWords[i] = new WordInfo(titleWords[i].Text, titleWords[i].FirstCharIndex, titleWords[i].WordIndex, WordLocation.Title); + } + + // Prepare keywords + WordInfo[] words = new WordInfo[keywords.Length]; + int count = 0; + for(int i = 0; i < words.Length; i++) { + words[i] = new WordInfo(keywords[i], (ushort)count, (ushort)i, WordLocation.Keywords); + count += 1 + keywords[i].Length; + } + + return connector.SaveDataForDocument(document, contentWords, titleWords, words, state); + } + + /// + /// Removes a document from the index. + /// + /// The document to remove. + /// A state object that is passed to the IndexStorer SaveDate/DeleteData function. + public void RemoveDocument(IDocument document, object state) { + if(document == null) throw new ArgumentNullException("document"); + + connector.DeleteDataForDocument(document, state); + } + + /// + /// Performs a search in the index. + /// + /// The search parameters. + /// The results. + public SearchResultCollection Search(SearchParameters parameters) { + if(parameters == null) throw new ArgumentNullException("parameters"); + + using(IWordFetcher fetcher = connector.GetWordFetcher()) { + if(parameters.DocumentTypeTags == null) { + return ScrewTurn.Wiki.SearchEngine.Tools.SearchInternal(parameters.Query, null, false, parameters.Options, fetcher); + } + else { + return ScrewTurn.Wiki.SearchEngine.Tools.SearchInternal(parameters.Query, parameters.DocumentTypeTags, true, parameters.Options, fetcher); + } + } + } + + } + + /// + /// Implements a word fetcher for a SQL-based index. + /// + public class SqlWordFetcher : IWordFetcher { + + private DbConnection connection; + private TryFindWord implementation; + + /// + /// Initializes a new instance of the class. + /// + /// An open database connection. + /// The method implementation. + public SqlWordFetcher(DbConnection connection, TryFindWord implementation) { + if(connection == null) throw new ArgumentNullException("connection"); + if(implementation == null) throw new ArgumentNullException("implementation"); + + this.connection = connection; + this.implementation = implementation; + } + + /// + /// Tries to get a word. + /// + /// The text of the word. + /// The found word, if any, null otherwise. + /// true if the word is found, false otherwise. + public bool TryGetWord(string text, out Word word) { + return implementation(text, out word, connection); + } + + #region IDisposable Members + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() { + try { + connection.Close(); + } + catch { } + } + + #endregion + + } + +} diff --git a/SqlProvidersCommon/SqlPagesStorageProviderBase.cs b/SqlProvidersCommon/SqlPagesStorageProviderBase.cs new file mode 100644 index 0000000..c88f087 --- /dev/null +++ b/SqlProvidersCommon/SqlPagesStorageProviderBase.cs @@ -0,0 +1,4403 @@ + +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon { + + /// + /// Implements a base class for a SQL pages storage provider. + /// + public abstract class SqlPagesStorageProviderBase : SqlStorageProviderBase, IPagesStorageProviderV30 { + + private const int MaxStatementsInBatch = 20; + + private const int FirstRevision = 0; + private const int CurrentRevision = -1; + private const int DraftRevision = -100; + + private IIndex index; + + private bool alwaysGenerateDocument = false; + + /// + /// Initializes the Storage Provider. + /// + /// The Host of the Component. + /// The Configuration data, if any. + /// If the configuration string is not valid, the methoud should throw a . + public new void Init(IHostV30 host, string config) { + base.Init(host, config); + + index = new SqlIndex(new IndexConnector(GetWordFetcher, GetSize, GetCount, ClearIndex, DeleteDataForDocument, SaveDataForDocument, TryFindWord)); + } + + #region Index and Search Engine + + /// + /// Sets test flags (to be used only for tests). + /// + /// A value indicating whether to always generate a result when resolving a document, + /// even when the page does not exist. + public void SetFlags(bool alwaysGenerateDocument) { + this.alwaysGenerateDocument = alwaysGenerateDocument; + } + + /// + /// Gets a word fetcher. + /// + /// The word fetcher. + private IWordFetcher GetWordFetcher() { + return new SqlWordFetcher(GetCommandBuilder().GetConnection(connString), TryFindWord); + } + + /// + /// Gets the search index (only used for testing purposes). + /// + public IIndex Index { + get { return index; } + } + + /// + /// Performs a search in the index. + /// + /// The search parameters. + /// The results. + /// If is null. + public SearchResultCollection PerformSearch(SearchParameters parameters) { + if(parameters == null) throw new ArgumentNullException("parameters"); + + return index.Search(parameters); + } + + /// + /// Rebuilds the search index. + /// + public void RebuildIndex() { + index.Clear(null); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + List allNamespaces = new List(GetNamespaces()); + allNamespaces.Add(null); + + int indexedElements = 0; + + foreach(NamespaceInfo nspace in allNamespaces) { + foreach(PageInfo page in GetPages(transaction, nspace)) { + IndexPage(GetContent(page), transaction); + indexedElements++; + + foreach(Message msg in GetMessages(transaction, page)) { + IndexMessageTree(page, msg, transaction); + indexedElements++; + } + + // Every 10 indexed documents, commit the transaction to + // reduce the number of database locks + if(indexedElements >= 10) { + CommitTransaction(transaction); + indexedElements = 0; + + connection = builder.GetConnection(connString); + transaction = BeginTransaction(connection); + } + } + } + + CommitTransaction(transaction); + } + + /// + /// Gets a value indicating whether the search engine index is corrupted and needs to be rebuilt. + /// + public bool IsIndexCorrupted { + get { return false; } + } + + /// + /// Handles the construction of an for the search engine. + /// + /// The input dumped document. + /// The resulting . + private IDocument BuildDocument(DumpedDocument dumpedDocument) { + if(alwaysGenerateDocument) { + return new DummyDocument() { + ID = dumpedDocument.ID, + Name = dumpedDocument.Name, + Title = dumpedDocument.Title, + TypeTag = dumpedDocument.TypeTag, + DateTime = dumpedDocument.DateTime + }; + } + + if(dumpedDocument.TypeTag == PageDocument.StandardTypeTag) { + string pageName = PageDocument.GetPageName(dumpedDocument.Name); + + PageInfo page = GetPage(pageName); + + if(page == null) return null; + else return new PageDocument(page, dumpedDocument, TokenizeContent); + } + else if(dumpedDocument.TypeTag == MessageDocument.StandardTypeTag) { + string pageFullName; + int id; + MessageDocument.GetMessageDetails(dumpedDocument.Name, out pageFullName, out id); + + PageInfo page = GetPage(pageFullName); + if(page == null) return null; + else return new MessageDocument(page, id, dumpedDocument, TokenizeContent); + } + else return null; + } + + // Extremely dirty way for testing the search engine in combination with alwaysGenerateDocument + private class DummyDocument : IDocument { + + public uint ID { get; set; } + + public string Name { get; set; } + + public string Title { get; set; } + + public string TypeTag { get; set; } + + public DateTime DateTime { get; set; } + + public WordInfo[] Tokenize(string content) { + return ScrewTurn.Wiki.SearchEngine.Tools.Tokenize(content); + } + + } + + /// + /// Gets some statistics about the search engine index. + /// + /// The total number of documents. + /// The total number of unique words. + /// The total number of word-document occurrences. + /// The approximated size, in bytes, of the search engine index. + public void GetIndexStats(out int documentCount, out int wordCount, out int occurrenceCount, out long size) { + documentCount = index.TotalDocuments; + wordCount = index.TotalWords; + occurrenceCount = index.TotalOccurrences; + size = GetSize(); + } + + /// + /// Gets the approximate size, in bytes, of the search engine index. + /// + private long GetSize() { + // 1. Size of documents: 8 + 2*20 + 2*30 + 2*1 + 8 = 118 bytes + // 2. Size of words: 8 + 2*8 = 24 bytes + // 3. Size of mappings: 8 + 8 + 2 + 2 + 1 = 21 bytes + // 4. Size = Size * 2 + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + long size = 0; + + string query = queryBuilder.SelectCountFrom("IndexDocument"); + DbCommand command = builder.GetCommand(connection, query, new List()); + int rows = ExecuteScalar(command, -1, false); + + if(rows == -1) return 0; + size += rows * 118; + + query = queryBuilder.SelectCountFrom("IndexWord"); + command = builder.GetCommand(connection, query, new List()); + rows = ExecuteScalar(command, -1, false); + + if(rows == -1) return 0; + size += rows * 24; + + query = queryBuilder.SelectCountFrom("IndexWordMapping"); + command = builder.GetCommand(connection, query, new List()); + rows = ExecuteScalar(command, -1, false); + + if(rows == -1) return 0; + size += rows * 21; + + CloseConnection(connection); + + return size * 2; + } + + /// + /// Gets the number of elements in the index. + /// + /// The type of elements. + /// The number of elements. + private int GetCount(IndexElementType element) { + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + int count = 0; + + string elemName = ""; + if(element == IndexElementType.Documents) elemName = "IndexDocument"; + else if(element == IndexElementType.Words) elemName = "IndexWord"; + else if(element == IndexElementType.Occurrences) elemName = "IndexWordMapping"; + else throw new NotSupportedException("Unsupported element type"); + + string query = queryBuilder.SelectCountFrom(elemName); + + DbCommand command = builder.GetCommand(connection, query, new List()); + count = ExecuteScalar(command, -1, true); + + return count; + } + + /// + /// Deletes all data associated to a document. + /// + /// The document. + /// A state object passed from the index (can be null or a ). + private void DeleteDataForDocument(IDocument document, object state) { + // 1. Delete all data related to a document + // 2. Delete all words that have no more mappings + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("IndexDocument"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "DocName"); + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "DocName", document.Name)); + + string subQuery = queryBuilder.SelectFrom("IndexWordMapping", new string[] { "Word" }); + subQuery = queryBuilder.GroupBy(subQuery, new string[] { "Word" }); + string query2 = queryBuilder.DeleteFrom("IndexWord"); + query2 = queryBuilder.WhereNotInSubquery(query2, "IndexWord", "Id", subQuery); + + query = queryBuilder.AppendForBatch(query, query2); + + DbCommand command = null; + if(state != null) command = builder.GetCommand((DbTransaction)state, query, parameters); + else command = builder.GetCommand(connString, query, parameters); + + // Close only if state is null + ExecuteNonQuery(command, state == null); + } + + /// + /// Saves data for a new document. + /// + /// The document. + /// The content words. + /// The title words. + /// The keywords. + /// A state object passed from the index (can be null or a ). + /// The number of stored occurrences. + private int SaveDataForDocument(IDocument document, WordInfo[] content, WordInfo[] title, WordInfo[] keywords, object state) { + // 1. Insert document + // 2. Insert all new words + // 3. Load all word IDs + // 4. Insert mappings + + // On error, return without rolling back if state != null, rollback otherwise + // On completion, commit if state == null + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + DbTransaction transaction = null; + if(state != null) transaction = (DbTransaction)state; + else { + DbConnection connection = builder.GetConnection(connString); + transaction = BeginTransaction(connection); + } + + uint freeDocumentId = GetFreeElementId(IndexElementType.Documents, transaction); + uint freeWordId = GetFreeElementId(IndexElementType.Words, transaction); + + // Insert the document + string query = queryBuilder.InsertInto("IndexDocument", + new string[] { "Id", "Name", "Title", "TypeTag", "DateTime" }, + new string[] { "Id", "Name", "Title", "TypeTag", "DateTime" }); + + List parameters = new List(5); + parameters.Add(new Parameter(ParameterType.Int32, "Id", (int)freeDocumentId)); + parameters.Add(new Parameter(ParameterType.String, "Name", document.Name)); + parameters.Add(new Parameter(ParameterType.String, "Title", document.Title)); + parameters.Add(new Parameter(ParameterType.String, "TypeTag", document.TypeTag)); + parameters.Add(new Parameter(ParameterType.DateTime, "DateTime", document.DateTime)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + if(ExecuteNonQuery(command, false) != 1) { + if(state == null) RollbackTransaction(transaction); + return -1; + } + document.ID = freeDocumentId; + + List allWords = new List(content.Length + title.Length + keywords.Length); + allWords.AddRange(content); + allWords.AddRange(title); + allWords.AddRange(keywords); + + List existingWords = new List(allWords.Count / 2); + + Dictionary wordIds = new Dictionary(1024); + + // Try to blindly insert all words (assumed to be lowercase and clean from diacritics) + + query = queryBuilder.InsertInto("IndexWord", new string[] { "Id", "Text" }, new string[] { "Id", "Text" }); + + parameters = new List(2); + parameters.Add(new Parameter(ParameterType.Int32, "Id", 0)); + parameters.Add(new Parameter(ParameterType.String, "Text", "")); + + foreach(WordInfo word in allWords) { + parameters[0].Value = (int)freeWordId; + parameters[1].Value = word.Text; + + command = builder.GetCommand(transaction, query, parameters); + + if(ExecuteNonQuery(command, false, false) == 1) { + wordIds.Add(word.Text, freeWordId); + freeWordId++; + } + else { + existingWords.Add(word); + } + } + + // Load IDs of all existing words + query = queryBuilder.SelectFrom("IndexWord", new string[] { "Id" }); + query = queryBuilder.Where(query, "Text", WhereOperator.Equals, "Text"); + + parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Text", "")); + + foreach(WordInfo word in existingWords) { + parameters[0].Value = word.Text; + + command = builder.GetCommand(transaction, query, parameters); + + int id = ExecuteScalar(command, -1, false); + if(id == -1) { + if(state == null) RollbackTransaction(transaction); + return -1; + } + + if(!wordIds.ContainsKey(word.Text)) { + wordIds.Add(word.Text, (uint)id); + } + else if(wordIds[word.Text] != (uint)id) throw new InvalidOperationException("Word ID mismatch"); + } + + // Insert all mappings + query = queryBuilder.InsertInto("IndexWordMapping", + new string[] { "Word", "Document", "FirstCharIndex", "WordIndex", "Location" }, + new string[] { "Word", "Document", "FirstCharIndex", "WordIndex", "Location" }); + + parameters = new List(5); + parameters.Add(new Parameter(ParameterType.Int32, "Word", 0)); + parameters.Add(new Parameter(ParameterType.Int32, "Document", (int)freeDocumentId)); + parameters.Add(new Parameter(ParameterType.Int16, "FirstCharIndex", 0)); + parameters.Add(new Parameter(ParameterType.Int16, "WordIndex", 0)); + parameters.Add(new Parameter(ParameterType.Byte, "Location", 0)); + + foreach(WordInfo word in allWords) { + parameters[0].Value = (int)wordIds[word.Text]; + parameters[1].Value = (int)freeDocumentId; + parameters[2].Value = (short)word.FirstCharIndex; + parameters[3].Value = (short)word.WordIndex; + parameters[4].Value = word.Location.Location; + + command = builder.GetCommand(transaction, query, parameters); + + if(ExecuteNonQuery(command, false) != 1) { + if(state == null) RollbackTransaction(transaction); + return -1; + } + } + + if(state == null) CommitTransaction(transaction); + + return allWords.Count; + } + + /// + /// Gets a free element ID from the database. + /// + /// The element type. + /// The current database transaction. + /// The free element ID. + private uint GetFreeElementId(IndexElementType element, DbTransaction transaction) { + if(element == IndexElementType.Occurrences) throw new ArgumentException("Element cannot be Occurrences", "element"); + + string table = element == IndexElementType.Documents ? "IndexDocument" : "IndexWord"; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom(table, new string[] { "Id" }); + query = queryBuilder.OrderBy(query, new string[] { "Id" }, new Ordering[] { Ordering.Desc }); + + DbCommand command = builder.GetCommand(transaction, query, new List()); + + int id = ExecuteScalar(command, -1, false); + + if(id == -1) return 0; + else return (uint)id + 1; + } + + /// + /// Tries to load all data related to a word from the database. + /// + /// The word text. + /// The returned word. + /// An open database connection. + /// true if the word is found, false otherwise. + private bool TryFindWord(string text, out Word word, DbConnection connection) { + // 1. Find word - if not found, return + // 2. Read all raw word mappings + // 3. Read all documents (unique) + // 4. Build result data structure + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("IndexWord", new string[] { "Id" }); + query = queryBuilder.Where(query, "Text", WhereOperator.Equals, "Text"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Text", text)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + int wordId = ExecuteScalar(command, -1, false); + + if(wordId == -1) { + word = null; + return false; + } + + // Read all raw mappings + query = queryBuilder.SelectFrom("IndexWordMapping"); + query = queryBuilder.Where(query, "Word", WhereOperator.Equals, "WordId"); + + parameters = new List(1); + parameters.Add(new Parameter(ParameterType.Int32, "WordId", wordId)); + + command = builder.GetCommand(connection, query, parameters); + + DbDataReader reader = ExecuteReader(command, false); + + List mappings = new List(2048); + while(reader != null && reader.Read()) { + mappings.Add(new DumpedWordMapping((uint)wordId, + (uint)(int)reader["Document"], + (ushort)(short)reader["FirstCharIndex"], (ushort)(short)reader["WordIndex"], + (byte)reader["Location"])); + } + CloseReader(reader); + + if(mappings.Count == 0) { + word = null; + return false; + } + + // Find all documents + query = queryBuilder.SelectFrom("IndexDocument"); + query = queryBuilder.Where(query, "Id", WhereOperator.Equals, "DocId"); + + parameters = new List(1); + parameters.Add(new Parameter(ParameterType.Int32, "DocId", 0)); + + Dictionary documents = new Dictionary(64); + foreach(DumpedWordMapping map in mappings) { + uint docId = map.DocumentID; + if(documents.ContainsKey(docId)) continue; + + parameters[0].Value = (int)docId; + command = builder.GetCommand(connection, query, parameters); + + reader = ExecuteReader(command, false); + + if(reader != null && reader.Read()) { + DumpedDocument dumpedDoc = new DumpedDocument(docId, + reader["Name"] as string, reader["Title"] as string, + reader["TypeTag"] as string, + (DateTime)reader["DateTime"]); + + IDocument document = BuildDocument(dumpedDoc); + + if(document != null) documents.Add(docId, document); + } + CloseReader(reader); + } + + OccurrenceDictionary occurrences = new OccurrenceDictionary(mappings.Count); + foreach(DumpedWordMapping map in mappings) { + if(!occurrences.ContainsKey(documents[map.DocumentID])) { + occurrences.Add(documents[map.DocumentID], new SortedBasicWordInfoSet(2)); + } + + occurrences[documents[map.DocumentID]].Add(new BasicWordInfo( + map.FirstCharIndex, map.WordIndex, WordLocation.GetInstance(map.Location))); + } + + word = new Word((uint)wordId, text, occurrences); + return true; + } + + /// + /// Clears the index. + /// + /// A state object passed from the index. + private void ClearIndex(object state) { + // state can be null, depending on when the method is called + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("IndexWordMapping"); + query = queryBuilder.AppendForBatch(query, queryBuilder.DeleteFrom("IndexWord")); + query = queryBuilder.AppendForBatch(query, queryBuilder.DeleteFrom("IndexDocument")); + + DbCommand command = null; + if(state == null) command = builder.GetCommand(connString, query, new List()); + else command = builder.GetCommand((DbTransaction)state, query, new List()); + + ExecuteNonQuery(command, state == null); + } + + /// + /// Tokenizes page content. + /// + /// The content to tokenize. + /// The tokenized words. + private static WordInfo[] TokenizeContent(string content) { + WordInfo[] words = SearchEngine.Tools.Tokenize(content); + return words; + } + + /// + /// Indexes a page. + /// + /// The page content. + /// The current transaction. + /// The number of indexed words, including duplicates. + private int IndexPage(PageContent content, DbTransaction transaction) { + string documentName = PageDocument.GetDocumentName(content.PageInfo); + + DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(content.PageInfo, content.Title), + PageDocument.StandardTypeTag, content.LastModified); + + // Store the document + // The content should always be prepared using IHost.PrepareForSearchEngineIndexing() + int count = index.StoreDocument(new PageDocument(content.PageInfo, ddoc, TokenizeContent), + content.Keywords, host.PrepareContentForIndexing(content.PageInfo, content.Content), transaction); + + if(count == 0 && content.Content.Length > 0) { + host.LogEntry("Indexed 0 words for page " + content.PageInfo.FullName + ": possible index corruption. Please report this error to the developers", + LogEntryType.Warning, null, this); + } + + return count; + } + + /// + /// Removes a page from the search engine index. + /// + /// The content of the page to remove. + /// The current transaction. + private void UnindexPage(PageContent content, DbTransaction transaction) { + string documentName = PageDocument.GetDocumentName(content.PageInfo); + + DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(content.PageInfo, content.Title), + PageDocument.StandardTypeTag, content.LastModified); + index.RemoveDocument(new PageDocument(content.PageInfo, ddoc, TokenizeContent), transaction); + } + + /// + /// Indexes a message tree. + /// + /// The page. + /// The root message. + /// The current transaction. + private void IndexMessageTree(PageInfo page, Message root, DbTransaction transaction) { + IndexMessage(page, root.ID, root.Subject, root.DateTime, root.Body, transaction); + foreach(Message reply in root.Replies) { + IndexMessageTree(page, reply, transaction); + } + } + + /// + /// Indexes a message. + /// + /// The page. + /// The message ID. + /// The subject. + /// The date/time. + /// The body. + /// The current transaction. + /// The number of indexed words, including duplicates. + private int IndexMessage(PageInfo page, int id, string subject, DateTime dateTime, string body, DbTransaction transaction) { + // Trim "RE:" to avoid polluting the search engine index + if(subject.ToLowerInvariant().StartsWith("re:") && subject.Length > 3) subject = subject.Substring(3).Trim(); + + string documentName = MessageDocument.GetDocumentName(page, id); + + DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(null, subject), + MessageDocument.StandardTypeTag, dateTime); + + // Store the document + // The content should always be prepared using IHost.PrepareForSearchEngineIndexing() + int count = index.StoreDocument(new MessageDocument(page, id, ddoc, TokenizeContent), null, + host.PrepareContentForIndexing(null, body), transaction); + + if(count == 0 && body.Length > 0) { + host.LogEntry("Indexed 0 words for message " + page.FullName + ":" + id.ToString() + ": possible index corruption. Please report this error to the developers", + LogEntryType.Warning, null, this); + } + + return count; + } + + /// + /// Removes a message tree from the search engine index. + /// + /// The page. + /// The tree root. + /// The current transaction. + private void UnindexMessageTree(PageInfo page, Message root, DbTransaction transaction) { + UnindexMessage(page, root.ID, root.Subject, root.DateTime, root.Body, transaction); + foreach(Message reply in root.Replies) { + UnindexMessageTree(page, reply, transaction); + } + } + + /// + /// Removes a message from the search engine index. + /// + /// The page. + /// The message ID. + /// The subject. + /// The date/time. + /// The body. + /// The current transaction. + /// The number of indexed words, including duplicates. + private void UnindexMessage(PageInfo page, int id, string subject, DateTime dateTime, string body, DbTransaction transaction) { + // Trim "RE:" to avoid polluting the search engine index + if(subject.ToLowerInvariant().StartsWith("re:") && subject.Length > 3) subject = subject.Substring(3).Trim(); + + string documentName = MessageDocument.GetDocumentName(page, id); + + DumpedDocument ddoc = new DumpedDocument(0, documentName, host.PrepareTitleForIndexing(null, subject), + MessageDocument.StandardTypeTag, DateTime.Now); + index.RemoveDocument(new MessageDocument(page, id, ddoc, TokenizeContent), transaction); + } + + #endregion + + #region IPagesStorageProvider Members + + /// + /// Gets a namespace. + /// + /// A database transaction. + /// The name of the namespace (cannot be null or empty). + /// The , or null if no namespace is found. + private NamespaceInfo GetNamespace(DbTransaction transaction, string name) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + // select ... from Namespace left join Page on Namespace.DefaultPage = Page.Name where Namespace.Name = and (Namespace.DefaultPage is null or Page.Namespace = ) + string query = queryBuilder.SelectFrom("Namespace", "Page", "DefaultPage", "Name", Join.LeftJoin, new string[] { "Name", "DefaultPage" }, new string[] { "CreationDateTime" }); + query = queryBuilder.Where(query, "Namespace", "Name", WhereOperator.Equals, "Name1"); + query = queryBuilder.AndWhere(query, "Namespace", "DefaultPage", WhereOperator.IsNull, null, true, false); + query = queryBuilder.OrWhere(query, "Page", "Namespace", WhereOperator.Equals, "Name2", false, true); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name1", name)); + parameters.Add(new Parameter(ParameterType.String, "Name2", name)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + NamespaceInfo result = null; + + if(reader.Read()) { + string realName = reader["Namespace_Name"] as string; + string page = GetNullableColumn(reader, "Namespace_DefaultPage", null); + PageInfo defaultPage = string.IsNullOrEmpty(page) ? null : + new PageInfo(NameTools.GetFullName(realName, page), this, (DateTime)reader["Page_CreationDateTime"]); + + result = new NamespaceInfo(realName, this, defaultPage); + } + + CloseReader(reader); + + return result; + } + else return null; + } + + /// + /// Gets a namespace. + /// + /// A database connection. + /// The name of the namespace (cannot be null or empty). + /// The , or null if no namespace is found. + private NamespaceInfo GetNamespace(DbConnection connection, string name) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + // select ... from Namespace left join Page on Namespace.DefaultPage = Page.Name where Namespace.Name = and (Namespace.DefaultPage is null or Page.Namespace = ) + string query = queryBuilder.SelectFrom("Namespace", "Page", "DefaultPage", "Name", Join.LeftJoin, new string[] { "Name", "DefaultPage" }, new string[] { "CreationDateTime" }); + query = queryBuilder.Where(query, "Namespace", "Name", WhereOperator.Equals, "Name1"); + query = queryBuilder.AndWhere(query, "Namespace", "DefaultPage", WhereOperator.IsNull, null, true, false); + query = queryBuilder.OrWhere(query, "Page", "Namespace", WhereOperator.Equals, "Name2", false, true); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name1", name)); + parameters.Add(new Parameter(ParameterType.String, "Name2", name)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + NamespaceInfo result = null; + + if(reader.Read()) { + string realName = reader["Namespace_Name"] as string; + string page = GetNullableColumn(reader, "Namespace_DefaultPage", null); + PageInfo defaultPage = string.IsNullOrEmpty(page) ? null : + new PageInfo(NameTools.GetFullName(realName, page), this, (DateTime)reader["Page_CreationDateTime"]); + + result = new NamespaceInfo(realName, this, defaultPage); + } + + CloseReader(reader); + + return result; + } + else return null; + } + + /// + /// Gets a namespace. + /// + /// The name of the namespace (cannot be null or empty). + /// The , or null if no namespace is found. + /// If is null. + /// If is empty. + public NamespaceInfo GetNamespace(string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + NamespaceInfo nspace = GetNamespace(connection, name); + CloseConnection(connection); + + return nspace; + } + + /// + /// Gets all the sub-namespaces. + /// + /// The sub-namespaces, sorted by name. + public NamespaceInfo[] GetNamespaces() { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + // select ... from Namespace left join Page on Namespace.DefaultPage = Page.Name where Namespace.Name <> '' and (Namespace.DefaultPage is null or Page.Namespace <> '') + string query = queryBuilder.SelectFrom("Namespace", "Page", "DefaultPage", "Name", Join.LeftJoin, new string[] { "Name", "DefaultPage" }, new string[] { "CreationDateTime" }); + query = queryBuilder.Where(query, "Namespace", "Name", WhereOperator.NotEquals, "Empty1"); + query = queryBuilder.AndWhere(query, "Namespace", "DefaultPage", WhereOperator.IsNull, null, true, false); + query = queryBuilder.OrWhere(query, "Page", "Namespace", WhereOperator.NotEquals, "Empty2", false, true); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Empty1", "")); + parameters.Add(new Parameter(ParameterType.String, "Empty2", "")); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(10); + + while(reader.Read()) { + string realName = reader["Namespace_Name"] as string; + string page = GetNullableColumn(reader, "Namespace_DefaultPage", null); + PageInfo defaultPage = string.IsNullOrEmpty(page) ? null : + new PageInfo(NameTools.GetFullName(realName, page), this, (DateTime)reader["Page_CreationDateTime"]); + + // The query returns duplicate entries if the main page of two or more namespaces have the same name + if(result.Find(n => { return n.Name.Equals(realName); }) == null) { + result.Add(new NamespaceInfo(realName, this, defaultPage)); + } + } + + CloseReader(command, reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Adds a new namespace. + /// + /// The name of the namespace. + /// The correct object. + /// If is null. + /// If is empty. + public NamespaceInfo AddNamespace(string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.InsertInto("Namespace", new string[] { "Name" }, new string[] { "Name" }); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + int rows = ExecuteNonQuery(command); + + if(rows == 1) return new NamespaceInfo(name, this, null); + else return null; + } + + /// + /// Renames a namespace. + /// + /// The namespace to rename. + /// The new name of the namespace. + /// The correct object. + /// If or are null. + /// If is empty. + public NamespaceInfo RenameNamespace(NamespaceInfo nspace, string newName) { + if(nspace == null) throw new ArgumentNullException("nspace"); + if(newName == null) throw new ArgumentNullException("newName"); + if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(GetNamespace(transaction, nspace.Name) == null) { + RollbackTransaction(transaction); + return null; + } + + foreach(PageInfo page in GetPages(transaction, nspace)) { + PageContent content = GetContent(transaction, page, CurrentRevision); + if(content != null) { + UnindexPage(content, transaction); + } + Message[] messages = GetMessages(transaction, page); + if(messages != null) { + foreach(Message msg in messages) { + UnindexMessageTree(page, msg, transaction); + } + } + } + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.Update("Namespace", new string[] { "Name" }, new string[] { "NewName" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "OldName"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "NewName", newName)); + parameters.Add(new Parameter(ParameterType.String, "OldName", nspace.Name)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows > 0) { + NamespaceInfo result = GetNamespace(transaction, newName); + + foreach(PageInfo page in GetPages(transaction, result)) { + PageContent content = GetContent(transaction, page, CurrentRevision); + if(content != null) { + IndexPage(content, transaction); + } + Message[] messages = GetMessages(transaction, page); + if(messages != null) { + foreach(Message msg in messages) { + IndexMessageTree(page, msg, transaction); + } + } + } + + CommitTransaction(transaction); + + return result; + } + else { + RollbackTransaction(transaction); + return null; + } + } + + /// + /// Sets the default page of a namespace. + /// + /// The namespace of which to set the default page. + /// The page to use as default page, or null. + /// The correct object. + /// If is null. + public NamespaceInfo SetNamespaceDefaultPage(NamespaceInfo nspace, PageInfo page) { + if(nspace == null) throw new ArgumentNullException("nspace"); + + // Namespace existence is verified by the affected rows (should be 1) + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(page != null && GetPage(transaction, page.FullName) == null) { + RollbackTransaction(transaction); + return null; + } + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.Update("Namespace", new string[] { "DefaultPage" }, new string[] { "DefaultPage" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(2); + if(page == null) parameters.Add(new Parameter(ParameterType.String, "DefaultPage", DBNull.Value)); + else parameters.Add(new Parameter(ParameterType.String, "DefaultPage", NameTools.GetLocalName(page.FullName))); + parameters.Add(new Parameter(ParameterType.String, "Name", nspace.Name)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == 1) { + CommitTransaction(transaction); + return new NamespaceInfo(nspace.Name, this, page); + } + else { + RollbackTransaction(transaction); + return null; + } + } + + /// + /// Removes a namespace. + /// + /// The namespace to remove. + /// true if the namespace is removed, false otherwise. + /// If is null. + public bool RemoveNamespace(NamespaceInfo nspace) { + if(nspace == null) throw new ArgumentNullException("nspace"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + foreach(PageInfo page in GetPages(transaction, nspace)) { + PageContent content = GetContent(transaction, page, CurrentRevision); + UnindexPage(content, transaction); + foreach(Message msg in GetMessages(transaction, page)) { + UnindexMessageTree(page, msg, transaction); + } + } + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("Namespace"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Name", nspace.Name)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + if(rows > 0) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows > 0; + } + + /// + /// Determines whether a page is the default page of its namespace. + /// + /// A database transaction. + /// The page. + /// true if the page is the default page, false otherwise. + private bool IsDefaultPage(DbTransaction transaction, PageInfo page) { + string nspaceName = NameTools.GetNamespace(page.FullName); + if(string.IsNullOrEmpty(nspaceName)) return false; + + NamespaceInfo nspace = GetNamespace(transaction, nspaceName); + if(nspace == null) return false; + else { + if(nspace.DefaultPage != null) return new PageNameComparer().Compare(nspace.DefaultPage, page) == 0; + else return false; + } + } + + /// + /// Moves a page from its namespace into another. + /// + /// The page to move. + /// The destination namespace (null for the root). + /// A value indicating whether to copy the page categories in the destination + /// namespace, if not already available. + /// The correct instance of . + /// If is null. + public PageInfo MovePage(PageInfo page, NamespaceInfo destination, bool copyCategories) { + if(page == null) throw new ArgumentNullException("page"); + + // Check: + // 1. Same namespace - ROOT, SUB (explicit check) + // 2. Destination existence (update query affects 0 rows because it would break a FK) + // 3. Page existence in target (update query affects 0 rows because it would break a FK) + // 4. Page is default page of its namespace (explicit check) + + string destinationName = destination != null ? destination.Name : ""; + string sourceName = null; + string pageName = null; + NameTools.ExpandFullName(page.FullName, out sourceName, out pageName); + if(sourceName == null) sourceName = ""; + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(destinationName.ToLowerInvariant() == sourceName.ToLowerInvariant()) return null; + if(IsDefaultPage(transaction, page)) { + RollbackTransaction(transaction); + return null; + } + + PageContent currentContent = GetContent(transaction, page, CurrentRevision); + if(currentContent != null) { + UnindexPage(currentContent, transaction); + foreach(Message msg in GetMessages(transaction, page)) { + UnindexMessageTree(page, msg, transaction); + } + } + + CategoryInfo[] currCategories = GetCategories(transaction, sourceName == "" ? null : GetNamespace(transaction, sourceName)); + + // Remove bindings + RebindPage(transaction, page, new string[0]); + + string[] newCategories = new string[0]; + + if(copyCategories) { + // Retrieve categories for page + // Copy missing ones in destination + + string lowerPageName = page.FullName.ToLowerInvariant(); + + List pageCategories = new List(10); + foreach(CategoryInfo cat in currCategories) { + if(Array.Find(cat.Pages, (s) => { return s.ToLowerInvariant() == lowerPageName; }) != null) { + pageCategories.Add(NameTools.GetLocalName(cat.FullName)); + } + } + + // Create categories into destination without checking existence (AddCategory will return null) + string tempName = destinationName == "" ? null : destinationName; + newCategories = new string[pageCategories.Count]; + + for(int i = 0; i < pageCategories.Count; i++) { + CategoryInfo added = AddCategory(tempName, pageCategories[i]); + newCategories[i] = added.FullName; + } + } + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.Update("Page", new string[] { "Namespace" }, new string[] { "Destination" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Source"); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Destination", destinationName)); + parameters.Add(new Parameter(ParameterType.String, "Name", pageName)); + parameters.Add(new Parameter(ParameterType.String, "Source", sourceName)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows > 0) { + PageInfo result = new PageInfo(NameTools.GetFullName(destinationName, pageName), this, page.CreationDateTime); + + // Re-bind categories + if(copyCategories) { + bool rebound = RebindPage(transaction, result, newCategories); + if(!rebound) { + RollbackTransaction(transaction); + return null; + } + } + + PageContent newContent = GetContent(transaction, result, CurrentRevision); + IndexPage(newContent, transaction); + foreach(Message msg in GetMessages(transaction, result)) { + IndexMessageTree(result, msg, transaction); + } + + CommitTransaction(transaction); + + return result; + } + else { + RollbackTransaction(transaction); + return null; + } + } + + /// + /// Gets a category. + /// + /// A database transaction. + /// The full name of the category. + /// The , or null if no category is found. + private CategoryInfo GetCategory(DbTransaction transaction, string fullName) { + string nspace = null; + string name = null; + NameTools.ExpandFullName(fullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Category", "CategoryBinding", new string[] { "Name", "Namespace" }, new string[] { "Category", "Namespace" }, Join.LeftJoin, + new string[] { "Name", "Namespace" }, new string[] { "Page" }); + query = queryBuilder.Where(query, "Category", "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.AndWhere(query, "Category", "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + CategoryInfo result = null; + List pages = new List(50); + + while(reader.Read()) { + if(result == null) result = new CategoryInfo(NameTools.GetFullName(reader["Category_Namespace"] as string, reader["Category_Name"] as string), this); + + if(!IsDBNull(reader, "CategoryBinding_Page")) { + pages.Add(NameTools.GetFullName(reader["Category_Namespace"] as string, reader["CategoryBinding_Page"] as string)); + } + } + + CloseReader(reader); + + if(result != null) result.Pages = pages.ToArray(); + + return result; + } + else return null; + } + + /// + /// Gets a category. + /// + /// A database connection. + /// The full name of the category. + /// The , or null if no category is found. + private CategoryInfo GetCategory(DbConnection connection, string fullName) { + string nspace = null; + string name = null; + NameTools.ExpandFullName(fullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Category", "CategoryBinding", new string[] { "Name", "Namespace" }, new string[] { "Category", "Namespace" }, Join.LeftJoin, + new string[] { "Name", "Namespace" }, new string[] { "Page" }); + query = queryBuilder.Where(query, "Category", "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.AndWhere(query, "Category", "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + CategoryInfo result = null; + List pages = new List(50); + + while(reader.Read()) { + if(result == null) result = new CategoryInfo(NameTools.GetFullName(reader["Category_Namespace"] as string, reader["Category_Name"] as string), this); + + if(!IsDBNull(reader, "CategoryBinding_Page")) { + pages.Add(NameTools.GetFullName(reader["Category_Namespace"] as string, reader["CategoryBinding_Page"] as string)); + } + } + + CloseReader(reader); + + if(result != null) result.Pages = pages.ToArray(); + + return result; + } + else return null; + } + + /// + /// Gets a category. + /// + /// The full name of the category. + /// The , or null if no category is found. + /// If is null. + /// If is empty. + public CategoryInfo GetCategory(string fullName) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + CategoryInfo category = GetCategory(connection, fullName); + CloseConnection(connection); + + return category; + } + + /// + /// Gets all the Categories in a namespace. + /// + /// A database transaction. + /// The namespace. + /// All the Categories in the namespace. The array is not sorted. + private CategoryInfo[] GetCategories(DbTransaction transaction, NamespaceInfo nspace) { + string nspaceName = nspace != null ? nspace.Name : ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Category", "CategoryBinding", new string[] { "Name", "Namespace" }, new string[] { "Category", "Namespace" }, Join.LeftJoin, + new string[] { "Name", "Namespace" }, new string[] { "Page" }); + query = queryBuilder.Where(query, "Category", "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.OrderBy(query, new string[] { "Category_Name", "CategoryBinding_Page" }, new Ordering[] { Ordering.Asc, Ordering.Asc }); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspaceName)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(20); + List pages = new List(50); + + string prevName = "|||"; + string name = null; + + while(reader.Read()) { + name = reader["Category_Name"] as string; + + if(name != prevName) { + if(prevName != "|||") { + result[result.Count - 1].Pages = pages.ToArray(); + pages.Clear(); + } + + result.Add(new CategoryInfo(NameTools.GetFullName(reader["Category_Namespace"] as string, name), this)); + } + + prevName = name; + if(!IsDBNull(reader, "CategoryBinding_Page")) { + pages.Add(NameTools.GetFullName(reader["Category_Namespace"] as string, reader["CategoryBinding_Page"] as string)); + } + } + + CloseReader(reader); + + if(result.Count > 0) result[result.Count - 1].Pages = pages.ToArray(); + + return result.ToArray(); + } + else return null; + } + + /// + /// Gets all the Categories in a namespace. + /// + /// A database connection. + /// The namespace. + /// All the Categories in the namespace. The array is not sorted. + private CategoryInfo[] GetCategories(DbConnection connection, NamespaceInfo nspace) { + string nspaceName = nspace != null ? nspace.Name : ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Category", "CategoryBinding", new string[] { "Name", "Namespace" }, new string[] { "Category", "Namespace" }, Join.LeftJoin, + new string[] { "Name", "Namespace" }, new string[] { "Page" }); + query = queryBuilder.Where(query, "Category", "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.OrderBy(query, new string[] { "Category_Name", "CategoryBinding_Page" }, new Ordering[] { Ordering.Asc, Ordering.Asc }); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspaceName)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(20); + List pages = new List(50); + + string prevName = "|||"; + string name = null; + + while(reader.Read()) { + name = reader["Category_Name"] as string; + + if(name != prevName) { + if(prevName != "|||") { + result[result.Count - 1].Pages = pages.ToArray(); + pages.Clear(); + } + + result.Add(new CategoryInfo(NameTools.GetFullName(reader["Category_Namespace"] as string, name), this)); + } + + prevName = name; + if(!IsDBNull(reader, "CategoryBinding_Page")) { + pages.Add(NameTools.GetFullName(reader["Category_Namespace"] as string, reader["CategoryBinding_Page"] as string)); + } + } + + CloseReader(reader); + + if(result.Count > 0) result[result.Count - 1].Pages = pages.ToArray(); + + return result.ToArray(); + } + else return null; + } + + /// + /// Gets all the Categories in a namespace. + /// + /// The namespace. + /// All the Categories in the namespace, sorted by name. + public CategoryInfo[] GetCategories(NamespaceInfo nspace) { + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + CategoryInfo[] categories = GetCategories(connection, nspace); + CloseConnection(connection); + + return categories; + } + + /// + /// Gets all the categories of a page. + /// + /// The page. + /// The categories, sorted by name. + /// If is null. + public CategoryInfo[] GetCategoriesForPage(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string nspace, pageName; + NameTools.ExpandFullName(page.FullName, out nspace, out pageName); + if(nspace == null) nspace = ""; + + string query = queryBuilder.SelectFrom("Category", "CategoryBinding", new string[] { "Name", "Namespace" }, new string[] { "Category", "Namespace" }, Join.LeftJoin, + new string[] { "Name", "Namespace" }, new string[] { "Page" }); + query = queryBuilder.Where(query, "CategoryBinding", "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.AndWhere(query, "CategoryBinding", "Page", WhereOperator.Equals, "Page"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.String, "Page", pageName)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(20); + List pages = new List(50); + + string prevName = "|||"; + string name = null; + + while(reader.Read()) { + name = reader["Category_Name"] as string; + + if(name != prevName) { + if(prevName != "|||") { + result[result.Count - 1].Pages = pages.ToArray(); + pages.Clear(); + } + + result.Add(new CategoryInfo(NameTools.GetFullName(reader["Category_Namespace"] as string, name), this)); + } + + prevName = name; + if(!IsDBNull(reader, "CategoryBinding_Page")) { + pages.Add(NameTools.GetFullName(reader["Category_Namespace"] as string, reader["CategoryBinding_Page"] as string)); + } + } + + CloseReader(command, reader); + + if(result.Count > 0) result[result.Count - 1].Pages = pages.ToArray(); + + return result.ToArray(); + } + else return null; + } + + /// + /// Adds a Category. + /// + /// The target namespace (null for the root). + /// The Category name. + /// The correct CategoryInfo object. + /// The method should set category's Pages to an empty array. + /// If is null. + /// If is empty. + public CategoryInfo AddCategory(string nspace, string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).InsertInto("Category", new string[] { "Name", "Namespace" }, new string[] { "Name", "Namespace" }); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + int rows = ExecuteNonQuery(command); + + if(rows == 1) return new CategoryInfo(NameTools.GetFullName(nspace, name), this); + else return null; + } + + /// + /// Renames a Category. + /// + /// The Category to rename. + /// The new Name. + /// The correct CategoryInfo object. + /// If or are null. + /// If is empty. + public CategoryInfo RenameCategory(CategoryInfo category, string newName) { + if(category == null) throw new ArgumentNullException("category"); + if(newName == null) throw new ArgumentNullException("newName"); + if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); + + string nspace = null; + string name = null; + NameTools.ExpandFullName(category.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.Update("Category", new string[] { "Name" }, new string[] { "NewName" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "OldName"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "NewName", newName)); + parameters.Add(new Parameter(ParameterType.String, "OldName", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows > 0) { + CategoryInfo result = GetCategory(transaction, NameTools.GetFullName(nspace, newName)); + CommitTransaction(transaction); + return result; + } + else { + RollbackTransaction(transaction); + return null; + } + } + + /// + /// Removes a Category. + /// + /// A database transaction. + /// The Category to remove. + /// True if the Category has been removed successfully. + private bool RemoveCategory(DbTransaction transaction, CategoryInfo category) { + string nspace = null; + string name = null; + NameTools.ExpandFullName(category.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("Category"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + return rows > 0; + } + + /// + /// Removes a Category. + /// + /// A database connection. + /// The Category to remove. + /// True if the Category has been removed successfully. + private bool RemoveCategory(DbConnection connection, CategoryInfo category) { + string nspace = null; + string name = null; + NameTools.ExpandFullName(category.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("Category"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + return rows > 0; + } + + /// + /// Removes a Category. + /// + /// The Category to remove. + /// True if the Category has been removed successfully. + /// If is null. + public bool RemoveCategory(CategoryInfo category) { + if(category == null) throw new ArgumentNullException("category"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + bool removed = RemoveCategory(connection, category); + CloseConnection(connection); + + return removed; + } + + /// + /// Merges two Categories. + /// + /// The source Category. + /// The destination Category. + /// The correct object. + /// The destination Category remains, while the source Category is deleted, and all its Pages re-bound + /// in the destination Category. + /// If or are null. + public CategoryInfo MergeCategories(CategoryInfo source, CategoryInfo destination) { + if(source == null) throw new ArgumentNullException("source"); + if(destination == null) throw new ArgumentNullException("destination"); + + // 1. Check for same namespace + // 2. Load all pages in source + // 3. Load all pages in destination + // 4. Merge lists in memory + // 5. Delete all destination bindings + // 6. Delete source cat + // 7. Insert new bindings stored in memory + + string sourceNs = NameTools.GetNamespace(source.FullName); + string destinationNs = NameTools.GetNamespace(destination.FullName); + + // If one is null and the other not null, fail + if(sourceNs == null && destinationNs != null || sourceNs != null && destinationNs == null) return null; + else { + // Both non-null or both null + if(sourceNs != null) { + // Both non-null, check names + NamespaceInfo tempSource = new NamespaceInfo(sourceNs, this, null); + NamespaceInfo tempDest = new NamespaceInfo(destinationNs, this, null); + // Different names, fail + if(new NamespaceComparer().Compare(tempSource, tempDest) != 0) return null; + } + // else both null, OK + } + + string nspace = sourceNs != null ? sourceNs : ""; + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + CategoryInfo actualSource = GetCategory(transaction, source.FullName); + CategoryInfo actualDestination = GetCategory(transaction, destination.FullName); + + if(actualSource == null) { + RollbackTransaction(transaction); + return null; + } + if(actualDestination == null) { + RollbackTransaction(transaction); + return null; + } + + string destinationName = NameTools.GetLocalName(actualDestination.FullName); + + string[] mergedPages = MergeArrays(actualSource.Pages, actualDestination.Pages); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("CategoryBinding"); + query = queryBuilder.Where(query, "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.AndWhere(query, "Category", WhereOperator.Equals, "Category"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.String, "Category", destinationName)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == -1) { + RollbackTransaction(transaction); + return null; + } + + if(!RemoveCategory(transaction, source)) { + RollbackTransaction(transaction); + return null; + } + + string finalQuery = ""; + parameters = new List(MaxStatementsInBatch * 3); + rows = 0; + int count = 1; + string countString; + + foreach(string page in mergedPages) { + // This batch is executed in small chunks (MaxStatementsInBatch) to avoid exceeding DB's max batch length/size + + countString = count.ToString(); + + query = queryBuilder.InsertInto("CategoryBinding", new string[] { "Namespace", "Category", "Page" }, + new string[] { "Namespace" + countString, "Category" + countString, "Page" + countString }); + finalQuery = queryBuilder.AppendForBatch(finalQuery, query); + + parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace)); + parameters.Add(new Parameter(ParameterType.String, "Category" + countString, destinationName)); + parameters.Add(new Parameter(ParameterType.String, "Page" + countString, NameTools.GetLocalName(page))); + + count++; + + if(count == MaxStatementsInBatch) { + // Batch is complete -> execute + command = builder.GetCommand(transaction, finalQuery, parameters); + rows += ExecuteNonQuery(command, false); + + count = 1; + finalQuery = ""; + parameters.Clear(); + } + } + + if(finalQuery.Length > 0) { + // Execute remaining queries, if any + command = builder.GetCommand(transaction, finalQuery, parameters); + rows += ExecuteNonQuery(command, false); + } + + if(rows == mergedPages.Length) { + CommitTransaction(transaction); + CategoryInfo result = new CategoryInfo(actualDestination.FullName, this); + result.Pages = mergedPages; + return result; + } + else { + RollbackTransaction(transaction); + return null; + } + } + + /// + /// Merges two arrays of strings. + /// + /// The first array. + /// The second array. + /// The merged array. + private static string[] MergeArrays(string[] array1, string[] array2) { + List result = new List(array1.Length + array2.Length); + + // A) BinarySearch is O(log n), but Insert is O(n) (+ QuickSort which is O(n*log n)) + // B) A linear search is O(n), and Add is O(1) (given that the list is already big enough) + // --> B is faster, even when result approaches a size of array1.Length + array2.Length + + StringComparer comp = StringComparer.OrdinalIgnoreCase; + + result.AddRange(array1); + + foreach(string value in array2) { + if(result.Find(x => { return comp.Compare(x, value) == 0; }) == null) { + result.Add(value); + } + } + + return result.ToArray(); + } + + /// + /// Gets a page. + /// + /// A database transaction. + /// The full name of the page. + /// The , or null if no page is found. + private PageInfo GetPage(DbTransaction transaction, string fullName) { + string nspace, name; + NameTools.ExpandFullName(fullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Page"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + PageInfo result = null; + + if(reader.Read()) { + result = new PageInfo(NameTools.GetFullName(reader["Namespace"] as string, reader["Name"] as string), + this, (DateTime)reader["CreationDateTime"]); + } + + CloseReader(reader); + + return result; + } + else return null; + } + + /// + /// Gets a page. + /// + /// A database connection. + /// The full name of the page. + /// The , or null if no page is found. + private PageInfo GetPage(DbConnection connection, string fullName) { + string nspace, name; + NameTools.ExpandFullName(fullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Page"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + PageInfo result = null; + + if(reader.Read()) { + result = new PageInfo(NameTools.GetFullName(reader["Namespace"] as string, reader["Name"] as string), + this, (DateTime)reader["CreationDateTime"]); + } + + CloseReader(reader); + + return result; + } + else return null; + } + + /// + /// Gets a page. + /// + /// The full name of the page. + /// The , or null if no page is found. + /// If is null. + /// If is empty. + public PageInfo GetPage(string fullName) { + if(fullName == null) throw new ArgumentNullException("fullName"); + if(fullName.Length == 0) throw new ArgumentException("Full Name cannot be empty", "fullName"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + PageInfo page = GetPage(connection, fullName); + CloseConnection(connection); + + return page; + } + + /// + /// Gets all the Pages in a namespace. + /// + /// A database transaction. + /// The namespace (null for the root). + /// All the Pages in the namespace. The array is not sorted. + private PageInfo[] GetPages(DbTransaction transaction, NamespaceInfo nspace) { + string nspaceName = nspace != null ? nspace.Name : ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Page"); + query = queryBuilder.Where(query, "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspaceName)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(100); + + while(reader.Read()) { + result.Add(new PageInfo(NameTools.GetFullName(reader["Namespace"] as string, reader["Name"] as string), + this, (DateTime)reader["CreationDateTime"])); + } + + CloseReader(reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Gets all the Pages in a namespace. + /// + /// A database connection. + /// The namespace (null for the root). + /// All the Pages in the namespace. The array is not sorted. + private PageInfo[] GetPages(DbConnection connection, NamespaceInfo nspace) { + string nspaceName = nspace != null ? nspace.Name : ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Page"); + query = queryBuilder.Where(query, "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspaceName)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(100); + + while(reader.Read()) { + result.Add(new PageInfo(NameTools.GetFullName(reader["Namespace"] as string, reader["Name"] as string), + this, (DateTime)reader["CreationDateTime"])); + } + + CloseReader(reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Gets all the Pages in a namespace. + /// + /// The namespace (null for the root). + /// All the Pages in the namespace. The array is not sorted. + public PageInfo[] GetPages(NamespaceInfo nspace) { + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + PageInfo[] pages = GetPages(connection, nspace); + CloseConnection(connection); + + return pages; + } + + /// + /// Gets all the pages in a namespace that are bound to zero categories. + /// + /// The namespace (null for the root). + /// The pages, sorted by name. + public PageInfo[] GetUncategorizedPages(NamespaceInfo nspace) { + string nspaceName = nspace != null ? nspace.Name : ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Page"); + query = queryBuilder.SelectFrom("Page", "CategoryBinding", "Name", "Page", Join.LeftJoin); + query = queryBuilder.Where(query, "CategoryBinding", "Category", WhereOperator.IsNull, null); + query = queryBuilder.AndWhere(query, "Page", "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspaceName)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(100); + + while(reader.Read()) { + result.Add(new PageInfo(NameTools.GetFullName(reader["Namespace"] as string, reader["Name"] as string), + this, (DateTime)reader["CreationDateTime"])); + } + + CloseReader(command, reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Gets the content of a specific revision of a page. + /// + /// A database connection. + /// The page. + /// The revision. + /// The content. + private PageContent GetContent(DbConnection connection, PageInfo page, int revision) { + // Internal version to work with GetContent, GetBackupContent, GetDraft + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string name, nspace; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + string query = queryBuilder.SelectFrom("PageContent", "PageKeyword", new string[] { "Page", "Namespace", "Revision" }, new string[] { "Page", "Namespace", "Revision" }, Join.LeftJoin, + new string [] { "Title", "User", "LastModified", "Comment", "Content", "Description" }, new string[] { "Keyword" }); + query = queryBuilder.Where(query, "PageContent", "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "PageContent", "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.AndWhere(query, "PageContent", "Revision", WhereOperator.Equals, "Revision"); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.Int16, "Revision", (short)revision)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + PageContent result = null; + + string title = null, user = null, comment = null, content = null, description = null; + DateTime dateTime = DateTime.MinValue; + List keywords = new List(10); + + while(reader.Read()) { + if(title == null) { + title = reader["PageContent_Title"] as string; + user = reader["PageContent_User"] as string; + dateTime = (DateTime)reader["PageContent_LastModified"]; + comment = GetNullableColumn(reader, "PageContent_Comment", ""); + content = reader["PageContent_Content"] as string; + description = GetNullableColumn(reader, "PageContent_Description", null); + } + + if(!IsDBNull(reader, "PageKeyword_Keyword")) { + keywords.Add(reader["PageKeyword_Keyword"] as string); + } + } + + if(title != null) { + result = new PageContent(page, title, user, dateTime, comment, content, keywords.ToArray(), description); + } + + CloseReader(reader); + + return result; + } + else return null; + } + + /// + /// Gets the content of a specific revision of a page. + /// + /// A database transaction. + /// The page. + /// The revision. + /// The content. + private PageContent GetContent(DbTransaction transaction, PageInfo page, int revision) { + // Internal version to work with GetContent, GetBackupContent, GetDraft + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string name, nspace; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + string query = queryBuilder.SelectFrom("PageContent", "PageKeyword", new string[] { "Page", "Namespace", "Revision" }, new string[] { "Page", "Namespace", "Revision" }, Join.LeftJoin, + new string[] { "Title", "User", "LastModified", "Comment", "Content", "Description" }, new string[] { "Keyword" }); + query = queryBuilder.Where(query, "PageContent", "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "PageContent", "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.AndWhere(query, "PageContent", "Revision", WhereOperator.Equals, "Revision"); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.Int16, "Revision", (short)revision)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + PageContent result = null; + + string title = null, user = null, comment = null, content = null, description = null; + DateTime dateTime = DateTime.MinValue; + List keywords = new List(10); + + while(reader.Read()) { + if(title == null) { + title = reader["PageContent_Title"] as string; + user = reader["PageContent_User"] as string; + dateTime = (DateTime)reader["PageContent_LastModified"]; + comment = GetNullableColumn(reader, "PageContent_Comment", ""); + content = reader["PageContent_Content"] as string; + description = GetNullableColumn(reader, "PageContent_Description", null); + } + + if(!IsDBNull(reader, "PageKeyword_Keyword")) { + keywords.Add(reader["PageKeyword_Keyword"] as string); + } + } + + if(title != null) { + result = new PageContent(page, title, user, dateTime, comment, content, keywords.ToArray(), description); + } + + CloseReader(reader); + + return result; + } + else return null; + } + + /// + /// Gets the Content of a Page. + /// + /// The Page. + /// The Page Content object, null if the page does not exist or is null, + /// or an empty instance if the content could not be retrieved (). + public PageContent GetContent(PageInfo page) { + if(page == null) return null; + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + PageContent content = GetContent(connection, page, CurrentRevision); + CloseConnection(connection); + + return content; + } + + /// + /// Gets the content of a draft of a Page. + /// + /// The Page. + /// The draft, or null if no draft exists. + /// If is null. + public PageContent GetDraft(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + PageContent content = GetContent(connection, page, DraftRevision); + CloseConnection(connection); + + return content; + } + + /// + /// Deletes a draft of a Page. + /// + /// The page. + /// true if the draft is deleted, false otherwise. + /// If is null. + public bool DeleteDraft(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + bool deleted = DeleteContent(connection, page, DraftRevision); + CloseConnection(connection); + + return deleted; + } + + /// + /// Gets the Backup/Revision numbers of a Page. + /// + /// A database transaction. + /// The Page to get the Backups of. + /// The Backup/Revision numbers. + private int[] GetBackups(DbTransaction transaction, PageInfo page) { + if(GetPage(transaction, page.FullName) == null) { + return null; + } + + string name, nspace; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("PageContent", new string[] { "Revision" }); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.AndWhere(query, "Revision", WhereOperator.GreaterThanOrEqualTo, "Revision"); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.Int16, "Revision", FirstRevision)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(100); + + while(reader.Read()) { + result.Add((short)reader["Revision"]); + } + + CloseReader(reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Gets the Backup/Revision numbers of a Page. + /// + /// A database connection. + /// The Page to get the Backups of. + /// The Backup/Revision numbers. + private int[] GetBackups(DbConnection connection, PageInfo page) { + if(GetPage(connection, page.FullName) == null) { + return null; + } + + string name, nspace; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("PageContent", new string[] { "Revision" }); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.AndWhere(query, "Revision", WhereOperator.GreaterThanOrEqualTo, "Revision"); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.Int16, "Revision", FirstRevision)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(100); + + while(reader.Read()) { + result.Add((short)reader["Revision"]); + } + + CloseReader(reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Gets the Backup/Revision numbers of a Page. + /// + /// The Page to get the Backups of. + /// The Backup/Revision numbers. + /// If is null. + public int[] GetBackups(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + int[] revisions = GetBackups(connection, page); + CloseConnection(connection); + + return revisions; + } + + /// + /// Gets the Content of a Backup of a Page. + /// + /// The Page to get the backup of. + /// The Backup/Revision number. + /// The Page Backup. + /// If is null. + /// If is less than zero. + public PageContent GetBackupContent(PageInfo page, int revision) { + if(page == null) throw new ArgumentNullException("page"); + if(revision < 0) throw new ArgumentOutOfRangeException("revision", "Invalid Revision"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + PageContent content = GetContent(connection, page, revision); + CloseConnection(connection); + + return content; + } + + /// + /// Stores the content for a revision. + /// + /// A database transaction. + /// The content. + /// The revision. + /// true if the content is stored, false otherwise. + private bool SetContent(DbTransaction transaction, PageContent content, int revision) { + string name, nspace; + NameTools.ExpandFullName(content.PageInfo.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.InsertInto("PageContent", + new string[] { "Page", "Namespace", "Revision", "Title", "User", "LastModified", "Comment", "Content", "Description" }, + new string[] { "Page", "Namespace", "Revision", "Title", "User", "LastModified", "Comment", "Content", "Description" }); + + List parameters = new List(9); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.Int16, "Revision", revision)); + parameters.Add(new Parameter(ParameterType.String, "Title", content.Title)); + parameters.Add(new Parameter(ParameterType.String, "User", content.User)); + parameters.Add(new Parameter(ParameterType.DateTime, "LastModified", content.LastModified)); + if(!string.IsNullOrEmpty(content.Comment)) parameters.Add(new Parameter(ParameterType.String, "Comment", content.Comment)); + else parameters.Add(new Parameter(ParameterType.String, "Comment", DBNull.Value)); + parameters.Add(new Parameter(ParameterType.String, "Content", content.Content)); + if(!string.IsNullOrEmpty(content.Description)) parameters.Add(new Parameter(ParameterType.String, "Description", content.Description)); + else parameters.Add(new Parameter(ParameterType.String, "Description", DBNull.Value)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows != 1) return false; + + if(content.Keywords.Length > 0) { + parameters = new List(content.Keywords.Length * 4); + string fullQuery = ""; + int count = 0; + string countString; + foreach(string kw in content.Keywords) { + countString = count.ToString(); + + query = queryBuilder.InsertInto("PageKeyword", new string[] { "Page", "Namespace", "Revision", "Keyword" }, + new string[] { "Page" + countString, "Namespace" + countString, "Revision" + countString, "Keyword" + countString }); + fullQuery = queryBuilder.AppendForBatch(fullQuery, query); + + parameters.Add(new Parameter(ParameterType.String, "Page" + countString, name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace)); + parameters.Add(new Parameter(ParameterType.Int16, "Revision" + countString, revision)); + parameters.Add(new Parameter(ParameterType.String, "Keyword" + countString, kw)); + + count++; + } + + command = builder.GetCommand(transaction, fullQuery, parameters); + + rows = ExecuteNonQuery(command, false); + + return rows == content.Keywords.Length; + } + else return true; + } + + /// + /// Deletes a revision of a page content. + /// + /// A database transaction. + /// The page. + /// The revision. + /// true if the content ir deleted, false otherwise. + private bool DeleteContent(DbTransaction transaction, PageInfo page, int revision) { + string name, nspace; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("PageContent"); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.AndWhere(query, "Revision", WhereOperator.Equals, "Revision"); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.String, "Revision", revision)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + return rows > 0; + } + + /// + /// Deletes a revision of a page content. + /// + /// A database connection. + /// The page. + /// The revision. + /// true if the content ir deleted, false otherwise. + private bool DeleteContent(DbConnection connection, PageInfo page, int revision) { + string name, nspace; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("PageContent"); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.AndWhere(query, "Revision", WhereOperator.Equals, "Revision"); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.String, "Revision", revision)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + return rows > 0; + } + + /// + /// Forces to overwrite or create a Backup. + /// + /// The Backup content. + /// The revision. + /// True if the Backup has been created successfully. + /// If is null. + /// If is less than zero. + public bool SetBackupContent(PageContent content, int revision) { + if(content == null) throw new ArgumentNullException("content"); + if(revision < 0) throw new ArgumentOutOfRangeException("revision", "Invalid Revision"); + + // 1. DeletebBackup, if any + // 2. Set new content + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + DeleteContent(transaction, content.PageInfo, revision); + + bool set = SetContent(transaction, content, revision); + + if(set) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return set; + } + + /// + /// Adds a Page. + /// + /// The target namespace (null for the root). + /// The Page Name. + /// The creation Date/Time. + /// The correct PageInfo object or null. + /// This method should not create the content of the Page. + /// If is null. + /// If is empty. + public PageInfo AddPage(string nspace, string name, DateTime creationDateTime) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).InsertInto("Page", new string[] { "Name", "Namespace", "CreationDateTime" }, + new string[] { "Name", "Namespace", "CreationDateTime" }); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.DateTime, "CreationDateTime", creationDateTime)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + int rows = ExecuteNonQuery(command); + + if(rows == 1) { + return new PageInfo(NameTools.GetFullName(nspace, name), this, creationDateTime); + } + else return null; + } + + /// + /// Renames a Page. + /// + /// The Page to rename. + /// The new Name. + /// The correct object. + /// If or are null. + /// If is empty. + public PageInfo RenamePage(PageInfo page, string newName) { + if(page == null) throw new ArgumentNullException("page"); + if(newName == null) throw new ArgumentNullException("newName"); + if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); + + // Check + // 1. Page is default page of its namespace + // 2. New name already exists + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(GetPage(transaction, page.FullName) == null) { + RollbackTransaction(transaction); + return null; + } + if(IsDefaultPage(transaction, page)) { + RollbackTransaction(transaction); + return null; + } + if(GetPage(transaction, NameTools.GetFullName(NameTools.GetNamespace(page.FullName), NameTools.GetLocalName(newName))) != null) { + RollbackTransaction(transaction); + return null; + } + + PageContent currentContent = GetContent(transaction, page, CurrentRevision); + UnindexPage(currentContent, transaction); + foreach(Message msg in GetMessages(transaction, page)) { + UnindexMessageTree(page, msg, transaction); + } + + string nspace, name; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + CategoryInfo[] currCategories = GetCategories(transaction, nspace == "" ? null : GetNamespace(transaction, nspace)); + string lowerPageName = page.FullName.ToLowerInvariant(); + List pageCategories = new List(10); + foreach(CategoryInfo cat in currCategories) { + if(Array.Find(cat.Pages, (s) => { return s.ToLowerInvariant() == lowerPageName; }) != null) { + pageCategories.Add(NameTools.GetLocalName(cat.FullName)); + } + } + + RebindPage(transaction, page, new string[0]); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.Update("Page", new string[] { "Name" }, new string[] { "NewName" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "OldName"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "NewName", newName)); + parameters.Add(new Parameter(ParameterType.String, "OldName", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows > 0) { + PageInfo result = new PageInfo(NameTools.GetFullName(nspace, newName), this, page.CreationDateTime); + + RebindPage(transaction, result, pageCategories.ToArray()); + + PageContent newContent = GetContent(transaction, result, CurrentRevision); + + IndexPage(newContent, transaction); + foreach(Message msg in GetMessages(transaction, result)) { + IndexMessageTree(result, msg, transaction); + } + + CommitTransaction(transaction); + + return result; + } + else { + RollbackTransaction(transaction); + return null; + } + } + + /// + /// Modifies the Content of a Page. + /// + /// The Page. + /// The Title of the Page. + /// The Username. + /// The Date/Time. + /// The Comment of the editor, about this revision. + /// The Page Content. + /// The keywords, usually used for SEO. + /// The description, usually used for SEO. + /// The save mode for this modification. + /// true if the Page has been modified successfully, false otherwise. + /// If saveMode equals Draft and a draft already exists, it is overwritten. + /// If , , or are null. + /// If or are empty. + public bool ModifyPage(PageInfo page, string title, string username, DateTime dateTime, string comment, string content, string[] keywords, string description, SaveMode saveMode) { + if(page == null) throw new ArgumentNullException("page"); + if(title == null) throw new ArgumentNullException("title"); + if(title.Length == 0) throw new ArgumentException("Title cannot be empty", "title"); + if(username == null) throw new ArgumentNullException("username"); + if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username"); + if(content == null) throw new ArgumentNullException("content"); // content can be empty + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + PageContent currentContent = GetContent(transaction, page, CurrentRevision); + + PageContent pageContent = new PageContent(page, title, username, dateTime, comment, content, keywords != null ? keywords : new string[0], description); + + switch(saveMode) { + case SaveMode.Backup: + // Do backup (if there is something to backup), delete current version (if any), store new version + if(currentContent != null) UnindexPage(currentContent, transaction); + Backup(transaction, page); + DeleteContent(transaction, page, CurrentRevision); + bool done1 = SetContent(transaction, pageContent, CurrentRevision); + if(done1) IndexPage(pageContent, transaction); + + if(done1) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return done1; + case SaveMode.Normal: + // Delete current version (if any), store new version + if(currentContent != null) UnindexPage(currentContent, transaction); + DeleteContent(transaction, page, CurrentRevision); + bool done2 = SetContent(transaction, pageContent, CurrentRevision); + if(done2) IndexPage(pageContent, transaction); + + if(done2) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return done2; + case SaveMode.Draft: + // Delete current draft (if any), store new draft + DeleteContent(transaction, page, DraftRevision); + bool done3 = SetContent(transaction, pageContent, DraftRevision); + + if(done3) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return done3; + default: + RollbackTransaction(transaction); + throw new NotSupportedException(); + } + } + + /// + /// Backs up the content of a page. + /// + /// A database transaction. + /// The page. + /// true if the backup is performed, false otherwise. + private bool Backup(DbTransaction transaction, PageInfo page) { + PageContent currentContent = GetContent(transaction, page, CurrentRevision); + + if(currentContent != null) { + // Insert a new revision + int[] backups = GetBackups(transaction, page); + if(backups == null) return false; + + int revision = backups.Length > 0 ? backups[backups.Length - 1] + 1 : FirstRevision; + bool set = SetContent(transaction, currentContent, revision); + + return set; + } + else return false; + } + + /// + /// Performs the rollback of a Page to a specified revision. + /// + /// The Page to rollback. + /// The Revision to rollback the Page to. + /// true if the rollback succeeded, false otherwise. + /// If is null. + /// If is less than zero. + public bool RollbackPage(PageInfo page, int revision) { + if(page == null) throw new ArgumentNullException("page"); + if(revision < 0) throw new ArgumentOutOfRangeException("revision", "Invalid Revision"); + + // 1. Load specific revision's content + // 2. Modify page with loaded content, performing backup + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + PageContent targetContent = GetContent(transaction, page, revision); + + if(targetContent == null) { + RollbackTransaction(transaction); + return false; + } + + UnindexPage(GetContent(transaction, page, CurrentRevision), transaction); + + bool done = Backup(transaction, page); + if(!done) { + RollbackTransaction(transaction); + return false; + } + + done = DeleteContent(transaction, page, CurrentRevision); + if(!done) { + RollbackTransaction(transaction); + return false; + } + + done = SetContent(transaction, targetContent, CurrentRevision); + if(!done) { + RollbackTransaction(transaction); + return false; + } + + IndexPage(targetContent, transaction); + + CommitTransaction(transaction); + + return true; + } + + /// + /// Deletes the Backups of a Page, up to a specified revision. + /// + /// The Page to delete the backups of. + /// The newest revision to delete (newer revision are kept) o -1 to delete all the Backups. + /// true if the deletion succeeded, false otherwise. + /// If is null. + /// If is less than -1. + public bool DeleteBackups(PageInfo page, int revision) { + if(page == null) throw new ArgumentNullException("page"); + if(revision < -1) throw new ArgumentOutOfRangeException("revision", "Invalid Revision"); + + // 1. Retrieve target content (revision-1 = first kept revision) + // 2. Replace the current content (delete, store) + // 3. Delete all older revisions up to the specified on (included) "N-m...N" + // 4. Re-number remaining revisions starting from FirstRevision (zero) to revision-1 (don't re-number revs -1, -100) + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(GetPage(transaction, page.FullName) == null) { + RollbackTransaction(transaction); + return false; + } + + int[] baks = GetBackups(transaction, page); + if(baks.Length > 0 && revision > baks[baks.Length - 1]) { + RollbackTransaction(transaction); + return true; + } + + string nspace, name; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("PageContent"); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + if(revision != -1) query = queryBuilder.AndWhere(query, "Revision", WhereOperator.LessThanOrEqualTo, "Revision"); + query = queryBuilder.AndWhere(query, "Revision", WhereOperator.GreaterThanOrEqualTo, "FirstRevision"); + + List parameters = new List(4); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + if(revision != -1) parameters.Add(new Parameter(ParameterType.Int16, "Revision", revision)); + parameters.Add(new Parameter(ParameterType.Int16, "FirstRevision", FirstRevision)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == -1) { + RollbackTransaction(transaction); + return false; + } + + if(revision != -1) { + int revisionDelta = revision + 1; + + query = queryBuilder.UpdateIncrement("PageContent", "Revision", -revisionDelta); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.AndWhere(query, "Revision", WhereOperator.GreaterThanOrEqualTo, "FirstRevision"); + + parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.Int16, "FirstRevision", FirstRevision)); + + command = builder.GetCommand(transaction, query, parameters); + + rows = ExecuteNonQuery(command, false); + + if(rows > 0) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows >= 0; + } + else { + CommitTransaction(transaction); + return true; + } + } + + /// + /// Removes a Page. + /// + /// The Page to remove. + /// True if the Page is removed successfully. + /// If is null. + public bool RemovePage(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(IsDefaultPage(transaction, page)) { + RollbackTransaction(transaction); + return false; + } + + PageContent currentContent = GetContent(transaction, page, CurrentRevision); + if(currentContent != null) { + UnindexPage(currentContent, transaction); + foreach(Message msg in GetMessages(transaction, page)) { + UnindexMessageTree(page, msg, transaction); + } + } + + RebindPage(transaction, page, new string[0]); + + string nspace, name; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("Page"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows > 0) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows > 0; + } + + /// + /// Binds a Page with one or more Categories. + /// + /// A database transaction. + /// The Page to bind. + /// The Categories to bind the Page with. + /// True if the binding succeeded. + /// After a successful operation, the Page is bound with all and only the categories passed as argument. + private bool RebindPage(DbTransaction transaction, PageInfo page, string[] categories) { + string nspace, name; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("CategoryBinding"); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows < 0) return false; + + if(categories.Length > 0) { + string finalQuery = ""; + parameters = new List(categories.Length * 3); + int count = 0; + string countString; + + foreach(string cat in categories) { + countString = count.ToString(); + + query = queryBuilder.InsertInto("CategoryBinding", new string[] { "Namespace", "Category", "Page" }, + new string[] { "Namespace" + countString, "Category" + countString, "Page" + countString }); + finalQuery = queryBuilder.AppendForBatch(finalQuery, query); + + parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace)); + parameters.Add(new Parameter(ParameterType.String, "Category" + countString, NameTools.GetLocalName(cat))); + parameters.Add(new Parameter(ParameterType.String, "Page" + countString, name)); + + count++; + } + + command = builder.GetCommand(transaction, finalQuery, parameters); + + rows = ExecuteNonQuery(command, false); + + return rows == categories.Length; + } + else return true; + } + + /// + /// Binds a Page with one or more Categories. + /// + /// A database connection. + /// The Page to bind. + /// The Categories to bind the Page with. + /// True if the binding succeeded. + /// After a successful operation, the Page is bound with all and only the categories passed as argument. + private bool RebindPage(DbConnection connection, PageInfo page, string[] categories) { + string nspace, name; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("CategoryBinding"); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows < 0) return false; + + if(categories.Length > 0) { + string finalQuery = ""; + parameters = new List(categories.Length * 3); + int count = 0; + string countString; + + foreach(string cat in categories) { + countString = count.ToString(); + + query = queryBuilder.InsertInto("CategoryBinding", new string[] { "Namespace", "Category", "Page" }, + new string[] { "Namespace" + countString, "Category" + countString, "Page" + countString }); + finalQuery = queryBuilder.AppendForBatch(finalQuery, query); + + parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace)); + parameters.Add(new Parameter(ParameterType.String, "Category" + countString, NameTools.GetLocalName(cat))); + parameters.Add(new Parameter(ParameterType.String, "Page" + countString, name)); + + count++; + } + + command = builder.GetCommand(connection, finalQuery, parameters); + + rows = ExecuteNonQuery(command, false); + + return rows == categories.Length; + } + else return true; + } + + /// + /// Binds a Page with one or more Categories. + /// + /// The Page to bind. + /// The Categories to bind the Page with. + /// True if the binding succeeded. + /// After a successful operation, the Page is bound with all and only the categories passed as argument. + /// If or are null. + public bool RebindPage(PageInfo page, string[] categories) { + if(page == null) throw new ArgumentNullException("page"); + if(categories == null) throw new ArgumentNullException("categories"); + + foreach(string cat in categories) { + if(cat == null) throw new ArgumentNullException("categories"); + if(cat.Length == 0) throw new ArgumentException("Category item cannot be empty", "categories"); + } + + // 1. Delete old bindings + // 2. Store new bindings + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + bool rebound = RebindPage(connection, page, categories); + CloseConnection(connection); + + return rebound; + } + + /// + /// Gets the Page Messages. + /// + /// A database transaction. + /// The Page. + /// The list of the first-level Messages, containing the replies properly nested, sorted by date/time. + private Message[] GetMessages(DbTransaction transaction, PageInfo page) { + if(GetPage(transaction, page.FullName) == null) return null; + + // 1. Load all messages in memory in a dictionary id->message + // 2. Build tree using ParentID + + string nspace, name; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Message", new string[] { "Id", "Parent", "Username", "Subject", "DateTime", "Body" }); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.OrderBy(query, new string[] { "DateTime" }, new Ordering[] { Ordering.Asc }); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + Dictionary allMessages = new Dictionary(50); + List ids = new List(50); + List parents = new List(50); + + while(reader.Read()) { + Message msg = new Message((short)reader["Id"], reader["Username"] as string, reader["Subject"] as string, + (DateTime)reader["DateTime"], reader["Body"] as string); + + ids.Add((short)msg.ID); + + // Import from V2: parent = -1, otherwise null + if(!IsDBNull(reader, "Parent")) { + short par = (short)reader["Parent"]; + if(par >= 0) parents.Add(par); + else parents.Add(null); + } + else parents.Add(null); + + allMessages.Add((short)msg.ID, msg); + } + + CloseReader(reader); + + // Add messages to their parents and build the top-level messages list + List result = new List(20); + + for(int i = 0; i < ids.Count; i++) { + short? currentParent = parents[i]; + short currentId = ids[i]; + + if(currentParent.HasValue) { + List replies = new List(allMessages[currentParent.Value].Replies); + replies.Add(allMessages[currentId]); + allMessages[currentParent.Value].Replies = replies.ToArray(); + } + else result.Add(allMessages[currentId]); + } + + return result.ToArray(); + } + else return null; + } + + /// + /// Gets the Page Messages. + /// + /// A database connection. + /// The Page. + /// The list of the first-level Messages, containing the replies properly nested, sorted by date/time. + private Message[] GetMessages(DbConnection connection, PageInfo page) { + if(GetPage(connection, page.FullName) == null) return null; + + // 1. Load all messages in memory in a dictionary id->message + // 2. Build tree using ParentID + + string nspace, name; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Message", new string[] { "Id", "Parent", "Username", "Subject", "DateTime", "Body" }); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.OrderBy(query, new string[] { "DateTime" }, new Ordering[] { Ordering.Asc }); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + Dictionary allMessages = new Dictionary(50); + List ids = new List(50); + List parents = new List(50); + + while(reader.Read()) { + Message msg = new Message((short)reader["Id"], reader["Username"] as string, reader["Subject"] as string, + (DateTime)reader["DateTime"], reader["Body"] as string); + + ids.Add((short)msg.ID); + + // Import from V2: parent = -1, otherwise null + if(!IsDBNull(reader, "Parent")) { + short par = (short)reader["Parent"]; + if(par >= 0) parents.Add(par); + else parents.Add(null); + } + else parents.Add(null); + + allMessages.Add((short)msg.ID, msg); + } + + CloseReader(reader); + + // Add messages to their parents and build the top-level messages list + List result = new List(20); + + for(int i = 0; i < ids.Count; i++) { + short? currentParent = parents[i]; + short currentId = ids[i]; + + if(currentParent.HasValue) { + List replies = new List(allMessages[currentParent.Value].Replies); + replies.Add(allMessages[currentId]); + allMessages[currentParent.Value].Replies = replies.ToArray(); + } + else result.Add(allMessages[currentId]); + } + + return result.ToArray(); + } + else return null; + } + + /// + /// Gets the Page Messages. + /// + /// The Page. + /// The list of the first-level Messages, containing the replies properly nested, sorted by date/time. + /// If is null. + public Message[] GetMessages(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + Message[] messages = GetMessages(connection, page); + CloseConnection(connection); + + return messages; + } + + /// + /// Gets the total number of Messages in a Page Discussion. + /// + /// The Page. + /// The number of messages. + /// If is null. + public int GetMessageCount(PageInfo page) { + if(page == null) throw new ArgumentNullException("page"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + if(GetPage(connection, page.FullName) == null) { + CloseConnection(connection); + return -1; + } + + string nspace, name; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectCountFrom("Message"); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + int count = ExecuteScalar(command, 0); + + return count; + } + + /// + /// Removes all messages for a page and stores the new messages. + /// + /// The page. + /// The new messages to store. + /// true if the messages are stored, false otherwise. + /// If or are null. + public bool BulkStoreMessages(PageInfo page, Message[] messages) { + if(page == null) throw new ArgumentNullException("page"); + if(messages == null) throw new ArgumentNullException("messages"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(GetPage(transaction, page.FullName) == null) { + RollbackTransaction(transaction); + return false; + } + + foreach(Message msg in GetMessages(transaction, page)) { + UnindexMessageTree(page, msg, transaction); + } + + string nspace, name; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("Message"); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + ExecuteNonQuery(command, false); + + List allMessages; + List parents; + + UnTreeMessages(messages, out allMessages, out parents, -1); + + string finalQuery = ""; + int count = 1; + string countString; + parameters = new List(MaxStatementsInBatch * 8); + + int rowsDone = 0; + + for(int i = 0; i < allMessages.Count; i++) { + // Execute the batch in smaller chunks + + Message msg = allMessages[i]; + int parent = parents[i]; + + countString = count.ToString(); + + query = queryBuilder.InsertInto("Message", new string[] { "Page", "Namespace", "Id", "Parent", "Username", "Subject", "DateTime", "Body" }, + new string[] { "Page" + countString, "Namespace" + countString, "Id" + countString, "Parent" + countString, "Username" + countString, "Subject" + countString, "DateTime" + countString, "Body" + countString }); + + parameters.Add(new Parameter(ParameterType.String, "Page" + countString, name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace)); + parameters.Add(new Parameter(ParameterType.Int16, "Id" + countString, (short)msg.ID)); + if(parent != -1) parameters.Add(new Parameter(ParameterType.Int16, "Parent" + countString, parent)); + else parameters.Add(new Parameter(ParameterType.Int16, "Parent" + countString, DBNull.Value)); + parameters.Add(new Parameter(ParameterType.String, "Username" + countString, msg.Username)); + parameters.Add(new Parameter(ParameterType.String, "Subject" + countString, msg.Subject)); + parameters.Add(new Parameter(ParameterType.DateTime, "DateTime" + countString, msg.DateTime)); + parameters.Add(new Parameter(ParameterType.String, "Body" + countString, msg.Body)); + + finalQuery = queryBuilder.AppendForBatch(finalQuery, query); + + count++; + + if(count == MaxStatementsInBatch) { + command = builder.GetCommand(transaction, finalQuery, parameters); + + rowsDone += ExecuteNonQuery(command, false); + + finalQuery = ""; + count = 1; + parameters.Clear(); + } + } + + if(finalQuery.Length > 0) { + command = builder.GetCommand(transaction, finalQuery, parameters); + + rowsDone += ExecuteNonQuery(command, false); + } + + if(rowsDone == allMessages.Count) { + foreach(Message msg in messages) { + IndexMessageTree(page, msg, transaction); + } + CommitTransaction(transaction); + return true; + } + else { + RollbackTransaction(transaction); + return false; + } + } + + /// + /// Deconstructs a tree of messages and converts it into a flat list. + /// + /// The input tree. + /// The resulting flat message list. + /// The list of parent IDs. + /// The current parent ID. + private static void UnTreeMessages(Message[] messages, out List flatList, out List parents, int parent) { + flatList = new List(20); + parents = new List(20); + + flatList.AddRange(messages); + for(int i = 0; i < messages.Length; i++) { + parents.Add(parent); + } + + foreach(Message msg in messages) { + List temp; + List tempParents; + + UnTreeMessages(msg.Replies, out temp, out tempParents, msg.ID); + + flatList.AddRange(temp); + parents.AddRange(tempParents); + } + + } + + /// + /// Adds a new Message to a Page. + /// + /// The Page. + /// The Username. + /// The Subject. + /// The Date/Time. + /// The Body. + /// The Parent Message ID, or -1. + /// True if the Message is added successfully. + /// If , , or are null. + /// If or are empty. + /// If is less than -1. + public bool AddMessage(PageInfo page, string username, string subject, DateTime dateTime, string body, int parent) { + if(page == null) throw new ArgumentNullException("page"); + if(username == null) throw new ArgumentNullException("username"); + if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username"); + if(subject == null) throw new ArgumentNullException("subject"); + if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject"); + if(body == null) throw new ArgumentNullException("body"); // body can be empty + if(parent < -1) throw new ArgumentOutOfRangeException("parent", "Invalid Parent Message ID"); + + string nspace, name; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(parent != -1 && FindMessage(GetMessages(transaction, page), parent) == null) { + RollbackTransaction(transaction); + return false; + } + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + short freeId = -1; + + string query = queryBuilder.SelectFrom("Message", new string[] { "Id" }); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.OrderBy(query, new string[] { "Id" }, new Ordering[] { Ordering.Desc }); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + freeId = ExecuteScalar(command, -1, false); + + if(freeId == -1) freeId = 0; + else freeId++; + + query = queryBuilder.InsertInto("Message", new string[] { "Page", "Namespace", "Id", "Parent", "Username", "Subject", "DateTime", "Body" }, + new string[] { "Page", "Namespace", "Id", "Parent", "Username", "Subject", "DateTime", "Body" }); + + parameters = new List(8); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.Int16, "Id", freeId)); + if(parent != -1) parameters.Add(new Parameter(ParameterType.Int16, "Parent", parent)); + else parameters.Add(new Parameter(ParameterType.Int16, "Parent", DBNull.Value)); + parameters.Add(new Parameter(ParameterType.String, "Username", username)); + parameters.Add(new Parameter(ParameterType.String, "Subject", subject)); + parameters.Add(new Parameter(ParameterType.DateTime, "DateTime", dateTime)); + parameters.Add(new Parameter(ParameterType.String, "Body", body)); + + command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == 1) { + IndexMessage(page, freeId, subject, dateTime, body, transaction); + + CommitTransaction(transaction); + return true; + } + else { + RollbackTransaction(transaction); + return false; + } + } + + /// + /// Finds a Message in a Message tree. + /// + /// The Message tree. + /// The ID of the Message to find. + /// The Message or null. + /// The method is recursive. + private static Message FindMessage(IEnumerable messages, int id) { + Message result = null; + foreach(Message msg in messages) { + if(msg.ID == id) { + result = msg; + } + if(result == null) { + result = FindMessage(msg.Replies, id); + } + if(result != null) break; + } + return result; + } + + /// + /// Finds the anchestor/parent of a Message. + /// + /// The Messages. + /// The Message ID. + /// The anchestor Message or null. + private static Message FindAnchestor(IEnumerable messages, int id) { + Message result = null; + foreach(Message msg in messages) { + for(int k = 0; k < msg.Replies.Length; k++) { + if(msg.Replies[k].ID == id) { + result = msg; + break; + } + if(result == null) { + result = FindAnchestor(msg.Replies, id); + } + } + if(result != null) break; + } + return result; + } + + /// + /// Removes a Message. + /// + /// A database transaction. + /// The Page. + /// The ID of the Message to remove. + /// A value specifying whether or not to remove the replies. + /// True if the Message is removed successfully. + private bool RemoveMessage(DbTransaction transaction, PageInfo page, int id, bool removeReplies) { + string nspace, name; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + Message[] messages = GetMessages(transaction, page); + if(messages == null) return false; + Message message = FindMessage(messages, id); + if(message == null) return false; + Message parent = FindAnchestor(messages, id); + int parentId = parent != null ? parent.ID : -1; + + UnindexMessage(page, message.ID, message.Subject, message.DateTime, message.Body, transaction); + + if(removeReplies) { + // Recursively remove all replies BEFORE removing parent (depth-first) + foreach(Message reply in message.Replies) { + if(!RemoveMessage(transaction, page, reply.ID, true)) return false; + } + } + + // Remove this message + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("Message"); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.AndWhere(query, "Id", WhereOperator.Equals, "Id"); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.Int16, "Id", (short)id)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(!removeReplies && rows == 1) { + // Update replies' parent id + + query = queryBuilder.Update("Message", new string[] { "Parent" }, new string[] { "NewParent" }); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.AndWhere(query, "Parent", WhereOperator.Equals, "OldParent"); + + parameters = new List(4); + if(parentId != -1) parameters.Add(new Parameter(ParameterType.Int16, "NewParent", parentId)); + else parameters.Add(new Parameter(ParameterType.Int16, "NewParent", DBNull.Value)); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.Int16, "OldParent", (short)id)); + + command = builder.GetCommand(transaction, query, parameters); + + rows = ExecuteNonQuery(command, false); + } + + return rows > 0; + } + + /// + /// Removes a Message. + /// + /// The Page. + /// The ID of the Message to remove. + /// A value specifying whether or not to remove the replies. + /// True if the Message is removed successfully. + /// If is null. + /// If is less than zero. + public bool RemoveMessage(PageInfo page, int id, bool removeReplies) { + if(page == null) throw new ArgumentNullException("page"); + if(id < 0) throw new ArgumentOutOfRangeException("id", "Invalid ID"); + + // 1. If removeReplies, recursively delete all messages with parent == id + // Else remove current message, updating all replies' parent id (set to this message's parent or to NULL) + // 2. If removeReplies, unindex the whole message tree + // Else unindex only this message + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + bool done = RemoveMessage(transaction, page, id, removeReplies); + + if(done) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return done; + } + + /// + /// Modifies a Message. + /// + /// The Page. + /// The ID of the Message to modify. + /// The Username. + /// The Subject. + /// The Date/Time. + /// The Body. + /// True if the Message is modified successfully. + /// If , , or are null. + /// If is less than zero. + /// If or are empty. + public bool ModifyMessage(PageInfo page, int id, string username, string subject, DateTime dateTime, string body) { + if(page == null) throw new ArgumentNullException("page"); + if(id < 0) throw new ArgumentOutOfRangeException("id", "Invalid Message ID"); + if(username == null) throw new ArgumentNullException("username"); + if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username"); + if(subject == null) throw new ArgumentNullException("subject"); + if(subject.Length == 0) throw new ArgumentException("Subject cannot be empty", "subject"); + if(body == null) throw new ArgumentNullException("body"); // body can be empty + + string nspace, name; + NameTools.ExpandFullName(page.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + Message[] messages = GetMessages(transaction, page); + if(messages == null) { + RollbackTransaction(transaction); + return false; + } + Message oldMessage = FindMessage(messages, id); + + if(oldMessage == null) { + RollbackTransaction(transaction); + return false; + } + + UnindexMessage(page, oldMessage.ID, oldMessage.Subject, oldMessage.DateTime, oldMessage.Body, transaction); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.Update("Message", new string[] { "Username", "Subject", "DateTime", "Body" }, new string[] { "Username", "Subject", "DateTime", "Body" }); + query = queryBuilder.Where(query, "Page", WhereOperator.Equals, "Page"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.AndWhere(query, "Id", WhereOperator.Equals, "Id"); + + List parameters = new List(7); + parameters.Add(new Parameter(ParameterType.String, "Username", username)); + parameters.Add(new Parameter(ParameterType.String, "Subject", subject)); + parameters.Add(new Parameter(ParameterType.DateTime, "DateTime", dateTime)); + parameters.Add(new Parameter(ParameterType.String, "Body", body)); + parameters.Add(new Parameter(ParameterType.String, "Page", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + parameters.Add(new Parameter(ParameterType.Int16, "Id", id)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == 1) { + IndexMessage(page, id, subject, dateTime, body, transaction); + CommitTransaction(transaction); + return true; + } + else { + RollbackTransaction(transaction); + return false; + } + } + + /// + /// Gets all the Navigation Paths in a Namespace. + /// + /// The Namespace. + /// All the Navigation Paths, sorted by name. + public NavigationPath[] GetNavigationPaths(NamespaceInfo nspace) { + string nspaceName = nspace != null ? nspace.Name : ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("NavigationPath", new string[] { "Name", "Namespace", "Page" }); + query = queryBuilder.Where(query, "Namespace", WhereOperator.Equals, "Namespace"); + query = queryBuilder.OrderBy(query, new string[] { "Namespace", "Name", "Number" }, new Ordering[] { Ordering.Asc, Ordering.Asc, Ordering.Asc }); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspaceName)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(10); + + string prevName = "|||"; + string name; + string actualNamespace = ""; + List pages = new List(10); + + while(reader.Read()) { + name = reader["Name"] as string; + + if(name != prevName) { + actualNamespace = reader["Namespace"] as string; + + if(prevName != "|||") { + result[result.Count - 1].Pages = pages.ToArray(); + pages.Clear(); + } + + result.Add(new NavigationPath(NameTools.GetFullName(actualNamespace, name), this)); + } + + prevName = name; + pages.Add(NameTools.GetFullName(actualNamespace, reader["Page"] as string)); + } + + if(result.Count > 0) { + result[result.Count - 1].Pages = pages.ToArray(); + } + + CloseReader(command, reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Adds a new Navigation Path. + /// + /// A database transaction. + /// The target namespace (null for the root). + /// The Name of the Path. + /// The Pages array. + /// The correct object. + private NavigationPath AddNavigationPath(DbTransaction transaction, string nspace, string name, PageInfo[] pages) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query, finalQuery = ""; + List parameters = new List(3 * pages.Length); + int count = 0; + string countString; + + foreach(PageInfo page in pages) { + countString = count.ToString(); + + query = queryBuilder.InsertInto("NavigationPath", new string[] { "Name", "Namespace", "Page", "Number" }, + new string[] { "Name" + countString, "Namespace" + countString, "Page" + countString, "Number" + countString }); + + parameters.Add(new Parameter(ParameterType.String, "Name" + countString, name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace" + countString, nspace)); + parameters.Add(new Parameter(ParameterType.String, "Page" + countString, NameTools.GetLocalName(page.FullName))); + parameters.Add(new Parameter(ParameterType.Int32, "Number" + countString, (short)count)); + + finalQuery = queryBuilder.AppendForBatch(finalQuery, query); + + count++; + } + + DbCommand command = builder.GetCommand(transaction, finalQuery, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == pages.Length) { + NavigationPath result = new NavigationPath(NameTools.GetFullName(nspace, name), this); + result.Pages = Array.ConvertAll(pages, (x) => { return x.FullName; }); + return result; + } + else return null; + } + + /// + /// Adds a new Navigation Path. + /// + /// The target namespace (null for the root). + /// The Name of the Path. + /// The Pages array. + /// The correct object. + /// If or are null. + /// If or are empty. + public NavigationPath AddNavigationPath(string nspace, string name, PageInfo[] pages) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(pages == null) throw new ArgumentNullException("pages"); + if(pages.Length == 0) throw new ArgumentException("Pages cannot be empty"); + + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + foreach(PageInfo page in pages) { + if(page == null) { + RollbackTransaction(transaction); + throw new ArgumentNullException("pages"); + } + if(GetPage(transaction, page.FullName) == null) { + RollbackTransaction(transaction); + throw new ArgumentException("Page not found", "pages"); + } + } + + NavigationPath path = AddNavigationPath(transaction, nspace, name, pages); + + if(path != null) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return path; + } + + /// + /// Modifies an existing navigation path. + /// + /// The navigation path to modify. + /// The new pages array. + /// The correct object. + /// If or are null. + /// If is empty. + public NavigationPath ModifyNavigationPath(NavigationPath path, PageInfo[] pages) { + if(path == null) throw new ArgumentNullException("path"); + if(pages == null) throw new ArgumentNullException("pages"); + if(pages.Length == 0) throw new ArgumentException("Pages cannot be empty"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + foreach(PageInfo page in pages) { + if(page == null) { + RollbackTransaction(transaction); + throw new ArgumentNullException("pages"); + } + if(GetPage(transaction, page.FullName) == null) { + RollbackTransaction(transaction); + throw new ArgumentException("Page not found", "pages"); + } + } + + if(RemoveNavigationPath(transaction, path)) { + string nspace, name; + NameTools.ExpandFullName(path.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + NavigationPath result = AddNavigationPath(transaction, nspace, name, pages); + + if(result != null) { + CommitTransaction(transaction); + return result; + } + else { + RollbackTransaction(transaction); + return null; + } + } + else { + RollbackTransaction(transaction); + return null; + } + } + + /// + /// Removes a Navigation Path. + /// + /// A database transaction. + /// The navigation path to remove. + /// true if the path is removed, false otherwise. + private bool RemoveNavigationPath(DbTransaction transaction, NavigationPath path) { + string nspace, name; + NameTools.ExpandFullName(path.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("NavigationPath"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + return rows > 0; + } + + /// + /// Removes a Navigation Path. + /// + /// A database connection. + /// The navigation path to remove. + /// true if the path is removed, false otherwise. + private bool RemoveNavigationPath(DbConnection connection, NavigationPath path) { + string nspace, name; + NameTools.ExpandFullName(path.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("NavigationPath"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Namespace", WhereOperator.Equals, "Namespace"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Namespace", nspace)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + return rows > 0; + } + + /// + /// Removes a Navigation Path. + /// + /// The navigation path to remove. + /// true if the path is removed, false otherwise. + /// If is null. + public bool RemoveNavigationPath(NavigationPath path) { + if(path == null) throw new ArgumentNullException("path"); + + string nspace, name; + NameTools.ExpandFullName(path.FullName, out nspace, out name); + if(nspace == null) nspace = ""; + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + bool removed = RemoveNavigationPath(connection, path); + CloseConnection(connection); + + return removed; + } + + /// + /// Gets all the snippets. + /// + /// All the snippets, sorted by name. + public Snippet[] GetSnippets() { + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).SelectFrom("Snippet"); + + DbCommand command = builder.GetCommand(connString, query, new List()); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(10); + + while(reader.Read()) { + result.Add(new Snippet(reader["Name"] as string, reader["Content"] as string, this)); + } + + CloseReader(command, reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Adds a new snippet. + /// + /// A database transaction. + /// The name of the snippet. + /// The content of the snippet. + /// The correct object. + private Snippet AddSnippet(DbTransaction transaction, string name, string content) { + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).InsertInto("Snippet", new string[] { "Name", "Content" }, new string[] { "Name", "Content" }); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Content", content)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == 1) { + return new Snippet(name, content, this); + } + else return null; + } + + /// + /// Adds a new snippet. + /// + /// A database connection. + /// The name of the snippet. + /// The content of the snippet. + /// The correct object. + private Snippet AddSnippet(DbConnection connection, string name, string content) { + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).InsertInto("Snippet", new string[] { "Name", "Content" }, new string[] { "Name", "Content" }); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Content", content)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == 1) { + return new Snippet(name, content, this); + } + else return null; + } + + /// + /// Adds a new snippet. + /// + /// The name of the snippet. + /// The content of the snippet. + /// The correct object. + /// If or are null. + /// If is empty. + public Snippet AddSnippet(string name, string content) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(content == null) throw new ArgumentNullException("content"); // content can be empty + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + Snippet snippet = AddSnippet(connection, name, content); + CloseConnection(connection); + + return snippet; + } + + /// + /// Modifies an existing snippet. + /// + /// The name of the snippet to modify. + /// The content of the snippet. + /// The correct object. + /// If or are null. + /// If is empty. + public Snippet ModifySnippet(string name, string content) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(content == null) throw new ArgumentNullException("content"); // content can be empty + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(RemoveSnippet(transaction, name)) { + Snippet result = AddSnippet(transaction, name, content); + + if(result != null) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return result; + } + else { + RollbackTransaction(transaction); + return null; + } + } + + /// + /// Removes a new Snippet. + /// + /// A database transaction. + /// The Name of the Snippet to remove. + /// true if the snippet is removed, false otherwise. + private bool RemoveSnippet(DbTransaction transaction, string name) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("Snippet"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + return rows == 1; + } + + /// + /// Removes a new Snippet. + /// + /// A database connection. + /// The Name of the Snippet to remove. + /// true if the snippet is removed, false otherwise. + private bool RemoveSnippet(DbConnection connection, string name) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("Snippet"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + return rows == 1; + } + + /// + /// Removes a new Snippet. + /// + /// The Name of the Snippet to remove. + /// true if the snippet is removed, false otherwise. + /// If is null. + /// If is empty. + public bool RemoveSnippet(string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + bool removed = RemoveSnippet(connection, name); + CloseConnection(connection); + + return removed; + } + + /// + /// Gets all the content templates. + /// + /// All the content templates, sorted by name. + public ContentTemplate[] GetContentTemplates() { + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).SelectFrom("ContentTemplate"); + + DbCommand command = builder.GetCommand(connString, query, new List()); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(10); + + while(reader.Read()) { + result.Add(new ContentTemplate(reader["Name"] as string, reader["Content"] as string, this)); + } + + CloseReader(command, reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Adds a new content template. + /// + /// A database transaction. + /// The name of template. + /// The content of the template. + /// The correct object. + private ContentTemplate AddContentTemplate(DbTransaction transaction, string name, string content) { + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).InsertInto("ContentTemplate", new string[] { "Name", "Content" }, new string[] { "Name", "Content" }); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Content", content)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == 1) { + return new ContentTemplate(name, content, this); + } + else return null; + } + + /// + /// Adds a new content template. + /// + /// A database connection. + /// The name of template. + /// The content of the template. + /// The correct object. + private ContentTemplate AddContentTemplate(DbConnection connection, string name, string content) { + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).InsertInto("ContentTemplate", new string[] { "Name", "Content" }, new string[] { "Name", "Content" }); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Content", content)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == 1) { + return new ContentTemplate(name, content, this); + } + else return null; + } + + /// + /// Adds a new content template. + /// + /// The name of template. + /// The content of the template. + /// The correct object. + /// If or are null. + /// If is empty. + public ContentTemplate AddContentTemplate(string name, string content) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(content == null) throw new ArgumentNullException("content"); // content can be empty + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + ContentTemplate template = AddContentTemplate(connection, name, content); + CloseConnection(connection); + + return template; + } + + /// + /// Modifies an existing content template. + /// + /// The name of the template to modify. + /// The content of the template. + /// The correct object. + /// If or are null. + /// If is empty. + public ContentTemplate ModifyContentTemplate(string name, string content) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(content == null) throw new ArgumentNullException("content"); // content can be empty + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(RemoveContentTemplate(transaction, name)) { + ContentTemplate template = AddContentTemplate(transaction, name, content); + + if(template != null) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return template; + } + else { + RollbackTransaction(transaction); + return null; + } + } + + /// + /// Removes a content template. + /// + /// A database transaction. + /// The name of the template to remove. + /// true if the template is removed, false otherwise. + private bool RemoveContentTemplate(DbTransaction transaction, string name) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("ContentTemplate"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + return rows == 1; + } + + /// + /// Removes a content template. + /// + /// A database connection. + /// The name of the template to remove. + /// true if the template is removed, false otherwise. + private bool RemoveContentTemplate(DbConnection connection, string name) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("ContentTemplate"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + return rows == 1; + } + + /// + /// Removes a content template. + /// + /// The name of the template to remove. + /// true if the template is removed, false otherwise. + /// If is null. + /// If is empty. + public bool RemoveContentTemplate(string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + bool removed = RemoveContentTemplate(connection, name); + CloseConnection(connection); + + return removed; + } + + #endregion + + #region IStorageProvider Members + + /// + /// Gets a value specifying whether the provider is read-only, i.e. it can only provide data and not store it. + /// + public bool ReadOnly { + get { return false; } + } + + #endregion + + } + +} diff --git a/SqlProvidersCommon/SqlProvidersCommon.csproj b/SqlProvidersCommon/SqlProvidersCommon.csproj new file mode 100644 index 0000000..56b66f8 --- /dev/null +++ b/SqlProvidersCommon/SqlProvidersCommon.csproj @@ -0,0 +1,89 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {617D5D30-97F9-48B2-903D-29D4524492E8} + Library + Properties + ScrewTurn.Wiki.Plugins.SqlCommon + ScrewTurn.Wiki.Plugins.SqlCommon + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + bin\Debug\ScrewTurn.Wiki.Plugins.SqlCommon.XML + + + none + true + bin\Release\ + TRACE + prompt + 4 + true + bin\Release\ScrewTurn.Wiki.Plugins.SqlCommon.xml + + + + + 3.5 + + + + + + + + AssemblyVersion.cs + + + + + + + + + + + + + + + + + + + + + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81} + AclEngine + + + {531A83D6-76F9-4014-91C5-295818E2D948} + PluginFramework + + + {2DF980A6-4742-49B1-A090-DE79314644D0} + SearchEngine + + + + + \ No newline at end of file diff --git a/SqlProvidersCommon/SqlSettingsStorageProviderBase.cs b/SqlProvidersCommon/SqlSettingsStorageProviderBase.cs new file mode 100644 index 0000000..4056edb --- /dev/null +++ b/SqlProvidersCommon/SqlSettingsStorageProviderBase.cs @@ -0,0 +1,1419 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.AclEngine; +using System.Data.Common; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon { + + /// + /// Implements a base class for a SQL settings storage provider. + /// + public abstract class SqlSettingsStorageProviderBase : SqlStorageProviderBase, ISettingsStorageProviderV30 { + + private const int EstimatedLogEntrySize = 100; // bytes + private const int MaxAssemblySize = 5242880; // 5 MB + private const int MaxParametersInQuery = 50; + + private IAclManager aclManager; + private AclStorerBase aclStorer; + + /// + /// Initializes the Storage Provider. + /// + /// The Host of the Component. + /// The Configuration data, if any. + /// If the configuration string is not valid, the methoud should throw a . + public new void Init(IHostV30 host, string config) { + base.Init(host, config); + + aclManager = new StandardAclManager(); + aclStorer = new SqlAclStorer(aclManager, LoadDataInternal, DeleteEntries, StoreEntries); + aclStorer.LoadData(); + } + + /// + /// Gets the default users storage provider, when no value is stored in the database. + /// + protected abstract string DefaultUsersStorageProvider { get; } + + /// + /// Gets the default pages storage provider, when no value is stored in the database. + /// + protected abstract string DefaultPagesStorageProvider { get; } + + /// + /// Gets the default files storage provider, when no value is stored in the database. + /// + protected abstract string DefaultFilesStorageProvider { get; } + + #region ISettingsStorageProvider Members + + /// + /// Retrieves the value of a Setting. + /// + /// The name of the Setting. + /// The value of the Setting, or null. + /// If name is null. + /// If name is empty. + public string GetSetting(string name) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Setting"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + string result = null; + + if(reader.Read()) { + result = reader["Value"] as string; + } + + CloseReader(command, reader); + + // HACK: this allows to correctly initialize a fully SQL-based wiki instance without any user intervention + if(string.IsNullOrEmpty(result)) { + if(name == "DefaultUsersProvider") result = DefaultUsersStorageProvider; + if(name == "DefaultPagesProvider") result = DefaultPagesStorageProvider; + if(name == "DefaultFilesProvider") result = DefaultFilesStorageProvider; + } + + return result; + } + else return null; + } + + /// + /// Stores the value of a Setting. + /// + /// The name of the Setting. + /// The value of the Setting. Value cannot contain CR and LF characters, which will be removed. + /// True if the Setting is stored, false otherwise. + /// This method stores the Value immediately. + public bool SetSetting(string name, string value) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + + // 1. Delete old value, if any + // 2. Store new value + + // Nulls are converted to empty strings + if(value == null) value = ""; + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("Setting"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == -1) { + RollbackTransaction(transaction); + return false; // Deletion command failed (0-1 are OK) + } + + query = queryBuilder.InsertInto("Setting", + new string[] { "Name", "Value" }, new string[] { "Name", "Value" }); + parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Value", value)); + + command = builder.GetCommand(transaction, query, parameters); + + rows = ExecuteNonQuery(command, false); + + if(rows == 1) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows == 1; + } + + /// + /// Gets the all the setting values. + /// + /// All the settings. + public IDictionary GetAllSettings() { + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).SelectFrom("Setting"); + + DbCommand command = builder.GetCommand(connString, query, new List()); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + Dictionary result = new Dictionary(50); + + while(reader.Read()) { + result.Add(reader["Name"] as string, reader["Value"] as string); + } + + CloseReader(command, reader); + + return result; + } + else return null; + } + + /// + /// Starts a Bulk update of the Settings so that a bulk of settings can be set before storing them. + /// + public void BeginBulkUpdate() { + // Do nothing - currently not supported + } + + /// + /// Ends a Bulk update of the Settings and stores the settings. + /// + public void EndBulkUpdate() { + // Do nothing - currently not supported + } + + /// + /// Converts an to its character representation. + /// + /// The . + /// Th haracter representation. + private static char EntryTypeToChar(EntryType type) { + switch(type) { + case EntryType.Error: + return 'E'; + case EntryType.Warning: + return 'W'; + case EntryType.General: + return 'G'; + default: + throw new NotSupportedException(); + } + } + + /// + /// Converts the character representation of an back to the enumeration value. + /// + /// The character representation. + /// The. + private static EntryType EntryTypeFromChar(char c) { + switch(char.ToUpperInvariant(c)) { + case 'E': + return EntryType.Error; + case 'W': + return EntryType.Warning; + case 'G': + return EntryType.General; + default: + throw new NotSupportedException(); + } + } + + /// + /// Records a message to the System Log. + /// + /// The Log Message. + /// The Type of the Entry. + /// The User. + /// This method should not write messages to the Log using the method IHost.LogEntry. + /// This method should also never throw exceptions (except for parameter validation). + /// If message or user are null. + /// If message or user are empty. + public void LogEntry(string message, EntryType entryType, string user) { + if(message == null) throw new ArgumentNullException("message"); + if(message.Length == 0) throw new ArgumentException("Message cannot be empty", "message"); + if(user == null) throw new ArgumentNullException("user"); + if(user.Length == 0) throw new ArgumentException("User cannot be empty", "user"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.InsertInto("Log", + new string[] { "DateTime", "EntryType", "User", "Message" }, new string[] { "DateTime", "EntryType", "User", "Message" }); + + List parameters = new List(4); + parameters.Add(new Parameter(ParameterType.DateTime, "DateTime", DateTime.Now)); + parameters.Add(new Parameter(ParameterType.Char, "EntryType", EntryTypeToChar(entryType))); + parameters.Add(new Parameter(ParameterType.String, "User", user)); + parameters.Add(new Parameter(ParameterType.String, "Message", message)); + + try { + DbCommand command = builder.GetCommand(connString, query, parameters); + + ExecuteNonQuery(command, true); + + // No transaction - accurate log sizing is not really a concern + + int logSize = LogSize; + if(logSize > int.Parse(host.GetSettingValue(SettingName.MaxLogSize))) { + CutLog((int)(logSize * 0.75)); + } + } + catch { } + } + + /// + /// Reduces the size of the Log to the specified size (or less). + /// + /// The size to shrink the log to (in bytes). + private void CutLog(int size) { + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectCountFrom("Log"); + + DbCommand command = builder.GetCommand(transaction, query, new List()); + + int rows = ExecuteScalar(command, -1, false); + + if(rows == -1) { + RollbackTransaction(transaction); + return; + } + + int estimatedSize = rows * EstimatedLogEntrySize; + + if(size < estimatedSize) { + + int difference = estimatedSize - size; + int entriesToDelete = difference / EstimatedLogEntrySize; + // Add 10% to avoid 1-by-1 deletion when adding new entries + entriesToDelete += entriesToDelete / 10; + + if(entriesToDelete > 0) { + // This code is not optimized, but it surely works in most DBMS + query = queryBuilder.SelectFrom("Log", new string[] { "Id" }); + query = queryBuilder.OrderBy(query, new string[] { "Id" }, new Ordering[] { Ordering.Asc }); + + command = builder.GetCommand(transaction, query, new List()); + + DbDataReader reader = ExecuteReader(command); + + List ids = new List(entriesToDelete); + + if(reader != null) { + while(reader.Read() && ids.Count < entriesToDelete) { + ids.Add((int)reader["Id"]); + } + + CloseReader(reader); + } + + if(ids.Count > 0) { + // Given that the IDs to delete can be many, the query is split in many chunks, each one deleting 50 items + // This works-around the problem of too many parameters in a RPC call of SQL Server + // See also CutRecentChangesIfNecessary + + for(int chunk = 0; chunk <= ids.Count / MaxParametersInQuery; chunk++) { + query = queryBuilder.DeleteFrom("Log"); + List parms = new List(MaxParametersInQuery); + List parameters = new List(MaxParametersInQuery); + + for(int i = chunk * MaxParametersInQuery; i < Math.Min(ids.Count, (chunk + 1) * MaxParametersInQuery); i++) { + parms.Add("P" + i.ToString()); + parameters.Add(new Parameter(ParameterType.Int32, parms[parms.Count - 1], ids[i])); + } + + query = queryBuilder.WhereIn(query, "Id", parms.ToArray()); + + command = builder.GetCommand(transaction, query, parameters); + + if(ExecuteNonQuery(command, false) < 0) { + RollbackTransaction(transaction); + return; + } + } + } + + CommitTransaction(transaction); + } + } + } + + /// + /// Gets all the Log Entries, sorted by date/time (oldest to newest). + /// + /// The Log Entries. + public LogEntry[] GetLogEntries() { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("Log", new string[] { "DateTime", "EntryType", "User", "Message" }); + query = queryBuilder.OrderBy(query, new string[] { "DateTime" }, new Ordering[] { Ordering.Asc }); + + DbCommand command = builder.GetCommand(connString, query, new List()); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(100); + + while(reader.Read()) { + result.Add(new LogEntry(EntryTypeFromChar((reader["EntryType"] as string)[0]), + (DateTime)reader["DateTime"], reader["Message"] as string, reader["User"] as string)); + } + + CloseReader(command, reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Clear the Log. + /// + public void ClearLog() { + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).DeleteFrom("Log"); + + DbCommand command = builder.GetCommand(connString, query, new List()); + + ExecuteNonQuery(command); + } + + /// + /// Gets the current size of the Log, in KB. + /// + public int LogSize { + get { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectCountFrom("Log"); + + DbCommand command = builder.GetCommand(connString, query, new List()); + + int rows = ExecuteScalar(command, -1); + + if(rows == -1) return 0; + + int estimatedSize = rows * EstimatedLogEntrySize; + + return estimatedSize / 1024; + } + } + + /// + /// Gets a meta-data item's content. + /// + /// The item. + /// The tag that specifies the context (usually the namespace). + /// The content. + public string GetMetaDataItem(MetaDataItem item, string tag) { + if(tag == null) tag = ""; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("MetaDataItem", new string[] { "Data" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Tag", WhereOperator.Equals, "Tag"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", item.ToString())); + parameters.Add(new Parameter(ParameterType.String, "Tag", tag)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + string value = ExecuteScalar(command, ""); + + return value; + } + + /// + /// Sets a meta-data items' content. + /// + /// The item. + /// The tag that specifies the context (usually the namespace). + /// The content. + /// true if the content is set, false otherwise. + public bool SetMetaDataItem(MetaDataItem item, string tag, string content) { + if(tag == null) tag = ""; + if(content == null) content = ""; + + // 1. Delete old value, if any + // 2. Store new value + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("MetaDataItem"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + query = queryBuilder.AndWhere(query, "Tag", WhereOperator.Equals, "Tag"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", item.ToString())); + parameters.Add(new Parameter(ParameterType.String, "Tag", tag)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == -1) { + RollbackTransaction(transaction); + return false; + } + + query = queryBuilder.InsertInto("MetaDataItem", new string[] { "Name", "Tag", "Data" }, new string[] { "Name", "Tag", "Content" }); + + parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Name", item.ToString())); + parameters.Add(new Parameter(ParameterType.String, "Tag", tag)); + parameters.Add(new Parameter(ParameterType.String, "Content", content)); + + command = builder.GetCommand(transaction, query, parameters); + + rows = ExecuteNonQuery(command, false); + + if(rows == 1) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows == 1; + } + + /// + /// Converts a to its character representation. + /// + /// The . + /// The character representation. + private static char RecentChangeToChar(ScrewTurn.Wiki.PluginFramework.Change change) { + switch(change) { + case ScrewTurn.Wiki.PluginFramework.Change.MessageDeleted: + return 'M'; + case ScrewTurn.Wiki.PluginFramework.Change.MessageEdited: + return 'E'; + case ScrewTurn.Wiki.PluginFramework.Change.MessagePosted: + return 'P'; + case ScrewTurn.Wiki.PluginFramework.Change.PageDeleted: + return 'D'; + case ScrewTurn.Wiki.PluginFramework.Change.PageRenamed: + return 'N'; + case ScrewTurn.Wiki.PluginFramework.Change.PageRolledBack: + return 'R'; + case ScrewTurn.Wiki.PluginFramework.Change.PageUpdated: + return 'U'; + default: + throw new NotSupportedException(); + } + } + + /// + /// Converts a character representation of a back to the enum value. + /// + /// The character representation. + /// The . + private static ScrewTurn.Wiki.PluginFramework.Change RecentChangeFromChar(char c) { + switch(char.ToUpperInvariant(c)) { + case 'M': + return ScrewTurn.Wiki.PluginFramework.Change.MessageDeleted; + case 'E': + return ScrewTurn.Wiki.PluginFramework.Change.MessageEdited; + case 'P': + return ScrewTurn.Wiki.PluginFramework.Change.MessagePosted; + case 'D': + return ScrewTurn.Wiki.PluginFramework.Change.PageDeleted; + case 'N': + return ScrewTurn.Wiki.PluginFramework.Change.PageRenamed; + case 'R': + return ScrewTurn.Wiki.PluginFramework.Change.PageRolledBack; + case 'U': + return ScrewTurn.Wiki.PluginFramework.Change.PageUpdated; + default: + throw new NotSupportedException(); + } + } + + /// + /// Gets the recent changes of the Wiki. + /// + /// The recent Changes, oldest to newest. + public RecentChange[] GetRecentChanges() { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("RecentChange", new string[] { "Page", "Title", "MessageSubject", "DateTime", "User", "Change", "Description" }); + query = queryBuilder.OrderBy(query, new string[] { "DateTime" }, new Ordering[] { Ordering.Asc }); + + DbCommand command = builder.GetCommand(connString, query, new List()); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(100); + + while(reader.Read()) { + result.Add(new RecentChange(reader["Page"] as string, reader["Title"] as string, + GetNullableColumn(reader, "MessageSubject", ""), + (DateTime)reader["DateTime"], reader["User"] as string, RecentChangeFromChar(((string)reader["Change"])[0]), + GetNullableColumn(reader, "Description", ""))); + } + + CloseReader(command, reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Adds a new change. + /// + /// The page name. + /// The page title. + /// The message subject (or null). + /// The date/time. + /// The user. + /// The change. + /// The description (optional). + /// true if the change is saved, false otherwise. + /// If page, title or user are null. + /// If page, title or user are empty. + public bool AddRecentChange(string page, string title, string messageSubject, DateTime dateTime, string user, ScrewTurn.Wiki.PluginFramework.Change change, string descr) { + if(page == null) throw new ArgumentNullException("page"); + if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); + if(title == null) throw new ArgumentNullException("title"); + if(title.Length == 0) throw new ArgumentException("Title cannot be empty", "title"); + if(user == null) throw new ArgumentNullException("user"); + if(user.Length == 0) throw new ArgumentException("User cannot be empty", "user"); + + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).InsertInto("RecentChange", new string[] { "Page", "Title", "MessageSubject", "DateTime", "User", "Change", "Description" }, + new string[] { "Page", "Title", "MessageSubject", "DateTime", "User", "Change", "Description" }); + + List parameters = new List(7); + parameters.Add(new Parameter(ParameterType.String, "Page", page)); + parameters.Add(new Parameter(ParameterType.String, "Title", title)); + if(!string.IsNullOrEmpty(messageSubject)) parameters.Add(new Parameter(ParameterType.String, "MessageSubject", messageSubject)); + else parameters.Add(new Parameter(ParameterType.String, "MessageSubject", DBNull.Value)); + parameters.Add(new Parameter(ParameterType.DateTime, "DateTime", dateTime)); + parameters.Add(new Parameter(ParameterType.String, "User", user)); + parameters.Add(new Parameter(ParameterType.Char, "Change", RecentChangeToChar(change))); + if(!string.IsNullOrEmpty(descr)) parameters.Add(new Parameter(ParameterType.String, "Description", descr)); + else parameters.Add(new Parameter(ParameterType.String, "Description", DBNull.Value)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + int rows = ExecuteNonQuery(command); + + CutRecentChangesIfNecessary(); + + return rows == 1; + } + + /// + /// Cuts the recent changes if necessary. + /// + private void CutRecentChangesIfNecessary() { + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectCountFrom("RecentChange"); + + DbCommand command = builder.GetCommand(transaction, query, new List()); + + int rows = ExecuteScalar(command, -1, false); + + int maxChanges = int.Parse(host.GetSettingValue(SettingName.MaxRecentChanges)); + + if(rows > maxChanges) { + // Remove 10% of old changes to avoid 1-by-1 deletion every time a change is made + int entriesToDelete = maxChanges / 10; + if(entriesToDelete > rows) entriesToDelete = rows; + //entriesToDelete += entriesToDelete / 10; + + // This code is not optimized, but it surely works in most DBMS + query = queryBuilder.SelectFrom("RecentChange", new string[] { "Id" }); + query = queryBuilder.OrderBy(query, new string[] { "Id" }, new Ordering[] { Ordering.Asc }); + + command = builder.GetCommand(transaction, query, new List()); + + DbDataReader reader = ExecuteReader(command); + + List ids = new List(entriesToDelete); + + if(reader != null) { + while(reader.Read() && ids.Count < entriesToDelete) { + ids.Add((int)reader["Id"]); + } + + CloseReader(reader); + } + + if(ids.Count > 0) { + // Given that the IDs to delete can be many, the query is split in many chunks, each one deleting 50 items + // This works-around the problem of too many parameters in a RPC call of SQL Server + // See also CutLog + + for(int chunk = 0; chunk <= ids.Count / MaxParametersInQuery; chunk++) { + query = queryBuilder.DeleteFrom("RecentChange"); + List parms = new List(MaxParametersInQuery); + List parameters = new List(MaxParametersInQuery); + + for(int i = chunk * MaxParametersInQuery; i < Math.Min(ids.Count, (chunk + 1) * MaxParametersInQuery); i++) { + parms.Add("P" + i.ToString()); + parameters.Add(new Parameter(ParameterType.Int32, parms[parms.Count - 1], ids[i])); + } + + query = queryBuilder.WhereIn(query, "Id", parms.ToArray()); + + command = builder.GetCommand(transaction, query, parameters); + + if(ExecuteNonQuery(command, false) < 0) { + RollbackTransaction(transaction); + return; + } + } + } + } + + CommitTransaction(transaction); + } + + /// + /// Lists the stored plugin assemblies. + /// + public string[] ListPluginAssemblies() { + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).SelectFrom("PluginAssembly", new string[] { "Name" }); + + DbCommand command = builder.GetCommand(connString, query, new List()); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(10); + + while(reader.Read()) { + result.Add(reader["Name"] as string); + } + + CloseReader(command, reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Stores a plugin's assembly, overwriting existing ones if present. + /// + /// The file name of the assembly, such as "Assembly.dll". + /// The assembly content. + /// true if the assembly is stored, false otherwise. + /// If filename or assembly are null. + /// If filename or assembly are empty. + public bool StorePluginAssembly(string filename, byte[] assembly) { + if(filename == null) throw new ArgumentNullException("filename"); + if(filename.Length == 0) throw new ArgumentException("Filename cannot be empty", "filename"); + if(assembly == null) throw new ArgumentNullException("assembly"); + if(assembly.Length == 0) throw new ArgumentException("Assembly cannot be empty", "assembly"); + if(assembly.Length > MaxAssemblySize) throw new ArgumentException("Assembly is too big", "assembly"); + + // 1. Delete old plugin assembly, if any + // 2. Store new assembly + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + DeletePluginAssembly(transaction, filename); + + string query = QueryBuilder.NewQuery(builder).InsertInto("PluginAssembly", new string[] { "Name", "Assembly" }, new string[] { "Name", "Assembly" }); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", filename)); + parameters.Add(new Parameter(ParameterType.ByteArray, "Assembly", assembly)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == 1) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows == 1; + } + + /// + /// Retrieves a plugin's assembly. + /// + /// The file name of the assembly. + /// The assembly content, or null. + /// If filename is null. + /// If filename is empty. + public byte[] RetrievePluginAssembly(string filename) { + if(filename == null) throw new ArgumentNullException("filename"); + if(filename.Length == 0) throw new ArgumentException("Filename cannot be empty", "filename"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("PluginAssembly", new string[] { "Assembly" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Name", filename)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + byte[] result = null; + + if(reader.Read()) { + result = GetBinaryColumn(reader, "Assembly", MaxAssemblySize); + } + + CloseReader(command, reader); + + return result; + } + else return null; + } + + /// + /// Removes a plugin's assembly. + /// + /// A database transaction. + /// The file name of the assembly to remove, such as "Assembly.dll". + /// true if the assembly is removed, false otherwise. + private bool DeletePluginAssembly(DbTransaction transaction, string filename) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("PluginAssembly"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Name", filename)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + return rows == 1; + } + + /// + /// Removes a plugin's assembly. + /// + /// A database connection. + /// The file name of the assembly to remove, such as "Assembly.dll". + /// true if the assembly is removed, false otherwise. + private bool DeletePluginAssembly(DbConnection connection, string filename) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("PluginAssembly"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Name", filename)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + return rows == 1; + } + + /// + /// Removes a plugin's assembly. + /// + /// The file name of the assembly to remove, such as "Assembly.dll". + /// true if the assembly is removed, false otherwise. + /// If filename is null. + /// If filename is empty. + public bool DeletePluginAssembly(string filename) { + if(filename == null) throw new ArgumentNullException(filename); + if(filename.Length == 0) throw new ArgumentException("Filename cannot be empty", "filename"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + + bool deleted = DeletePluginAssembly(connection, filename); + CloseConnection(connection); + + return deleted; + } + + /// + /// Prepares the plugin status row, if necessary. + /// + /// A database transaction. + /// The Type name of the plugin. + private void PreparePluginStatusRow(DbTransaction transaction, string typeName) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectCountFrom("PluginStatus"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Name", typeName)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteScalar(command, -1, false); + + if(rows == -1) return; + + if(rows == 0) { + // Insert a neutral row (enabled, empty config) + + query = queryBuilder.InsertInto("PluginStatus", new string[] { "Name", "Enabled", "Configuration" }, new string[] { "Name", "Enabled", "Configuration" }); + + parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Name", typeName)); + parameters.Add(new Parameter(ParameterType.Boolean, "Enabled", true)); + parameters.Add(new Parameter(ParameterType.String, "Configuration", "")); + + command = builder.GetCommand(transaction, query, parameters); + + ExecuteNonQuery(command, false); + } + } + + /// + /// Sets the status of a plugin. + /// + /// The Type name of the plugin. + /// The plugin status. + /// true if the status is stored, false otherwise. + /// If typeName is null. + /// If typeName is empty. + public bool SetPluginStatus(string typeName, bool enabled) { + if(typeName == null) throw new ArgumentNullException("typeName"); + if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + PreparePluginStatusRow(transaction, typeName); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.Update("PluginStatus", new string[] { "Enabled" }, new string[] { "Enabled" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.Boolean, "Enabled", enabled)); + parameters.Add(new Parameter(ParameterType.String, "Name", typeName)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == 1) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows == 1; + } + + /// + /// Gets the status of a plugin. + /// + /// The Type name of the plugin. + /// The status (false for disabled, true for enabled), or true if no status is found. + /// If typeName is null. + /// If typeName is empty. + public bool GetPluginStatus(string typeName) { + if(typeName == null) throw new ArgumentNullException("typeName"); + if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("PluginStatus", new string[] { "Enabled" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Name", typeName)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + bool? enabled = null; + + if(reader != null && reader.Read()) { + if(!IsDBNull(reader, "Enabled")) enabled = (bool)reader["Enabled"]; + } + CloseReader(command, reader); + + if(enabled.HasValue) return enabled.Value; + else { + if(typeName == "ScrewTurn.Wiki.UsersStorageProvider" || + typeName == "ScrewTurn.Wiki.PagesStorageProvider" || + typeName == "ScrewTurn.Wiki.FilesStorageProvider") return false; + else return true; + } + } + + /// + /// Sets the configuration of a plugin. + /// + /// The Type name of the plugin. + /// The configuration. + /// true if the configuration is stored, false otherwise. + /// If typeName is null. + /// If typeName is empty. + public bool SetPluginConfiguration(string typeName, string config) { + if(typeName == null) throw new ArgumentNullException("typeName"); + if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName"); + + if(config == null) config = ""; + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + PreparePluginStatusRow(transaction, typeName); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.Update("PluginStatus", new string[] { "Configuration" }, new string[] { "Configuration" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Configuration", config)); + parameters.Add(new Parameter(ParameterType.String, "Name", typeName)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == 1) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows == 1; + } + + /// + /// Gets the configuration of a plugin. + /// + /// The Type name of the plugin. + /// The plugin configuration, or String.Empty. + /// If typeName is null. + /// If typeName is empty. + public string GetPluginConfiguration(string typeName) { + if(typeName == null) throw new ArgumentNullException("typeName"); + if(typeName.Length == 0) throw new ArgumentException("Type Name cannot be empty", "typeName"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("PluginStatus", new string[] { "Configuration" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Name", typeName)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + string result = ExecuteScalar(command, ""); + + return result; + } + + /// + /// Gets the ACL Manager instance. + /// + public IAclManager AclManager { + get { return aclManager; } + } + + /// + /// Converts a to its corresponding character representation. + /// + /// The . + /// The character representation. + private static char AclEntryValueToChar(Value value) { + switch(value) { + case Value.Grant: + return 'G'; + case Value.Deny: + return 'D'; + default: + throw new NotSupportedException(); + } + } + + /// + /// Converts a character representation of a back to the enum value. + /// + /// The character representation. + /// The . + private static Value AclEntryValueFromChar(char c) { + switch(char.ToUpperInvariant(c)) { + case 'G': + return Value.Grant; + case 'D': + return Value.Deny; + default: + throw new NotSupportedException(); + } + } + + /// + /// Loads data from storage. + /// + /// The loaded ACL entries. + private AclEntry[] LoadDataInternal() { + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).SelectFrom("AclEntry"); + + DbCommand command = builder.GetCommand(connString, query, new List()); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(50); + + while(reader.Read()) { + result.Add(new AclEntry(reader["Resource"] as string, reader["Action"] as string, reader["Subject"] as string, + AclEntryValueFromChar(((string)reader["Value"])[0]))); + } + + CloseReader(command, reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Deletes some entries. + /// + /// The entries to delete. + private void DeleteEntries(AclEntry[] entries) { + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + foreach(AclEntry entry in entries) { + string query = queryBuilder.DeleteFrom("AclEntry"); + query = queryBuilder.Where(query, "Resource", WhereOperator.Equals, "Resource"); + query = queryBuilder.AndWhere(query, "Action", WhereOperator.Equals, "Action"); + query = queryBuilder.AndWhere(query, "Subject", WhereOperator.Equals, "Subject"); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Resource", entry.Resource)); + parameters.Add(new Parameter(ParameterType.String, "Action", entry.Action)); + parameters.Add(new Parameter(ParameterType.String, "Subject", entry.Subject)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + if(ExecuteNonQuery(command, false) != 1) { + RollbackTransaction(transaction); + return; + } + } + + CommitTransaction(transaction); + } + + /// + /// Stores some entries. + /// + /// The entries to store. + private void StoreEntries(AclEntry[] entries) { + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + foreach(AclEntry entry in entries) { + string query = queryBuilder.InsertInto("AclEntry", new string[] { "Resource", "Action", "Subject", "Value" }, new string[] { "Resource", "Action", "Subject", "Value" }); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Resource", entry.Resource)); + parameters.Add(new Parameter(ParameterType.String, "Action", entry.Action)); + parameters.Add(new Parameter(ParameterType.String, "Subject", entry.Subject)); + parameters.Add(new Parameter(ParameterType.Char, "Value", AclEntryValueToChar(entry.Value))); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + if(ExecuteNonQuery(command, false) != 1) { + RollbackTransaction(transaction); + return; + } + } + + CommitTransaction(transaction); + } + + /// + /// Stores the outgoing links of a page, overwriting existing data. + /// + /// The full name of the page. + /// The full names of the pages that page links to. + /// true if the outgoing links are stored, false otherwise. + /// If page or outgoingLinks are null. + /// If page or outgoingLinks are empty. + public bool StoreOutgoingLinks(string page, string[] outgoingLinks) { + if(page == null) throw new ArgumentNullException("page"); + if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); + if(outgoingLinks == null) throw new ArgumentNullException("outgoingLinks"); + + foreach(string link in outgoingLinks) { + if(link == null) throw new ArgumentNullException("outgoingLinks"); + if(link.Length == 0) throw new ArgumentException("Link cannot be empty", "outgoingLinks"); + } + + // 1. Delete old values, if any + // 2. Store new values + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("OutgoingLink"); + query = queryBuilder.Where(query, "Source", WhereOperator.Equals, "Source"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Source", page)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + if(ExecuteNonQuery(command, false) < 0) { + RollbackTransaction(transaction); + return false; + } + + foreach(string link in outgoingLinks) { + query = queryBuilder.InsertInto("OutgoingLink", new string[] { "Source", "Destination" }, new string[] { "Source", "Destination" }); + + parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Source", page)); + parameters.Add(new Parameter(ParameterType.String, "Destination", link)); + + command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows != 1) { + RollbackTransaction(transaction); + return false; + } + } + + CommitTransaction(transaction); + return true; + } + + /// + /// Gets the outgoing links of a page. + /// + /// The full name of the page. + /// The outgoing links. + /// If page is null. + /// If page is empty. + public string[] GetOutgoingLinks(string page) { + if(page == null) throw new ArgumentNullException("page"); + if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("OutgoingLink", new string[] { "Destination" }); + query = queryBuilder.Where(query, "Source", WhereOperator.Equals, "Source"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Source", page)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(20); + + while(reader.Read()) { + result.Add(reader["Destination"] as string); + } + + CloseReader(command, reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Gets all the outgoing links stored. + /// + /// The outgoing links, in a dictionary in the form page->outgoing_links. + public IDictionary GetAllOutgoingLinks() { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + // Explicit columns in order to allow usage of GROUP BY + string query = queryBuilder.SelectFrom("OutgoingLink", new string[] { "Source", "Destination" }); + query = queryBuilder.GroupBy(query, new string[] { "Source", "Destination" }); + + DbCommand command = builder.GetCommand(connString, query, new List()); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + Dictionary result = new Dictionary(100); + + string prevSource = "|||"; + string source = null; + List destinations = new List(20); + + while(reader.Read()) { + source = reader["Source"] as string; + + if(source != prevSource) { + if(prevSource != "|||") { + result.Add(prevSource, destinations.ToArray()); + destinations.Clear(); + } + } + + prevSource = source; + destinations.Add(reader["Destination"] as string); + } + + result.Add(prevSource, destinations.ToArray()); + + CloseReader(command, reader); + + return result; + } + else return null; + } + + /// + /// Deletes the outgoing links of a page and all the target links that include the page. + /// + /// The full name of the page. + /// true if the links are deleted, false otherwise. + /// If page is null. + /// If page is empty. + public bool DeleteOutgoingLinks(string page) { + if(page == null) throw new ArgumentNullException("page"); + if(page.Length == 0) throw new ArgumentException("Page cannot be empty", "page"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("OutgoingLink"); + query = queryBuilder.Where(query, "Source", WhereOperator.Equals, "Source"); + query = queryBuilder.OrWhere(query, "Destination", WhereOperator.Equals, "Destination"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Source", page)); + parameters.Add(new Parameter(ParameterType.String, "Destination", page)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + int rows = ExecuteNonQuery(command); + + return rows > 0; + } + + /// + /// Updates all outgoing links data for a page rename. + /// + /// The old page name. + /// The new page name. + /// true if the data is updated, false otherwise. + /// If oldName or newName are null. + /// If oldName or newName are empty. + public bool UpdateOutgoingLinksForRename(string oldName, string newName) { + if(oldName == null) throw new ArgumentNullException("oldName"); + if(oldName.Length == 0) throw new ArgumentException("Old Name cannot be empty", "oldName"); + if(newName == null) throw new ArgumentNullException("newName"); + if(newName.Length == 0) throw new ArgumentException("New Name cannot be empty", "newName"); + + // 1. Rename sources + // 2. Rename destinations + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.Update("OutgoingLink", new string[] { "Source" }, new string[] { "NewSource" }); + query = queryBuilder.Where(query, "Source", WhereOperator.Equals, "OldSource"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "NewSource", newName)); + parameters.Add(new Parameter(ParameterType.String, "OldSource", oldName)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == -1) { + RollbackTransaction(transaction); + return false; + } + + bool somethingUpdated = rows > 0; + + query = queryBuilder.Update("OutgoingLink", new string[] { "Destination" }, new string[] { "NewDestination" }); + query = queryBuilder.Where(query, "Destination", WhereOperator.Equals, "OldDestination"); + + parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "NewDestination", newName)); + parameters.Add(new Parameter(ParameterType.String, "OldDestination", oldName)); + + command = builder.GetCommand(transaction, query, parameters); + + rows = ExecuteNonQuery(command, false); + + if(rows >= 0) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return somethingUpdated || rows > 0; + } + + #endregion + + } + +} diff --git a/SqlProvidersCommon/SqlStorageProviderBase.cs b/SqlProvidersCommon/SqlStorageProviderBase.cs new file mode 100644 index 0000000..bad68bb --- /dev/null +++ b/SqlProvidersCommon/SqlStorageProviderBase.cs @@ -0,0 +1,125 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Data.Common; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon { + + /// + /// Implements a base class for a SQL storage provider. + /// + public abstract class SqlStorageProviderBase : SqlClassBase { + + /// + /// The connection string. + /// + protected string connString; + + /// + /// The host. + /// + protected IHostV30 host; + + /// + /// Gets a new command builder object. + /// + /// The command builder. + protected abstract ICommandBuilder GetCommandBuilder(); + + /// + /// Logs an exception. + /// + /// The exception. + protected override void LogException(Exception ex) { + try { + host.LogEntry(ex.ToString(), LogEntryType.Error, null, this); + } + catch { } + } + + #region IProvider Members + + /// + /// Validates a connection string. + /// + /// The connection string to validate. + /// If the connection string is invalid, the method throws . + protected abstract void ValidateConnectionString(string connString); + + /// + /// Creates or updates the database schema if necessary. + /// + protected abstract void CreateOrUpdateDatabaseIfNecessary(); + + /// + /// Tries to load the configuration from a corresponding v2 provider. + /// + /// The configuration, or an empty string. + protected abstract string TryLoadV2Configuration(); + + /// + /// Tries to load the configuration of the corresponding settings storage provider. + /// + /// The configuration, or an empty string. + protected abstract string TryLoadSettingsStorageProviderConfiguration(); + + /// + /// Initializes the Storage Provider. + /// + /// The Host of the Component. + /// The Configuration data, if any. + /// If host or config are null. + /// If config is not valid or is incorrect. + public void Init(IHostV30 host, string config) { + if(host == null) throw new ArgumentNullException("host"); + if(config == null) throw new ArgumentNullException("config"); + + this.host = host; + + if(config.Length == 0) { + // Try to load v2 provider configuration + config = TryLoadV2Configuration(); + } + + if(config == null || config.Length == 0) { + // Try to load Settings Storage Provider configuration + config = TryLoadSettingsStorageProviderConfiguration(); + } + + if(config == null) config = ""; + + ValidateConnectionString(config); + + connString = config; + + CreateOrUpdateDatabaseIfNecessary(); + } + + /// + /// Method invoked on shutdown. + /// + /// This method might not be invoked in some cases. + public void Shutdown() { + } + + /// + /// Gets the Information about the Provider. + /// + public abstract ComponentInformation Information { + get; + } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + public abstract string ConfigHelpHtml { + get; + } + + #endregion + + } + +} diff --git a/SqlProvidersCommon/SqlUsersStorageProviderBase.cs b/SqlProvidersCommon/SqlUsersStorageProviderBase.cs new file mode 100644 index 0000000..40e87b2 --- /dev/null +++ b/SqlProvidersCommon/SqlUsersStorageProviderBase.cs @@ -0,0 +1,1161 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using System.Data.Common; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon { + + /// + /// Implements a base class for a SQL users storage provider. + /// + public abstract class SqlUsersStorageProviderBase : SqlStorageProviderBase, IUsersStorageProviderV30 { + + #region IUsersStorageProvider Members + + /// + /// Tests a Password for a User account. + /// + /// The User account. + /// The Password to test. + /// True if the Password is correct. + /// If or are null. + public bool TestAccount(UserInfo user, string password) { + if(user == null) throw new ArgumentNullException("user"); + if(password == null) throw new ArgumentNullException("password"); + + return TryManualLogin(user.Username, password) != null; + } + + /// + /// Gets the complete list of Users. + /// + /// All the Users, sorted by username. + public UserInfo[] GetUsers() { + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).SelectFrom( + "User", "UserGroupMembership", "Username", "User", Join.LeftJoin, + new string[] { "Username", "DisplayName", "Email", "Active", "DateTime" }, + new string[] { "UserGroup" }); + + DbCommand command = builder.GetCommand(connString, query, new List()); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(100); + + string prevUsername = "|||"; + string username = null; + string displayName; + string email; + bool active; + DateTime dateTime; + List groups = new List(5); + + while(reader.Read()) { + username = reader["User_Username"] as string; + + if(username != prevUsername) { + // Set previous user's groups + if(prevUsername != "|||") { + result[result.Count - 1].Groups = groups.ToArray(); + groups.Clear(); + } + + // Read new data + displayName = GetNullableColumn(reader, "User_DisplayName", null); + email = reader["User_Email"] as string; + active = (bool)reader["User_Active"]; + dateTime = (DateTime)reader["User_DateTime"]; + + // Append new user + result.Add(new UserInfo(username, displayName, email, active, dateTime, this)); + } + + // Keep reading groups + prevUsername = username; + if(!IsDBNull(reader, "UserGroupMembership_UserGroup")) { + groups.Add(reader["UserGroupMembership_UserGroup"] as string); + } + } + + if(result.Count > 0) { + result[result.Count - 1].Groups = groups.ToArray(); + } + + CloseReader(command, reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Adds a new User. + /// + /// The Username. + /// The display name (can be null). + /// The Password. + /// The Email address. + /// A value indicating whether the account is active. + /// The Account creation Date/Time. + /// The correct object or null. + /// If , or are null. + /// If , or are empty. + public UserInfo AddUser(string username, string displayName, string password, string email, bool active, DateTime dateTime) { + if(username == null) throw new ArgumentNullException("username"); + if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username"); + if(password == null) throw new ArgumentNullException("password"); + if(password.Length == 0) throw new ArgumentException("Password cannot be empty", "password"); + if(email == null) throw new ArgumentNullException("email"); + if(email.Length == 0) throw new ArgumentException("Email cannot be empty", "email"); + + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).InsertInto("User", + new string[] { "Username", "PasswordHash", "DisplayName", "Email", "Active", "DateTime" }, + new string[] { "Username", "PasswordHash", "DisplayName", "Email", "Active", "DateTime" }); + + List parameters = new List(6); + parameters.Add(new Parameter(ParameterType.String, "Username", username)); + parameters.Add(new Parameter(ParameterType.String, "PasswordHash", Hash.Compute(password))); + if(string.IsNullOrEmpty(displayName)) parameters.Add(new Parameter(ParameterType.String, "DisplayName", DBNull.Value)); + else parameters.Add(new Parameter(ParameterType.String, "DisplayName", displayName)); + parameters.Add(new Parameter(ParameterType.String, "Email", email)); + parameters.Add(new Parameter(ParameterType.Boolean, "Active", active)); + parameters.Add(new Parameter(ParameterType.DateTime, "DateTime", dateTime)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + int rows = ExecuteNonQuery(command); + + if(rows == 1) { + return new UserInfo(username, displayName, email, active, dateTime, this); + } + else return null; + } + + /// + /// Gets the user groups of a user. + /// + /// A database transaction. + /// The username. + /// The groups. + private string[] GetUserGroups(DbTransaction transaction, string username) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("UserGroupMembership", new string[] { "UserGroup" }); + query = queryBuilder.Where(query, "User", WhereOperator.Equals, "Username"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Username", username)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(5); + + while(reader.Read()) { + result.Add(reader["UserGroup"] as string); + } + + CloseReader(reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Gets the user groups of a user. + /// + /// A database connection. + /// The username. + /// The groups. + private string[] GetUserGroups(DbConnection connection, string username) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("UserGroupMembership", new string[] { "UserGroup" }); + query = queryBuilder.Where(query, "User", WhereOperator.Equals, "Username"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Username", username)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(5); + + while(reader.Read()) { + result.Add(reader["UserGroup"] as string); + } + + CloseReader(reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Modifies a User. + /// + /// The Username of the user to modify. + /// The new display name (can be null). + /// The new Password (null or blank to keep the current password). + /// The new Email address. + /// A value indicating whether the account is active. + /// The correct object or null. + /// If user or newEmail are null. + /// If newEmail is empty. + public UserInfo ModifyUser(UserInfo user, string newDisplayName, string newPassword, string newEmail, bool newActive) { + if(user == null) throw new ArgumentNullException("user"); + if(newEmail == null) throw new ArgumentNullException("newEmail"); + if(newEmail.Length == 0) throw new ArgumentException("New Email cannot be empty", "newEmail"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = ""; + if(string.IsNullOrEmpty(newPassword)) { + query = queryBuilder.Update("User", + new string[] { "DisplayName", "Email", "Active", }, + new string[] { "DisplayName", "Email", "Active", }); + } + else { + query = queryBuilder.Update("User", + new string[] { "PasswordHash", "DisplayName", "Email", "Active", }, + new string[] { "PasswordHash", "DisplayName", "Email", "Active", }); + } + query = queryBuilder.Where(query, "Username", WhereOperator.Equals, "Username"); + + List parameters = new List(5); + if(!string.IsNullOrEmpty(newPassword)) { + parameters.Add(new Parameter(ParameterType.String, "PasswordHash", Hash.Compute(newPassword))); + } + if(string.IsNullOrEmpty(newDisplayName)) parameters.Add(new Parameter(ParameterType.String, "DisplayName", DBNull.Value)); + else parameters.Add(new Parameter(ParameterType.String, "DisplayName", newDisplayName)); + parameters.Add(new Parameter(ParameterType.String, "Email", newEmail)); + parameters.Add(new Parameter(ParameterType.Boolean, "Active", newActive)); + parameters.Add(new Parameter(ParameterType.String, "Username", user.Username)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == 1) { + UserInfo result = new UserInfo(user.Username, newDisplayName, newEmail, newActive, user.DateTime, this); + result.Groups = GetUserGroups(transaction, user.Username); + + CommitTransaction(transaction); + return result; + } + else { + RollbackTransaction(transaction); + return null; + } + } + + /// + /// Removes a User. + /// + /// The User to remove. + /// True if the User has been removed successfully. + /// If user is null. + public bool RemoveUser(UserInfo user) { + if(user == null) throw new ArgumentNullException("user"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("User"); + query = queryBuilder.Where(query, "Username", WhereOperator.Equals, "Username"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Username", user.Username)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + int rows = ExecuteNonQuery(command); + + return rows == 1; + } + + /// + /// Gets all the user groups. + /// + /// All the groups, sorted by name. + public UserGroup[] GetUserGroups() { + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).SelectFrom( + "UserGroup", "UserGroupMembership", "Name", "UserGroup", Join.LeftJoin, + new string[] { "Name", "Description" }, new string[] { "User" }); + + DbCommand command = builder.GetCommand(connString, query, new List()); + + DbDataReader reader = ExecuteReader(command); + + string previousName = "|||"; + string name = null; + string description; + List users = new List(50); + + if(reader != null) { + List result = new List(5); + + while(reader.Read()) { + name = reader["UserGroup_Name"] as string; + + if(name != previousName) { + // Set previous group's users + if(previousName != "|||") { + result[result.Count - 1].Users = users.ToArray(); + users.Clear(); + } + + // Read new data + description = reader["UserGroup_Description"] as string; + + // Append new group + result.Add(new UserGroup(name, description, this)); + } + + // Keep reading users + previousName = name; + if(!IsDBNull(reader, "UserGroupMembership_User")) { + users.Add(reader["UserGroupMembership_User"] as string); + } + } + + if(result.Count > 0) { + result[result.Count - 1].Users = users.ToArray(); + } + + CloseReader(command, reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Adds a new user group. + /// + /// The name of the group. + /// The description of the group. + /// The correct object or null. + /// If name or description are null. + /// If name is empty. + public UserGroup AddUserGroup(string name, string description) { + if(name == null) throw new ArgumentNullException("name"); + if(name.Length == 0) throw new ArgumentException("Name cannot be empty", "name"); + if(description == null) throw new ArgumentNullException("description"); + + ICommandBuilder builder = GetCommandBuilder(); + + string query = QueryBuilder.NewQuery(builder).InsertInto("UserGroup", + new string[] { "Name", "Description" }, new string[] { "Name", "Description" }); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Name", name)); + parameters.Add(new Parameter(ParameterType.String, "Description", description)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + int rows = ExecuteNonQuery(command); + + if(rows == 1) { + return new UserGroup(name, description, this); + } + else return null; + } + + /// + /// Gets the users of a group. + /// + /// A database transaction. + /// The group. + /// The users. + private string[] GetUserGroupUsers(DbTransaction transaction, string group) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("UserGroupMembership", new string[] { "User" }); + query = queryBuilder.Where(query, "UserGroup", WhereOperator.Equals, "UserGroup"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "UserGroup", group)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(100); + + while(reader.Read()) { + result.Add(reader["User"] as string); + } + + CloseReader(reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Gets the users of a group. + /// + /// A database connection. + /// The group. + /// The users. + private string[] GetUserGroupUsers(DbConnection connection, string group) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("UserGroupMembership", new string[] { "User" }); + query = queryBuilder.Where(query, "UserGroup", WhereOperator.Equals, "UserGroup"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "UserGroup", group)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + List result = new List(100); + + while(reader.Read()) { + result.Add(reader["User"] as string); + } + + CloseReader(reader); + + return result.ToArray(); + } + else return null; + } + + /// + /// Modifies a user group. + /// + /// The group to modify. + /// The new description of the group. + /// The correct object or null. + /// If group or description are null. + public UserGroup ModifyUserGroup(UserGroup group, string description) { + if(group == null) throw new ArgumentNullException("group"); + if(description == null) throw new ArgumentNullException("description"); + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.Update("UserGroup", + new string[] { "Description" }, new string[] { "Description" }); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Description", description)); + parameters.Add(new Parameter(ParameterType.String, "Name", group.Name)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows == 1) { + UserGroup result = new UserGroup(group.Name, description, this); + result.Users = GetUserGroupUsers(transaction, group.Name); + + CommitTransaction(transaction); + return result; + } + else { + RollbackTransaction(transaction); + return null; + } + } + + /// + /// Removes a user group. + /// + /// The group to remove. + /// true if the group is removed, false otherwise. + /// If group is null. + public bool RemoveUserGroup(UserGroup group) { + if(group == null) throw new ArgumentNullException("group"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("UserGroup"); + query = queryBuilder.Where(query, "Name", WhereOperator.Equals, "Name"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Name", group.Name)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + int rows = ExecuteNonQuery(command); + + return rows == 1; + } + + /// + /// Verifies that a user exists. + /// + /// A database transaction. + /// The username. + /// true if the user exists, false otherwise. + private bool UserExists(DbTransaction transaction, string username) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectCountFrom("User"); + query = queryBuilder.Where(query, "Username", WhereOperator.Equals, "Username"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Username", username)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int count = ExecuteScalar(command, -1, false); + + return count == 1; + } + + /// + /// Verifies that a user exists. + /// + /// A database connection. + /// The username. + /// true if the user exists, false otherwise. + private bool UserExists(DbConnection connection, string username) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectCountFrom("User"); + query = queryBuilder.Where(query, "Username", WhereOperator.Equals, "Username"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Username", username)); + + DbCommand command = builder.GetCommand(connection, query, parameters); + + int count = ExecuteScalar(command, -1, false); + + return count == 1; + } + + /// + /// Removes the user group membership for a user. + /// + /// A database transaction. + /// The username. + private void RemoveUserGroupMembership(DbTransaction transaction, string username) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("UserGroupMembership"); + query = queryBuilder.Where(query, "User", WhereOperator.Equals, "Username"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Username", username)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + ExecuteNonQuery(command, false); + } + + /// + /// Sets the group memberships of a user account. + /// + /// The user account. + /// The groups the user account is member of. + /// The correct object or null. + /// If user or groups are null. + public UserInfo SetUserMembership(UserInfo user, string[] groups) { + if(user == null) throw new ArgumentNullException("user"); + if(groups == null) throw new ArgumentNullException("groups"); + + // 1. Remove old user group membership + // 2. Add new memberships, one by one + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + if(!UserExists(transaction, user.Username)) { + RollbackTransaction(transaction); + return null; + } + + RemoveUserGroupMembership(transaction, user.Username); + + string query = QueryBuilder.NewQuery(builder).InsertInto("UserGroupMembership", + new string[] { "User", "UserGroup" }, new string[] { "User", "UserGroup" }); + + foreach(string group in groups) { + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "User", user.Username)); + parameters.Add(new Parameter(ParameterType.String, "UserGroup", group)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + if(rows != 1) { + RollbackTransaction(transaction); + return null; + } + } + + UserInfo result = new UserInfo(user.Username, user.DisplayName, user.Email, user.Active, user.DateTime, this); + result.Groups = groups; + + CommitTransaction(transaction); + + return result; + } + + /// + /// Tries to login a user directly through the provider. + /// + /// The username. + /// The password. + /// The correct UserInfo object, or null. + /// If username or password are null. + public UserInfo TryManualLogin(string username, string password) { + if(username == null) throw new ArgumentNullException("username"); + if(password == null) throw new ArgumentNullException("password"); + + // Shortcut + if(username.Length == 0) return null; + if(password.Length == 0) return null; + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom( + "User", "UserGroupMembership", "Username", "User", Join.LeftJoin, + new string[] { "Username", "PasswordHash", "DisplayName", "Email", "Active", "DateTime" }, + new string[] { "UserGroup" }); + query = queryBuilder.Where(query, "User", "Username", WhereOperator.Equals, "Username"); + query = queryBuilder.AndWhere(query, "User", "PasswordHash", WhereOperator.Equals, "PasswordHash"); + + string providedPasswordHash = Hash.Compute(password); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Username", username)); + parameters.Add(new Parameter(ParameterType.String, "PasswordHash", providedPasswordHash)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + UserInfo result = null; + + string realUsername = null; + string passwordHash = null; + string displayName; + string email; + bool active; + DateTime dateTime; + List groups = new List(5); + + while(reader.Read()) { + if(result == null) { + // Read data + realUsername = reader["User_Username"] as string; + passwordHash = reader["User_PasswordHash"] as string; + displayName = GetNullableColumn(reader, "User_DisplayName", null); + email = reader["User_Email"] as string; + active = (bool)reader["User_Active"]; + dateTime = (DateTime)reader["User_DateTime"]; + + // Create user + result = new UserInfo(realUsername, displayName, email, active, dateTime, this); + } + + // Keep reading groups + if(!IsDBNull(reader, "UserGroupMembership_UserGroup")) { + groups.Add(reader["UserGroupMembership_UserGroup"] as string); + } + } + + if(result != null) { + result.Groups = groups.ToArray(); + } + + CloseReader(command, reader); + + if(result != null) { + if(result.Active && + string.CompareOrdinal(result.Username, username) == 0 && + string.CompareOrdinal(passwordHash, providedPasswordHash) == 0) { + return result; + } + else return null; + } + else return null; + } + else return null; + } + + /// + /// Tries to login a user directly through the provider using + /// the current HttpContext and without username/password. + /// + /// The current HttpContext. + /// The correct UserInfo object, or null. + /// If context is null. + public UserInfo TryAutoLogin(System.Web.HttpContext context) { + if(context == null) throw new ArgumentNullException("context"); + + return null; + } + + /// + /// Gets a user account. + /// + /// The username. + /// The , or null. + /// If username is null. + /// If username is empty. + public UserInfo GetUser(string username) { + if(username == null) throw new ArgumentNullException("username"); + if(username.Length == 0) throw new ArgumentException("Username cannot be empty", "username"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom( + "User", "UserGroupMembership", "Username", "User", Join.LeftJoin, + new string[] { "Username", "DisplayName", "Email", "Active", "DateTime" }, + new string[] { "UserGroup" }); + query = queryBuilder.Where(query, "User", "Username", WhereOperator.Equals, "Username"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Username", username)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + UserInfo result = null; + + string realUsername = null; + string displayName; + string email; + bool active; + DateTime dateTime; + List groups = new List(5); + + while(reader.Read()) { + if(result == null) { + // Read data + realUsername = reader["User_Username"] as string; + displayName = GetNullableColumn(reader, "User_DisplayName", null); + email = reader["User_Email"] as string; + active = (bool)reader["User_Active"]; + dateTime = (DateTime)reader["User_DateTime"]; + + // Create user + result = new UserInfo(realUsername, displayName, email, active, dateTime, this); + } + + // Keep reading groups + if(!IsDBNull(reader, "UserGroupMembership_UserGroup")) { + groups.Add(reader["UserGroupMembership_UserGroup"] as string); + } + } + + if(result != null) { + result.Groups = groups.ToArray(); + } + + CloseReader(command, reader); + + return result; + } + else return null; + } + + /// + /// Gets a user account. + /// + /// The email address. + /// The first user found with the specified email address, or null. + /// If email is null. + /// If email is empty. + public UserInfo GetUserByEmail(string email) { + if(email == null) throw new ArgumentNullException("email"); + if(email.Length == 0) throw new ArgumentException("Email cannot be empty", "email"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom( + "User", "UserGroupMembership", "Username", "User", Join.LeftJoin, + new string[] { "Username", "DisplayName", "Email", "Active", "DateTime" }, + new string[] { "UserGroup" }); + query = queryBuilder.Where(query, "User", "Email", WhereOperator.Equals, "Email"); + + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Email", email)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + UserInfo result = null; + + string username = null; + string displayName; + string realEmail = null; + bool active; + DateTime dateTime; + List groups = new List(5); + + while(reader.Read()) { + if(result == null) { + // Read data + username = reader["User_Username"] as string; + displayName = GetNullableColumn(reader, "User_DisplayName", null); + realEmail = reader["User_Email"] as string; + active = (bool)reader["User_Active"]; + dateTime = (DateTime)reader["User_DateTime"]; + + // Create user + result = new UserInfo(username, displayName, realEmail, active, dateTime, this); + } + + // Keep reading groups + if(!IsDBNull(reader, "UserGroupMembership_UserGroup")) { + groups.Add(reader["UserGroupMembership_UserGroup"] as string); + } + } + + if(result != null) { + result.Groups = groups.ToArray(); + } + + CloseReader(command, reader); + + return result; + } + else return null; + } + + /// + /// Notifies the provider that a user has logged in through the authentication cookie. + /// + /// The user who has logged in. + /// If user is null. + public void NotifyCookieLogin(UserInfo user) { + if(user == null) throw new ArgumentNullException("user"); + // Nothing to do + } + + /// + /// Notifies the provider that a user has logged out. + /// + /// The user who has logged out. + /// If user is null. + public void NotifyLogout(UserInfo user) { + if(user == null) throw new ArgumentNullException("user"); + // Nothing to do + } + + /// + /// Removes a user data element. + /// + /// A database transaction. + /// The username. + /// The key. + /// true if the data element is removed, false otherwise. + private bool RemoveUserData(DbTransaction transaction, string username, string key) { + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.DeleteFrom("UserData"); + query = queryBuilder.Where(query, "User", WhereOperator.Equals, "Username"); + query = queryBuilder.AndWhere(query, "Key", WhereOperator.Equals, "Key"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Username", username)); + parameters.Add(new Parameter(ParameterType.String, "Key", key)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + + return rows != -1; // Success also if no elements are removed + } + + /// + /// Stores a user data element, overwriting the previous one if present. + /// + /// The user the data belongs to. + /// The key of the data element (case insensitive). + /// The value of the data element, null for deleting the data. + /// true if the data element is stored, false otherwise. + /// If user or key are null. + /// If key is empty. + public bool StoreUserData(UserInfo user, string key, string value) { + if(user == null) throw new ArgumentNullException("user"); + if(key == null) throw new ArgumentNullException("key"); + if(key.Length == 0) throw new ArgumentException("Key cannot be empty", "key"); + + // 1. Remove previous key, if present + // 2. Add new key, if value != null + + ICommandBuilder builder = GetCommandBuilder(); + DbConnection connection = builder.GetConnection(connString); + DbTransaction transaction = BeginTransaction(connection); + + bool done = RemoveUserData(transaction, user.Username, key); + + if(done) { + if(value != null) { + string query = QueryBuilder.NewQuery(builder).InsertInto("UserData", + new string[] { "User", "Key", "Data" }, new string[] { "Username", "Key", "Data" }); + + List parameters = new List(3); + parameters.Add(new Parameter(ParameterType.String, "Username", user.Username)); + parameters.Add(new Parameter(ParameterType.String, "Key", key)); + parameters.Add(new Parameter(ParameterType.String, "Data", value)); + + DbCommand command = builder.GetCommand(transaction, query, parameters); + + int rows = ExecuteNonQuery(command, false); + if(rows == 1) CommitTransaction(transaction); + else RollbackTransaction(transaction); + + return rows == 1; + } + else { + CommitTransaction(transaction); + return true; + } + } + else { + RollbackTransaction(transaction); + return false; + } + } + + /// + /// Gets a user data element, if any. + /// + /// The user the data belongs to. + /// The key of the data element. + /// The value of the data element, or null if the element is not found. + /// If user or key are null. + /// If key is empty. + public string RetrieveUserData(UserInfo user, string key) { + if(user == null) throw new ArgumentNullException("user"); + if(key == null) throw new ArgumentNullException("key"); + if(key.Length == 0) throw new ArgumentException("Key cannot be empty", "key"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("UserData", new string[] { "Data" }); + query = queryBuilder.Where(query, "User", WhereOperator.Equals, "Username"); + query = queryBuilder.AndWhere(query, "Key", WhereOperator.Equals, "Key"); + + List parameters = new List(2); + parameters.Add(new Parameter(ParameterType.String, "Username", user.Username)); + parameters.Add(new Parameter(ParameterType.String, "Key", key)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + string data = null; + + if(reader.Read()) { + data = reader["Data"] as string; + } + + CloseReader(command, reader); + + return data; + } + else return null; + } + + /// + /// Retrieves all the user data elements for a user. + /// + /// The user. + /// The user data elements (key->value). + /// If user is null. + public IDictionary RetrieveAllUserData(UserInfo user) { + if(user == null) throw new ArgumentNullException("user"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("UserData", new string[] { "Key", "Data" }); + query = queryBuilder.Where(query, "User", WhereOperator.Equals, "Username"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Username", user.Username)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + Dictionary result = new Dictionary(10); + + while(reader.Read()) { + result.Add(reader["Key"] as string, reader["Data"] as string); + } + + CloseReader(command, reader); + + return result; + } + else return null; + } + + /// + /// Gets all the users that have the specified element in their data. + /// + /// The key of the data. + /// The users and the data. + /// If key is null. + /// If key is empty. + public IDictionary GetUsersWithData(string key) { + if(key == null) throw new ArgumentNullException("key"); + if(key.Length == 0) throw new ArgumentException("Key cannot be empty", "key"); + + ICommandBuilder builder = GetCommandBuilder(); + QueryBuilder queryBuilder = new QueryBuilder(builder); + + string query = queryBuilder.SelectFrom("User", "UserData", "Username", "User", Join.RightJoin, + new string[] { "Username", "DisplayName", "Email", "Active", "DateTime" }, new string[] { "Data" }, + "UserGroupMembership", "User", Join.LeftJoin, new string[] { "UserGroup" }); + query = queryBuilder.Where(query, "UserData", "Key", WhereOperator.Equals, "Key"); + + List parameters = new List(1); + parameters.Add(new Parameter(ParameterType.String, "Key", key)); + + DbCommand command = builder.GetCommand(connString, query, parameters); + + DbDataReader reader = ExecuteReader(command); + + if(reader != null) { + Dictionary result = new Dictionary(100); + + string prevUsername = "|||"; + UserInfo prevUser = null; + string username = null; + string displayName; + string email; + bool active; + DateTime dateTime; + string data; + List groups = new List(5); + + while(reader.Read()) { + username = reader["User_Username"] as string; + + if(username != prevUsername) { + // Set previous user's groups + if(prevUsername != "|||") { + prevUser.Groups = groups.ToArray(); + groups.Clear(); + } + + // Read new data + displayName = GetNullableColumn(reader, "User_DisplayName", null); + email = reader["User_Email"] as string; + active = (bool)reader["User_Active"]; + dateTime = (DateTime)reader["User_DateTime"]; + data = reader["UserData_Data"] as string; + + // Append new user + prevUser = new UserInfo(username, displayName, email, active, dateTime, this); + result.Add(prevUser, data); + } + + // Keep reading groups + prevUsername = username; + if(!IsDBNull(reader, "UserGroupMembership_UserGroup")) { + groups.Add(reader["UserGroupMembership_UserGroup"] as string); + } + } + + if(prevUser != null) { + prevUser.Groups = groups.ToArray(); + } + + CloseReader(command, reader); + + return result; + } + else return null; + } + + #endregion + + /// + /// Gets a value indicating whether user accounts are read-only. + /// + public bool UserAccountsReadOnly { + get { return false; } + } + + /// + /// Gets a value indicating whether user groups are read-only. If so, the provider + /// should support default user groups as defined in the wiki configuration. + /// + public bool UserGroupsReadOnly { + get { return false; } + } + + /// + /// Gets a value indicating whether group membership is read-only (if + /// is false, then this property must be false). If this property is true, the provider + /// should return membership data compatible with default user groups. + /// + public bool GroupMembershipReadOnly { + get { return false; } + } + + /// + /// Gets a value indicating whether users' data is read-only. + /// + public bool UsersDataReadOnly { + get { return false; } + } + + } + +} diff --git a/SqlProvidersCommon/Tools.cs b/SqlProvidersCommon/Tools.cs new file mode 100644 index 0000000..1ff3bc4 --- /dev/null +++ b/SqlProvidersCommon/Tools.cs @@ -0,0 +1,43 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace ScrewTurn.Wiki.Plugins.SqlCommon { + + /// + /// Implements tools. + /// + public static class Tools { + + /// + /// Reads the contents of a in a byte array, beginning at the current position through the end. + /// + /// The . + /// The output byte array (allocated by the method). + /// The max size to read. + /// The number of bytes read, or -maxSize if the max size is exceeded. + public static int ReadStream(Stream stream, ref byte[] buffer, int maxSize) { + int read = 0; + int total = 0; + + byte[] temp = new byte[maxSize]; + + do { + read = stream.Read(temp, total, temp.Length - total); + total += read; + + if(total > maxSize) return -maxSize; + + } while(read > 0); + + buffer = new byte[total]; + Buffer.BlockCopy(temp, 0, buffer, 0, (int)total); + + return total; + } + + } + +} diff --git a/SqlServerProviders-Tests/Properties/AssemblyInfo.cs b/SqlServerProviders-Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8ccfed3 --- /dev/null +++ b/SqlServerProviders-Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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("ScrewTurn Wiki SQL Server Providers Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// 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("3586540c-3722-413f-a9e5-69a778931f2e")] diff --git a/SqlServerProviders-Tests/SqlServerFilesStorageProviderTests.cs b/SqlServerProviders-Tests/SqlServerFilesStorageProviderTests.cs new file mode 100644 index 0000000..00bfce7 --- /dev/null +++ b/SqlServerProviders-Tests/SqlServerFilesStorageProviderTests.cs @@ -0,0 +1,104 @@ + +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.Tests; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Plugins.SqlServer.Tests { + + [TestFixture] + public class SqlServerFilesStorageProviderTests : FilesStorageProviderTestScaffolding { + + //private const string ConnString = "Data Source=(local)\\SQLExpress;User ID=sa;Password=password;"; + private const string ConnString = "Data Source=(local)\\SQLExpress;Integrated Security=SSPI;"; + private const string InitialCatalog = "Initial Catalog=ScrewTurnWikiTest;"; + + public override IFilesStorageProviderV30 GetProvider() { + SqlServerFilesStorageProvider prov = new SqlServerFilesStorageProvider(); + prov.Init(MockHost(), ConnString + InitialCatalog); + return prov; + } + + [TestFixtureSetUp] + public void FixtureSetUp() { + // Create database with no tables + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "if (select count(*) from sys.databases where [Name] = 'ScrewTurnWikiTest') = 0 begin create database [ScrewTurnWikiTest] end"; + cmd.ExecuteNonQuery(); + + cn.Close(); + } + + [TearDown] + public new void TearDown() { + base.TearDown(); + + // Clear all tables + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "use [ScrewTurnWikiTest]; delete from [Attachment]; delete from [File]; delete from [Directory] where [FullPath] <> '/';"; + try { + cmd.ExecuteNonQuery(); + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + + cn.Close(); + } + + [TestFixtureTearDown] + public void FixtureTearDown() { + // Delete database + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "alter database [ScrewTurnWikiTest] set single_user with rollback immediate"; + try { + cmd.ExecuteNonQuery(); + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + + cmd = cn.CreateCommand(); + cmd.CommandText = "drop database [ScrewTurnWikiTest]"; + try { + cmd.ExecuteNonQuery(); + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + + cn.Close(); + } + + [Test] + public void Init() { + IFilesStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), ConnString + InitialCatalog); + + Assert.IsNotNull(prov.Information, "Information should not be null"); + } + + [TestCase("", ExpectedException = typeof(InvalidConfigurationException))] + [TestCase("blah", ExpectedException = typeof(InvalidConfigurationException))] + [TestCase("Data Source=(local)\\SQLExpress;User ID=inexistent;Password=password;InitialCatalog=Inexistent;", ExpectedException = typeof(InvalidConfigurationException))] + public void Init_InvalidConnString(string c) { + IFilesStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), c); + } + + } + +} diff --git a/SqlServerProviders-Tests/SqlServerPagesStorageProviderTests.cs b/SqlServerProviders-Tests/SqlServerPagesStorageProviderTests.cs new file mode 100644 index 0000000..a9437fa --- /dev/null +++ b/SqlServerProviders-Tests/SqlServerPagesStorageProviderTests.cs @@ -0,0 +1,280 @@ + +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.Tests; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.Plugins.SqlCommon; +using ScrewTurn.Wiki.SearchEngine; + +namespace ScrewTurn.Wiki.Plugins.SqlServer.Tests { + + [TestFixture] + public class SqlServerPagesStorageProviderTests : PagesStorageProviderTestScaffolding { + + //private const string ConnString = "Data Source=(local)\\SQLExpress;User ID=sa;Password=password;"; + private const string ConnString = "Data Source=(local)\\SQLExpress;Integrated Security=SSPI;"; + private const string InitialCatalog = "Initial Catalog=ScrewTurnWikiTest;"; + + public override IPagesStorageProviderV30 GetProvider() { + SqlServerPagesStorageProvider prov = new SqlServerPagesStorageProvider(); + prov.Init(MockHost(), ConnString + InitialCatalog); + return prov; + } + + [TestFixtureSetUp] + public void FixtureSetUp() { + // Create database with no tables + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "if (select count(*) from sys.databases where [Name] = 'ScrewTurnWikiTest') = 0 begin create database [ScrewTurnWikiTest] end"; + cmd.ExecuteNonQuery(); + + cn.Close(); + } + + [TearDown] + public new void TearDown() { + base.TearDown(); + + // Clear all tables + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "use [ScrewTurnWikiTest]; delete from [IndexWordMapping]; delete from [IndexWord]; delete from [IndexDocument]; delete from [ContentTemplate]; delete from [Snippet]; delete from [NavigationPath]; delete from [Message]; delete from [PageKeyword]; delete from [PageContent]; delete from [CategoryBinding]; delete from [Page]; delete from [Category]; delete from [Namespace] where [Name] <> '';"; + try { + cmd.ExecuteNonQuery(); + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + + cn.Close(); + } + + [TestFixtureTearDown] + public void FixtureTearDown() { + // Delete database + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "alter database [ScrewTurnWikiTest] set single_user with rollback immediate"; + try { + cmd.ExecuteNonQuery(); + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + + cmd = cn.CreateCommand(); + cmd.CommandText = "drop database [ScrewTurnWikiTest]"; + try { + cmd.ExecuteNonQuery(); + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + + cn.Close(); + + // This is neede because the pooled connection are using a session + // that is now invalid due to the commands executed above + SqlConnection.ClearAllPools(); + } + + [Test] + public void Init() { + IPagesStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), ConnString + InitialCatalog); + + Assert.IsNotNull(prov.Information, "Information should not be null"); + } + + [TestCase("", ExpectedException = typeof(InvalidConfigurationException))] + [TestCase("blah", ExpectedException = typeof(InvalidConfigurationException))] + [TestCase("Data Source=(local)\\SQLExpress;User ID=inexistent;Password=password;InitialCatalog=Inexistent;", ExpectedException = typeof(InvalidConfigurationException))] + public void Init_InvalidConnString(string c) { + IPagesStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), c); + } + + [Test] + public void Init_Upgrade() { + FixtureTearDown(); + + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "create database [ScrewTurnWikiTest];"; + cmd.ExecuteNonQuery(); + cn.Close(); + + cn = new SqlConnection(ConnString + InitialCatalog); + cn.Open(); + + cmd = cn.CreateCommand(); + cmd.CommandText = +@"CREATE TABLE [PagesProviderVersion] ( + [Version] varchar(12) PRIMARY KEY +); +INSERT INTO [PagesProviderVersion] ([Version]) VALUES ('Irrelevant'); +create table [Page] ( + [Name] nvarchar(128) primary key, + [Status] char not null default ('N'), -- (P)ublic, N(ormal), (L)ocked + [CreationDateTime] datetime not null +); + +create table [PageContent] ( + [Page] nvarchar(128) references [Page]([Name]) on update cascade on delete cascade, + [Revision] int not null default ((-1)), -- -1 for Current Revision + [Title] nvarchar(256) not null, + [DateTime] datetime not null, + [Username] nvarchar(64) not null, + [Content] ntext not null, + [Comment] nvarchar(128) not null, + primary key ([Page], [Revision]) +); + +create table [Category] ( + [Name] nvarchar(128) primary key +); + +create table [CategoryBinding] ( + [Category] nvarchar(128) references [Category]([Name]) on update cascade on delete cascade, + [Page] nvarchar(128) references [Page]([Name]) on update cascade on delete cascade, + primary key ([Category], [Page]) +); + +create table [Message] ( + [ID] int primary key identity, + [Page] nvarchar(128) references [Page]([Name]) on update cascade on delete cascade, + [Parent] int not null, -- -1 for no parent + [Username] nvarchar(64) not null, + [DateTime] datetime not null, + [Subject] nvarchar(128) not null, + [Body] ntext not null +); + +create table [Snippet] ( + [Name] nvarchar(128) primary key, + [Content] ntext not null +); + +create table [NavigationPath] ( + [Name] nvarchar(128) not null primary key +); + +create table [NavigationPathBinding] ( + [NavigationPath] nvarchar(128) not null references [NavigationPath]([Name]) on delete cascade, + [Page] nvarchar(128) not null references [Page]([Name]) on update cascade on delete cascade, + [Number] int not null, + primary key ([NavigationPath], [Page], [Number]) +); + +insert into [Page] ([Name], [Status], [CreationDateTime]) values ('Page1', 'N', '2008/12/31 12:12:12'); +insert into [Page] ([Name], [Status], [CreationDateTime]) values ('Page2', 'L', '2008/12/31 12:12:12'); +insert into [Page] ([Name], [Status], [CreationDateTime]) values ('Page.WithDot', 'P', '2008/12/31 12:12:12'); + +insert into [PageContent] ([Page], [Revision], [Title], [DateTime], [Username], [Content], [Comment]) values ('Page1', -1, 'Page1 Title', '2008/12/31 14:14:14', 'SYSTEM', 'Test Content 1', 'Comment 1'); +insert into [PageContent] ([Page], [Revision], [Title], [DateTime], [Username], [Content], [Comment]) values ('Page1', 0, 'Page1 Title 0', '2008/12/31 12:12:12', 'SYSTEM', 'Test Content 0', ''); +insert into [PageContent] ([Page], [Revision], [Title], [DateTime], [Username], [Content], [Comment]) values ('Page2', -1, 'Page2 Title', '2008/12/31 14:14:14', 'SYSTEM', 'Test Content 2', 'Comment 2'); +insert into [PageContent] ([Page], [Revision], [Title], [DateTime], [Username], [Content], [Comment]) values ('Page.WithDot', -1, 'Page.WithDot Title', '2008/12/31 14:14:14', 'SYSTEM', 'Test Content 3', 'Comment 3'); + +insert into [Category] ([Name]) values ('Cat1'); +insert into [Category] ([Name]) values ('Cat2'); +insert into [Category] ([Name]) values ('Cat.WithDot'); + +insert into [CategoryBinding] ([Category], [Page]) values ('Cat1', 'Page1'); +insert into [CategoryBinding] ([Category], [Page]) values ('Cat2', 'Page1'); +insert into [CategoryBinding] ([Category], [Page]) values ('Cat1', 'Page2'); +insert into [CategoryBinding] ([Category], [Page]) values ('Cat2', 'Page.WithDot'); +insert into [CategoryBinding] ([Category], [Page]) values ('Cat.WithDot', 'Page.WithDot'); + +insert into [Message] ([Page], [Parent], [Username], [DateTime], [Subject], [Body]) values ('Page1', -1, 'SYSTEM', '2008/12/31 16:16:16', 'Test 1', 'Body 1'); +insert into [Message] ([Page], [Parent], [Username], [DateTime], [Subject], [Body]) values ('Page1', 0, 'SYSTEM', '2008/12/31 16:16:16', 'Test 1.1', 'Body 1.1'); +insert into [Message] ([Page], [Parent], [Username], [DateTime], [Subject], [Body]) values ('Page.WithDot', -1, 'SYSTEM', '2008/12/31 16:16:16', 'Test dot', 'Body dot'); + +insert into [Snippet] ([Name], [Content]) values ('Snip', 'Content'); + +insert into [NavigationPath] ([Name]) values ('Path'); + +insert into [NavigationPathBinding] ([NavigationPath], [Page], [Number]) values ('Path', 'Page1', 1); +insert into [NavigationPathBinding] ([NavigationPath], [Page], [Number]) values ('Path', 'Page2', 2); +insert into [NavigationPathBinding] ([NavigationPath], [Page], [Number]) values ('Path', 'Page.WithDot', 3);"; + + bool done = false; + try { + cmd.ExecuteNonQuery(); + done = true; + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex); + } + finally { + cn.Close(); + } + + if(!done) throw new Exception("Could not generate v2 test database"); + + MockRepository mocks = new MockRepository(); + IHostV30 host = mocks.DynamicMock(); + Expect.Call(host.UpgradePageStatusToAcl(null, 'L')).IgnoreArguments().Repeat.Twice().Return(true); + + mocks.Replay(host); + + SqlServerPagesStorageProvider prov = new SqlServerPagesStorageProvider(); + prov.Init(host, ConnString + InitialCatalog); + + Snippet[] snippets = prov.GetSnippets(); + Assert.AreEqual(1, snippets.Length, "Wrong snippet count"); + Assert.AreEqual("Snip", snippets[0].Name, "Wrong snippet name"); + Assert.AreEqual("Content", snippets[0].Content, "Wrong snippet content"); + + PageInfo[] pages = prov.GetPages(null); + Assert.AreEqual(3, pages.Length, "Wrong page count"); + Assert.AreEqual("Page_WithDot", pages[0].FullName, "Wrong page name"); + Assert.AreEqual("Page1", pages[1].FullName, "Wrong page name"); + Assert.AreEqual("Page2", pages[2].FullName, "Wrong page name"); + + Assert.AreEqual("Test Content 3", prov.GetContent(pages[0]).Content, "Wrong content"); + Assert.AreEqual("Test Content 1", prov.GetContent(pages[1]).Content, "Wrong content"); + Assert.AreEqual("Test Content 0", prov.GetBackupContent(pages[1], 0).Content, "Wrong backup content"); + Assert.AreEqual("Test Content 2", prov.GetContent(pages[2]).Content, "Wrong content"); + + Message[] messages = prov.GetMessages(pages[0]); + Assert.AreEqual(1, messages.Length, "Wrong message count"); + Assert.AreEqual("Test dot", messages[0].Subject, "Wrong message subject"); + + CategoryInfo[] categories = prov.GetCategories(null); + Assert.AreEqual(3, categories.Length, "Wrong category count"); + Assert.AreEqual("Cat_WithDot", categories[0].FullName, "Wrong category name"); + Assert.AreEqual(1, categories[0].Pages.Length, "Wrong page count"); + Assert.AreEqual("Page_WithDot", categories[0].Pages[0], "Wrong page"); + Assert.AreEqual("Cat1", categories[1].FullName, "Wrong category name"); + Assert.AreEqual("Page1", categories[1].Pages[0], "Wrong page"); + Assert.AreEqual("Page2", categories[1].Pages[1], "Wrong page"); + Assert.AreEqual("Cat2", categories[2].FullName, "Wrong category name"); + Assert.AreEqual("Page_WithDot", categories[2].Pages[0], "Wrong page"); + Assert.AreEqual("Page1", categories[2].Pages[1], "Wrong page"); + + NavigationPath[] paths = prov.GetNavigationPaths(null); + Assert.AreEqual(1, paths.Length, "Wrong navigation path count"); + Assert.AreEqual("Path", paths[0].FullName, "Wrong navigation path name"); + Assert.AreEqual("Page1", paths[0].Pages[0], "Wrong page"); + Assert.AreEqual("Page2", paths[0].Pages[1], "Wrong page"); + Assert.AreEqual("Page_WithDot", paths[0].Pages[2], "Wrong page"); + + mocks.Verify(host); + } + + } + +} diff --git a/SqlServerProviders-Tests/SqlServerPagesStorageProvider_SqlIndexTests.cs b/SqlServerProviders-Tests/SqlServerPagesStorageProvider_SqlIndexTests.cs new file mode 100644 index 0000000..34b96d9 --- /dev/null +++ b/SqlServerProviders-Tests/SqlServerPagesStorageProvider_SqlIndexTests.cs @@ -0,0 +1,126 @@ + +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.IO; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.SearchEngine; +using ScrewTurn.Wiki.SearchEngine.Tests; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Plugins.SqlServer.Tests { + + [TestFixture] + public class SqlServerPagesStorageProvider_SqlIndexTests : IndexBaseTests { + + //private const string ConnString = "Data Source=(local)\\SQLExpress;User ID=sa;Password=password;"; + private const string ConnString = "Data Source=(local)\\SQLExpress;Integrated Security=SSPI;"; + private const string InitialCatalog = "Initial Catalog=ScrewTurnWikiTest;"; + + private MockRepository mocks = new MockRepository(); + private string testDir = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Guid.NewGuid().ToString()); + + private delegate string ToStringDelegate(PageInfo p, string input); + + protected IHostV30 MockHost() { + if(!Directory.Exists(testDir)) Directory.CreateDirectory(testDir); + + IHostV30 host = mocks.DynamicMock(); + Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.AtLeastOnce(); + Expect.Call(host.PrepareContentForIndexing(null, null)).IgnoreArguments().Do((ToStringDelegate)delegate(PageInfo p, string input) { return input; }).Repeat.Any(); + Expect.Call(host.PrepareTitleForIndexing(null, null)).IgnoreArguments().Do((ToStringDelegate)delegate(PageInfo p, string input) { return input; }).Repeat.Any(); + + mocks.Replay(host); + + return host; + } + + public IPagesStorageProviderV30 GetProvider() { + SqlServerPagesStorageProvider prov = new SqlServerPagesStorageProvider(); + prov.Init(MockHost(), ConnString + InitialCatalog); + return prov; + } + + [TestFixtureSetUp] + public void FixtureSetUp() { + // Create database with no tables + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "if (select count(*) from sys.databases where [Name] = 'ScrewTurnWikiTest') = 0 begin create database [ScrewTurnWikiTest] end"; + cmd.ExecuteNonQuery(); + + cn.Close(); + } + + [TearDown] + public void TearDown() { + try { + Directory.Delete(testDir, true); + } + catch { } + + // Clear all tables + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "use [ScrewTurnWikiTest]; delete from [IndexWordMapping]; delete from [IndexWord]; delete from [IndexDocument]; delete from [ContentTemplate]; delete from [Snippet]; delete from [NavigationPath]; delete from [Message]; delete from [PageKeyword]; delete from [PageContent]; delete from [CategoryBinding]; delete from [Page]; delete from [Category]; delete from [Namespace] where [Name] <> '';"; + try { + cmd.ExecuteNonQuery(); + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + + cn.Close(); + } + + [TestFixtureTearDown] + public void FixtureTearDown() { + // Delete database + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "alter database [ScrewTurnWikiTest] set single_user with rollback immediate"; + try { + cmd.ExecuteNonQuery(); + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + + cmd = cn.CreateCommand(); + cmd.CommandText = "drop database [ScrewTurnWikiTest]"; + try { + cmd.ExecuteNonQuery(); + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + + cn.Close(); + + // This is neede because the pooled connection are using a session + // that is now invalid due to the commands executed above + SqlConnection.ClearAllPools(); + } + + /// + /// Gets the instance of the index to test. + /// + /// The instance of the index. + protected override IIndex GetIndex() { + SqlServerPagesStorageProvider prov = GetProvider() as SqlServerPagesStorageProvider; + prov.SetFlags(true); + return prov.Index; + } + + } + +} diff --git a/SqlServerProviders-Tests/SqlServerProviders-Tests.csproj b/SqlServerProviders-Tests/SqlServerProviders-Tests.csproj new file mode 100644 index 0000000..5506b1e --- /dev/null +++ b/SqlServerProviders-Tests/SqlServerProviders-Tests.csproj @@ -0,0 +1,96 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {F75DFFE1-A8CF-4CC2-B15F-3EC7EAADDCFC} + Library + Properties + ScrewTurn.Wiki.Plugins.SqlServer.Tests + ScrewTurn.Wiki.Plugins.SqlServer.Tests + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + none + true + bin\Release\ + TRACE + prompt + 4 + true + + + + False + ..\References\Tools\NUnit\framework\nunit.framework.dll + + + False + ..\References\Tools\Rhino.Mocks\Rhino.Mocks.dll + + + + 3.5 + + + + + + + AssemblyVersion.cs + + + + + + + + + + + {531A83D6-76F9-4014-91C5-295818E2D948} + PluginFramework + + + {07625628-842E-4CAA-A029-4D6852C7CA20} + SearchEngine-Tests + + + {2DF980A6-4742-49B1-A090-DE79314644D0} + SearchEngine + + + {617D5D30-97F9-48B2-903D-29D4524492E8} + SqlProvidersCommon + + + {ECB488D9-C8E9-41E0-BE27-27F41F76F8A0} + SqlServerProviders + + + {F865670A-DEDE-41B5-B426-48D73C3B5B1C} + TestScaffolding + + + + + \ No newline at end of file diff --git a/SqlServerProviders-Tests/SqlServerSettingsStorageProviderTests.cs b/SqlServerProviders-Tests/SqlServerSettingsStorageProviderTests.cs new file mode 100644 index 0000000..aa608f9 --- /dev/null +++ b/SqlServerProviders-Tests/SqlServerSettingsStorageProviderTests.cs @@ -0,0 +1,104 @@ + +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.Tests; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Plugins.SqlServer.Tests { + + [TestFixture] + public class SqlServerSettingsStorageProviderTests : SettingsStorageProviderTestScaffolding { + + //private const string ConnString = "Data Source=(local)\\SQLExpress;User ID=sa;Password=password;"; + private const string ConnString = "Data Source=(local)\\SQLExpress;Integrated Security=SSPI;"; + private const string InitialCatalog = "Initial Catalog=ScrewTurnWikiTest;"; + + public override ISettingsStorageProviderV30 GetProvider() { + SqlServerSettingsStorageProvider prov = new SqlServerSettingsStorageProvider(); + prov.Init(MockHost(), ConnString + InitialCatalog); + return prov; + } + + [TestFixtureSetUp] + public void FixtureSetUp() { + // Create database with no tables + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "if (select count(*) from sys.databases where [Name] = 'ScrewTurnWikiTest') = 0 begin create database [ScrewTurnWikiTest] end"; + cmd.ExecuteNonQuery(); + + cn.Close(); + } + + [TearDown] + public new void TearDown() { + base.TearDown(); + + // Clear all tables + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "use [ScrewTurnWikiTest]; delete from [AclEntry]; delete from [OutgoingLink]; delete from [PLuginStatus]; delete from [PluginAssembly]; delete from [RecentChange]; delete from [MetaDataItem]; delete from [Log];delete from [Setting];"; + try { + cmd.ExecuteNonQuery(); + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + + cn.Close(); + } + + [TestFixtureTearDown] + public void FixtureTearDown() { + // Delete database + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "alter database [ScrewTurnWikiTest] set single_user with rollback immediate"; + try { + cmd.ExecuteNonQuery(); + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + + cmd = cn.CreateCommand(); + cmd.CommandText = "drop database [ScrewTurnWikiTest]"; + try { + cmd.ExecuteNonQuery(); + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + + cn.Close(); + } + + [Test] + public void Init() { + ISettingsStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), ConnString + InitialCatalog); + + Assert.IsNotNull(prov.Information, "Information should not be null"); + } + + [TestCase("", ExpectedException = typeof(InvalidConfigurationException))] + [TestCase("blah", ExpectedException = typeof(InvalidConfigurationException))] + [TestCase("Data Source=(local)\\SQLExpress;User ID=inexistent;Password=password;InitialCatalog=Inexistent;", ExpectedException = typeof(InvalidConfigurationException))] + public void Init_InvalidConnString(string c) { + ISettingsStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), c); + } + + } + +} diff --git a/SqlServerProviders-Tests/SqlServerUsersStorageProviderTests.cs b/SqlServerProviders-Tests/SqlServerUsersStorageProviderTests.cs new file mode 100644 index 0000000..81d14f2 --- /dev/null +++ b/SqlServerProviders-Tests/SqlServerUsersStorageProviderTests.cs @@ -0,0 +1,191 @@ + +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.Tests; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Plugins.SqlServer.Tests { + + [TestFixture] + public class SqlServerUsersStorageProviderTests : UsersStorageProviderTestScaffolding { + + //private const string ConnString = "Data Source=(local)\\SQLExpress;User ID=sa;Password=password;"; + private const string ConnString = "Data Source=(local)\\SQLExpress;Integrated Security=SSPI;"; + private const string InitialCatalog = "Initial Catalog=ScrewTurnWikiTest;"; + + public override IUsersStorageProviderV30 GetProvider() { + SqlServerUsersStorageProvider prov = new SqlServerUsersStorageProvider(); + prov.Init(MockHost(), ConnString + InitialCatalog); + return prov; + } + + [TestFixtureSetUp] + public void FixtureSetUp() { + // Create database with no tables + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "if (select count(*) from sys.databases where [Name] = 'ScrewTurnWikiTest') = 0 begin create database [ScrewTurnWikiTest] end"; + cmd.ExecuteNonQuery(); + + cn.Close(); + } + + [TearDown] + public new void TearDown() { + base.TearDown(); + + // Clear all tables + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "use [ScrewTurnWikiTest]; delete from [UserData]; delete from [UserGroupMembership]; delete from [UserGroup]; delete from [User];"; + try { + cmd.ExecuteNonQuery(); + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + + cn.Close(); + } + + [TestFixtureTearDown] + public void FixtureTearDown() { + // Delete database + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "alter database [ScrewTurnWikiTest] set single_user with rollback immediate"; + try { + cmd.ExecuteNonQuery(); + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + + cmd = cn.CreateCommand(); + cmd.CommandText = "drop database [ScrewTurnWikiTest]"; + try { + cmd.ExecuteNonQuery(); + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + + cn.Close(); + + // This is neede because the pooled connection are using a session + // that is now invalid due to the commands executed above + SqlConnection.ClearAllPools(); + } + + [Test] + public void Init() { + IUsersStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), ConnString + InitialCatalog); + + Assert.IsNotNull(prov.Information, "Information should not be null"); + } + + [TestCase("", ExpectedException = typeof(InvalidConfigurationException))] + [TestCase("blah", ExpectedException = typeof(InvalidConfigurationException))] + [TestCase("Data Source=(local)\\SQLExpress;User ID=inexistent;Password=password;InitialCatalog=Inexistent;", ExpectedException = typeof(InvalidConfigurationException))] + public void Init_InvalidConnString(string c) { + IUsersStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), c); + } + + [Test] + public void Init_Upgrade() { + FixtureTearDown(); + + SqlConnection cn = new SqlConnection(ConnString); + cn.Open(); + + SqlCommand cmd = cn.CreateCommand(); + cmd.CommandText = "create database [ScrewTurnWikiTest];"; + cmd.ExecuteNonQuery(); + cn.Close(); + + cn = new SqlConnection(ConnString + InitialCatalog); + cn.Open(); + + cmd = cn.CreateCommand(); + cmd.CommandText = +@"CREATE TABLE [UsersProviderVersion] ( + [Version] varchar(12) PRIMARY KEY +); +INSERT INTO [UsersProviderVersion] ([Version]) VALUES ('Irrelevant'); + +CREATE TABLE [User] ( + [Username] nvarchar(128) PRIMARY KEY, + [PasswordHash] varchar(128) NOT NULL, + [Email] varchar(128) NOT NULL, + [DateTime] datetime NOT NULL, + [Active] bit NOT NULL DEFAULT ((0)), + [Admin] bit NOT NULL DEFAULT ((0)) +); + +INSERT INTO [User] ([Username], [PasswordHash], [Email], [DateTime], [Active], [Admin]) values ('user', 'hash', 'email@users.com', '2008/12/27 12:12:12', 'true', 'false'); +INSERT INTO [User] ([Username], [PasswordHash], [Email], [DateTime], [Active], [Admin]) values ('user2', 'hash2', 'email2@users.com', '2008/12/27 12:12:13', 'false', 'true');"; + + bool done = false; + try { + cmd.ExecuteNonQuery(); + done = true; + } + catch(SqlException sqlex) { + Console.WriteLine(sqlex.ToString()); + } + finally { + cn.Close(); + } + + if(!done) throw new Exception("Could not create v2 test database"); + + MockRepository mocks = new MockRepository(); + IHostV30 host = mocks.DynamicMock(); + Expect.Call(host.GetSettingValue(SettingName.AdministratorsGroup)).Return("Administrators").Repeat.Once(); + Expect.Call(host.GetSettingValue(SettingName.UsersGroup)).Return("Users").Repeat.Once(); + + Expect.Call(host.UpgradeSecurityFlagsToGroupsAcl(null, null)).IgnoreArguments().Repeat.Times(1).Return(true); + + mocks.Replay(host); + + IUsersStorageProviderV30 prov = new SqlServerUsersStorageProvider(); + prov.Init(host, ConnString + InitialCatalog); + + mocks.Verify(host); + + UserInfo[] users = prov.GetUsers(); + + Assert.AreEqual(2, users.Length, "Wrong user count"); + + Assert.AreEqual("user", users[0].Username, "Wrong username"); + Assert.IsNull(users[0].DisplayName, "Display name should be null"); + Assert.AreEqual("email@users.com", users[0].Email, "Wrong email"); + Assert.AreEqual("2008/12/27 12:12:12", users[0].DateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss"), "Wrong date/time"); + Assert.IsTrue(users[0].Active, "User should be active"); + Assert.AreEqual(1, users[0].Groups.Length, "Wrong group count"); + Assert.AreEqual("Users", users[0].Groups[0], "Wrong group"); + + Assert.AreEqual("user2", users[1].Username, "Wrong username"); + Assert.IsNull(users[1].DisplayName, "Display name should be null"); + Assert.AreEqual("email2@users.com", users[1].Email, "Wrong email"); + Assert.AreEqual("2008/12/27 12:12:13", users[1].DateTime.ToString("yyyy'/'MM'/'dd' 'HH':'mm':'ss"), "Wrong date/time"); + Assert.IsFalse(users[1].Active, "User should be inactive"); + Assert.AreEqual(1, users[1].Groups.Length, "Wrong group count"); + Assert.AreEqual("Administrators", users[1].Groups[0], "Wrong group"); + } + + } + +} diff --git a/SqlServerProviders/FilesDatabase.sql b/SqlServerProviders/FilesDatabase.sql new file mode 100644 index 0000000..8f94624 --- /dev/null +++ b/SqlServerProviders/FilesDatabase.sql @@ -0,0 +1,47 @@ + +create table [Directory] ( + [FullPath] nvarchar(250) not null, + [Parent] nvarchar(250), + constraint [PK_Directory] primary key clustered ([FullPath]) +) + +create table [File] ( + [Name] nvarchar(200) not null, + [Directory] nvarchar(250) not null + constraint [FK_File_Directory] references [Directory]([FullPath]) + on delete cascade on update cascade, + [Size] bigint not null, + [Downloads] int not null, + [LastModified] datetime not null, + [Data] varbinary(max) not null, + constraint [PK_File] primary key clustered ([Name], [Directory]) +) + +create table [Attachment] ( + [Name] nvarchar(200) not null, + [Page] nvarchar(200) not null, + [Size] bigint not null, + [Downloads] int not null, + [LastModified] datetime not null, + [Data] varbinary(max) not null, + constraint[PK_Attachment] primary key clustered ([Name], [Page]) +) + +if (select count(*) from sys.tables where [Name] = 'Version') = 0 +begin + create table [Version] ( + [Component] varchar(100) not null, + [Version] int not null, + constraint [PK_Version] primary key clustered ([Component]) + ) +end + +if (select count([Version]) from [Version] where [Component] = 'Files') = 0 +begin + insert into [Version] ([Component], [Version]) values ('Files', 3000) +end + +if (select count([FullPath]) from [Directory] where [FullPath] = '/') = 0 +begin + insert into [Directory] ([FullPath], [Parent]) values ('/', NULL) +end diff --git a/SqlServerProviders/PagesDatabase.sql b/SqlServerProviders/PagesDatabase.sql new file mode 100644 index 0000000..eb5330e --- /dev/null +++ b/SqlServerProviders/PagesDatabase.sql @@ -0,0 +1,146 @@ + +create table [Namespace] ( + [Name] nvarchar(100) not null, + [DefaultPage] nvarchar(200), + constraint [PK_Namespace] primary key clustered ([Name]) +) + +create table [Category]( + [Name] nvarchar(100) not null, + [Namespace] nvarchar(100) not null + constraint [FK_Category_Namespace] references [Namespace]([Name]) + on delete cascade on update cascade, + constraint [PK_Category] primary key clustered ([Name], [Namespace]) +) + +create table [Page] ( + [Name] nvarchar(200) not null, + [Namespace] nvarchar(100) not null + constraint [FK_Page_Namespace] references [Namespace]([Name]) + on delete cascade on update cascade, + [CreationDateTime] datetime not null, + constraint [PK_Page] primary key clustered ([Name], [Namespace]) +) + +-- Deleting/Renaming/Moving a page requires manually updating the binding +create table [CategoryBinding] ( + [Namespace] nvarchar(100) not null + constraint [FK_CategoryBinding_Namespace] references [Namespace]([Name]), + [Category] nvarchar(100) not null, + [Page] nvarchar(200) not null, + constraint [FK_CategoryBinding_Category] foreign key ([Category], [Namespace]) references [Category]([Name], [Namespace]) + on delete cascade on update cascade, + constraint [FK_CategoryBinding_Page] foreign key ([Page], [Namespace]) references [Page]([Name], [Namespace]) + on delete no action on update no action, + constraint [PK_CategoryBinding] primary key clustered ([Namespace], [Page], [Category]) +) + +create table [PageContent] ( + [Page] nvarchar(200) not null, + [Namespace] nvarchar(100) not null, + [Revision] smallint not null, + [Title] nvarchar(200) not null, + [User] nvarchar(100) not null, + [LastModified] datetime not null, + [Comment] nvarchar(300), + [Content] nvarchar(max) not null, + [Description] nvarchar(200), + constraint [FK_PageContent_Page] foreign key ([Page], [Namespace]) references [Page]([Name], [Namespace]) + on delete cascade on update cascade, + constraint [PK_PageContent] primary key clustered ([Page], [Namespace], [Revision]) +) + +create table [PageKeyword] ( + [Page] nvarchar(200) not null, + [Namespace] nvarchar(100) not null, + [Revision] smallint not null, + [Keyword] nvarchar(50) not null, + constraint [FK_PageKeyword_PageContent] foreign key ([Page], [Namespace], [Revision]) references [PageContent]([Page], [Namespace], [Revision]) + on delete cascade on update cascade, + constraint [PK_PageKeyword] primary key clustered ([Page], [Namespace], [Revision], [Keyword]) +) + +create table [Message] ( + [Page] nvarchar(200) not null, + [Namespace] nvarchar(100) not null, + [Id] smallint not null, + [Parent] smallint, + [Username] nvarchar(100) not null, + [Subject] nvarchar(200) not null, + [DateTime] datetime not null, + [Body] nvarchar(max) not null, + constraint [FK_Message_Page] foreign key ([Page], [Namespace]) references [Page]([Name], [Namespace]) + on delete cascade on update cascade, + constraint [PK_Message] primary key clustered ([Page], [Namespace], [Id]) +) + +create table [NavigationPath] ( + [Name] nvarchar(100) not null, + [Namespace] nvarchar(100) not null, + [Page] nvarchar(200) not null, + [Number] smallint not null, + constraint [FK_NavigationPath_Page] foreign key ([Page], [Namespace]) references [Page]([Name], [Namespace]) + on delete cascade on update cascade, + constraint [PK_NavigationPath] primary key clustered ([Name], [Namespace], [Page]) +) + +create table [Snippet] ( + [Name] nvarchar(200) not null, + [Content] nvarchar(max) not null, + constraint [PK_Snippet] primary key clustered ([Name]) +) + +create table [ContentTemplate] ( + [Name] nvarchar(200) not null, + [Content] nvarchar(max) not null, + constraint [PK_ContentTemplate] primary key clustered ([Name]) +) + +create table [IndexDocument] ( + [Id] int not null, + [Name] nvarchar(200) not null + constraint [UQ_IndexDocument] unique, + [Title] nvarchar(200) not null, + [TypeTag] varchar(10) not null, + [DateTime] datetime not null, + constraint [PK_IndexDocument] primary key clustered ([Id]) +) + +create table [IndexWord] ( + [Id] int not null, + [Text] nvarchar(200) not null + constraint [UQ_IndexWord] unique, + constraint [PK_IndexWord] primary key clustered ([Id]) +) + +create table [IndexWordMapping] ( + [Word] int not null + constraint [FK_IndexWordMapping_IndexWord] references [IndexWord]([Id]) + on delete cascade on update cascade, + [Document] int not null + constraint [FK_IndexWordMapping_IndexDocument] references [IndexDocument]([Id]) + on delete cascade on update cascade, + [FirstCharIndex] smallint not null, + [WordIndex] smallint not null, + [Location] tinyint not null, + constraint [PK_IndexWordMapping] primary key clustered ([Word], [Document], [FirstCharIndex], [WordIndex], [Location]) +) + +if (select count(*) from sys.tables where [Name] = 'Version') = 0 +begin + create table [Version] ( + [Component] varchar(100) not null, + [Version] int not null, + constraint [PK_Version] primary key clustered ([Component]) + ) +end + +if (select count([Version]) from [Version] where [Component] = 'Pages') = 0 +begin + insert into [Version] ([Component], [Version]) values ('Pages', 3001) +end + +if (select count([Name]) from [Namespace] where [Name] = '') = 0 +begin + insert into [Namespace] ([Name], [DefaultPage]) values ('', null) +end diff --git a/SqlServerProviders/PagesDatabase_3000to3001.sql b/SqlServerProviders/PagesDatabase_3000to3001.sql new file mode 100644 index 0000000..f41f827 --- /dev/null +++ b/SqlServerProviders/PagesDatabase_3000to3001.sql @@ -0,0 +1,36 @@ + +drop table [IndexWordMapping] +drop table [IndexWord] +drop table [IndexDocument] + +create table [IndexDocument] ( + [Id] int not null, + [Name] nvarchar(200) not null + constraint [UQ_IndexDocument] unique, + [Title] nvarchar(200) not null, + [TypeTag] varchar(10) not null, + [DateTime] datetime not null, + constraint [PK_IndexDocument] primary key clustered ([Id]) +) + +create table [IndexWord] ( + [Id] int not null, + [Text] nvarchar(200) not null + constraint [UQ_IndexWord] unique, + constraint [PK_IndexWord] primary key clustered ([Id]) +) + +create table [IndexWordMapping] ( + [Word] int not null + constraint [FK_IndexWordMapping_IndexWord] references [IndexWord]([Id]) + on delete cascade on update cascade, + [Document] int not null + constraint [FK_IndexWordMapping_IndexDocument] references [IndexDocument]([Id]) + on delete cascade on update cascade, + [FirstCharIndex] smallint not null, + [WordIndex] smallint not null, + [Location] tinyint not null, + constraint [PK_IndexWordMapping] primary key clustered ([Word], [Document], [FirstCharIndex], [WordIndex], [Location]) +) + +update [Version] set [Version] = 3001 where [Component] = 'Pages' diff --git a/SqlServerProviders/Properties/AssemblyInfo.cs b/SqlServerProviders/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7177661 --- /dev/null +++ b/SqlServerProviders/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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("ScrewTurn Wiki SQL Server Providers")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// 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("9e2877f9-b7ab-4f1e-be86-3c7d1cf5dbab")] diff --git a/SqlServerProviders/Properties/Resources.Designer.cs b/SqlServerProviders/Properties/Resources.Designer.cs new file mode 100644 index 0000000..71786ad --- /dev/null +++ b/SqlServerProviders/Properties/Resources.Designer.cs @@ -0,0 +1,198 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4016 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki.Plugins.SqlServer.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ScrewTurn.Wiki.Plugins.SqlServer.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to + ///create table [Directory] ( + /// [FullPath] nvarchar(250) not null, + /// [Parent] nvarchar(250), + /// constraint [PK_Directory] primary key clustered ([FullPath]) + ///) + /// + ///create table [File] ( + /// [Name] nvarchar(200) not null, + /// [Directory] nvarchar(250) not null + /// constraint [FK_File_Directory] references [Directory]([FullPath]) + /// on delete cascade on update cascade, + /// [Size] bigint not null, + /// [Downloads] int not null, + /// [LastModified] datetime not null, + /// [Data] varbinary(max) not null, + /// constraint [PK_File] pri [rest of string was truncated]";. + /// + internal static string FilesDatabase { + get { + return ResourceManager.GetString("FilesDatabase", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to + ///create table [Namespace] ( + /// [Name] nvarchar(100) not null, + /// [DefaultPage] nvarchar(200), + /// constraint [PK_Namespace] primary key clustered ([Name]) + ///) + /// + ///create table [Category]( + /// [Name] nvarchar(100) not null, + /// [Namespace] nvarchar(100) not null + /// constraint [FK_Category_Namespace] references [Namespace]([Name]) + /// on delete cascade on update cascade, + /// constraint [PK_Category] primary key clustered ([Name], [Namespace]) + ///) + /// + ///create table [Page] ( + /// [Name] nvarchar(200) not null, + /// [Namespace] nvar [rest of string was truncated]";. + /// + internal static string PagesDatabase { + get { + return ResourceManager.GetString("PagesDatabase", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to + ///drop table [IndexWordMapping] + ///drop table [IndexWord] + ///drop table [IndexDocument] + /// + ///create table [IndexDocument] ( + /// [Id] int not null, + /// [Name] nvarchar(200) not null + /// constraint [UQ_IndexDocument] unique, + /// [Title] nvarchar(200) not null, + /// [TypeTag] varchar(10) not null, + /// [DateTime] datetime not null, + /// constraint [PK_IndexDocument] primary key clustered ([Id]) + ///) + /// + ///create table [IndexWord] ( + /// [Id] int not null, + /// [Text] nvarchar(200) not null + /// constraint [UQ_IndexKeyword] unique, + /// constraint [rest of string was truncated]";. + /// + internal static string PagesDatabase_3000to3001 { + get { + return ResourceManager.GetString("PagesDatabase_3000to3001", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to + ///create table [Setting] ( + /// [Name] varchar(100) not null, + /// [Value] nvarchar(4000) not null, + /// constraint [PK_Setting] primary key clustered ([Name]) + ///) + /// + ///create table [Log] ( + /// [Id] int not null identity, + /// [DateTime] datetime not null, + /// [EntryType] char not null, + /// [User] nvarchar(100) not null, + /// [Message] nvarchar(4000) not null, + /// constraint [PK_Log] primary key clustered ([Id]) + ///) + /// + ///create table [MetaDataItem] ( + /// [Name] varchar(100) not null, + /// [Tag] nvarchar(100) not null, + /// [Data] nvarchar(4000 [rest of string was truncated]";. + /// + internal static string SettingsDatabase { + get { + return ResourceManager.GetString("SettingsDatabase", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to + ///create table [User] ( + /// [Username] nvarchar(100) not null, + /// [PasswordHash] varchar(100) not null, + /// [DisplayName] nvarchar(150), + /// [Email] varchar(100) not null, + /// [Active] bit not null, + /// [DateTime] datetime not null, + /// constraint [PK_User] primary key clustered ([USername]) + ///) + /// + ///create table [UserGroup] ( + /// [Name] nvarchar(100) not null, + /// [Description] nvarchar(150), + /// constraint [PK_UserGroup] primary key clustered ([Name]) + ///) + /// + ///create table [UserGroupMembership] ( + /// [User] nvarchar(100) not null + /// [rest of string was truncated]";. + /// + internal static string UsersDatabase { + get { + return ResourceManager.GetString("UsersDatabase", resourceCulture); + } + } + } +} diff --git a/SqlServerProviders/Properties/Resources.resx b/SqlServerProviders/Properties/Resources.resx new file mode 100644 index 0000000..b790b17 --- /dev/null +++ b/SqlServerProviders/Properties/Resources.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\FilesDatabase.sql;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + ..\PagesDatabase.sql;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + ..\PagesDatabase_3000to3001.sql;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + ..\SettingsDatabase.sql;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + ..\UsersDatabase.sql;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + \ No newline at end of file diff --git a/SqlServerProviders/SettingsDatabase.sql b/SqlServerProviders/SettingsDatabase.sql new file mode 100644 index 0000000..5783055 --- /dev/null +++ b/SqlServerProviders/SettingsDatabase.sql @@ -0,0 +1,75 @@ + +create table [Setting] ( + [Name] varchar(100) not null, + [Value] nvarchar(4000) not null, + constraint [PK_Setting] primary key clustered ([Name]) +) + +create table [Log] ( + [Id] int not null identity, + [DateTime] datetime not null, + [EntryType] char not null, + [User] nvarchar(100) not null, + [Message] nvarchar(4000) not null, + constraint [PK_Log] primary key clustered ([Id]) +) + +create table [MetaDataItem] ( + [Name] varchar(100) not null, + [Tag] nvarchar(100) not null, + [Data] nvarchar(4000) not null, + constraint [PK_MetaDataItem] primary key clustered ([Name], [Tag]) +) + +create table [RecentChange] ( + [Id] int not null identity, + [Page] nvarchar(200) not null, + [Title] nvarchar(200) not null, + [MessageSubject] nvarchar(200), + [DateTime] datetime not null, + [User] nvarchar(100) not null, + [Change] char not null, + [Description] nvarchar(4000), + constraint [PK_RecentChange] primary key clustered ([Id]) +) + +create table [PluginAssembly] ( + [Name] varchar(100) not null, + [Assembly] varbinary(max) not null, + constraint [PK_PluginAssembly] primary key clustered ([Name]) +) + +create table [PluginStatus] ( + [Name] varchar(150) not null, + [Enabled] bit not null, + [Configuration] nvarchar(4000) not null, + constraint [PK_PluginStatus] primary key clustered ([Name]) +) + +create table [OutgoingLink] ( + [Source] nvarchar(100) not null, + [Destination] nvarchar(100) not null, + constraint [PK_OutgoingLink] primary key clustered ([Source], [Destination]) +) + +create table [AclEntry] ( + [Resource] nvarchar(200) not null, + [Action] varchar(50) not null, + [Subject] nvarchar(100) not null, + [Value] char not null, + constraint [PK_AclEntry] primary key clustered ([Resource], [Action], [Subject]) +) + +if (select count(*) from sys.tables where [Name] = 'Version') = 0 +begin + create table [Version] ( + [Component] varchar(100) not null, + [Version] int not null, + constraint [PK_Version] primary key clustered ([Component]) + ) +end + +if (select count([Version]) from [Version] where [Component] = 'Settings') = 0 +begin + insert into [Version] ([Component], [Version]) values ('Settings', 3000) +end diff --git a/SqlServerProviders/SqlServerCommandBuilder.cs b/SqlServerProviders/SqlServerCommandBuilder.cs new file mode 100644 index 0000000..4557868 --- /dev/null +++ b/SqlServerProviders/SqlServerCommandBuilder.cs @@ -0,0 +1,128 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.Plugins.SqlCommon; +using System.Data.Common; +using System.Data.SqlClient; + +namespace ScrewTurn.Wiki.Plugins.SqlServer { + + /// + /// Implements a command builder for SQL Server. + /// + public class SqlServerCommandBuilder : ICommandBuilder { + + /// + /// Gets the table and column name prefix. + /// + public string ObjectNamePrefix { + get { return "["; } + } + + /// + /// Gets the table and column name suffix. + /// + public string ObjectNameSuffix { + get { return "]"; } + } + + /// + /// Gets the parameter name prefix. + /// + public string ParameterNamePrefix { + get { return "@"; } + } + + /// + /// Gets the parameter name suffix. + /// + public string ParameterNameSuffix { + get { return ""; } + } + + /// + /// Gets the parameter name placeholder. + /// + public string ParameterPlaceholder { + get { throw new NotImplementedException(); } + } + + /// + /// Gets a value indicating whether to use named parameters. If false, + /// parameter placeholders will be equal to . + /// + public bool UseNamedParameters { + get { return true; } + } + + /// + /// Gets the string to use in order to separate queries in a batch. + /// + public string BatchQuerySeparator { + get { return "; "; } + } + + /// + /// Gets a new database connection, open. + /// + /// The connection string. + /// The connection. + public DbConnection GetConnection(string connString) { + DbConnection cn = new SqlConnection(connString); + cn.Open(); + + return cn; + } + + /// + /// Gets a properly built database command, with the underlying connection already open. + /// + /// The connection string. + /// The prepared query. + /// The parameters, if any. + /// The command. + public DbCommand GetCommand(string connString, string preparedQuery, List parameters) { + return GetCommand(GetConnection(connString), preparedQuery, parameters); + } + + /// + /// Gets a properly built database command, re-using an open connection. + /// + /// The open connection to use. + /// The prepared query. + /// The parameters, if any. + /// The command. + public DbCommand GetCommand(DbConnection connection, string preparedQuery, List parameters) { + DbCommand cmd = connection.CreateCommand(); + cmd.CommandText = preparedQuery; + + foreach(Parameter param in parameters) { + cmd.Parameters.Add(new SqlParameter("@" + param.Name, param.Value)); + } + + return cmd; + } + + /// + /// Gets a properly built database command, re-using an open connection and a begun transaction. + /// + /// The transaction. + /// The prepared query. + /// The parameters, if any. + /// The command. + public DbCommand GetCommand(DbTransaction transaction, string preparedQuery, List parameters) { + DbCommand cmd = transaction.Connection.CreateCommand(); + cmd.Transaction = transaction; + cmd.CommandText = preparedQuery; + + foreach(Parameter param in parameters) { + cmd.Parameters.Add(new SqlParameter("@" + param.Name, param.Value)); + } + + return cmd; + } + + } + +} diff --git a/SqlServerProviders/SqlServerFilesStorageProvider.cs b/SqlServerProviders/SqlServerFilesStorageProvider.cs new file mode 100644 index 0000000..a19243b --- /dev/null +++ b/SqlServerProviders/SqlServerFilesStorageProvider.cs @@ -0,0 +1,177 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.Plugins.SqlCommon; +using ScrewTurn.Wiki.PluginFramework; +using System.Data.SqlClient; + +namespace ScrewTurn.Wiki.Plugins.SqlServer { + + /// + /// Implements a SQL Server-based files storage provider. + /// + public class SqlServerFilesStorageProvider : SqlFilesStorageProviderBase { + + private readonly ComponentInformation info = new ComponentInformation("SQL Server Files Storage Provider", "ScrewTurn Software", "3.0.0.180", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/SQLServerProv/Files.txt"); + + private readonly SqlServerCommandBuilder commandBuilder = new SqlServerCommandBuilder(); + + private const int CurrentSchemaVersion = 3000; + + /// + /// Gets a new command with an open connection. + /// + /// The connection string. + /// The command. + private SqlCommand GetCommand(string connString) { + return commandBuilder.GetCommand(connString, "select current_user", new List()) as SqlCommand; + } + + /// + /// Gets a new command builder object. + /// + /// The command builder. + protected override ICommandBuilder GetCommandBuilder() { + return commandBuilder; + } + + /// + /// Validates a connection string. + /// + /// The connection string to validate. + /// If the connection string is invalid, the method throws . + protected override void ValidateConnectionString(string connString) { + SqlCommand cmd = null; + try { + cmd = GetCommand(connString); + } + catch(SqlException ex) { + throw new InvalidConfigurationException("Provided connection string is not valid", ex); + } + catch(InvalidOperationException ex) { + throw new InvalidConfigurationException("Provided connection string is not valid", ex); + } + catch(ArgumentException ex) { + throw new InvalidConfigurationException("Provided connection string is not valid", ex); + } + finally { + try { + cmd.Connection.Close(); + } + catch { } + } + } + + /// + /// Detects whether the database schema exists. + /// + /// true if the schema exists, false otherwise. + private bool SchemaExists() { + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = "select [Version] from [Version] where [Component] = 'Files'"; + + bool exists = false; + + try { + int version = ExecuteScalar(cmd, -1); + if(version > CurrentSchemaVersion) throw new InvalidConfigurationException("The version of the database schema is greater than the supported version"); + exists = version != -1; + } + catch(SqlException) { + exists = false; + } + finally { + try { + cmd.Connection.Close(); + } + catch { } + } + + return exists; + } + + /// + /// Detects whether the database schema needs to be updated. + /// + /// true if an update is needed, false otherwise. + private bool SchemaNeedsUpdate() { + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = "select [Version] from [Version] where [Component] = 'Files'"; + + bool exists = false; + + try { + int version = ExecuteScalar(cmd, -1); + exists = version < CurrentSchemaVersion; + } + catch(SqlException) { + exists = false; + } + finally { + try { + cmd.Connection.Close(); + } + catch { } + } + + return exists; + } + + /// + /// Creates the standard database schema. + /// + private void CreateStandardSchema() { + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = Properties.Resources.FilesDatabase; + + cmd.ExecuteNonQuery(); + + cmd.Connection.Close(); + } + + /// + /// Creates or updates the database schema if necessary. + /// + protected override void CreateOrUpdateDatabaseIfNecessary() { + if(!SchemaExists()) { + CreateStandardSchema(); + } + if(SchemaNeedsUpdate()) { + // Run minor update batches... + } + } + + /// + /// Tries to load the configuration from a corresponding v2 provider. + /// + /// The configuration, or an empty string. + protected override string TryLoadV2Configuration() { + return ""; + } + + /// + /// Tries to load the configuration of the corresponding settings storage provider. + /// + /// The configuration, or an empty string. + protected override string TryLoadSettingsStorageProviderConfiguration() { + return host.GetProviderConfiguration(typeof(SqlServerSettingsStorageProvider).FullName); + } + + /// + /// Gets the Information about the Provider. + /// + public override ComponentInformation Information { + get { return info; } + } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + public override string ConfigHelpHtml { + get { return "Connection string format:
    Data Source=Database Address and Instance;Initial Catalog=Database name;User ID=login;Password=password;"; } + } + + } + +} diff --git a/SqlServerProviders/SqlServerPagesStorageProvider.cs b/SqlServerProviders/SqlServerPagesStorageProvider.cs new file mode 100644 index 0000000..01fe063 --- /dev/null +++ b/SqlServerProviders/SqlServerPagesStorageProvider.cs @@ -0,0 +1,427 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.Plugins.SqlCommon; +using System.Data.SqlClient; + +namespace ScrewTurn.Wiki.Plugins.SqlServer { + + /// + /// Implements a SQL Server-based users storage provider. + /// + public class SqlServerPagesStorageProvider : SqlPagesStorageProviderBase, IPagesStorageProviderV30 { + + private readonly ComponentInformation info = new ComponentInformation("SQL Server Pages Storage Provider", "ScrewTurn Software", "3.0.0.237", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/SQLServerProv/Pages.txt"); + + private readonly SqlServerCommandBuilder commandBuilder = new SqlServerCommandBuilder(); + + private const int CurrentSchemaVersion = 3001; + + /// + /// Gets a new command with an open connection. + /// + /// The connection string. + /// The command. + private SqlCommand GetCommand(string connString) { + return commandBuilder.GetCommand(connString, "select current_user", new List()) as SqlCommand; + } + + /// + /// Gets a new command builder object. + /// + /// The command builder. + protected override ICommandBuilder GetCommandBuilder() { + return commandBuilder; + } + + /// + /// Validates a connection string. + /// + /// The connection string to validate. + /// If the connection string is invalid, the method throws . + protected override void ValidateConnectionString(string connString) { + SqlCommand cmd = null; + try { + cmd = GetCommand(connString); + } + catch(SqlException ex) { + throw new InvalidConfigurationException("Provided connection string is not valid", ex); + } + catch(InvalidOperationException ex) { + throw new InvalidConfigurationException("Provided connection string is not valid", ex); + } + catch(ArgumentException ex) { + throw new InvalidConfigurationException("Provided connection string is not valid", ex); + } + finally { + try { + cmd.Connection.Close(); + } + catch { } + } + } + + /// + /// Detects whether the database schema exists. + /// + /// true if the schema exists, false otherwise. + private bool SchemaExists() { + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = "select [Version] from [Version] where [Component] = 'Pages'"; + + bool exists = false; + + try { + int version = ExecuteScalar(cmd, -1); + if(version > CurrentSchemaVersion) throw new InvalidConfigurationException("The version of the database schema is greater than the supported version"); + exists = version != -1; + } + catch(SqlException) { + exists = false; + } + finally { + try { + cmd.Connection.Close(); + } + catch { } + } + + return exists; + } + + /// + /// Detects whether the database schema needs to be updated. + /// + /// true if an update is needed, false otherwise. + private bool SchemaNeedsUpdate() { + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = "select [Version] from [Version] where [Component] = 'Pages'"; + + bool exists = false; + + try { + int version = ExecuteScalar(cmd, -1); + exists = version < CurrentSchemaVersion; + } + catch(SqlException) { + exists = false; + } + finally { + try { + cmd.Connection.Close(); + } + catch { } + } + + return exists; + } + + /// + /// Creates the standard database schema. + /// + private void CreateStandardSchema() { + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = Properties.Resources.PagesDatabase; + + cmd.ExecuteNonQuery(); + + cmd.Connection.Close(); + } + + /// + /// Creates or updates the database schema if necessary. + /// + protected override void CreateOrUpdateDatabaseIfNecessary() { + if(!SchemaExists()) { + // Verify if an upgrade from version 2.0 is possible + if(SchemaAllowsUpgradeFrom20()) { + UpgradeFrom20(); + } + else { + // If not, create the standard schema + CreateStandardSchema(); + } + } + if(SchemaNeedsUpdate()) { + // Run minor update batches... + Update3000to3001(); + // Other update batches + } + } + + /// + /// Updates the database schema from version 3000 to version 3001. + /// + private void Update3000to3001() { + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = Properties.Resources.PagesDatabase_3000to3001; + + cmd.ExecuteNonQuery(); + + cmd.Connection.Close(); + } + + /// + /// Detects whether an upgrade is possible from version 2.0. + /// + /// true if the upgrade is possible, false otherwise. + private bool SchemaAllowsUpgradeFrom20() { + // Look for 'PagesProviderVersion' table + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = "select count(*) from sys.tables where [name] = 'PagesProviderVersion'"; + + int count = ExecuteScalar(cmd, -1); + + return count == 1; + } + + /// + /// Upgrades the database schema and data from version 2.0. + /// + private void UpgradeFrom20() { + // Procedure + // 1. Rename old tables (_v2) and create new schema + // 2. Copy snippets + // 3. Copy pages + // 4. Copy messages + // 5. Copy categories + // 6. Copy category bindings + // 7. Copy nav paths + // 8. Rename offending pages and categories (names with dots) leveraging cascaded FKs + // 9. Update security to use ACL + + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = +@"exec sp_rename 'Page', 'Page_v2'; +exec sp_rename 'PageContent', 'PageContent_v2'; +exec sp_rename 'Category', 'Category_v2'; +exec sp_rename 'CategoryBinding', 'CategoryBinding_v2'; +exec sp_rename 'Message', 'Message_v2'; +exec sp_rename 'Snippet', 'Snippet_v2'; +exec sp_rename 'NavigationPath', 'NavigationPath_v2'; +exec sp_rename 'NavigationPathBinding', 'NavigationPathBinding_v2'; +exec sp_rename 'PagesProviderVersion', 'PagesProviderVersion_v2';"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + + CreateStandardSchema(); + + cmd = GetCommand(connString); + cmd.CommandText = "insert into [Snippet] select [Name], [Content] from [Snippet_v2]"; + cmd.ExecuteNonQuery(); + + Dictionary pageStatus = new Dictionary(500); + + cmd.CommandText = "select * from [Page_v2]"; + using(SqlDataReader reader = cmd.ExecuteReader()) { + SqlCommand insertCmd = GetCommand(connString); + insertCmd.CommandText = "insert into [Page] ([Name], [Namespace], [CreationDateTime]) values (@Name, '', @CreationDateTime)"; + + while(reader.Read()) { + insertCmd.Parameters.Clear(); + insertCmd.Parameters.Add(new SqlParameter("@Name", reader["Name"] as string)); + insertCmd.Parameters.Add(new SqlParameter("@CreationDateTime", (DateTime)reader["CreationDateTime"])); + pageStatus.Add(reader["Name"] as string, (reader["Status"] as string).ToUpperInvariant()[0]); + + insertCmd.ExecuteNonQuery(); + } + + insertCmd.Connection.Close(); + } + + cmd.CommandText = "select * from [PageContent_v2]"; + using(SqlDataReader reader = cmd.ExecuteReader()) { + SqlCommand insertCmd = GetCommand(connString); + insertCmd.CommandText = "insert into [PageContent] ([Page], [Namespace], [Revision], [Title], [User], [LastModified], [Comment], [Content], [Description]) values (@Page, '', @Revision, @Title, @User, @LastModified, @Comment, @Content, NULL)"; + + while(reader.Read()) { + insertCmd.Parameters.Clear(); + insertCmd.Parameters.Add(new SqlParameter("@Page", reader["Page"] as string)); + insertCmd.Parameters.Add(new SqlParameter("@Revision", (short)(int)reader["Revision"])); + insertCmd.Parameters.Add(new SqlParameter("@Title", reader["Title"] as string)); + insertCmd.Parameters.Add(new SqlParameter("@User", reader["Username"] as string)); + insertCmd.Parameters.Add(new SqlParameter("@LastModified", (DateTime)reader["DateTime"])); + insertCmd.Parameters.Add(new SqlParameter("@Comment", reader["Comment"] as string)); // Cannot be null in v2 + insertCmd.Parameters.Add(new SqlParameter("@Content", reader["Content"] as string)); + + insertCmd.ExecuteNonQuery(); + } + + insertCmd.Connection.Close(); + } + + cmd.CommandText = "select * from [Message_v2]"; + using(SqlDataReader reader = cmd.ExecuteReader()) { + SqlCommand insertCmd = GetCommand(connString); + insertCmd.CommandText = "insert into [Message] ([Page], [Namespace], [Id], [Parent], [Username], [Subject], [DateTime], [Body]) values (@Page, '', @Id, @Parent, @Username, @Subject, @DateTime, @Body)"; + + while(reader.Read()) { + insertCmd.Parameters.Clear(); + insertCmd.Parameters.Add(new SqlParameter("@Page", reader["Page"] as string)); + insertCmd.Parameters.Add(new SqlParameter("@Id", (short)(int)reader["ID"])); + int parent = (int)reader["Parent"]; + if(parent == -1) insertCmd.Parameters.Add(new SqlParameter("@Parent", DBNull.Value)); + else insertCmd.Parameters.Add(new SqlParameter("@Parent", (short)parent)); + insertCmd.Parameters.Add(new SqlParameter("@Username", reader["Username"] as string)); + insertCmd.Parameters.Add(new SqlParameter("@Subject", reader["Subject"] as string)); + insertCmd.Parameters.Add(new SqlParameter("@DateTime", (DateTime)reader["DateTime"])); + insertCmd.Parameters.Add(new SqlParameter("@Body", reader["Body"] as string)); + + insertCmd.ExecuteNonQuery(); + } + + insertCmd.Connection.Close(); + } + + cmd.CommandText = "select * from [Category_v2]"; + using(SqlDataReader reader = cmd.ExecuteReader()) { + SqlCommand insertCmd = GetCommand(connString); + insertCmd.CommandText = "insert into [Category] ([Name], [Namespace]) values (@Name, '')"; + + while(reader.Read()) { + insertCmd.Parameters.Clear(); + insertCmd.Parameters.Add(new SqlParameter("@Name", reader["Name"] as string)); + + insertCmd.ExecuteNonQuery(); + } + + insertCmd.Connection.Close(); + } + + cmd.CommandText = "select * from [CategoryBinding_v2]"; + using(SqlDataReader reader = cmd.ExecuteReader()) { + SqlCommand insertCmd = GetCommand(connString); + insertCmd.CommandText = "insert into [CategoryBinding] ([Namespace], [Category], [Page]) values ('', @Category, @Page)"; + + while(reader.Read()) { + insertCmd.Parameters.Clear(); + insertCmd.Parameters.Add(new SqlParameter("@Category", reader["Category"] as string)); + insertCmd.Parameters.Add(new SqlParameter("@Page", reader["Page"] as string)); + + insertCmd.ExecuteNonQuery(); + } + + insertCmd.Connection.Close(); + } + + cmd.CommandText = "select * from [NavigationPathBinding_v2]"; + using(SqlDataReader reader = cmd.ExecuteReader()) { + SqlCommand insertCmd = GetCommand(connString); + insertCmd.CommandText = "insert into [NavigationPath] ([Name], [Namespace], [Page], [Number]) values (@Name, '', @Page, @Number)"; + + while(reader.Read()) { + insertCmd.Parameters.Clear(); + insertCmd.Parameters.Add(new SqlParameter("@Name", reader["NavigationPath"] as string)); + insertCmd.Parameters.Add(new SqlParameter("@Page", reader["Page"] as string)); + insertCmd.Parameters.Add(new SqlParameter("@Number", (short)(int)reader["Number"])); + + insertCmd.ExecuteNonQuery(); + } + + insertCmd.Connection.Close(); + } + + // Rename pages and categories + List allPages = new List(500); + List allCategories = new List(50); + + cmd.CommandText = "select [Name] from [Page]"; + using(SqlDataReader reader = cmd.ExecuteReader()) { + while(reader.Read()) { + allPages.Add(reader["Name"] as string); + } + } + + cmd.CommandText = "select [Name] from [Category]"; + using(SqlDataReader reader = cmd.ExecuteReader()) { + while(reader.Read()) { + allCategories.Add(reader["Name"] as string); + } + } + + cmd.CommandText = "alter table [CategoryBinding] nocheck constraint all"; + cmd.ExecuteNonQuery(); + + cmd.CommandText = "update [Page] set [Name] = @NewName where [Name] = @OldName; update [CategoryBinding] set [Page] = @NewName2 where [Page] = @OldName2"; + foreach(string page in allPages) { + if(page.Contains(".")) { + cmd.Parameters.Clear(); + cmd.Parameters.Add(new SqlParameter("@NewName", page.Replace(".", "_"))); + cmd.Parameters.Add(new SqlParameter("@OldName", page)); + cmd.Parameters.Add(new SqlParameter("@NewName2", page.Replace(".", "_"))); + cmd.Parameters.Add(new SqlParameter("@OldName2", page)); + + cmd.ExecuteNonQuery(); + } + } + + cmd.CommandText = "alter table [CategoryBinding] with check check constraint all"; + cmd.ExecuteNonQuery(); + + cmd.CommandText = "update [Category] set [Name] = @NewName where [Name] = @OldName"; + foreach(string category in allCategories) { + if(category.Contains(".")) { + cmd.Parameters.Clear(); + cmd.Parameters.Add(new SqlParameter("@NewName", category.Replace(".", "_"))); + cmd.Parameters.Add(new SqlParameter("@OldName", category)); + + cmd.ExecuteNonQuery(); + } + } + + cmd.Connection.Close(); + + string[] keys = new string[pageStatus.Count]; + pageStatus.Keys.CopyTo(keys, 0); + foreach(string key in keys) { + if(key.Contains(".")) { + char status = pageStatus[key]; + pageStatus.Remove(key); + pageStatus.Add(key.Replace(".", "_"), status); + } + } + + // Setup permissions for single pages + foreach(KeyValuePair pair in pageStatus) { + if(pair.Value != 'N') { + // Need to set permissions emulating old-style behavior + host.UpgradePageStatusToAcl(new PageInfo(pair.Key, this, DateTime.MinValue), pair.Value); + } + } + } + + /// + /// Tries to load the configuration from a corresponding v2 provider. + /// + /// The configuration, or an empty string. + protected override string TryLoadV2Configuration() { + return host.GetProviderConfiguration("ScrewTurn.Wiki.PluginPack.SqlServerPagesStorageProvider"); + } + + /// + /// Tries to load the configuration of the corresponding settings storage provider. + /// + /// The configuration, or an empty string. + protected override string TryLoadSettingsStorageProviderConfiguration() { + return host.GetProviderConfiguration(typeof(SqlServerSettingsStorageProvider).FullName); + } + + /// + /// Gets the Information about the Provider. + /// + public override ComponentInformation Information { + get { return info; } + } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + public override string ConfigHelpHtml { + get { return "Connection string format:
    Data Source=Database Address and Instance;Initial Catalog=Database name;User ID=login;Password=password;"; } + } + + } + +} diff --git a/SqlServerProviders/SqlServerProviders.csproj b/SqlServerProviders/SqlServerProviders.csproj new file mode 100644 index 0000000..5da9f88 --- /dev/null +++ b/SqlServerProviders/SqlServerProviders.csproj @@ -0,0 +1,143 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {ECB488D9-C8E9-41E0-BE27-27F41F76F8A0} + Library + Properties + ScrewTurn.Wiki.Plugins.SqlServer + SqlServerProviders + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + bin\Debug\SqlServerProviders.XML + + + none + true + bin\Release\ + TRACE + prompt + 4 + true + bin\Release\SqlServerProviders.XML + + + + + 3.5 + + + + + + + + AssemblyVersion.cs + + + SqlProvidersCommon\Hash.cs + + + SqlProvidersCommon\ICommandBuilder.cs + + + SqlProvidersCommon\IIndexConnector.cs + + + SqlProvidersCommon\IndexConnector.cs + + + SqlProvidersCommon\Parameter.cs + + + SqlProvidersCommon\QueryBuilder.cs + + + SqlProvidersCommon\SqlAclStorer.cs + + + SqlProvidersCommon\SqlClassBase.cs + + + SqlProvidersCommon\SqlFilesStorageProviderBase.cs + + + SqlProvidersCommon\SqlIndex.cs + + + SqlProvidersCommon\SqlPagesStorageProviderBase.cs + + + SqlProvidersCommon\SqlSettingsStorageProviderBase.cs + + + SqlProvidersCommon\SqlStorageProviderBase.cs + + + SqlProvidersCommon\SqlUsersStorageProviderBase.cs + + + SqlProvidersCommon\Tools.cs + + + + True + True + Resources.resx + + + + + + + + + + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81} + AclEngine + + + {531A83D6-76F9-4014-91C5-295818E2D948} + PluginFramework + + + {2DF980A6-4742-49B1-A090-DE79314644D0} + SearchEngine + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + + \ No newline at end of file diff --git a/SqlServerProviders/SqlServerSettingsStorageProvider.cs b/SqlServerProviders/SqlServerSettingsStorageProvider.cs new file mode 100644 index 0000000..bdc2559 --- /dev/null +++ b/SqlServerProviders/SqlServerSettingsStorageProvider.cs @@ -0,0 +1,198 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.Plugins.SqlCommon; +using ScrewTurn.Wiki.PluginFramework; +using System.Data.SqlClient; + +namespace ScrewTurn.Wiki.Plugins.SqlServer { + + /// + /// Implements a SQL Server-based settings storage provider. + /// + public class SqlServerSettingsStorageProvider : SqlSettingsStorageProviderBase { + + private readonly ComponentInformation info = new ComponentInformation("SQL Server Settings Storage Provider", "ScrewTurn Software", "3.0.0.235", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/SQLServerProv/Settings.txt"); + + private readonly SqlServerCommandBuilder commandBuilder = new SqlServerCommandBuilder(); + + private const int CurrentSchemaVersion = 3000; + + /// + /// Gets a new command with an open connection. + /// + /// The connection string. + /// The command. + private SqlCommand GetCommand(string connString) { + return commandBuilder.GetCommand(connString, "select current_user", new List()) as SqlCommand; + } + + /// + /// Gets a new command builder object. + /// + /// The command builder. + protected override ICommandBuilder GetCommandBuilder() { + return commandBuilder; + } + + /// + /// Validates a connection string. + /// + /// The connection string to validate. + /// If the connection string is invalid, the method throws . + protected override void ValidateConnectionString(string connString) { + SqlCommand cmd = null; + try { + cmd = GetCommand(connString); + } + catch(SqlException ex) { + throw new InvalidConfigurationException("Provided connection string is not valid", ex); + } + catch(InvalidOperationException ex) { + throw new InvalidConfigurationException("Provided connection string is not valid", ex); + } + catch(ArgumentException ex) { + throw new InvalidConfigurationException("Provided connection string is not valid", ex); + } + finally { + try { + cmd.Connection.Close(); + } + catch { } + } + } + + /// + /// Detects whether the database schema exists. + /// + /// true if the schema exists, false otherwise. + private bool SchemaExists() { + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = "select [Version] from [Version] where [Component] = 'Settings'"; + + bool exists = false; + + try { + int version = ExecuteScalar(cmd, -1); + if(version > CurrentSchemaVersion) throw new InvalidConfigurationException("The version of the database schema is greater than the supported version"); + exists = version != -1; + } + catch(SqlException) { + exists = false; + } + finally { + try { + cmd.Connection.Close(); + } + catch { } + } + + return exists; + } + + /// + /// Detects whether the database schema needs to be updated. + /// + /// true if an update is needed, false otherwise. + private bool SchemaNeedsUpdate() { + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = "select [Version] from [Version] where [Component] = 'Settings'"; + + bool exists = false; + + try { + int version = ExecuteScalar(cmd, -1); + exists = version < CurrentSchemaVersion; + } + catch(SqlException) { + exists = false; + } + finally { + try { + cmd.Connection.Close(); + } + catch { } + } + + return exists; + } + + /// + /// Creates the standard database schema. + /// + private void CreateStandardSchema() { + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = Properties.Resources.SettingsDatabase; + + cmd.ExecuteNonQuery(); + + cmd.Connection.Close(); + } + + /// + /// Creates or updates the database schema if necessary. + /// + protected override void CreateOrUpdateDatabaseIfNecessary() { + if(!SchemaExists()) { + CreateStandardSchema(); + } + if(SchemaNeedsUpdate()) { + // Run minor update batches... + } + } + + /// + /// Tries to load the configuration from a corresponding v2 provider. + /// + /// The configuration, or an empty string. + protected override string TryLoadV2Configuration() { + return ""; + } + + /// + /// Tries to load the configuration of the corresponding settings storage provider. + /// + /// The configuration, or an empty string. + protected override string TryLoadSettingsStorageProviderConfiguration() { + return ""; + } + + /// + /// Gets the Information about the Provider. + /// + public override ComponentInformation Information { + get { return info; } + } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + public override string ConfigHelpHtml { + get { return "Connection string format:
    Data Source=Database Address and Instance;Initial Catalog=Database name;User ID=login;Password=password;"; } + } + + /// + /// Gets the default users storage provider, when no value is stored in the database. + /// + protected override string DefaultUsersStorageProvider { + get { return typeof(SqlServerUsersStorageProvider).FullName; } + } + + /// + /// Gets the default pages storage provider, when no value is stored in the database. + /// + protected override string DefaultPagesStorageProvider { + get { return typeof(SqlServerPagesStorageProvider).FullName; } + } + + /// + /// Gets the default files storage provider, when no value is stored in the database. + /// + protected override string DefaultFilesStorageProvider { + get { return typeof(SqlServerFilesStorageProvider).FullName; } + } + + } + +} diff --git a/SqlServerProviders/SqlServerUsersStorageProvider.cs b/SqlServerProviders/SqlServerUsersStorageProvider.cs new file mode 100644 index 0000000..75c2969 --- /dev/null +++ b/SqlServerProviders/SqlServerUsersStorageProvider.cs @@ -0,0 +1,263 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using ScrewTurn.Wiki.Plugins.SqlCommon; +using ScrewTurn.Wiki.PluginFramework; +using System.Data.SqlClient; + +namespace ScrewTurn.Wiki.Plugins.SqlServer { + + /// + /// Implements a SQL Server-based users storage provider. + /// + public class SqlServerUsersStorageProvider : SqlUsersStorageProviderBase, IUsersStorageProviderV30 { + + private readonly ComponentInformation info = new ComponentInformation("SQL Server Users Storage Provider", "ScrewTurn Software", "3.0.0.180", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/SQLServerProv/Users.txt"); + + private readonly SqlServerCommandBuilder commandBuilder = new SqlServerCommandBuilder(); + + private const int CurrentSchemaVersion = 3000; + + /// + /// Gets a new command with an open connection. + /// + /// The connection string. + /// The command. + private SqlCommand GetCommand(string connString) { + return commandBuilder.GetCommand(connString, "select current_user", new List()) as SqlCommand; + } + + /// + /// Gets a new command builder object. + /// + /// The command builder. + protected override ICommandBuilder GetCommandBuilder() { + return commandBuilder; + } + + /// + /// Validates a connection string. + /// + /// The connection string to validate. + /// If the connection string is invalid, the method throws . + protected override void ValidateConnectionString(string connString) { + SqlCommand cmd = null; + try { + cmd = GetCommand(connString); + } + catch(SqlException ex) { + throw new InvalidConfigurationException("Provided connection string is not valid", ex); + } + catch(InvalidOperationException ex) { + throw new InvalidConfigurationException("Provided connection string is not valid", ex); + } + catch(ArgumentException ex) { + throw new InvalidConfigurationException("Provided connection string is not valid", ex); + } + finally { + try { + cmd.Connection.Close(); + } + catch { } + } + } + + /// + /// Detects whether the database schema exists. + /// + /// true if the schema exists, false otherwise. + private bool SchemaExists() { + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = "select [Version] from [Version] where [Component] = 'Users'"; + + bool exists = false; + + try { + int version = ExecuteScalar(cmd, -1); + if(version > CurrentSchemaVersion) throw new InvalidConfigurationException("The version of the database schema is greater than the supported version"); + exists = version != -1; + } + catch(SqlException) { + exists = false; + } + finally { + try { + cmd.Connection.Close(); + } + catch { } + } + + return exists; + } + + /// + /// Detects whether the database schema needs to be updated. + /// + /// true if an update is needed, false otherwise. + private bool SchemaNeedsUpdate() { + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = "select [Version] from [Version] where [Component] = 'Users'"; + + bool exists = false; + + try { + int version = ExecuteScalar(cmd, -1); + exists = version < CurrentSchemaVersion; + } + catch(SqlException) { + exists = false; + } + finally { + try { + cmd.Connection.Close(); + } + catch { } + } + + return exists; + } + + /// + /// Creates the standard database schema. + /// + private void CreateStandardSchema() { + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = Properties.Resources.UsersDatabase; + + cmd.ExecuteNonQuery(); + + cmd.Connection.Close(); + } + + /// + /// Creates or updates the database schema if necessary. + /// + protected override void CreateOrUpdateDatabaseIfNecessary() { + if(!SchemaExists()) { + // Verify if an upgrade from version 2.0 is possible + if(SchemaAllowsUpgradeFrom20()) { + UpgradeFrom20(); + } + else { + // If not, create the standard schema + CreateStandardSchema(); + } + } + if(SchemaNeedsUpdate()) { + // Run minor update batches... + } + } + + /// + /// Detects whether an upgrade is possible from version 2.0. + /// + /// true if the upgrade is possible, false otherwise. + private bool SchemaAllowsUpgradeFrom20() { + // Look for 'UsersProviderVersion' tables + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = "select count(*) from sys.tables where [name] = 'UsersProviderVersion'"; + + int count = ExecuteScalar(cmd, -1); + + return count == 1; + } + + /// + /// Upgrades the database schema and data from version 2.0. + /// + private void UpgradeFrom20() { + // 1. Load all user data in memory + // 2. Rename old tables so they won't get in the way but the can still be recovered (_v2) + // 3. Create new schema + // 4. Add new users and default groups (admins, users) + + SqlCommand cmd = GetCommand(connString); + cmd.CommandText = "select * from [User]"; + + SqlDataReader reader = cmd.ExecuteReader(); + + string administratorsGroup = host.GetSettingValue(SettingName.AdministratorsGroup); + string usersGroup = host.GetSettingValue(SettingName.UsersGroup); + + List newUsers = new List(100); + List passwordHashes = new List(100); + + while(reader.Read()) { + string username = reader["Username"] as string; + string passwordHash = reader["PasswordHash"] as string; + string email = reader["Email"] as string; + DateTime dateTime = (DateTime)reader["DateTime"]; + bool active = (bool)reader["Active"]; + bool admin = (bool)reader["Admin"]; + + UserInfo temp = new UserInfo(username, null, email, active, dateTime, this); + temp.Groups = admin ? new string[] { administratorsGroup } : new string[] { usersGroup }; + newUsers.Add(temp); + passwordHashes.Add(passwordHash); + } + + reader.Close(); + cmd.Connection.Close(); + + cmd = GetCommand(connString); + cmd.CommandText = "exec sp_rename 'UsersProviderVersion', 'UsersProviderVersion_v2'; exec sp_rename 'User', 'User_v2';"; + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + + CreateStandardSchema(); + + UserGroup admins = AddUserGroup(administratorsGroup, "Built-in Administrators"); + UserGroup users = AddUserGroup(usersGroup, "Built-in Users"); + + for(int i = 0; i < newUsers.Count; i++) { + cmd = GetCommand(connString); + cmd.CommandText = "insert into [User] ([Username], [PasswordHash], [Email], [Active], [DateTime]) values (@Username, @PasswordHash, @Email, @Active, @DateTime)"; + cmd.Parameters.Add(new SqlParameter("@Username", newUsers[i].Username)); + cmd.Parameters.Add(new SqlParameter("@PasswordHash", passwordHashes[i])); + cmd.Parameters.Add(new SqlParameter("@Email", newUsers[i].Email)); + cmd.Parameters.Add(new SqlParameter("@Active", newUsers[i].Active)); + cmd.Parameters.Add(new SqlParameter("@DateTime", newUsers[i].DateTime)); + + cmd.ExecuteNonQuery(); + cmd.Connection.Close(); + + SetUserMembership(newUsers[i], newUsers[i].Groups); + } + + host.UpgradeSecurityFlagsToGroupsAcl(admins, users); + } + + /// + /// Tries to load the configuration from a corresponding v2 provider. + /// + /// The configuration, or an empty string. + protected override string TryLoadV2Configuration() { + return host.GetProviderConfiguration("ScrewTurn.Wiki.PluginPack.SqlServerUsersStorageProvider"); + } + + /// + /// Tries to load the configuration of the corresponding settings storage provider. + /// + /// The configuration, or an empty string. + protected override string TryLoadSettingsStorageProviderConfiguration() { + return host.GetProviderConfiguration(typeof(SqlServerSettingsStorageProvider).FullName); + } + + /// + /// Gets the Information about the Provider. + /// + public override ComponentInformation Information { + get { return info; } + } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + public override string ConfigHelpHtml { + get { return "Connection string format:
    Data Source=Database Address and Instance;Initial Catalog=Database name;User ID=login;Password=password;"; } + } + + } + +} diff --git a/SqlServerProviders/UsersDatabase.sql b/SqlServerProviders/UsersDatabase.sql new file mode 100644 index 0000000..dc51c5a --- /dev/null +++ b/SqlServerProviders/UsersDatabase.sql @@ -0,0 +1,49 @@ + +create table [User] ( + [Username] nvarchar(100) not null, + [PasswordHash] varchar(100) not null, + [DisplayName] nvarchar(150), + [Email] varchar(100) not null, + [Active] bit not null, + [DateTime] datetime not null, + constraint [PK_User] primary key clustered ([USername]) +) + +create table [UserGroup] ( + [Name] nvarchar(100) not null, + [Description] nvarchar(150), + constraint [PK_UserGroup] primary key clustered ([Name]) +) + +create table [UserGroupMembership] ( + [User] nvarchar(100) not null + constraint [FK_UserGroupMembership_User] references [User]([Username]) + on delete cascade on update cascade, + [UserGroup] nvarchar(100) not null + constraint [FK_UserGroupMembership_UserGroup] references [UserGroup]([Name]) + on delete cascade on update cascade, + constraint [PK_UserGroupMembership] primary key clustered ([User], [UserGroup]) +) + +create table [UserData] ( + [User] nvarchar(100) not null + constraint [FK_UserData_User] references [User]([Username]) + on delete cascade on update cascade, + [Key] nvarchar(100) not null, + [Data] nvarchar(4000) not null, + constraint [PK_UserData] primary key clustered ([User], [Key]) +) + +if (select count(*) from sys.tables where [Name] = 'Version') = 0 +begin + create table [Version] ( + [Component] varchar(100) not null, + [Version] int not null, + constraint [PK_Version] primary key clustered ([Component]) + ) +end + +if (select count([Version]) from [Version] where [Component] = 'Users') = 0 +begin + insert into [Version] ([Component], [Version]) values ('Users', 3000) +end diff --git a/TestScaffolding/CacheProviderTestScaffolding.cs b/TestScaffolding/CacheProviderTestScaffolding.cs new file mode 100644 index 0000000..c318cbc --- /dev/null +++ b/TestScaffolding/CacheProviderTestScaffolding.cs @@ -0,0 +1,506 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Tests { + + [TestFixture] + public abstract class CacheProviderTestScaffolding { + + private MockRepository mocks = new MockRepository(); + + protected IHostV30 MockHost() { + IHostV30 host = mocks.DynamicMock(); + + Expect.Call(host.GetSettingValue(SettingName.CacheSize)).Return("20").Repeat.Any(); + Expect.Call(host.GetSettingValue(SettingName.EditingSessionTimeout)).Return("1").Repeat.Any(); + + mocks.Replay(host); + + return host; + } + + protected IPagesStorageProviderV30 MockPagesProvider() { + IPagesStorageProviderV30 prov = mocks.DynamicMock(); + + mocks.Replay(prov); + + return prov; + } + + public abstract ICacheProviderV30 GetProvider(); + + [Test] + public void Init() { + ICacheProviderV30 prov = GetProvider(); + prov.Init(MockHost(), ""); + + Assert.IsNotNull(prov.Information, "Information should not be null"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Init_NullHost() { + ICacheProviderV30 prov = GetProvider(); + + prov.Init(null, ""); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Init_NullConfig() { + ICacheProviderV30 prov = GetProvider(); + prov.Init(MockHost(), null); + } + + [Test] + public void SetOnlineUsers_GetOnlineUsers() { + ICacheProviderV30 prov = GetProvider(); + + prov.OnlineUsers = 100; + Assert.AreEqual(100, prov.OnlineUsers, "Wrong online users count"); + + prov.OnlineUsers++; + Assert.AreEqual(101, prov.OnlineUsers, "Wrong online users count"); + + prov.OnlineUsers--; + Assert.AreEqual(100, prov.OnlineUsers, "Wrong online users count"); + } + + [Test] + public void SetPseudoCacheValue_GetPseudoCacheValue() { + ICacheProviderV30 prov = GetProvider(); + + prov.SetPseudoCacheValue("Name", "Value"); + prov.SetPseudoCacheValue("Test", "Blah"); + + Assert.AreEqual("Value", prov.GetPseudoCacheValue("Name"), "Wrong pseudo-cache value"); + Assert.AreEqual("Blah", prov.GetPseudoCacheValue("Test"), "Wrong pseudo-cache value"); + Assert.IsNull(prov.GetPseudoCacheValue("Inexistent"), "Pseudo-cache value should be null"); + + prov.SetPseudoCacheValue("Name", null); + prov.SetPseudoCacheValue("Test", ""); + + Assert.IsNull(prov.GetPseudoCacheValue("Name"), "Pseudo-cache value should be null"); + Assert.AreEqual("", prov.GetPseudoCacheValue("Test"), "Wrong pseudo-cache value"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void GetPseudoCacheValue_InvalidName(string n) { + ICacheProviderV30 prov = GetProvider(); + prov.GetPseudoCacheValue(n); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void SetPseudoCacheValue_InvalidName(string n) { + ICacheProviderV30 prov = GetProvider(); + prov.SetPseudoCacheValue(n, "Value"); + } + + [Test] + public void SetPageContent_GetPageContent() { + ICacheProviderV30 prov = GetProvider(); + + PageInfo p1 = new PageInfo("Page1", MockPagesProvider(), DateTime.Now); + PageInfo p2 = new PageInfo("Page2", MockPagesProvider(), DateTime.Now); + PageContent c1 = new PageContent(p1, "Page 1", "admin", DateTime.Now, "Comment", "Content", new string[] { "test", "page" }, null); + PageContent c2 = new PageContent(p2, "Page 2", "user", DateTime.Now, "", "Blah", null, null); + PageContent c3 = new PageContent(p2, "Page 5", "john", DateTime.Now, "Comm.", "Blah 222", null, "Description"); + + Assert.AreEqual(0, prov.PageCacheUsage, "Wrong cache usage"); + + prov.SetPageContent(p1, c1); + prov.SetPageContent(p2, c2); + prov.SetPageContent(p2, c3); + + Assert.AreEqual(2, prov.PageCacheUsage, "Wrong cache usage"); + + PageContent res = prov.GetPageContent(p1); + Assert.AreEqual(c1.PageInfo, res.PageInfo, "Wrong page info"); + Assert.AreEqual(c1.Title, res.Title, "Wrong title"); + Assert.AreEqual(c1.User, res.User, "Wrong user"); + Assert.AreEqual(c1.LastModified, res.LastModified, "Wrong date/time"); + Assert.AreEqual(c1.Comment, res.Comment, "Wrong comment"); + Assert.AreEqual(c1.Content, res.Content, "Wrong content"); + Assert.AreEqual(2, c1.Keywords.Length, "Wrong keyword count"); + Assert.AreEqual("test", c1.Keywords[0], "Wrong keyword"); + Assert.AreEqual("page", c1.Keywords[1], "Wrong keyword"); + Assert.IsNull(c1.Description, "Description should be null"); + + res = prov.GetPageContent(p2); + Assert.AreEqual(c3.PageInfo, res.PageInfo, "Wrong page info"); + Assert.AreEqual(c3.Title, res.Title, "Wrong title"); + Assert.AreEqual(c3.User, res.User, "Wrong user"); + Assert.AreEqual(c3.LastModified, res.LastModified, "Wrong date/time"); + Assert.AreEqual(c3.Comment, res.Comment, "Wrong comment"); + Assert.AreEqual(c3.Content, res.Content, "Wrong content"); + Assert.AreEqual(0, c3.Keywords.Length, "Keywords should be empty"); + Assert.AreEqual("Description", c3.Description, "Wrong description"); + + Assert.IsNull(prov.GetPageContent(new PageInfo("Blah", MockPagesProvider(), DateTime.Now)), "GetPageContent should return null"); + } + + [ExpectedException(typeof(ArgumentNullException))] + public void GetPageContent_NullPage() { + ICacheProviderV30 prov = GetProvider(); + prov.GetPageContent(null); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetPageContent_NullPage() { + ICacheProviderV30 prov = GetProvider(); + + PageInfo p1 = new PageInfo("Page1", MockPagesProvider(), DateTime.Now); + PageContent c1 = new PageContent(p1, "Page 1", "admin", DateTime.Now, "Comment", "Content", null, null); + + prov.SetPageContent(null, c1); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetPageContent_NullContent() { + ICacheProviderV30 prov = GetProvider(); + + PageInfo p1 = new PageInfo("Page1", MockPagesProvider(), DateTime.Now); + PageContent c1 = new PageContent(p1, "Page 1", "admin", DateTime.Now, "Comment", "Content", null, null); + + prov.SetPageContent(p1, null); + } + + [Test] + public void SetFormattedPageContent_GetFormattedPageContent() { + ICacheProviderV30 prov = GetProvider(); + + PageInfo p1 = new PageInfo("Page1", MockPagesProvider(), DateTime.Now); + PageInfo p2 = new PageInfo("Page2", MockPagesProvider(), DateTime.Now); + + Assert.AreEqual(0, prov.PageCacheUsage, "Wrong cache usage"); + + prov.SetFormattedPageContent(p1, "Content 1"); + prov.SetFormattedPageContent(p2, "Content 2"); + prov.SetFormattedPageContent(p1, "Content 1 mod"); + + Assert.AreEqual(0, prov.PageCacheUsage, "Wrong cache usage"); + + Assert.AreEqual("Content 1 mod", prov.GetFormattedPageContent(p1), "Wrong content"); + Assert.AreEqual("Content 2", prov.GetFormattedPageContent(p2), "Wrong content"); + + Assert.IsNull(prov.GetFormattedPageContent(new PageInfo("Blah", MockPagesProvider(), DateTime.Now)), "GetFormattedPageContent should return null"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void GetFormattedPageContent_NullPage() { + ICacheProviderV30 prov = GetProvider(); + prov.GetFormattedPageContent(null); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetFormattedPageContent_NullPage() { + ICacheProviderV30 prov = GetProvider(); + prov.SetFormattedPageContent(null, "Content"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetFormattedPageContent_NullContent() { + ICacheProviderV30 prov = GetProvider(); + + PageInfo p1 = new PageInfo("Page1", MockPagesProvider(), DateTime.Now); + + prov.SetFormattedPageContent(p1, null); + } + + [Test] + public void RemovePageContent() { + ICacheProviderV30 prov = GetProvider(); + + PageInfo p1 = new PageInfo("Page1", MockPagesProvider(), DateTime.Now); + PageInfo p2 = new PageInfo("Page2", MockPagesProvider(), DateTime.Now); + PageContent c1 = new PageContent(p1, "Page 1", "admin", DateTime.Now, "Comment", "Content", null, null); + PageContent c2 = new PageContent(p2, "Page 2", "user", DateTime.Now, "", "Blah", null, null); + + Assert.AreEqual(0, prov.PageCacheUsage, "Wrong cache usage"); + + prov.SetPageContent(p1, c1); + prov.SetPageContent(p2, c2); + prov.SetFormattedPageContent(p1, "Content 1"); + prov.SetFormattedPageContent(p2, "Content 2"); + + Assert.AreEqual(2, prov.PageCacheUsage, "Wrong cache usage"); + + prov.RemovePage(p2); + + Assert.IsNotNull(prov.GetFormattedPageContent(p1), "GetFormattedPageContent should not return null"); + Assert.IsNotNull(prov.GetPageContent(p1), "GetPageContent should not return null"); + + Assert.IsNull(prov.GetFormattedPageContent(p2), "GetFormattedPageContent should return null"); + Assert.IsNull(prov.GetPageContent(p2), "GetPageContent should return null"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemovePageContent_NullPage() { + ICacheProviderV30 prov = GetProvider(); + + prov.RemovePage(null); + } + + [Test] + public void ClearPageContentCache() { + ICacheProviderV30 prov = GetProvider(); + + PageInfo p1 = new PageInfo("Page1", MockPagesProvider(), DateTime.Now); + PageInfo p2 = new PageInfo("Page2", MockPagesProvider(), DateTime.Now); + PageContent c1 = new PageContent(p1, "Page 1", "admin", DateTime.Now, "Comment", "Content", null, null); + PageContent c2 = new PageContent(p2, "Page 2", "user", DateTime.Now, "", "Blah", null, null); + + Assert.AreEqual(0, prov.PageCacheUsage, "Wrong cache usage"); + + prov.SetPageContent(p1, c1); + prov.SetPageContent(p2, c2); + prov.SetFormattedPageContent(p1, "Content 1"); + prov.SetFormattedPageContent(p2, "Content 2"); + + Assert.AreEqual(2, prov.PageCacheUsage, "Wrong cache usage"); + + prov.ClearPageContentCache(); + + Assert.AreEqual(0, prov.PageCacheUsage, "Wrong cache usage"); + + Assert.IsNull(prov.GetPageContent(p1), "GetPageContent should return null"); + Assert.IsNull(prov.GetPageContent(p2), "GetPageContent should return null"); + Assert.IsNull(prov.GetFormattedPageContent(p1), "GetFormattedPageContent should return null"); + Assert.IsNull(prov.GetFormattedPageContent(p2), "GetFormattedPageContent should return null"); + } + + [Test] + public void ClearPseudoCache() { + ICacheProviderV30 prov = GetProvider(); + + prov.SetPseudoCacheValue("Test", "Value"); + prov.SetPseudoCacheValue("222", "VVV"); + + prov.ClearPseudoCache(); + + Assert.IsNull(prov.GetPseudoCacheValue("Test"), "GetPseudoCacheValue should return null"); + Assert.IsNull(prov.GetPseudoCacheValue("222"), "GetPseudoCacheValue should return null"); + } + + [Test] + public void CutCache() { + ICacheProviderV30 prov = GetProvider(); + + PageInfo p1 = new PageInfo("Page1", MockPagesProvider(), DateTime.Now); + PageInfo p2 = new PageInfo("Page2", MockPagesProvider(), DateTime.Now); + PageInfo p3 = new PageInfo("Page3", MockPagesProvider(), DateTime.Now); + PageContent c1 = new PageContent(p1, "Page 1", "admin", DateTime.Now, "Comment", "Content", null, null); + PageContent c2 = new PageContent(p2, "Page 2", "user", DateTime.Now, "", "Blah", null, null); + PageContent c3 = new PageContent(p3, "Page 3", "admin", DateTime.Now, "", "Content", null, null); + + Assert.AreEqual(0, prov.PageCacheUsage, "Wrong cache usage"); + + prov.SetPageContent(p1, c1); + prov.SetPageContent(p2, c2); + prov.SetPageContent(p3, c3); + prov.SetFormattedPageContent(p1, "Content 1"); + prov.SetFormattedPageContent(p3, "Content 2"); + + prov.GetPageContent(p3); + + Assert.AreEqual(3, prov.PageCacheUsage, "Wrong cache usage"); + + prov.CutCache(2); + + Assert.AreEqual(1, prov.PageCacheUsage, "Wrong cache usage"); + + Assert.IsNotNull(prov.GetPageContent(p3), "GetPageContent should not return null"); + Assert.IsNull(prov.GetPageContent(p2), "GetPageContent should not null"); + Assert.IsNull(prov.GetPageContent(p1), "GetPageContent should not null"); + + Assert.IsNotNull(prov.GetFormattedPageContent(p3), "GetFormattedPageContent should not return null"); + Assert.IsNull(prov.GetFormattedPageContent(p2), "GetFormattedPageContent should not null"); + Assert.IsNull(prov.GetFormattedPageContent(p1), "GetFormattedPageContent should not null"); + } + + [TestCase(-1, ExpectedException = typeof(ArgumentOutOfRangeException))] + [TestCase(0, ExpectedException = typeof(ArgumentOutOfRangeException))] + public void CutCache_InvalidSize(int s) { + ICacheProviderV30 prov = GetProvider(); + prov.CutCache(s); + } + + [Test] + public void RenewEditingSession_IsPageBeingEdited() { + ICacheProviderV30 prov = GetProvider(); + + prov.RenewEditingSession("Page", "User"); + + Assert.IsFalse(prov.IsPageBeingEdited("Page", "User"), "IsPageBeingEditing should return false"); + Assert.IsTrue(prov.IsPageBeingEdited("Page", "User2"), "IsPageBeingEditing should return true"); + Assert.IsFalse(prov.IsPageBeingEdited("Page2", "User"), "IsPageBeingEditing should return false"); + Assert.IsFalse(prov.IsPageBeingEdited("Page2", "User2"), "IsPageBeingEditing should return false"); + + // Wait for timeout to expire + System.Threading.Thread.Sleep(6500); + Assert.IsFalse(prov.IsPageBeingEdited("Page", "User2"), "IsPageBeingEdited should return false"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RenewEditingSession_InvalidPage(string p) { + ICacheProviderV30 prov = GetProvider(); + prov.RenewEditingSession(p, "User"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RenewEditingSession_InvalidUser(string u) { + ICacheProviderV30 prov = GetProvider(); + prov.RenewEditingSession("Page", u); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void IsPageBeingEdited_InvalidPage(string p) { + ICacheProviderV30 prov = GetProvider(); + prov.IsPageBeingEdited(p, "User"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void IsPageBeingEdited_InvalidUser(string u) { + ICacheProviderV30 prov = GetProvider(); + prov.IsPageBeingEdited("Page", u); + } + + [Test] + public void CancelEditingSession_IsPageBeingEdited() { + ICacheProviderV30 prov = GetProvider(); + + prov.RenewEditingSession("Page", "User"); + + Assert.IsFalse(prov.IsPageBeingEdited("Page", "User"), "IsPageBeingEditing should return false"); + Assert.IsTrue(prov.IsPageBeingEdited("Page", "User2"), "IsPageBeingEditing should return true"); + + prov.CancelEditingSession("Page", "User"); + + Assert.IsFalse(prov.IsPageBeingEdited("Page", "User"), "IsPageBeingEditing should return false"); + Assert.IsFalse(prov.IsPageBeingEdited("Page", "User2"), "IsPageBeingEditing should return false"); + + prov.RenewEditingSession("Page", "User1"); + prov.RenewEditingSession("Page", "User2"); + + prov.CancelEditingSession("Page", "User1"); + + Assert.IsTrue(prov.IsPageBeingEdited("Page", "User1"), "IsPageBeingEditing should return true"); + Assert.IsFalse(prov.IsPageBeingEdited("Page", "User2"), "IsPageBeingEditing should return false"); + + prov.CancelEditingSession("Page", "User2"); + + Assert.IsFalse(prov.IsPageBeingEdited("Page", "User2"), "IsPageBeingEditing should return false"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void CancelEditingSession_InvalidPage(string p) { + ICacheProviderV30 prov = GetProvider(); + prov.CancelEditingSession(p, "User"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void CancelEditingSession_InvalidUser(string u) { + ICacheProviderV30 prov = GetProvider(); + prov.CancelEditingSession("Page", u); + } + + [Test] + public void WhosEditing() { + ICacheProviderV30 prov = GetProvider(); + + prov.RenewEditingSession("Page", "User1"); + prov.RenewEditingSession("Page", "User2"); + + Assert.AreEqual("", prov.WhosEditing("Inexistent"), "Wrong result (should be empty)"); + + Assert.AreEqual("User1", prov.WhosEditing("Page"), "Wrong user"); + + prov.CancelEditingSession("Page", "User1"); + + Assert.AreEqual("User2", prov.WhosEditing("Page"), "Wrong user"); + + prov.CancelEditingSession("Page", "User2"); + + Assert.AreEqual("", prov.WhosEditing("Page"), "Wrong user"); + + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void WhosEditing_InvalidPage(string p) { + ICacheProviderV30 prov = GetProvider(); + prov.WhosEditing(p); + } + + [Test] + public void AddRedirection_GetDestination_RemovePageFromRedirections_Clear() { + ICacheProviderV30 prov = GetProvider(); + + Assert.IsNull(prov.GetRedirectionDestination("Page"), "No redirection should be in cache"); + + prov.AddRedirection("Page", "NS.OtherPage"); + prov.AddRedirection("NS.OtherPage", "Page3"); + prov.AddRedirection("ThirdPage", "Page"); + + Assert.AreEqual("NS.OtherPage", prov.GetRedirectionDestination("Page"), "Wrong destination"); + Assert.AreEqual("Page3", prov.GetRedirectionDestination("NS.OtherPage"), "Wrong destination"); + Assert.AreEqual("Page", prov.GetRedirectionDestination("ThirdPage"), "Wrong destination"); + + prov.RemovePageFromRedirections("Page"); + + Assert.IsNull(prov.GetRedirectionDestination("Page"), "No redirection should be in cache for Page"); + Assert.AreEqual("Page3", prov.GetRedirectionDestination("NS.OtherPage"), "Wrong destination"); + Assert.IsNull(prov.GetRedirectionDestination("Page"), "No redirection should be in cache for ThirdPage"); + + prov.ClearRedirections(); + + Assert.IsNull(prov.GetRedirectionDestination("Page"), "No redirection should be in cache"); + Assert.IsNull(prov.GetRedirectionDestination("NS.OtherPage"), "No redirection should be in cache"); + Assert.IsNull(prov.GetRedirectionDestination("Page"), "No redirection should be in cache"); + } + + [TestCase(null, "destination", ExpectedException = typeof(ArgumentNullException))] + [TestCase("", "destination", ExpectedException = typeof(ArgumentException))] + [TestCase("source", null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("source", "", ExpectedException = typeof(ArgumentException))] + public void AddRedirection_InvalidParameters(string src, string dest) { + ICacheProviderV30 prov = GetProvider(); + prov.AddRedirection(src, dest); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void GetRedirectionDestination_InvalidSource(string src) { + ICacheProviderV30 prov = GetProvider(); + prov.GetRedirectionDestination(src); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RemovePageFromRedirections_InvalidName(string name) { + ICacheProviderV30 prov = GetProvider(); + prov.RemovePageFromRedirections(name); + } + + } + +} diff --git a/TestScaffolding/FilesStorageProviderTestScaffolding.cs b/TestScaffolding/FilesStorageProviderTestScaffolding.cs new file mode 100644 index 0000000..8fbf29d --- /dev/null +++ b/TestScaffolding/FilesStorageProviderTestScaffolding.cs @@ -0,0 +1,1431 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Tests { + + [TestFixture] + public abstract class FilesStorageProviderTestScaffolding { + + private MockRepository mocks = new MockRepository(); + private string testDir = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Guid.NewGuid().ToString()); + + [TearDown] + public void TearDown() { + try { + Directory.Delete(testDir, true); + } + catch { + //Console.WriteLine("Test: could not delete temp directory"); + } + } + + protected IHostV30 MockHost() { + if(!Directory.Exists(testDir)) Directory.CreateDirectory(testDir); + + IHostV30 host = mocks.DynamicMock(); + Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.AtLeastOnce(); + + mocks.Replay(host); + + return host; + } + + protected IPagesStorageProviderV30 MockPagesProvider() { + IPagesStorageProviderV30 prov = mocks.DynamicMock(); + + mocks.Replay(prov); + + return prov; + } + + public abstract IFilesStorageProviderV30 GetProvider(); + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Init_NullHost() { + IFilesStorageProviderV30 prov = GetProvider(); + prov.Init(null, ""); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Init_NullConfig() { + IFilesStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), null); + } + + private Stream FillStream(string content) { + MemoryStream ms = new MemoryStream(); + byte[] buff = Encoding.UTF8.GetBytes(content); + ms.Write(buff, 0, buff.Length); + ms.Seek(0, SeekOrigin.Begin); + return ms; + } + + [Test] + public void StoreFile_ListFiles() { + IFilesStorageProviderV30 prov = GetProvider(); + + Assert.AreEqual(0, prov.ListFiles("/").Length, "Wrong file count"); + + using(Stream s = FillStream("File1")) { + Assert.IsTrue(prov.StoreFile("/File1.txt", s, false), "StoreFile should return true"); + } + using(Stream s = FillStream("File2")) { + Assert.IsTrue(prov.StoreFile("/File2.txt", s, true), "StoreFile should return true"); + } + + string[] files = prov.ListFiles("/"); + Assert.AreEqual(2, files.Length, "Wrong file count"); + Assert.AreEqual("/File1.txt", files[0], "Wrong file"); + Assert.AreEqual("/File2.txt", files[1], "Wrong file"); + } + + [Test] + public void StoreFile_SubDir() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.CreateDirectory("/", "Test"); + + Assert.AreEqual(0, prov.ListFiles("/Test").Length, "Wrong file count"); + + using(Stream s = FillStream("File1")) { + Assert.IsTrue(prov.StoreFile("/Test/File1.txt", s, false), "StoreFile should return true"); + } + using(Stream s = FillStream("File2")) { + Assert.IsTrue(prov.StoreFile("/Test/File2.txt", s, true), "StoreFile should return true"); + } + + string[] files = prov.ListFiles("/Test"); + Assert.AreEqual(2, files.Length, "Wrong file count"); + Assert.AreEqual("/Test/File1.txt", files[0], "Wrong file"); + Assert.AreEqual("/Test/File2.txt", files[1], "Wrong file"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void StoreFile_InvalidFullName(string fn) { + IFilesStorageProviderV30 prov = GetProvider(); + + using(Stream s = FillStream("Blah")) { + prov.StoreFile(fn, s, false); + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void StoreFile_NullStream() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.StoreFile("/Blah.txt", null, false); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void StoreFile_ClosedStream() { + IFilesStorageProviderV30 prov = GetProvider(); + + Stream s = FillStream("Blah"); + s.Close(); + + prov.StoreFile("Blah.txt", s, false); + } + + [Test] + public void StoreFile_Overwrite_RetrieveFile() { + IFilesStorageProviderV30 prov = GetProvider(); + + using(Stream s = FillStream("Blah")) { + Assert.IsTrue(prov.StoreFile("/File.txt", s, false), "StoreFile should return true"); + } + + using(Stream s = FillStream("Blah222")) { + Assert.IsFalse(prov.StoreFile("/File.txt", s, false), "StoreFile should return false"); + } + + MemoryStream ms = new MemoryStream(); + prov.RetrieveFile("/File.txt", ms, false); + ms.Seek(0, SeekOrigin.Begin); + string c = Encoding.UTF8.GetString(ms.ToArray()); + Assert.AreEqual("Blah", c, "Wrong content (seems modified"); + + using(Stream s = FillStream("Blah222")) { + Assert.IsTrue(prov.StoreFile("/File.txt", s, true), "StoreFile should return true"); + } + + ms = new MemoryStream(); + prov.RetrieveFile("/File.txt", ms, false); + ms.Seek(0, SeekOrigin.Begin); + c = Encoding.UTF8.GetString(ms.ToArray()); + Assert.AreEqual("Blah222", c, "Wrong content (seems modified"); + } + + [Test] + public void ListFiles_NullOrEmptyDirectory() { + IFilesStorageProviderV30 prov = GetProvider(); + + using(Stream s = FillStream("Blah")) { + Assert.IsTrue(prov.StoreFile("/File.txt", s, false), "StoreFile should return true"); + } + + string[] files = prov.ListFiles(null); + Assert.AreEqual(1, files.Length, "Wrong file count"); + Assert.AreEqual("/File.txt", files[0], "Wrong file"); + + files = prov.ListFiles(""); + Assert.AreEqual(1, files.Length, "Wrong file count"); + Assert.AreEqual("/File.txt", files[0], "Wrong file"); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void ListFiles_InexistentDirectory() { + IFilesStorageProviderV30 prov = GetProvider(); + prov.ListFiles("/dir/that/does/not/exist"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RetrieveFile_InvalidFile(string f) { + IFilesStorageProviderV30 prov = GetProvider(); + + using(MemoryStream s = new MemoryStream()) { + prov.RetrieveFile(f, s, false); + } + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void RetrieveFile_InexistentFile() { + IFilesStorageProviderV30 prov = GetProvider(); + + using(MemoryStream s = new MemoryStream()) { + prov.RetrieveFile("/Inexistent.txt", s, false); + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveFile_NullStream() { + IFilesStorageProviderV30 prov = GetProvider(); + + using(Stream s = FillStream("Blah")) { + prov.StoreFile("/File.txt", s, false); + } + + prov.RetrieveFile("/File.txt", null, false); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void RetrieveFile_ClosedStream() { + IFilesStorageProviderV30 prov = GetProvider(); + + using(Stream s = FillStream("Blah")) { + prov.StoreFile("/File.txt", s, false); + } + + MemoryStream s2 = new MemoryStream(); + s2.Close(); + prov.RetrieveFile("/File.txt", s2, false); + } + + [Test] + public void GetFileDetails_SetFileRetrievalCount() { + IFilesStorageProviderV30 prov = GetProvider(); + + DateTime now = DateTime.Now; + using(Stream s = FillStream("Content")) { + prov.StoreFile("/File.txt", s, false); + } + using(Stream s = FillStream("Content")) { + prov.StoreFile("/File2.txt", s, false); + } + + FileDetails details = prov.GetFileDetails("/File.txt"); + Assert.AreEqual(0, details.RetrievalCount, "Wrong file retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong file size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + using(Stream s = new MemoryStream()) { + prov.RetrieveFile("/File.txt", s, false); + } + details = prov.GetFileDetails("/File.txt"); + Assert.AreEqual(0, details.RetrievalCount, "Wrong file retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong file size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + using(Stream s = new MemoryStream()) { + prov.RetrieveFile("/File2.txt", s, true); + } + using(Stream s = new MemoryStream()) { + prov.RetrieveFile("/File.txt", s, true); + } + using(Stream s = new MemoryStream()) { + prov.RetrieveFile("/File.txt", s, true); + } + details = prov.GetFileDetails("/File.txt"); + Assert.AreEqual(2, details.RetrievalCount, "Wrong file retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong file size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + details = prov.GetFileDetails("/File2.txt"); + Assert.AreEqual(1, details.RetrievalCount, "Wrong file retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong file size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + prov.SetFileRetrievalCount("/File2.txt", 0); + + details = prov.GetFileDetails("/File.txt"); + Assert.AreEqual(2, details.RetrievalCount, "Wrong file retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong file size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + details = prov.GetFileDetails("/File2.txt"); + Assert.AreEqual(0, details.RetrievalCount, "Wrong file retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong file size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + prov.DeleteFile("/File.txt"); + + Assert.IsNull(prov.GetFileDetails("/File.txt"), "GetFileDetails should return null"); + + details = prov.GetFileDetails("/File2.txt"); + Assert.AreEqual(0, details.RetrievalCount, "Wrong file retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong file size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + } + + [Test] + public void GetFileDetails_RenameFile() { + IFilesStorageProviderV30 prov = GetProvider(); + + DateTime now = DateTime.Now; + using(Stream s = FillStream("Content")) { + prov.StoreFile("/File.txt", s, false); + } + + using(Stream s = new MemoryStream()) { + prov.RetrieveFile("/File.txt", s, true); + } + + prov.RenameFile("/File.txt", "/File2.txt"); + + FileDetails details = prov.GetFileDetails("/File.txt"); + Assert.IsNull(details, "GetFileDetails should return null"); + + details = prov.GetFileDetails("/File2.txt"); + Assert.AreEqual(1, details.RetrievalCount, "Wrong file retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong file size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + } + + [Test] + public void GetFileDetails_RenameDirectory() { + IFilesStorageProviderV30 prov = GetProvider(); + + DateTime now = DateTime.Now; + prov.CreateDirectory("/", "Dir"); + using(Stream s = FillStream("Content")) { + prov.StoreFile("/Dir/File.txt", s, false); + } + prov.CreateDirectory("/Dir/", "Sub"); + using(Stream s = FillStream("Content")) { + prov.StoreFile("/Dir/Sub/File.txt", s, false); + } + prov.CreateDirectory("/", "Dir100"); + using(Stream s = FillStream("Content")) { + prov.StoreFile("/Dir100/File.txt", s, false); + } + + using(Stream s = new MemoryStream()) { + prov.RetrieveFile("/Dir/File.txt", s, true); + } + using(Stream s = new MemoryStream()) { + prov.RetrieveFile("/Dir/Sub/File.txt", s, true); + } + using(Stream s = new MemoryStream()) { + prov.RetrieveFile("/Dir100/File.txt", s, true); + } + + prov.RenameDirectory("/Dir/", "/Dir2/"); + + FileDetails details; + + details = prov.GetFileDetails("/Dir100/File.txt"); + Assert.AreEqual(1, details.RetrievalCount, "Wrong file retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong file size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + Assert.IsNull(prov.GetFileDetails("/Dir/File.txt"), "GetFileDetails should return null"); + + details = prov.GetFileDetails("/Dir2/File.txt"); + Assert.AreEqual(1, details.RetrievalCount, "Wrong file retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong file size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + Assert.IsNull(prov.GetFileDetails("/Dir/Sub/File.txt"), "GetFileDetails should return null"); + + details = prov.GetFileDetails("/Dir2/Sub/File.txt"); + Assert.AreEqual(1, details.RetrievalCount, "Wrong file retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong file size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + } + + [Test] + public void GetFileDetails_DeleteDirectory() { + IFilesStorageProviderV30 prov = GetProvider(); + + DateTime now = DateTime.Now; + prov.CreateDirectory("/", "Dir"); + using(Stream s = FillStream("Content")) { + prov.StoreFile("/Dir/File.txt", s, false); + } + prov.CreateDirectory("/Dir/", "Sub"); + using(Stream s = FillStream("Content")) { + prov.StoreFile("/Dir/Sub/File.txt", s, false); + } + prov.CreateDirectory("/", "Dir2"); + using(Stream s = FillStream("Content")) { + prov.StoreFile("/Dir2/File.txt", s, false); + } + + using(Stream s = new MemoryStream()) { + prov.RetrieveFile("/Dir/File.txt", s, true); + } + using(Stream s = new MemoryStream()) { + prov.RetrieveFile("/Dir2/File.txt", s, true); + } + using(Stream s = new MemoryStream()) { + prov.RetrieveFile("/Dir/Sub/File.txt", s, true); + } + + prov.DeleteDirectory("/Dir/"); + + FileDetails details = prov.GetFileDetails("/Dir2/File.txt"); + Assert.AreEqual(1, details.RetrievalCount, "Wrong file retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong file size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + Assert.IsNull(prov.GetFileDetails("/Dir/File.txt"), "GetFileDetails should return null"); + Assert.IsNull(prov.GetFileDetails("/Dir/Sub/File.txt"), "GetFileDetails should return null"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void GetFileDetails_InvalidFile(string f) { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.GetFileDetails(f); + } + + [Test] + public void GetFileDetails_InexistentFile() { + IFilesStorageProviderV30 prov = GetProvider(); + + Assert.IsNull(prov.GetFileDetails("/Inexistent.txt"), "GetFileDetails should return null"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void SetFileRetrievalCount_InvalidFile(string f) { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.SetFileRetrievalCount(f, 10); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void SetFileRetrievalCount_NegativeCount() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.SetFileRetrievalCount("/File.txt", -1); + } + + [Test] + public void DeleteFile() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.CreateDirectory("/", "Sub"); + + using(Stream s = FillStream("Blah")) { + prov.StoreFile("/File.txt", s, false); + prov.StoreFile("/Sub/File.txt", s, false); + } + + Assert.IsTrue(prov.DeleteFile("/File.txt"), "DeleteFile should return true"); + Assert.IsTrue(prov.DeleteFile("/Sub/File.txt"), "DeleteFile should return true"); + Assert.AreEqual(0, prov.ListFiles("/").Length, "Wrong file count"); + Assert.AreEqual(0, prov.ListFiles("/Sub/").Length, "Wrong file count"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void DeleteFile_InvalidFile(string f) { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.DeleteFile(f); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void DeleteFile_InexistentFile() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.DeleteFile("/File.txt"); + } + + [Test] + public void RenameFile() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.CreateDirectory("/", "Sub"); + + using(Stream s = FillStream("Blah")) { + prov.StoreFile("/File.txt", s, false); + prov.StoreFile("/Sub/File.txt", s, false); + } + + Assert.IsTrue(prov.RenameFile("/File.txt", "/File2.txt"), "RenameFile should return true"); + Assert.IsTrue(prov.RenameFile("/Sub/File.txt", "/Sub/File2.txt"), "RenameFile should return true"); + + string[] files = prov.ListFiles("/"); + Assert.AreEqual(1, files.Length, "Wrong file count"); + Assert.AreEqual("/File2.txt", files[0], "Wrong file"); + + files = prov.ListFiles("/Sub/"); + Assert.AreEqual(1, files.Length, "Wrong file count"); + Assert.AreEqual("/Sub/File2.txt", files[0], "Wrong file"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RenameFile_InvalidFile(string f) { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.RenameFile(f, "/Blah.txt"); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void RenameFile_InexistentFile() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.RenameFile("/Blah.txt", "/Blah2.txt"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RenameFile_InvalidName(string n) { + IFilesStorageProviderV30 prov = GetProvider(); + + using(Stream s = FillStream("Blah")) { + prov.StoreFile("/File.txt", s, false); + } + + prov.RenameFile("/File.txt", n); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void RenameFile_ExistentName() { + IFilesStorageProviderV30 prov = GetProvider(); + + using(Stream s = FillStream("Blah")) { + prov.StoreFile("/File.txt", s, false); + prov.StoreFile("/File2.txt", s, false); + } + + prov.RenameFile("/File.txt", "/File2.txt"); + } + + [Test] + public void CreateDirectory_ListDirectories() { + IFilesStorageProviderV30 prov = GetProvider(); + + Assert.IsTrue(prov.CreateDirectory("/", "Dir1"), "CreateDirectory should return true"); + Assert.IsTrue(prov.CreateDirectory("/", "Dir2"), "CreateDirectory should return true"); + Assert.IsTrue(prov.CreateDirectory("/Dir1", "Sub"), "CreateDirectory should return true"); + + string[] dirs = prov.ListDirectories("/"); + Assert.AreEqual(2, dirs.Length, "Wrong dir count"); + Assert.AreEqual("/Dir1/", dirs[0], "Wrong dir"); + Assert.AreEqual("/Dir2/", dirs[1], "Wrong dir"); + + dirs = prov.ListDirectories("/Dir1/"); + Assert.AreEqual(1, dirs.Length, "Wrong dir count"); + Assert.AreEqual("/Dir1/Sub/", dirs[0], "Wrong dir"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void CreateDirectory_NullDirectory() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.CreateDirectory(null, "Dir"); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void CreateDirectory_InexistentDirectory() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.CreateDirectory("/Inexistent/Dir/", "Sub"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void CreateDirectory_InvalidName(string n) { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.CreateDirectory("/", n); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void CreateDirectory_ExistentName() { + IFilesStorageProviderV30 prov = GetProvider(); + + Assert.IsTrue(prov.CreateDirectory("/", "Dir"), "CreateDirectory should return true"); + prov.CreateDirectory("/", "Dir"); + } + + [Test] + public void ListDirectories_NullOrEmptyDirectory() { + IFilesStorageProviderV30 prov = GetProvider(); + + string[] dirs = prov.ListDirectories(null); + Assert.AreEqual(0, dirs.Length, "Wrong dir count"); + + prov.CreateDirectory("/", "Dir"); + + dirs = prov.ListDirectories(null); + Assert.AreEqual(1, dirs.Length, "Wrong dir count"); + Assert.AreEqual("/Dir/", dirs[0], "Wrong dir"); + + dirs = prov.ListDirectories(""); + Assert.AreEqual(1, dirs.Length, "Wrong dir count"); + Assert.AreEqual("/Dir/", dirs[0], "Wrong dir"); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void ListDirectories_InexistentDirectory() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.ListDirectories("/Inexistent/"); + } + + [Test] + public void DeleteDirectory() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.CreateDirectory("/", "Dir"); + prov.CreateDirectory("/", "Dir2"); + prov.CreateDirectory("/Dir/", "Sub"); + + using(Stream s = FillStream("Blah")) { + prov.StoreFile("/Dir/File.txt", s, false); + } + using(Stream s = FillStream("Blah")) { + prov.StoreFile("/Dir/Sub/File.txt", s, false); + } + + Assert.IsTrue(prov.DeleteDirectory("/Dir"), "DeleteDirectory should return true"); + Assert.AreEqual("/Dir2/", prov.ListDirectories("/")[0], "Wrong directory"); + Assert.IsTrue(prov.DeleteDirectory("/Dir2"), "DeleteDirectory should return true"); + Assert.AreEqual(0, prov.ListDirectories("/").Length, "Wrong dir count"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase("/", ExpectedException = typeof(ArgumentException))] + public void DeleteDirectory_InvalidDirectory(string d) { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.DeleteDirectory(d); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void DeleteDirectory_InexistentDirectory() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.DeleteDirectory("/Inexistent/"); + } + + [Test] + public void RenameDirectory() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.CreateDirectory("/", "Dir"); + using(Stream s = FillStream("Blah")) { + prov.StoreFile("/Dir/File.txt", s, false); + } + + Assert.IsTrue(prov.RenameDirectory("/Dir/", "/Dir2/"), "RenameDirectory should return true"); + + Assert.AreEqual("/Dir2/", prov.ListDirectories("/")[0], "Wrong directory"); + + bool thisHouldBeTrue = false; + try { + Assert.AreEqual(0, prov.ListFiles("/Dir/").Length, "Wrong file count"); + } + catch(ArgumentException) { + thisHouldBeTrue = true; + } + Assert.IsTrue(thisHouldBeTrue, "ListFiles did not throw an exception"); + Assert.AreEqual("/Dir2/File.txt", prov.ListFiles("/Dir2/")[0], "Wrong file"); + + prov.CreateDirectory("/Dir2/", "Sub"); + using(Stream s = FillStream("Blah")) { + prov.StoreFile("/Dir2/Sub/File.txt", s, false); + } + + Assert.IsTrue(prov.RenameDirectory("/Dir2/Sub/", "/Dir2/Sub2/"), "RenameDirectory should return true"); + + Assert.AreEqual("/Dir2/Sub2/", prov.ListDirectories("/Dir2/")[0], "Wrong dir"); + + thisHouldBeTrue = false; + try { + Assert.AreEqual(0, prov.ListFiles("/Dir/Sub/").Length, "Wrong file count"); + } + catch(ArgumentException) { + thisHouldBeTrue = true; + } + Assert.IsTrue(thisHouldBeTrue, "ListFiles did not throw an exception"); + Assert.AreEqual("/Dir2/Sub2/File.txt", prov.ListFiles("/Dir2/Sub2/")[0], "Wrong file"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase("/", ExpectedException = typeof(ArgumentException))] + public void RenameDirectory_InvalidDirectory(string d) { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.RenameDirectory(d, "/Dir/"); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void RenameDirectory_InexistentDirectory() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.RenameDirectory("/Inexistent/", "/Inexistent2/"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + [TestCase("/", ExpectedException = typeof(ArgumentException))] + public void RenameDirectory_InvalidNewDir(string n) { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.CreateDirectory("/", "Dir"); + + prov.RenameDirectory("/Dir/", n); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void RenameDirectory_ExistentNewDir() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.CreateDirectory("/", "Dir"); + prov.CreateDirectory("/", "Dir2"); + + prov.RenameDirectory("/Dir/", "/Dir2/"); + } + + [Test] + public void StorePageAttachment_ListPageAttachments_GetPagesWithAttachments() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi1 = new PageInfo("MainPage", null, DateTime.Now); + PageInfo pi2 = new PageInfo("Page2", null, DateTime.Now); + + Assert.AreEqual(0, prov.ListPageAttachments(pi1).Length, "Wrong attachment count"); + + using(Stream s = FillStream("Blah")) { + Assert.IsTrue(prov.StorePageAttachment(pi1, "File.txt", s, false), "StorePageAttachment should return true"); + Assert.IsTrue(prov.StorePageAttachment(pi1, "File2.txt", s, false), "StorePageAttachment should return true"); + Assert.IsTrue(prov.StorePageAttachment(pi2, "File.txt", s, false), "StorePageAttachment should return true"); + } + + string[] attachs = prov.ListPageAttachments(pi1); + Assert.AreEqual(2, attachs.Length, "Wrong attachment count"); + Assert.AreEqual("File.txt", attachs[0], "Wrong attachment"); + Assert.AreEqual("File2.txt", attachs[1], "Wrong attachment"); + + attachs = prov.ListPageAttachments(pi2); + Assert.AreEqual(1, attachs.Length, "Wrong attachment count"); + Assert.AreEqual("File.txt", attachs[0], "Wrong attachment"); + + string[] pages = prov.GetPagesWithAttachments(); + Assert.AreEqual(2, pages.Length, "Wrong page count"); + Assert.AreEqual(pi1.FullName, pages[0], "Wrong page"); + Assert.AreEqual(pi2.FullName, pages[1], "Wrong page"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ListPageAttachments_NullPage() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.ListPageAttachments(null); + } + + [Test] + public void ListPageAttachments_InexistentPage() { + IFilesStorageProviderV30 prov = GetProvider(); + + Assert.AreEqual(0, prov.ListPageAttachments(new PageInfo("Page", MockPagesProvider(), DateTime.Now)).Length, "Wrong attachment count"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void StorePageAttachment_NullPage() { + IFilesStorageProviderV30 prov = GetProvider(); + + using(Stream s = FillStream("Blah")) { + prov.StorePageAttachment(null, "File.txt", s, false); + } + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void StorePageAttachment_InvalidName(string n) { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + using(Stream s = FillStream("Blah")) { + prov.StorePageAttachment(pi, n, s, false); + } + } + + [Test] + public void StorePageAttachment_ExistentName() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + using(Stream s = FillStream("Blah")) { + Assert.IsTrue(prov.StorePageAttachment(pi, "File.txt", s, false), "StorePageAttachment should return true"); + Assert.IsFalse(prov.StorePageAttachment(pi, "File.txt", s, false), "StorePageAttachment should return false"); + } + } + + [Test] + public void StorePageAttachment_Overwrite_RetrievePageAttachment() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + using(Stream s = FillStream("Blah")) { + Assert.IsTrue(prov.StorePageAttachment(pi, "File.txt", s, false), "StorePageAttachment should return true"); + } + + using(Stream s = FillStream("Blah222")) { + Assert.IsTrue(prov.StorePageAttachment(pi, "File.txt", s, true), "StorePageAttachment should return true"); + } + + MemoryStream ms = new MemoryStream(); + Assert.IsTrue(prov.RetrievePageAttachment(pi, "File.txt", ms, false), "RetrievePageAttachment should return true"); + ms.Seek(0, SeekOrigin.Begin); + + Assert.AreEqual("Blah222", Encoding.UTF8.GetString(ms.ToArray()), "Wrong attachment content"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void StorePageAttachment_NullStream() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + prov.StorePageAttachment(pi, "File.txt", null, false); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void StorePageAttachment_ClosedStream() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + MemoryStream ms = new MemoryStream(); + ms.Close(); + prov.StorePageAttachment(pi, "File.txt", ms, false); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrievePageAttachment_NullPage() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + using(Stream s = FillStream("Blah")) { + prov.StorePageAttachment(pi, "File.txt", s, false); + } + + using(MemoryStream ms = new MemoryStream()) { + prov.RetrievePageAttachment(null, "File.txt", ms, false); + } + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void RetrievePageAttachment_InexistentPage() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + using(Stream s = FillStream("Blah")) { + prov.StorePageAttachment(pi, "File.txt", s, false); + } + + pi = new PageInfo("Page2", MockPagesProvider(), DateTime.Now); + + using(MemoryStream ms = new MemoryStream()) { + prov.RetrievePageAttachment(pi, "File.txt", ms, false); + } + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RetrievePageAttachment_InvalidName(string n) { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + using(MemoryStream ms = new MemoryStream()) { + prov.RetrievePageAttachment(pi, n, ms, false); + } + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void RetrievePageAttachment_InexistentName() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + using(MemoryStream ms = new MemoryStream()) { + prov.RetrievePageAttachment(pi, "File.txt", ms, false); + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrievePageAttachment_NullStream() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + using(Stream s = FillStream("Blah")) { + prov.StorePageAttachment(pi, "File.txt", s, false); + } + + prov.RetrievePageAttachment(pi, "File.txt", null, false); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void RetrievePageAttachment_ClosedStream() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + using(Stream s = FillStream("Blah")) { + prov.StorePageAttachment(pi, "File.txt", s, false); + } + + MemoryStream ms = new MemoryStream(); + ms.Close(); + prov.RetrievePageAttachment(pi, "File.txt", ms, false); + } + + [Test] + public void GetPageAttachmentDetails_SetPageAttachmentRetrievalCount() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo page1 = new PageInfo("Page1", null, DateTime.Now); + PageInfo page2 = new PageInfo("Page2", null, DateTime.Now); + + DateTime now = DateTime.Now; + using(Stream s = FillStream("Content")) { + prov.StorePageAttachment(page1, "File.txt", s, false); + } + using(Stream s = FillStream("Content")) { + prov.StorePageAttachment(page2, "File.txt", s, false); + } + using(Stream s = FillStream("Content")) { + prov.StorePageAttachment(page1, "File2.txt", s, false); + } + + FileDetails details; + + details = prov.GetPageAttachmentDetails(page1, "File.txt"); + Assert.AreEqual(0, details.RetrievalCount, "Wrong attachment retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong attachment size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + details = prov.GetPageAttachmentDetails(page2, "File.txt"); + Assert.AreEqual(0, details.RetrievalCount, "Wrong attachment retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong attachment size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + details = prov.GetPageAttachmentDetails(page1, "File2.txt"); + Assert.AreEqual(0, details.RetrievalCount, "Wrong attachment retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong attachment size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + using(Stream s = new MemoryStream()) { + prov.RetrievePageAttachment(page1, "File.txt", s, false); + } + details = prov.GetPageAttachmentDetails(page1, "File.txt"); + Assert.AreEqual(0, details.RetrievalCount, "Wrong attachment retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong attachment size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + using(Stream s = new MemoryStream()) { + prov.RetrievePageAttachment(page1, "File.txt", s, true); + } + using(Stream s = new MemoryStream()) { + prov.RetrievePageAttachment(page1, "File.txt", s, true); + } + using(Stream s = new MemoryStream()) { + prov.RetrievePageAttachment(page2, "File.txt", s, true); + } + + details = prov.GetPageAttachmentDetails(page1, "File.txt"); + Assert.AreEqual(2, details.RetrievalCount, "Wrong attachment retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong attachment size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + details = prov.GetPageAttachmentDetails(page2, "File.txt"); + Assert.AreEqual(1, details.RetrievalCount, "Wrong attachment retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong attachment size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + details = prov.GetPageAttachmentDetails(page1, "File2.txt"); + Assert.AreEqual(0, details.RetrievalCount, "Wrong attachment retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong attachment size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + prov.SetPageAttachmentRetrievalCount(page2, "File.txt", 0); + + details = prov.GetPageAttachmentDetails(page1, "File.txt"); + Assert.AreEqual(2, details.RetrievalCount, "Wrong attachment retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong attachment size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + details = prov.GetPageAttachmentDetails(page2, "File.txt"); + Assert.AreEqual(0, details.RetrievalCount, "Wrong attachment retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong attachment size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + details = prov.GetPageAttachmentDetails(page1, "File2.txt"); + Assert.AreEqual(0, details.RetrievalCount, "Wrong attachment retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong attachment size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + prov.DeletePageAttachment(page1, "File.txt"); + + Assert.IsNull(prov.GetPageAttachmentDetails(page1, "File.txt"), "GetPageAttachmentDetails should return null"); + + details = prov.GetPageAttachmentDetails(page2, "File.txt"); + Assert.AreEqual(0, details.RetrievalCount, "Wrong attachment retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong attachment size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + details = prov.GetPageAttachmentDetails(page1, "File2.txt"); + Assert.AreEqual(0, details.RetrievalCount, "Wrong attachment retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong attachment size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + } + + [Test] + public void GetPageAttachmentDetails_NotifyPageRenaming() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo page = new PageInfo("Page", null, DateTime.Now); + PageInfo page2 = new PageInfo("Page2", null, DateTime.Now); + PageInfo newPage = new PageInfo("newPage", null, DateTime.Now); + + DateTime now = DateTime.Now; + using(Stream s = FillStream("Content")) { + prov.StorePageAttachment(page, "File.txt", s, false); + } + using(Stream s = FillStream("Content")) { + prov.StorePageAttachment(page2, "File.txt", s, false); + } + + using(Stream s = new MemoryStream()) { + prov.RetrievePageAttachment(page, "File.txt", s, true); + } + using(Stream s = new MemoryStream()) { + prov.RetrievePageAttachment(page2, "File.txt", s, true); + } + + prov.NotifyPageRenaming(page, newPage); + + FileDetails details; + + Assert.IsNull(prov.GetPageAttachmentDetails(page, "File.txt"), "GetPageAttachmentDetails should return null"); + + details = prov.GetPageAttachmentDetails(page2, "File.txt"); + Assert.AreEqual(1, details.RetrievalCount, "Wrong attachment retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong attachment size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + + details = prov.GetPageAttachmentDetails(newPage, "File.txt"); + Assert.AreEqual(1, details.RetrievalCount, "Wrong attachment retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong attachment size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + } + + [Test] + public void GetPageAttachmentDetails_RenamePageAttachment() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo page = new PageInfo("Page", null, DateTime.Now); + + DateTime now = DateTime.Now; + using(Stream s = FillStream("Content")) { + prov.StorePageAttachment(page, "File.txt", s, false); + } + + using(Stream s = new MemoryStream()) { + prov.RetrievePageAttachment(page, "File.txt", s, true); + } + + prov.RenamePageAttachment(page, "File.txt", "File2.txt"); + + FileDetails details; + + Assert.IsNull(prov.GetPageAttachmentDetails(page, "File.txt"), "GetPageAttachmentDetails should return null"); + + details = prov.GetPageAttachmentDetails(page, "File2.txt"); + Assert.AreEqual(1, details.RetrievalCount, "Wrong attachment retrieval count"); + Assert.AreEqual(7, details.Size, "Wrong attachment size"); + Tools.AssertDateTimesAreEqual(now, details.LastModified, true); + } + + [Test] + public void GetPageAttachmentDetails_InexistentAttachment() { + IFilesStorageProviderV30 prov = GetProvider(); + + Assert.IsNull(prov.GetPageAttachmentDetails(new PageInfo("Inexistent", null, DateTime.Now), "File.txt"), "GetPageAttachmentDetails should retur null"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void GetPageAttachmentDetails_NullPage() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.GetPageAttachmentDetails(null, "File.txt"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void GetPageAttachmentDetails_InvalidName(string n) { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.GetPageAttachmentDetails(new PageInfo("Page", null, DateTime.Now), n); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetPageAttachmentRetrievalCount_NullPage() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.SetPageAttachmentRetrievalCount(null, "File.txt", 10); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void SetPageAttachmentRetrievalCount_InvalidFile(string f) { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.SetPageAttachmentRetrievalCount(new PageInfo("Page", null, DateTime.Now), f, 10); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void SetPageAttachmentRetrievalCount_NegativeCount() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.SetPageAttachmentRetrievalCount(new PageInfo("Page", null, DateTime.Now), "File.txt", -1); + } + + [Test] + public void DeletePageAttachment() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + using(Stream s = FillStream("Blah")) { + prov.StorePageAttachment(pi, "File.txt", s, false); + } + + Assert.IsTrue(prov.DeletePageAttachment(pi, "File.txt"), "DeletePageAttachment should return true"); + + Assert.IsNull(prov.GetPageAttachmentDetails(pi, "File.txt"), "GetPageAttachmentDetails should return null"); + Assert.AreEqual(0, prov.ListPageAttachments(pi).Length, "Wrong attachment count"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void DeletePageAttachment_NullPage() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.DeletePageAttachment(null, "File.txt"); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void DeletePageAttachment_InexistentPage() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + prov.DeletePageAttachment(pi, "File.txt"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void DeletePageAttachment_InvalidName(string n) { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + prov.DeletePageAttachment(pi, n); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void DeletePageAttachment_InexistentName() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + using(Stream s = FillStream("Blah")) { + prov.StorePageAttachment(pi, "File.txt", s, false); + } + + prov.DeletePageAttachment(pi, "File222.txt"); + } + + [Test] + public void RenamePageAttachment() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + using(Stream s = FillStream("Blah")) { + prov.StorePageAttachment(pi, "File.txt", s, false); + } + + Assert.IsTrue(prov.RenamePageAttachment(pi, "File.txt", "File2.txt"), "RenamePageAttachment should return true"); + + string[] attachs = prov.ListPageAttachments(pi); + Assert.AreEqual(1, attachs.Length, "Wrong attachment count"); + Assert.AreEqual("File2.txt", attachs[0], "Wrong attachment"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RenamePageAttachment_NullPage() { + IFilesStorageProviderV30 prov = GetProvider(); + + prov.RenamePageAttachment(null, "File.txt", "File2.txt"); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void RenamePageAttachment_InexistentPage() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + prov.RenamePageAttachment(pi, "File.txt", "File2.txt"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RenamePageAttachment_InvalidName(string n) { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + prov.RenamePageAttachment(pi, n, "File2.txt"); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void RenamePageAttachment_InexistentName() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + using(Stream s = FillStream("Blah")) { + prov.StorePageAttachment(pi, "File.txt", s, false); + } + + prov.RenamePageAttachment(pi, "File1.txt", "File2.txt"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RenamePageAttachment_InvalidNewName(string n) { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + prov.RenamePageAttachment(pi, "File.txt", n); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void RenamePageAttachment_ExistentNewName() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo pi = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + using(Stream s = FillStream("Blah")) { + prov.StorePageAttachment(pi, "File.txt", s, false); + prov.StorePageAttachment(pi, "File2.txt", s, false); + } + + prov.RenamePageAttachment(pi, "File.txt", "File2.txt"); + } + + [Test] + public void NotifyPageRenaming() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo p1 = new PageInfo("Page1", MockPagesProvider(), DateTime.Now); + PageInfo p2 = new PageInfo("Page2", MockPagesProvider(), DateTime.Now); + + using(Stream s = FillStream("Blah")) { + prov.StorePageAttachment(p1, "File1.txt", s, false); + prov.StorePageAttachment(p1, "File2.txt", s, false); + } + + prov.NotifyPageRenaming(p1, p2); + + Assert.AreEqual(0, prov.ListPageAttachments(p1).Length, "Wrong attachment count"); + + string[] attachs = prov.ListPageAttachments(p2); + Assert.AreEqual(2, attachs.Length, "Wrong attachment count"); + Assert.AreEqual("File1.txt", attachs[0], "Wrong attachment"); + Assert.AreEqual("File2.txt", attachs[1], "Wrong attachment"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void NotifyPageRenaming_NullOldPage() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo p2 = new PageInfo("Page2", MockPagesProvider(), DateTime.Now); + + prov.NotifyPageRenaming(null, p2); + } + + [Test] + public void NotifyPageRenaming_InexistentOldPage() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo p1 = new PageInfo("Page1", MockPagesProvider(), DateTime.Now); + PageInfo p2 = new PageInfo("Page2", MockPagesProvider(), DateTime.Now); + + prov.NotifyPageRenaming(p1, p2); + + // Nothing specific to verify + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void NotifyPageRenaming_NullNewPage() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo p1 = new PageInfo("Page1", MockPagesProvider(), DateTime.Now); + + prov.NotifyPageRenaming(p1, null); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void NotifyPageRenaming_ExistentNewPage() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo p1 = new PageInfo("Page1", MockPagesProvider(), DateTime.Now); + PageInfo p2 = new PageInfo("Page2", MockPagesProvider(), DateTime.Now); + + using(Stream s = FillStream("Blah")) { + prov.StorePageAttachment(p1, "File1.txt", s, false); + prov.StorePageAttachment(p2, "File2.txt", s, false); + } + + prov.NotifyPageRenaming(p1, p2); + } + + [Test] + public void CompleteTestsForCaseInsensitivity_Files() { + IFilesStorageProviderV30 prov = GetProvider(); + + using(Stream s = FillStream("Blah")) { + Assert.IsTrue(prov.StoreFile("/File.TXT", s, false), "StoreFile should return true"); + Assert.IsFalse(prov.StoreFile("/file.txt", s, false), "StoreFile should return false"); + prov.CreateDirectory("/", "Sub"); + Assert.IsTrue(prov.StoreFile("/Sub/File.TXT", s, false), "StoreFile should return true"); + Assert.IsFalse(prov.StoreFile("/SUB/File.TXT", s, false), "StoreFile should return false"); + } + + Assert.IsNotNull(prov.GetFileDetails("/file.TXT"), "GetFileDetails should return something"); + Assert.IsNotNull(prov.GetFileDetails("/suB/fILe.TXT"), "GetFileDetails should return something"); + + MemoryStream ms = new MemoryStream(); + Assert.IsTrue(prov.RetrieveFile("/FILE.tXt", ms, false), "RetrieveFile should return true"); + ms = new MemoryStream(); + Assert.IsTrue(prov.RetrieveFile("/SuB/FILe.tXt", ms, false), "RetrieveFile should return true"); + + Assert.IsTrue(prov.RenameFile("/FILE.TXT", "/NEWfile.txt"), "RenameFile should return true"); + Assert.IsTrue(prov.RenameFile("/SUB/FILE.TXT", "/sub/NEWfile.txt"), "RenameFile should return true"); + + Assert.IsTrue(prov.DeleteFile("/newfile.txt"), "DeleteFile should return true"); + Assert.IsTrue(prov.DeleteFile("/sub/newfile.txt"), "DeleteFile should return true"); + } + + [Test] + public void CompleteTestsForCaseInsensitivity_Attachments() { + IFilesStorageProviderV30 prov = GetProvider(); + + PageInfo page = new PageInfo("Page", MockPagesProvider(), DateTime.Now); + + using(Stream s = FillStream("Blah")) { + Assert.IsTrue(prov.StorePageAttachment(page, "Attachment.TXT", s, false), "StorePageAttachment should return true"); + Assert.IsFalse(prov.StorePageAttachment(page, "ATTACHMENT.txt", s, false), "StorePageAttachment should return false"); + } + + Assert.IsNotNull(prov.GetPageAttachmentDetails(page, "attachment.txt"), "GetPageAttachmentDetails should return a value"); + + MemoryStream ms = new MemoryStream(); + Assert.IsTrue(prov.RetrievePageAttachment(page, "Attachment.txt", ms, false), "RetrievePageAttachment should return true"); + + Assert.IsTrue(prov.RenamePageAttachment(page, "Attachment.txt", "NEWATT.txt"), "RenamePageAttachment should return true"); + + Assert.IsTrue(prov.DeletePageAttachment(page, "newatt.TXT"), "DeletePageAttachment should return true"); + } + + } + +} diff --git a/TestScaffolding/PagesStorageProviderTestScaffolding.cs b/TestScaffolding/PagesStorageProviderTestScaffolding.cs new file mode 100644 index 0000000..3e1f721 --- /dev/null +++ b/TestScaffolding/PagesStorageProviderTestScaffolding.cs @@ -0,0 +1,3651 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.SearchEngine; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Tests { + + [TestFixture] + public abstract class PagesStorageProviderTestScaffolding { + + private MockRepository mocks = new MockRepository(); + private string testDir = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Guid.NewGuid().ToString()); + + private delegate string ToStringDelegate(PageInfo p, string input); + + protected IHostV30 MockHost() { + if(!Directory.Exists(testDir)) Directory.CreateDirectory(testDir); + + IHostV30 host = mocks.DynamicMock(); + Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.AtLeastOnce(); + Expect.Call(host.PrepareContentForIndexing(null, null)).IgnoreArguments().Do((ToStringDelegate)delegate(PageInfo p, string input) { return input; }).Repeat.Any(); + Expect.Call(host.PrepareTitleForIndexing(null, null)).IgnoreArguments().Do((ToStringDelegate)delegate(PageInfo p, string input) { return input; }).Repeat.Any(); + + mocks.Replay(host); + + return host; + } + + public abstract IPagesStorageProviderV30 GetProvider(); + + [TearDown] + public void TearDown() { + try { + Directory.Delete(testDir, true); + } + catch { } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Init_NullHost() { + IPagesStorageProviderV30 prov = GetProvider(); + prov.Init(null, ""); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Init_NullConfig() { + IPagesStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), null); + } + + private void AssertNamespaceInfosAreEqual(NamespaceInfo expected, NamespaceInfo actual, bool checkProvider) { + Assert.AreEqual(expected.Name, actual.Name, "Wrong name"); + if(expected.DefaultPage == null) Assert.IsNull(actual.DefaultPage, "DefaultPage should be null"); + else AssertPageInfosAreEqual(expected.DefaultPage, actual.DefaultPage, true); + if(checkProvider) Assert.AreSame(expected.Provider, actual.Provider); + } + + [Test] + public void AddNamespace_GetNamespaces() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.AreEqual(0, prov.GetNamespaces().Length, "Wrong initial namespace count"); + + NamespaceInfo ns1 = prov.AddNamespace("Sub1"); + NamespaceInfo ns2 = prov.AddNamespace("Sub2"); + NamespaceInfo ns3 = prov.AddNamespace("Spaced Namespace"); + Assert.IsNull(prov.AddNamespace("Sub1"), "AddNamespace should return null"); + + AssertNamespaceInfosAreEqual(new NamespaceInfo("Sub1", prov, null), ns1, true); + AssertNamespaceInfosAreEqual(new NamespaceInfo("Sub2", prov, null), ns2, true); + AssertNamespaceInfosAreEqual(new NamespaceInfo("Spaced Namespace", prov, null), ns3, true); + + NamespaceInfo[] allNS = prov.GetNamespaces(); + Assert.AreEqual(3, allNS.Length, "Wrong namespace count"); + + Array.Sort(allNS, delegate(NamespaceInfo x, NamespaceInfo y) { return x.Name.CompareTo(y.Name); }); + AssertNamespaceInfosAreEqual(ns3, allNS[0], true); + AssertNamespaceInfosAreEqual(ns1, allNS[1], true); + AssertNamespaceInfosAreEqual(ns2, allNS[2], true); + } + + [Test] + public void AddNamespace_GetNamespaces_WithDefaultPages() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.AreEqual(0, prov.GetNamespaces().Length, "Wrong initial namespace count"); + + NamespaceInfo ns1 = prov.AddNamespace("Sub1"); + NamespaceInfo ns2 = prov.AddNamespace("Sub2"); + Assert.IsNull(prov.AddNamespace("Sub1"), "AddNamespace should return null"); + + PageInfo dp1 = prov.AddPage(ns1.Name, "MainPage", DateTime.Now); + ns1 = prov.SetNamespaceDefaultPage(ns1, dp1); + + PageInfo dp2 = prov.AddPage(ns2.Name, "MainPage", DateTime.Now); + ns2 = prov.SetNamespaceDefaultPage(ns2, dp2); + + AssertNamespaceInfosAreEqual(new NamespaceInfo("Sub1", prov, dp1), ns1, true); + AssertNamespaceInfosAreEqual(new NamespaceInfo("Sub2", prov, dp2), ns2, true); + + NamespaceInfo[] allNS = prov.GetNamespaces(); + Assert.AreEqual(2, allNS.Length, "Wrong namespace count"); + + Array.Sort(allNS, delegate(NamespaceInfo x, NamespaceInfo y) { return x.Name.CompareTo(y.Name); }); + AssertNamespaceInfosAreEqual(ns1, allNS[0], true); + AssertNamespaceInfosAreEqual(ns2, allNS[1], true); + } + + [Test] + public void AddNamespace_GetNamespace() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.IsNull(prov.GetNamespace("Sub1"), "GetNamespace should return null"); + + NamespaceInfo ns1 = prov.AddNamespace("Sub1"); + NamespaceInfo ns2 = prov.AddNamespace("Sub2"); + + Assert.IsNull(prov.GetNamespace("Sub3"), "GetNamespace should return null"); + + NamespaceInfo ns1Out = prov.GetNamespace("Sub1"); + NamespaceInfo ns2Out = prov.GetNamespace("Sub2"); + + AssertNamespaceInfosAreEqual(ns1, ns1Out, true); + AssertNamespaceInfosAreEqual(ns2, ns2Out, true); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void GetNamespace_InvalidName(string n) { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.GetNamespace(n); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AddNamespace_InvalidName(string n) { + IPagesStorageProviderV30 prov = GetProvider(); + prov.AddNamespace(n); + } + + [Test] + public void RenameNamespace() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo sub = prov.AddNamespace("Sub"); + prov.AddNamespace("Sub2"); + + CategoryInfo cat = prov.AddCategory(sub.Name, "Cat"); + + PageInfo page = prov.AddPage("Sub", "Page", DateTime.Now); + prov.AddMessage(page, "NUnit", "Test", DateTime.Now, "Body", -1); + + prov.SetNamespaceDefaultPage(sub, page); + + prov.RebindPage(page, new string[] { cat.FullName }); + + Assert.IsNull(prov.RenameNamespace(new NamespaceInfo("Inexistent", prov, null), "NewName"), "RenameNamespace should return null"); + + NamespaceInfo ns = prov.RenameNamespace(new NamespaceInfo("Sub", prov, null), "Sub1"); + + AssertNamespaceInfosAreEqual(new NamespaceInfo("Sub1", prov, new PageInfo("Sub1.Page", prov, page.CreationDateTime)), ns, true); + + NamespaceInfo[] allNS = prov.GetNamespaces(); + Assert.AreEqual(2, allNS.Length, "Wrong namespace count"); + + Array.Sort(allNS, delegate(NamespaceInfo x, NamespaceInfo y) { return x.Name.CompareTo(y.Name); }); + AssertNamespaceInfosAreEqual(new NamespaceInfo("Sub1", prov, new PageInfo("Sub1.Page", prov, page.CreationDateTime)), allNS[0], true); + AssertNamespaceInfosAreEqual(new NamespaceInfo("Sub2", prov, null), allNS[1], true); + + Assert.AreEqual(1, prov.GetMessages(new PageInfo(NameTools.GetFullName("Sub1", "Page"), prov, page.CreationDateTime)).Length, "Wrong message count"); + + CategoryInfo[] categories = prov.GetCategories(ns); + Assert.AreEqual(1, categories.Length, "Wrong category count"); + Assert.AreEqual(NameTools.GetFullName(ns.Name, NameTools.GetLocalName(cat.FullName)), categories[0].FullName, "Wrong category name"); + Assert.AreEqual(1, categories[0].Pages.Length, "Wrong page count"); + Assert.AreEqual(NameTools.GetFullName(ns.Name, NameTools.GetLocalName(page.FullName)), categories[0].Pages[0], "Wrong page"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RenameNamespace_NullNamespace() { + IPagesStorageProviderV30 prov = GetProvider(); + prov.RenameNamespace(null, "NewName"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RenameNamespace_InvalidNewName(string n) { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Sub"); + + prov.RenameNamespace(ns, n); + } + + [Test] + public void RenameNamespace_PerformSearch() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo page1 = prov.AddPage(ns.Name, "Page1", DateTime.Now); + prov.ModifyPage(page1, "Title1", "NUnit", DateTime.Now, "Comment1", "Content1", new string[0], "Descr1", SaveMode.Normal); + + PageInfo page2 = prov.AddPage(ns.Name, "Page2", DateTime.Now); + prov.ModifyPage(page2, "Title2", "NUnit", DateTime.Now, "Comment2", "Content2", new string[0], "Descr2", SaveMode.Normal); + + prov.AddMessage(page1, "NUnit", "Test1", DateTime.Now, "Body1", -1); + prov.AddMessage(page1, "NUnit", "Test2", DateTime.Now, "Body2", prov.GetMessages(page1)[0].ID); + + NamespaceInfo renamedNamespace = prov.RenameNamespace(ns, "NS_Ren"); + + SearchResultCollection result = prov.PerformSearch(new SearchParameters("content1 content2")); + Assert.AreEqual(2, result.Count, "Wrong result count"); + Assert.AreEqual(renamedNamespace.Name, NameTools.GetNamespace(PageDocument.GetPageName(result[0].Document.Name)), "Wrong document name"); + Assert.AreEqual(renamedNamespace.Name, NameTools.GetNamespace(PageDocument.GetPageName(result[1].Document.Name)), "Wrong document name"); + + result = prov.PerformSearch(new SearchParameters("test1 test2")); + Assert.AreEqual(2, result.Count, "Wrong result count"); + Assert.AreEqual(1, result[0].Matches.Count, "Wrong match count"); + Assert.AreEqual(1, result[1].Matches.Count, "Wrong match count"); + + string page; + int id; + + MessageDocument.GetMessageDetails(result[0].Document.Name, out page, out id); + Assert.AreEqual(renamedNamespace.Name, NameTools.GetNamespace(page), "Wrong document name"); + + MessageDocument.GetMessageDetails(result[1].Document.Name, out page, out id); + Assert.AreEqual(renamedNamespace.Name, NameTools.GetNamespace(page), "Wrong document name"); + } + + [Test] + public void SetNamespaceDefaultPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo page = prov.AddPage(ns.Name, "Page", DateTime.Now); + + Assert.IsNull(prov.SetNamespaceDefaultPage(new NamespaceInfo("Inexistent", prov, null), + new PageInfo(NameTools.GetFullName("Inexistent", "Page"), prov, DateTime.Now)), + "SetNamespaceDefaultPage should return null when the namespace does not exist"); + + Assert.IsNull(prov.SetNamespaceDefaultPage(ns, new PageInfo(NameTools.GetFullName(ns.Name, "Inexistent"), prov, DateTime.Now)), + "SetNamespaceDefaultPage should return null when the page does not exist"); + + NamespaceInfo result = prov.SetNamespaceDefaultPage(ns, page); + + AssertNamespaceInfosAreEqual(new NamespaceInfo(ns.Name, prov, page), result, true); + + result = prov.SetNamespaceDefaultPage(ns, null); + + AssertNamespaceInfosAreEqual(new NamespaceInfo(ns.Name, prov, null), result, true); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetNamespaceDefaultPage_NullNamespace() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + prov.SetNamespaceDefaultPage(null, page); + } + + [Test] + public void RemoveNamespace() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.IsFalse(prov.RemoveNamespace(new NamespaceInfo("Inexistent", prov, null)), "RemoveNamespace should return alse"); + + prov.AddNamespace("Sub"); + prov.AddNamespace("Sub2"); + + Assert.IsTrue(prov.RemoveNamespace(new NamespaceInfo("Sub2", prov, null)), "RemoveNamespace should return true"); + + NamespaceInfo[] allNS = prov.GetNamespaces(); + Assert.AreEqual(1, allNS.Length, "Wrong namespace count"); + + AssertNamespaceInfosAreEqual(new NamespaceInfo("Sub", prov, null), allNS[0], true); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveNamespace_NullNamespace() { + IPagesStorageProviderV30 prov = GetProvider(); + prov.RemoveNamespace(null); + } + + [Test] + public void RemoveNamespace_PerformSearch() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo page1 = prov.AddPage(ns.Name, "Page1", DateTime.Now); + prov.ModifyPage(page1, "Title1", "NUnit", DateTime.Now, "Comment1", "Content1", new string[0], "Descr1", SaveMode.Normal); + + prov.AddMessage(page1, "NUnit", "Test1", DateTime.Now, "Body1", -1); + prov.AddMessage(page1, "NUnit", "Test2", DateTime.Now, "Body2", prov.GetMessages(page1)[0].ID); + + PageInfo page2 = prov.AddPage(ns.Name, "Page2", DateTime.Now); + prov.ModifyPage(page2, "Title2", "NUnit", DateTime.Now, "Comment2", "Content2", new string[0], "Descr2", SaveMode.Normal); + + prov.RemoveNamespace(ns); + + Assert.AreEqual(0, prov.PerformSearch(new SearchParameters("content1 content2")).Count, "Wrong result count"); + + Assert.AreEqual(0, prov.PerformSearch(new SearchParameters("test1 test2 comment1 comment2")).Count, "Wrong result count"); + } + + [Test] + public void MovePage_Root2Sub_NoCategories() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + CategoryInfo cat1 = prov.AddCategory(null, "Category1"); + CategoryInfo cat2 = prov.AddCategory(null, "Category2"); + CategoryInfo cat3 = prov.AddCategory(ns.Name, "Category3"); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(page, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(page, "Title0", "NUnit0", DateTime.Now, "Comment0", "Content0", null, null, SaveMode.Backup); + prov.ModifyPage(page, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + prov.RebindPage(page, new string[] { cat1.FullName }); + prov.AddMessage(page, "NUnit", "Test", DateTime.Now, "Body", -1); + + PageInfo moved = prov.MovePage(page, ns, false); + + PageInfo expected = new PageInfo(NameTools.GetFullName(ns.Name, NameTools.GetLocalName(page.FullName)), prov, page.CreationDateTime); + + AssertPageInfosAreEqual(expected, moved, true); + + Assert.AreEqual(0, prov.GetPages(null).Length, "Wrong page count"); + + PageInfo[] allPages = prov.GetPages(ns); + Assert.AreEqual(1, allPages.Length, "Wrong page count"); + AssertPageInfosAreEqual(expected, allPages[0], true); + Assert.AreEqual(1, prov.GetMessages(expected).Length, "Wrong message count"); + + Assert.AreEqual(2, prov.GetCategories(null).Length, "Wrong category count"); + Assert.AreEqual(1, prov.GetCategories(ns).Length, "Wrong category count"); + + Assert.AreEqual(2, prov.GetBackups(expected).Length, "Wrong backup count"); + Assert.AreEqual("Content1", prov.GetContent(expected).Content, "Wrong content"); + } + + [Test] + public void MovePage_Root2Sub_WithCategories() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + CategoryInfo cat1 = prov.AddCategory(null, "Category1"); + CategoryInfo cat2 = prov.AddCategory(null, "Category2"); + CategoryInfo cat3 = prov.AddCategory(ns.Name, "Category3"); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(page, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(page, "Title0", "NUnit0", DateTime.Now, "Comment0", "Content0", null, null, SaveMode.Backup); + prov.ModifyPage(page, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + prov.RebindPage(page, new string[] { cat1.FullName }); + prov.AddMessage(page, "NUnit", "Test", DateTime.Now, "Body", -1); + + PageInfo moved = prov.MovePage(page, ns, true); + + PageInfo expected = new PageInfo(NameTools.GetFullName(ns.Name, NameTools.GetLocalName(page.FullName)), prov, page.CreationDateTime); + + AssertPageInfosAreEqual(expected, moved, true); + + Assert.AreEqual(0, prov.GetPages(null).Length, "Wrong page count"); + + PageInfo[] allPages = prov.GetPages(ns); + Assert.AreEqual(1, allPages.Length, "Wrong page count"); + AssertPageInfosAreEqual(expected, allPages[0], true); + Assert.AreEqual(1, prov.GetMessages(expected).Length, "Wrong message count"); + + Assert.AreEqual(2, prov.GetCategories(null).Length, "Wrong category count"); + Assert.AreEqual(2, prov.GetCategories(ns).Length, "Wrong category count"); + + CategoryInfo[] expectedCategories = new CategoryInfo[] { + new CategoryInfo(NameTools.GetFullName(ns.Name, NameTools.GetLocalName(cat1.FullName)), prov), + new CategoryInfo(NameTools.GetFullName(ns.Name, NameTools.GetLocalName(cat3.FullName)), prov) }; + expectedCategories[0].Pages = new string[] { NameTools.GetFullName(ns.Name, "Page") }; + + CategoryInfo[] actualCategories = prov.GetCategories(ns); + Assert.AreEqual(expectedCategories.Length, actualCategories.Length, "Wrong category count"); + Array.Sort(actualCategories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + AssertCategoryInfosAreEqual(expectedCategories[0], actualCategories[0], true); + AssertCategoryInfosAreEqual(expectedCategories[1], actualCategories[1], true); + + Assert.AreEqual(2, prov.GetBackups(expected).Length, "Wrong backup count"); + Assert.AreEqual("Content1", prov.GetContent(expected).Content, "Wrong content"); + } + + [Test] + public void MovePage_Sub2Root_NoCategories() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + CategoryInfo cat1 = prov.AddCategory(null, "Category1"); + CategoryInfo cat2 = prov.AddCategory(ns.Name, "Category2"); + CategoryInfo cat3 = prov.AddCategory(ns.Name, "Category3"); + + PageInfo page = prov.AddPage(ns.Name, "Page", DateTime.Now); + prov.ModifyPage(page, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(page, "Title0", "NUnit0", DateTime.Now, "Comment0", "Content0", null, null, SaveMode.Backup); + prov.ModifyPage(page, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + prov.RebindPage(page, new string[] { cat2.FullName }); + prov.AddMessage(page, "NUnit", "Test", DateTime.Now, "Body", -1); + + PageInfo moved = prov.MovePage(page, null, false); + + PageInfo expected = new PageInfo(NameTools.GetLocalName(page.FullName), prov, page.CreationDateTime); + + AssertPageInfosAreEqual(expected, moved, true); + + Assert.AreEqual(0, prov.GetPages(ns).Length, "Wrong page count"); + + PageInfo[] allPages = prov.GetPages(null); + Assert.AreEqual(1, allPages.Length, "Wrong page count"); + AssertPageInfosAreEqual(expected, allPages[0], true); + Assert.AreEqual(1, prov.GetMessages(expected).Length, "Wrong message count"); + + Assert.AreEqual(2, prov.GetCategories(ns).Length, "Wrong category count"); + Assert.AreEqual(1, prov.GetCategories(null).Length, "Wrong category count"); + + Assert.AreEqual(2, prov.GetBackups(expected).Length, "Wrong backup count"); + Assert.AreEqual("Content1", prov.GetContent(expected).Content, "Wrong content"); + } + + [Test] + public void MovePage_Sub2Root_WithCategories() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + CategoryInfo cat1 = prov.AddCategory(null, "Category1"); + CategoryInfo cat2 = prov.AddCategory(ns.Name, "Category2"); + CategoryInfo cat3 = prov.AddCategory(ns.Name, "Category3"); + + PageInfo page = prov.AddPage(ns.Name, "Page", DateTime.Now); + prov.ModifyPage(page, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(page, "Title0", "NUnit0", DateTime.Now, "Comment0", "Content0", null, null, SaveMode.Backup); + prov.ModifyPage(page, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + prov.RebindPage(page, new string[] { cat2.FullName }); + prov.AddMessage(page, "NUnit", "Test", DateTime.Now, "Body", -1); + + PageInfo moved = prov.MovePage(page, null, true); + + PageInfo expected = new PageInfo(NameTools.GetLocalName(page.FullName), prov, page.CreationDateTime); + + AssertPageInfosAreEqual(expected, moved, true); + + Assert.AreEqual(0, prov.GetPages(ns).Length, "Wrong page count"); + + PageInfo[] allPages = prov.GetPages(null); + Assert.AreEqual(1, allPages.Length, "Wrong page count"); + AssertPageInfosAreEqual(expected, allPages[0], true); + Assert.AreEqual(1, prov.GetMessages(expected).Length, "Wrong message count"); + + Assert.AreEqual(2, prov.GetCategories(null).Length, "Wrong category count"); + Assert.AreEqual(2, prov.GetCategories(ns).Length, "Wrong category count"); + + CategoryInfo[] expectedCategories = new CategoryInfo[] { + new CategoryInfo(NameTools.GetLocalName(cat1.FullName), prov), + new CategoryInfo(NameTools.GetLocalName(cat2.FullName), prov) }; + expectedCategories[1].Pages = new string[] { "Page" }; + + CategoryInfo[] actualCategories = prov.GetCategories(null); + Assert.AreEqual(expectedCategories.Length, actualCategories.Length, "Wrong category count"); + Array.Sort(actualCategories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + AssertCategoryInfosAreEqual(expectedCategories[0], actualCategories[0], true); + AssertCategoryInfosAreEqual(expectedCategories[1], actualCategories[1], true); + + Assert.AreEqual(2, prov.GetBackups(expected).Length, "Wrong backup count"); + Assert.AreEqual("Content1", prov.GetContent(expected).Content, "Wrong content"); + } + + [Test] + public void MovePage_Sub2Sub_NoCategories() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns1 = prov.AddNamespace("Namespace1"); + NamespaceInfo ns2 = prov.AddNamespace("Namespace2"); + CategoryInfo cat1 = prov.AddCategory(ns1.Name, "Category1"); + CategoryInfo cat2 = prov.AddCategory(ns1.Name, "Category2"); + CategoryInfo cat3 = prov.AddCategory(ns2.Name, "Category3"); + + PageInfo page = prov.AddPage(ns1.Name, "Page", DateTime.Now); + prov.ModifyPage(page, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(page, "Title0", "NUnit0", DateTime.Now, "Comment0", "Content0", null, null, SaveMode.Backup); + prov.ModifyPage(page, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + prov.RebindPage(page, new string[] { cat2.FullName }); + prov.AddMessage(page, "NUnit", "Test", DateTime.Now, "Body", -1); + + PageInfo moved = prov.MovePage(page, ns2, false); + + PageInfo expected = new PageInfo(NameTools.GetFullName(ns2.Name, NameTools.GetLocalName(page.FullName)), prov, page.CreationDateTime); + + AssertPageInfosAreEqual(expected, moved, true); + + Assert.AreEqual(0, prov.GetPages(ns1).Length, "Wrong page count"); + + PageInfo[] allPages = prov.GetPages(ns2); + Assert.AreEqual(1, allPages.Length, "Wrong page count"); + AssertPageInfosAreEqual(expected, allPages[0], true); + Assert.AreEqual(1, prov.GetMessages(expected).Length, "Wrong message count"); + + Assert.AreEqual(2, prov.GetCategories(ns1).Length, "Wrong category count"); + Assert.AreEqual(1, prov.GetCategories(ns2).Length, "Wrong category count"); + + Assert.AreEqual(2, prov.GetBackups(expected).Length, "Wrong backup count"); + Assert.AreEqual("Content1", prov.GetContent(expected).Content, "Wrong content"); + } + + [Test] + public void MovePage_Sub2Sub_WithCategories() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns1 = prov.AddNamespace("Namespace1"); + NamespaceInfo ns2 = prov.AddNamespace("Namespace2"); + CategoryInfo cat1 = prov.AddCategory(ns1.Name, "Category1"); + CategoryInfo cat2 = prov.AddCategory(ns1.Name, "Category2"); + CategoryInfo cat3 = prov.AddCategory(ns2.Name, "Category3"); + + PageInfo page = prov.AddPage(ns1.Name, "Page", DateTime.Now); + prov.ModifyPage(page, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(page, "Title0", "NUnit0", DateTime.Now, "Comment0", "Content0", null, null, SaveMode.Backup); + prov.ModifyPage(page, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + prov.RebindPage(page, new string[] { cat2.FullName }); + prov.AddMessage(page, "NUnit", "Test", DateTime.Now, "Body", -1); + + PageInfo moved = prov.MovePage(page, ns2, true); + + PageInfo expected = new PageInfo(NameTools.GetFullName(ns2.Name, NameTools.GetLocalName(page.FullName)), prov, page.CreationDateTime); + + AssertPageInfosAreEqual(expected, moved, true); + + Assert.AreEqual(0, prov.GetPages(ns1).Length, "Wrong page count"); + + PageInfo[] allPages = prov.GetPages(ns2); + Assert.AreEqual(1, allPages.Length, "Wrong page count"); + AssertPageInfosAreEqual(expected, allPages[0], true); + Assert.AreEqual(1, prov.GetMessages(expected).Length, "Wrong message count"); + + Assert.AreEqual(2, prov.GetCategories(ns2).Length, "Wrong category count"); + Assert.AreEqual(2, prov.GetCategories(ns1).Length, "Wrong category count"); + + CategoryInfo[] expectedCategories = new CategoryInfo[] { + new CategoryInfo(NameTools.GetFullName(ns2.Name, NameTools.GetLocalName(cat2.FullName)), prov), + new CategoryInfo(NameTools.GetFullName(ns2.Name, NameTools.GetLocalName(cat3.FullName)), prov) }; + expectedCategories[0].Pages = new string[] { moved.FullName }; + + CategoryInfo[] actualCategories = prov.GetCategories(ns2); + Assert.AreEqual(expectedCategories.Length, actualCategories.Length, "Wrong category count"); + Array.Sort(actualCategories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + AssertCategoryInfosAreEqual(expectedCategories[0], actualCategories[0], true); + AssertCategoryInfosAreEqual(expectedCategories[1], actualCategories[1], true); + + Assert.AreEqual(2, prov.GetBackups(expected).Length, "Wrong backup count"); + Assert.AreEqual("Content1", prov.GetContent(expected).Content, "Wrong content"); + } + + [Test] + public void MovePage_SameNamespace_Root2Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + Assert.IsNull(prov.MovePage(page, null, false), "MovePage should return null"); + } + + [Test] + public void MovePage_SameNamespace_Sub2Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + + PageInfo page = prov.AddPage("Namespace", "Page", DateTime.Now); + + Assert.IsNull(prov.MovePage(page, ns, false), "MovePage should return null"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void MovePage_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.MovePage(null, prov.AddNamespace("ns"), false); + } + + [Test] + public void MovePage_InexistentPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + + Assert.IsNull(prov.MovePage(new PageInfo("Page", prov, DateTime.Now), + ns, false), "MovePage should return null"); + + Assert.IsNull(prov.MovePage(new PageInfo(NameTools.GetFullName(ns.Name, "Page"), prov, DateTime.Now), + null, false), "MovePage should return null"); + } + + [Test] + public void MovePage_InexistentNamespace() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + PageInfo page = prov.AddPage(ns.Name, "Page", DateTime.Now); + + Assert.IsNull(prov.MovePage(page, new NamespaceInfo("Inexistent", prov, null), false), "MovePage should return null"); + } + + [Test] + public void MovePage_ExistentPage_Root2Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + PageInfo existing = prov.AddPage(ns.Name, "Page", DateTime.Now); + + Assert.IsNull(prov.MovePage(page, ns, false), "MovePage should return null"); + } + + [Test] + public void MovePage_ExistentPage_Sub2Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + + PageInfo page = prov.AddPage(ns.Name, "Page", DateTime.Now); + PageInfo existing = prov.AddPage(null, "Page", DateTime.Now); + + Assert.IsNull(prov.MovePage(page, null, false), "MovePage should return null"); + } + + [Test] + public void MovePage_DefaultPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo page = prov.AddPage("NS", "MainPage", DateTime.Now); + + prov.SetNamespaceDefaultPage(ns, page); + + Assert.IsNull(prov.MovePage(page, null, false), "Cannot move the default page"); + } + + [Test] + public void MovePage_Root2Sub_PerformSearch() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(page, "Title", "NUnit", DateTime.Now, "Comment", "Content", new string[0], "Descr", SaveMode.Backup); + + prov.AddMessage(page, "NUnit", "Test1", DateTime.Now, "Body1", -1); + prov.AddMessage(page, "NUnit", "Test2", DateTime.Now, "Body2", prov.GetMessages(page)[0].ID); + + PageInfo movedPage = prov.MovePage(page, ns, false); + + SearchResultCollection result = prov.PerformSearch(new SearchParameters("content")); + Assert.AreEqual(1, result.Count, "Wrong result count"); + Assert.AreEqual(movedPage.FullName, PageDocument.GetPageName(result[0].Document.Name), "Wrong document name"); + + result = prov.PerformSearch(new SearchParameters("test1 test2 body1 body2")); + Assert.AreEqual(2, result.Count, "Wrong result count"); + + string pageName; + int id; + + MessageDocument.GetMessageDetails(result[0].Document.Name, out pageName, out id); + Assert.AreEqual(ns.Name, NameTools.GetNamespace(pageName), "Wrong document name"); + + MessageDocument.GetMessageDetails(result[1].Document.Name, out pageName, out id); + Assert.AreEqual(ns.Name, NameTools.GetNamespace(pageName), "Wrong document name"); + } + + [Test] + public void MovePage_Sub2Root_PerformSearch() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo page = prov.AddPage(ns.Name, "Page", DateTime.Now); + prov.ModifyPage(page, "Title", "NUnit", DateTime.Now, "Comment", "Content", new string[0], "Descr", SaveMode.Backup); + + prov.AddMessage(page, "NUnit", "Test1", DateTime.Now, "Body1", -1); + prov.AddMessage(page, "NUnit", "Test2", DateTime.Now, "Body2", prov.GetMessages(page)[0].ID); + + PageInfo movedPage = prov.MovePage(page, null, false); + + SearchResultCollection result = prov.PerformSearch(new SearchParameters("content")); + Assert.AreEqual(1, result.Count, "Wrong result count"); + Assert.AreEqual(movedPage.FullName, PageDocument.GetPageName(result[0].Document.Name), "Wrong document name"); + + result = prov.PerformSearch(new SearchParameters("test1 test2 body1 body2")); + Assert.AreEqual(2, result.Count, "Wrong result count"); + + string pageName; + int id; + + MessageDocument.GetMessageDetails(result[0].Document.Name, out pageName, out id); + Assert.AreEqual(null, NameTools.GetNamespace(pageName), "Wrong document name"); + + MessageDocument.GetMessageDetails(result[1].Document.Name, out pageName, out id); + Assert.AreEqual(null, NameTools.GetNamespace(pageName), "Wrong document name"); + } + + [Test] + public void MovePage_Sub2Sub_PerformSearch() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns1 = prov.AddNamespace("NS1"); + NamespaceInfo ns2 = prov.AddNamespace("NS2"); + + PageInfo page = prov.AddPage(ns1.Name, "Page", DateTime.Now); + prov.ModifyPage(page, "Title", "NUnit", DateTime.Now, "Comment", "Content", new string[0], "Descr", SaveMode.Backup); + + prov.AddMessage(page, "NUnit", "Test1", DateTime.Now, "Body1", -1); + prov.AddMessage(page, "NUnit", "Test2", DateTime.Now, "Body2", prov.GetMessages(page)[0].ID); + + PageInfo movedPage = prov.MovePage(page, ns2, false); + + SearchResultCollection result = prov.PerformSearch(new SearchParameters("content")); + Assert.AreEqual(1, result.Count, "Wrong result count"); + Assert.AreEqual(movedPage.FullName, PageDocument.GetPageName(result[0].Document.Name), "Wrong document name"); + + result = prov.PerformSearch(new SearchParameters("test1 test2 body1 body2")); + Assert.AreEqual(2, result.Count, "Wrong result count"); + + string pageName; + int id; + + MessageDocument.GetMessageDetails(result[0].Document.Name, out pageName, out id); + Assert.AreEqual(ns2.Name, NameTools.GetNamespace(pageName), "Wrong document name"); + + MessageDocument.GetMessageDetails(result[1].Document.Name, out pageName, out id); + Assert.AreEqual(ns2.Name, NameTools.GetNamespace(pageName), "Wrong document name"); + } + + private void AssertCategoryInfosAreEqual(CategoryInfo expected, CategoryInfo actual, bool checkProvider) { + Assert.AreEqual(expected.FullName, actual.FullName, "Wrong full name"); + Assert.AreEqual(expected.Pages.Length, actual.Pages.Length, "Wrong page count"); + for(int i = 0; i < expected.Pages.Length; i++) { + Assert.AreEqual(expected.Pages[i], actual.Pages[i], "Wrong page at position " + i.ToString()); + } + if(checkProvider) Assert.AreSame(expected.Provider, actual.Provider, "Different provider instances"); + } + + [Test] + public void AddCategory_GetCategories_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.AreEqual(0, prov.GetCategories(null).Length, "Wrong initial category count"); + + CategoryInfo c1 = prov.AddCategory(null, "Category1"); + CategoryInfo c2 = prov.AddCategory(null, "Category2"); + + Assert.IsNull(prov.AddCategory(null, "Category1"), "AddCategory should return null"); + + AssertCategoryInfosAreEqual(new CategoryInfo("Category1", prov), c1, true); + AssertCategoryInfosAreEqual(new CategoryInfo("Category2", prov), c2, true); + + CategoryInfo[] categories = prov.GetCategories(null); + Array.Sort(categories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + + AssertCategoryInfosAreEqual(new CategoryInfo("Category1", prov), categories[0], true); + AssertCategoryInfosAreEqual(new CategoryInfo("Category2", prov), categories[1], true); + } + + [Test] + public void AddCategory_GetCategories_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + + Assert.AreEqual(0, prov.GetCategories(ns).Length, "Wrong initial category count"); + + CategoryInfo c1 = prov.AddCategory(ns.Name, "Category1"); + CategoryInfo c2 = prov.AddCategory(ns.Name, "Category2"); + + Assert.IsNull(prov.AddCategory(ns.Name, "Category1"), "AddCategory should return null"); + + AssertCategoryInfosAreEqual(new CategoryInfo(NameTools.GetFullName(ns.Name, "Category1"), prov), c1, true); + AssertCategoryInfosAreEqual(new CategoryInfo(NameTools.GetFullName(ns.Name, "Category2"), prov), c2, true); + + CategoryInfo[] categories = prov.GetCategories(ns); + Array.Sort(categories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + + AssertCategoryInfosAreEqual(new CategoryInfo(NameTools.GetFullName(ns.Name, "Category1"), prov), categories[0], true); + AssertCategoryInfosAreEqual(new CategoryInfo(NameTools.GetFullName(ns.Name, "Category2"), prov), categories[1], true); + } + + [Test] + public void GetCategoriesForPage_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + CategoryInfo c1 = prov.AddCategory(null, "Category1"); + CategoryInfo c2 = prov.AddCategory(null, "Category2"); + CategoryInfo c3 = prov.AddCategory(null, "Category3"); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + PageInfo page2 = prov.AddPage(null, "Page2", DateTime.Now); + + prov.RebindPage(page, new string[] { c1.FullName, c3.FullName }); + + Assert.AreEqual(0, prov.GetCategoriesForPage(page2).Length, "Wrong category count"); + CategoryInfo[] categories = prov.GetCategoriesForPage(page); + Assert.AreEqual(2, categories.Length, "Wrong category count"); + + Array.Sort(categories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + CategoryInfo cat1 = new CategoryInfo("Category1", prov); + CategoryInfo cat3 = new CategoryInfo("Category3", prov); + cat1.Pages = new string[] { page.FullName }; + cat3.Pages = new string[] { page.FullName }; + AssertCategoryInfosAreEqual(cat1, categories[0], true); + AssertCategoryInfosAreEqual(cat3, categories[1], true); + } + + [Test] + public void GetCategoriesForPage_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + + CategoryInfo c1 = prov.AddCategory(ns.Name, "Category1"); + CategoryInfo c2 = prov.AddCategory(ns.Name, "Category2"); + CategoryInfo c3 = prov.AddCategory(ns.Name, "Category3"); + + PageInfo page = prov.AddPage(ns.Name, "Page", DateTime.Now); + PageInfo page2 = prov.AddPage(ns.Name, "Page2", DateTime.Now); + + prov.RebindPage(page, new string[] { c1.FullName, c3.FullName }); + + Assert.AreEqual(0, prov.GetCategoriesForPage(page2).Length, "Wrong category count"); + CategoryInfo[] categories = prov.GetCategoriesForPage(page); + Assert.AreEqual(2, categories.Length, "Wrong category count"); + + Array.Sort(categories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + CategoryInfo cat1 = new CategoryInfo(NameTools.GetFullName(ns.Name, "Category1"), prov); + CategoryInfo cat3 = new CategoryInfo(NameTools.GetFullName(ns.Name, "Category3"), prov); + cat1.Pages = new string[] { page.FullName }; + cat3.Pages = new string[] { page.FullName }; + AssertCategoryInfosAreEqual(cat1, categories[0], true); + AssertCategoryInfosAreEqual(cat3, categories[1], true); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void GetCategoriesForPage_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.GetCategoriesForPage(null); + } + + [Test] + public void GetUncategorizedPages_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + CategoryInfo c1 = prov.AddCategory(null, "Category1"); + CategoryInfo c2 = prov.AddCategory(null, "Category2"); + CategoryInfo c3 = prov.AddCategory(null, "Category3"); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + PageInfo page2 = prov.AddPage(null, "Page2", DateTime.Now); + PageInfo page3 = prov.AddPage(null, "Page3", DateTime.Now); + + prov.RebindPage(page, new string[] { c1.FullName, c3.FullName }); + + PageInfo[] pages = prov.GetUncategorizedPages(null); + Assert.AreEqual(2, pages.Length, "Wrong page count"); + + AssertPageInfosAreEqual(page2, pages[0], true); + AssertPageInfosAreEqual(page3, pages[1], true); + } + + [Test] + public void GetUncategorizedPages_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + + CategoryInfo c1 = prov.AddCategory(ns.Name, "Category1"); + CategoryInfo c2 = prov.AddCategory(ns.Name, "Category2"); + CategoryInfo c3 = prov.AddCategory(ns.Name, "Category3"); + + PageInfo page = prov.AddPage(ns.Name, "Page", DateTime.Now); + PageInfo page2 = prov.AddPage(ns.Name, "Page2", DateTime.Now); + PageInfo page3 = prov.AddPage(ns.Name, "Page3", DateTime.Now); + + prov.RebindPage(page, new string[] { c1.FullName, c3.FullName }); + + PageInfo[] pages = prov.GetUncategorizedPages(ns); + Assert.AreEqual(2, pages.Length, "Wrong page count"); + + AssertPageInfosAreEqual(page2, pages[0], true); + AssertPageInfosAreEqual(page3, pages[1], true); + } + + [Test] + public void AddCategory_GetCategory_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.IsNull(prov.GetCategory("Category1"), "GetCategory should return null"); + + CategoryInfo c1 = prov.AddCategory(null, "Category1"); + CategoryInfo c2 = prov.AddCategory(null, "Category2"); + + Assert.IsNull(prov.GetCategory("Category3"), "GetCategory should return null"); + + CategoryInfo c1Out = prov.GetCategory("Category1"); + CategoryInfo c2Out = prov.GetCategory("Category2"); + + AssertCategoryInfosAreEqual(c1, c1Out, true); + AssertCategoryInfosAreEqual(c2, c2Out, true); + } + + [Test] + public void AddCategory_GetCategory_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.AddNamespace("NS"); + + Assert.IsNull(prov.GetCategory("NS.Category1"), "GetCategory should return null"); + + CategoryInfo c1 = prov.AddCategory("NS", "Category1"); + CategoryInfo c2 = prov.AddCategory("NS", "Category2"); + + Assert.IsNull(prov.GetCategory("NS.Category3"), "GetCategory should return null"); + + CategoryInfo c1Out = prov.GetCategory("NS.Category1"); + CategoryInfo c2Out = prov.GetCategory("NS.Category2"); + + AssertCategoryInfosAreEqual(c1, c1Out, true); + AssertCategoryInfosAreEqual(c2, c2Out, true); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void GetCategory_InvalidName(string n) { + IPagesStorageProviderV30 prov = GetProvider(); + prov.GetCategory(n); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AddCategory_InvalidCategory(string c) { + IPagesStorageProviderV30 prov = GetProvider(); + prov.AddCategory(null, c); + } + + [Test] + public void RenameCategory_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + CategoryInfo c1 = prov.AddCategory(null, "Category1"); + CategoryInfo c2 = prov.AddCategory(null, "Category2"); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + prov.RebindPage(page, new string[] { c2.FullName }); + + Assert.IsNull(prov.RenameCategory(new CategoryInfo("Inexistent", prov), "NewName"), "RenameCategory should return null"); + Assert.IsNull(prov.RenameCategory(c2, "Category1"), "RenameCategory should return null"); + + CategoryInfo c3 = new CategoryInfo("Category3", prov); + c3.Pages = new string[] { page.FullName }; + AssertCategoryInfosAreEqual(c3, prov.RenameCategory(c2, "Category3"), true); + + CategoryInfo[] categories = prov.GetCategories(null); + Assert.AreEqual(2, categories.Length, "Wrong category count"); + + Array.Sort(categories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + AssertCategoryInfosAreEqual(new CategoryInfo("Category1", prov), categories[0], true); + AssertCategoryInfosAreEqual(c3, categories[1], true); + } + + [Test] + public void RenameCategory_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + + CategoryInfo c1 = prov.AddCategory(ns.Name, "Category1"); + CategoryInfo c2 = prov.AddCategory(ns.Name, "Category2"); + + PageInfo page = prov.AddPage(ns.Name, "Page", DateTime.Now); + prov.RebindPage(page, new string[] { c2.FullName }); + + Assert.IsNull(prov.RenameCategory(new CategoryInfo(NameTools.GetFullName(ns.Name, "Inexistent"), prov), "NewName"), "RenameCategory should return null"); + Assert.IsNull(prov.RenameCategory(c2, "Category1"), "RenameCategory should return null"); + + CategoryInfo c3 = new CategoryInfo(NameTools.GetFullName(ns.Name, "Category3"), prov); + c3.Pages = new string[] { page.FullName }; + AssertCategoryInfosAreEqual(c3, prov.RenameCategory(c2, "Category3"), true); + + CategoryInfo[] categories = prov.GetCategories(ns); + Assert.AreEqual(2, categories.Length, "Wrong category count"); + + Array.Sort(categories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + AssertCategoryInfosAreEqual(new CategoryInfo(NameTools.GetFullName(ns.Name, "Category1"), prov), categories[0], true); + AssertCategoryInfosAreEqual(c3, categories[1], true); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RenameCategory_NullCategory() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.RenameCategory(null, "Name"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RenameCategory_InvalidNewName(string n) { + IPagesStorageProviderV30 prov = GetProvider(); + CategoryInfo c1 = prov.AddCategory(null, "Category1"); + prov.RenameCategory(c1, n); + } + + [Test] + public void RemoveCategory_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + CategoryInfo c1 = prov.AddCategory(null, "Category1"); + CategoryInfo c2 = prov.AddCategory(null, "Category2"); + + Assert.IsFalse(prov.RemoveCategory(new CategoryInfo("Inexistent", prov)), "RemoveCategory should return false"); + + Assert.IsTrue(prov.RemoveCategory(c1), "RemoveCategory should return true"); + + CategoryInfo[] categories = prov.GetCategories(null); + Assert.AreEqual(1, categories.Length, "Wrong category count"); + AssertCategoryInfosAreEqual(new CategoryInfo("Category2", prov), categories[0], true); + } + + [Test] + public void RemoveCategory_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + + CategoryInfo c1 = prov.AddCategory(ns.Name, "Category1"); + CategoryInfo c2 = prov.AddCategory(ns.Name, "Category2"); + + Assert.IsFalse(prov.RemoveCategory(new CategoryInfo(NameTools.GetFullName(ns.Name, "Inexistent"), prov)), "RemoveCategory should return false"); + + Assert.IsTrue(prov.RemoveCategory(c1), "RemoveCategory should return true"); + + CategoryInfo[] categories = prov.GetCategories(ns); + Assert.AreEqual(1, categories.Length, "Wrong category count"); + AssertCategoryInfosAreEqual(new CategoryInfo(NameTools.GetFullName(ns.Name, "Category2"), prov), categories[0], true); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveCategory_NullCategory() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.RemoveCategory(null); + } + + [Test] + public void MergeCategories_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + CategoryInfo cat1 = prov.AddCategory(null, "Cat1"); + CategoryInfo cat2 = prov.AddCategory(null, "Cat2"); + CategoryInfo cat3 = prov.AddCategory(null, "Cat3"); + + PageInfo page1 = prov.AddPage(null, "Page1", DateTime.Now); + PageInfo page2 = prov.AddPage(null, "Page2", DateTime.Now); + + prov.RebindPage(page1, new string[] { "Cat1", "Cat2" }); + prov.RebindPage(page2, new string[] { "Cat3" }); + + Assert.IsNull(prov.MergeCategories(new CategoryInfo("Inexistent", prov), cat1), "MergeCategories should return null"); + Assert.IsNull(prov.MergeCategories(cat1, new CategoryInfo("Inexistent", prov)), "MergeCategories should return null"); + + CategoryInfo merged = prov.MergeCategories(cat1, cat3); + Assert.IsNotNull(merged, "MergeCategories should return something"); + Assert.AreEqual("Cat3", merged.FullName, "Wrong name"); + + CategoryInfo[] categories = prov.GetCategories(null); + + Assert.AreEqual(2, categories.Length, "Wrong category count"); + + Array.Sort(categories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + + Assert.AreEqual(1, categories[0].Pages.Length, "Wrong page count"); + Assert.AreEqual("Page1", categories[0].Pages[0], "Wrong page at position 0"); + + Assert.AreEqual(2, categories[1].Pages.Length, "Wrong page count"); + Array.Sort(categories[1].Pages); + Assert.AreEqual("Page1", categories[1].Pages[0], "Wrong page at position 0"); + Assert.AreEqual("Page2", categories[1].Pages[1], "Wrong page at position 1"); + } + + [Test] + public void MergeCategories_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + + CategoryInfo cat1 = prov.AddCategory(ns.Name, "Cat1"); + CategoryInfo cat2 = prov.AddCategory(ns.Name, "Cat2"); + CategoryInfo cat3 = prov.AddCategory(ns.Name, "Cat3"); + + PageInfo page1 = prov.AddPage(ns.Name, "Page1", DateTime.Now); + PageInfo page2 = prov.AddPage(ns.Name, "Page2", DateTime.Now); + + prov.RebindPage(page1, new string[] { NameTools.GetFullName(ns.Name, "Cat1"), NameTools.GetFullName(ns.Name, "Cat2") }); + prov.RebindPage(page2, new string[] { NameTools.GetFullName(ns.Name, "Cat3") }); + + Assert.IsNull(prov.MergeCategories(new CategoryInfo(NameTools.GetFullName(ns.Name, "Inexistent"), prov), cat1), "MergeCategories should return null"); + Assert.IsNull(prov.MergeCategories(cat1, new CategoryInfo(NameTools.GetFullName(ns.Name, "Inexistent"), prov)), "MergeCategories should return null"); + + CategoryInfo merged = prov.MergeCategories(cat1, cat3); + Assert.IsNotNull(merged, "MergeCategories should return something"); + Assert.AreEqual(NameTools.GetFullName(ns.Name, "Cat3"), merged.FullName, "Wrong name"); + + CategoryInfo[] categories = prov.GetCategories(ns); + + Assert.AreEqual(2, categories.Length, "Wrong category count"); + + Array.Sort(categories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + + Assert.AreEqual(1, categories[0].Pages.Length, "Wrong page count"); + Assert.AreEqual(NameTools.GetFullName(ns.Name, "Page1"), categories[0].Pages[0], "Wrong page at position 0"); + + Assert.AreEqual(2, categories[1].Pages.Length, "Wrong page count"); + Array.Sort(categories[1].Pages); + Assert.AreEqual(NameTools.GetFullName(ns.Name, "Page1"), categories[1].Pages[0], "Wrong page at position 0"); + Assert.AreEqual(NameTools.GetFullName(ns.Name, "Page2"), categories[1].Pages[1], "Wrong page at position 1"); + } + + [Test] + public void MergeCategories_DifferentNamespaces() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns1 = prov.AddNamespace("Namespace1"); + NamespaceInfo ns2 = prov.AddNamespace("Namespace2"); + + CategoryInfo cat1 = prov.AddCategory(ns1.Name, "Cat1"); + CategoryInfo cat2 = prov.AddCategory(ns2.Name, "Cat2"); + CategoryInfo cat3 = prov.AddCategory(null, "Cat3"); + + // Sub 2 Sub + Assert.IsNull(prov.MergeCategories(cat1, cat2), "MergeCategories should return null"); + // Sub to Root + Assert.IsNull(prov.MergeCategories(cat2, cat3), "MergeCategories should return null"); + // Root to Sub + Assert.IsNull(prov.MergeCategories(cat3, cat1), "MergeCategories should return null"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void MergeCategories_NullSource() { + IPagesStorageProviderV30 prov = GetProvider(); + + CategoryInfo cat1 = prov.AddCategory(null, "Cat1"); + + prov.MergeCategories(null, cat1); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void MergeCategories_NullDestination() { + IPagesStorageProviderV30 prov = GetProvider(); + + CategoryInfo cat1 = prov.AddCategory(null, "Cat1"); + + prov.MergeCategories(cat1, null); + } + + [Test] + public void PerformSearch() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(page, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Backup); + + Assert.AreEqual(1, prov.PerformSearch(new SearchParameters("content")).Count, "Wrong result count"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void PerformSearch_NullParameters() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.PerformSearch(null); + } + + private void AssertPageInfosAreEqual(PageInfo expected, PageInfo actual, bool checkProvider) { + Assert.AreEqual(expected.FullName, actual.FullName, "Wrong name"); + Assert.AreEqual(expected.NonCached, actual.NonCached, "Wrong non-cached flag"); + Tools.AssertDateTimesAreEqual(expected.CreationDateTime, actual.CreationDateTime, true); + if(checkProvider) Assert.AreSame(expected.Provider, actual.Provider, "Different provider instances"); + } + + [Test] + public void AddPage_GetPages_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.AreEqual(0, prov.GetPages(null).Length, "Wrong initial page count"); + + PageInfo p1 = prov.AddPage(null, "Page1", DateTime.Now); + PageInfo p2 = prov.AddPage(null, "Page2", DateTime.Now); + + Assert.IsNull(prov.AddPage(null, "Page1", DateTime.Now.AddDays(-1)), "AddPage should return null"); + + AssertPageInfosAreEqual(new PageInfo("Page1", prov, p1.CreationDateTime), p1, true); + AssertPageInfosAreEqual(new PageInfo("Page2", prov, p2.CreationDateTime), p2, true); + + PageInfo[] pages = prov.GetPages(null); + + Assert.AreEqual(2, pages.Length, "Wrong page count"); + Array.Sort(pages, delegate(PageInfo x, PageInfo y) { return x.FullName.CompareTo(y.FullName); }); + AssertPageInfosAreEqual(new PageInfo("Page1", prov, p1.CreationDateTime), pages[0], true); + AssertPageInfosAreEqual(new PageInfo("Page2", prov, p2.CreationDateTime), pages[1], true); + } + + [Test] + public void AddPage_GetPages_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + + Assert.AreEqual(0, prov.GetPages(ns).Length, "Wrong initial page count"); + + PageInfo p1 = prov.AddPage(ns.Name, "Page1", DateTime.Now); + PageInfo p2 = prov.AddPage(ns.Name, "Page2", DateTime.Now); + + Assert.IsNull(prov.AddPage(ns.Name, "Page1", DateTime.Now.AddDays(-1)), "AddPage should return null"); + + AssertPageInfosAreEqual(new PageInfo(NameTools.GetFullName(ns.Name, "Page1"), prov, p1.CreationDateTime), p1, true); + AssertPageInfosAreEqual(new PageInfo(NameTools.GetFullName(ns.Name, "Page2"), prov, p2.CreationDateTime), p2, true); + + PageInfo[] pages = prov.GetPages(ns); + + Assert.AreEqual(2, pages.Length, "Wrong page count"); + Array.Sort(pages, delegate(PageInfo x, PageInfo y) { return x.FullName.CompareTo(y.FullName); }); + AssertPageInfosAreEqual(new PageInfo(NameTools.GetFullName(ns.Name, "Page1"), prov, p1.CreationDateTime), pages[0], true); + AssertPageInfosAreEqual(new PageInfo(NameTools.GetFullName(ns.Name, "Page2"), prov, p2.CreationDateTime), pages[1], true); + } + + [Test] + public void AddPage_GetPage_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.IsNull(prov.GetPage("Page1"), "GetPage should return null"); + + PageInfo p1 = prov.AddPage(null, "Page1", DateTime.Now); + PageInfo p2 = prov.AddPage(null, "Page2", DateTime.Now); + + Assert.IsNull(prov.GetPage("Page3"), "GetPage should return null"); + + PageInfo p1Out = prov.GetPage("Page1"); + PageInfo p2Out = prov.GetPage("Page2"); + + AssertPageInfosAreEqual(p1, p1Out, true); + AssertPageInfosAreEqual(p2, p2Out, true); + } + + [Test] + public void AddPage_GetPage_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.AddNamespace("NS"); + + Assert.IsNull(prov.GetPage("NS.Page1"), "GetPage should return null"); + + PageInfo p1 = prov.AddPage("NS", "Page1", DateTime.Now); + PageInfo p2 = prov.AddPage("NS", "Page2", DateTime.Now); + + Assert.IsNull(prov.GetPage("NS.Page3"), "GetPage should return null"); + + PageInfo p1Out = prov.GetPage("NS.Page1"); + PageInfo p2Out = prov.GetPage("NS.Page2"); + + AssertPageInfosAreEqual(p1, p1Out, true); + AssertPageInfosAreEqual(p2, p2Out, true); + } + + [Test] + public void AddPage_GetPage_SubSpaced() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.AddNamespace("Spaced Namespace"); + + Assert.IsNull(prov.GetPage("Spaced Namespace.Page1"), "GetPage should return null"); + + PageInfo p1 = prov.AddPage("Spaced Namespace", "Page1", DateTime.Now); + PageInfo p2 = prov.AddPage("Spaced Namespace", "Page2", DateTime.Now); + + Assert.IsNull(prov.GetPage("Spaced Namespace.Page3"), "GetPage should return null"); + + PageInfo p1Out = prov.GetPage("Spaced Namespace.Page1"); + PageInfo p2Out = prov.GetPage("Spaced Namespace.Page2"); + + AssertPageInfosAreEqual(p1, p1Out, true); + AssertPageInfosAreEqual(p2, p2Out, true); + } + + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void GetPage_InvalidName(string n) { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.GetPage(n); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AddPage_InvalidName(string n) { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.AddPage(null, n, DateTime.Now); + } + + private void AssertPageContentsAreEqual(PageContent expected, PageContent actual) { + AssertPageInfosAreEqual(expected.PageInfo, actual.PageInfo, true); + Assert.AreEqual(expected.Title, actual.Title, "Wrong title"); + Assert.AreEqual(expected.User, actual.User, "Wrong user"); + Tools.AssertDateTimesAreEqual(expected.LastModified, actual.LastModified, true); + Assert.AreEqual(expected.Comment, actual.Comment, "Wrong comment"); + Assert.AreEqual(expected.Content, actual.Content, "Wrong content"); + + if(expected.LinkedPages != null) { + Assert.IsNotNull(actual.LinkedPages, "LinkedPages is null"); + Assert.AreEqual(expected.LinkedPages.Length, actual.LinkedPages.Length, "Wrong linked page count"); + for(int i = 0; i < expected.LinkedPages.Length; i++) { + Assert.AreEqual(expected.LinkedPages[i], actual.LinkedPages[i], "Wrong linked page"); + } + } + + if(expected.Keywords != null) { + Assert.IsNotNull(actual.Keywords); + Assert.AreEqual(expected.Keywords.Length, actual.Keywords.Length, "Wrong keyword count"); + for(int i = 0; i < expected.Keywords.Length; i++) { + Assert.AreEqual(expected.Keywords[i], actual.Keywords[i], "Wrong keyword"); + } + } + + Assert.AreEqual(expected.Description, actual.Description, "Wrong description"); + } + + [Test] + public void ModifyPage_GetContent_GetBackups_GetBackupContent_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + + DateTime dt = DateTime.Now; + + Assert.IsFalse(prov.ModifyPage(new PageInfo("Inexistent", prov, DateTime.Now), + "Title", "NUnit", dt, "Comment", "Content", null, null, SaveMode.Normal), "ModifyPage should return false"); + + Assert.IsTrue(prov.ModifyPage(p, "Title", "NUnit", dt, null, "Content", + new string[] { "keyword1", "keyword2" }, "Description", SaveMode.Normal), "ModifyPage should return true"); + + // Test null/inexistent page + Assert.IsNull(prov.GetContent(null), "GetContent should return null"); + Assert.IsNull(prov.GetContent(new PageInfo("PPP", prov, DateTime.Now)), "GetContent should return null"); + + PageContent c = prov.GetContent(p); + Assert.IsNotNull(c, "GetContent should return something"); + + AssertPageContentsAreEqual(new PageContent(p, "Title", "NUnit", dt, "", "Content", + new string[] { "keyword1", "keyword2" }, "Description"), c); + + Assert.IsTrue(prov.ModifyPage(p, "Title1", "NUnit1", dt.AddDays(1), "Comment1", "Content1", null, null, SaveMode.Backup), "ModifyPage should return true"); + + int[] baks = prov.GetBackups(p); + Assert.AreEqual(1, baks.Length, "Wrong backup content"); + + PageContent backup = prov.GetBackupContent(p, baks[0]); + Assert.IsNotNull(backup, "GetBackupContent should return something"); + AssertPageContentsAreEqual(c, backup); + } + + [Test] + public void ModifyPage_GetContent_GetBackups_GetBackupContent_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + + PageInfo p = prov.AddPage(ns.Name, "Page", DateTime.Now); + + DateTime dt = DateTime.Now; + + Assert.IsFalse(prov.ModifyPage(new PageInfo(NameTools.GetFullName(ns.Name, "Inexistent"), prov, DateTime.Now), + "Title", "NUnit", dt, "Comment", "Content", null, null, SaveMode.Normal), "ModifyPage should return false"); + + Assert.IsTrue(prov.ModifyPage(p, "Title", "NUnit", dt, null, "Content", + new string[] { "keyword1", "keyword2" }, "Description", SaveMode.Normal), "ModifyPage should return true"); + + // Test null/inexistent page + Assert.IsNull(prov.GetContent(null), "GetContent should return null"); + Assert.IsNull(prov.GetContent(new PageInfo(NameTools.GetFullName(ns.Name, "PPP"), prov, DateTime.Now)), "GetContent should return null"); + + PageContent c = prov.GetContent(p); + Assert.IsNotNull(c, "GetContent should return something"); + + AssertPageContentsAreEqual(new PageContent(p, "Title", "NUnit", dt, "", "Content", + new string[] { "keyword1", "keyword2" }, "Description"), c); + + Assert.IsTrue(prov.ModifyPage(p, "Title1", "NUnit1", dt.AddDays(1), "Comment1", "Content1", null, null, SaveMode.Backup), "ModifyPage should return true"); + + int[] baks = prov.GetBackups(p); + Assert.AreEqual(1, baks.Length, "Wrong backup content"); + + PageContent backup = prov.GetBackupContent(p, baks[0]); + Assert.IsNotNull(backup, "GetBackupContent should return something"); + AssertPageContentsAreEqual(c, backup); + } + + [Test] + public void ModifyPage_PerformSearch() { + IPagesStorageProviderV30 prov = GetProvider(); + + // Added to check that pages inserted in reverse alphabetical order work with the search engine + PageInfo p0 = prov.AddPage(null, "PagZ", DateTime.Now); + prov.ModifyPage(p0, "ZZZ", "NUnit", DateTime.Now, "", "", null, "", SaveMode.Normal); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + + DateTime dt = DateTime.Now; + + Assert.IsTrue(prov.ModifyPage(p, "TitleOld", "NUnitOld", dt, "CommentOld", "ContentOld", + new string[] { "keyword3", "keyword4" }, null, SaveMode.Normal), "ModifyPage should return true"); + Assert.IsTrue(prov.ModifyPage(p, "Title", "NUnit", dt, "Comment", "Content", + new string[] { "keyword1", "keyword2" }, null, SaveMode.Normal), "ModifyPage should return true"); + + SearchResultCollection result = prov.PerformSearch(new SearchParameters("content")); + Assert.AreEqual(1, result.Count, "Wrong search result count"); + Assert.AreEqual(PageDocument.StandardTypeTag, result[0].Document.TypeTag, "Wrong type tag"); + Assert.AreEqual(1, result[0].Matches.Count, "Wrong match count"); + Assert.AreEqual(WordLocation.Content, result[0].Matches[0].Location, "Wrong word location"); + + result = prov.PerformSearch(new SearchParameters("title")); + Assert.AreEqual(1, result.Count, "Wrong search result count"); + Assert.AreEqual(PageDocument.StandardTypeTag, result[0].Document.TypeTag, "Wrong type tag"); + Assert.AreEqual(1, result[0].Matches.Count, "Wrong match count"); + Assert.AreEqual(WordLocation.Title, result[0].Matches[0].Location, "Wrong word location"); + + result = prov.PerformSearch(new SearchParameters("keyword1")); + Assert.AreEqual(1, result.Count, "Wrong search count"); + Assert.AreEqual(PageDocument.StandardTypeTag, result[0].Document.TypeTag, "Wrong type tag"); + Assert.AreEqual(1, result[0].Matches.Count, "Wrong match count"); + Assert.AreEqual(WordLocation.Keywords, result[0].Matches[0].Location, "Wrong word location"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ModifyPage_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + prov.ModifyPage(null, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Backup); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ModifyPage_InvalidTitle(string t) { + IPagesStorageProviderV30 prov = GetProvider(); + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(p, t, "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Backup); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ModifyPage_InvalidUsername(string u) { + IPagesStorageProviderV30 prov = GetProvider(); + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", u, DateTime.Now, "Comment", "Content", null, null, SaveMode.Backup); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ModifyPage_NullContent() { + IPagesStorageProviderV30 prov = GetProvider(); + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "", null, null, null, SaveMode.Backup); + } + + [Test] + public void ModifyPage_OddCombinationsOfKeywordsAndDescription() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + DateTime dt = DateTime.Now; + + Assert.IsTrue(prov.ModifyPage(page, "Title", "NUnit", dt, "", "Content", + null, null, SaveMode.Normal), "ModifyPage should return true"); + PageContent content = prov.GetContent(page); + AssertPageContentsAreEqual(new PageContent(page, "Title", "NUnit", dt, "", "Content", + new string[0], null), content); + + Assert.IsTrue(prov.ModifyPage(page, "Title", "NUnit", dt, "", "Content", + new string[0], null, SaveMode.Normal), "ModifyPage should return true"); + content = prov.GetContent(page); + AssertPageContentsAreEqual(new PageContent(page, "Title", "NUnit", dt, "", "Content", + new string[0], null), content); + + Assert.IsTrue(prov.ModifyPage(page, "Title", "NUnit", dt, "", "Content", + new string[] { "Blah" }, null, SaveMode.Normal), "ModifyPage should return true"); + content = prov.GetContent(page); + AssertPageContentsAreEqual(new PageContent(page, "Title", "NUnit", dt, "", "Content", + new string[] { "Blah" }, null), content); + + Assert.IsTrue(prov.ModifyPage(page, "Title", "NUnit", dt, "", "Content", + null, "", SaveMode.Normal), "ModifyPage should return true"); + content = prov.GetContent(page); + AssertPageContentsAreEqual(new PageContent(page, "Title", "NUnit", dt, "", "Content", + new string[0], null), content); + + Assert.IsTrue(prov.ModifyPage(page, "Title", "NUnit", dt, "", "Content", + null, "Descr", SaveMode.Normal), "ModifyPage should return true"); + content = prov.GetContent(page); + AssertPageContentsAreEqual(new PageContent(page, "Title", "NUnit", dt, "", "Content", + new string[0], "Descr"), content); + + Assert.IsTrue(prov.ModifyPage(page, "Title", "NUnit", dt, "", "Content", + new string[] { "Blah" }, "Descr", SaveMode.Normal), "ModifyPage should return true"); + content = prov.GetContent(page); + AssertPageContentsAreEqual(new PageContent(page, "Title", "NUnit", dt, "", "Content", + new string[] { "Blah" }, "Descr"), content); + } + + [Test] + public void ModifyPage_Draft_GetDraft_DeleteDraft() { + IPagesStorageProviderV30 prov = GetProvider(); + + DateTime dt = DateTime.Now; + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(page, "TitleOld", "NUnitOld", DateTime.Now, "CommentOld", "ContentOld", new string[0], "Blah", SaveMode.Normal); + Assert.IsTrue(prov.ModifyPage(page, "Title", "NUnit", DateTime.Now, "Comment", "Content", new string[0], "", SaveMode.Draft), "ModifyPage should return true"); + Assert.IsTrue(prov.ModifyPage(page, "Title2", "NUnit", dt, "", "Content2", new string[0], "Descr", SaveMode.Draft), "ModifyPage should return true"); + + PageContent content = prov.GetDraft(page); + AssertPageContentsAreEqual(new PageContent(page, "Title2", "NUnit", dt, "", "Content2", new string[0], "Descr"), content); + + Assert.IsTrue(prov.DeleteDraft(page), "DeleteDraft should return true"); + + Assert.IsNull(prov.GetDraft(page), "GetDraft should return null"); + } + + [Test] + public void ModifyPage_Draft_RemovePage() { + IPagesStorageProviderV30 prov = GetProvider(); + + DateTime dt = DateTime.Now; + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(page, "Title", "NUnit", dt, "", "", new string[0], "", SaveMode.Normal); + prov.ModifyPage(page, "Title", "NUnit", dt, "Comment", "Content", new string[0], "", SaveMode.Draft); + + prov.RemovePage(page); + + Assert.IsNull(prov.GetDraft(page), "GetDraft should return null"); + } + + [Test] + public void ModifyPage_Draft_RenamePage_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + DateTime dt = DateTime.Now; + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(page, "Title", "NUnit", dt, "", "", new string[0], "", SaveMode.Normal); + prov.ModifyPage(page, "Title", "NUnit", dt, "Comment", "Content", new string[0], "", SaveMode.Draft); + + PageInfo newPage = prov.RenamePage(page, "NewName"); + + PageContent content = prov.GetDraft(newPage); + + AssertPageContentsAreEqual(new PageContent(newPage, "Title", "NUnit", dt, "Comment", "Content", new string[0], null), content); + } + + [Test] + public void ModifyPage_Draft_RenamePage_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + DateTime dt = DateTime.Now; + prov.AddNamespace("NS"); + PageInfo page = prov.AddPage("NS", "Page", DateTime.Now); + prov.ModifyPage(page, "Title", "NUnit", dt, "", "", new string[0], "", SaveMode.Normal); + prov.ModifyPage(page, "Title", "NUnit", dt, "Comment", "Content", new string[0], "", SaveMode.Draft); + + PageInfo newPage = prov.RenamePage(page, "NewName"); + + PageContent content = prov.GetDraft(newPage); + + AssertPageContentsAreEqual(new PageContent(newPage, "Title", "NUnit", dt, "Comment", "Content", new string[0], null), content); + } + + [Test] + public void ModifyPage_Draft_RenameNamespace() { + IPagesStorageProviderV30 prov = GetProvider(); + + DateTime dt = DateTime.Now; + NamespaceInfo ns = prov.AddNamespace("NS"); + PageInfo page = prov.AddPage("NS", "Page", dt); + prov.ModifyPage(page, "Title", "NUnit", dt, "", "", new string[0], "", SaveMode.Normal); + prov.ModifyPage(page, "Title", "NUnit", dt, "Comment", "Content", new string[0], "", SaveMode.Draft); + + NamespaceInfo ns2 = prov.RenameNamespace(ns, "NS2"); + + PageInfo page2 = new PageInfo(NameTools.GetFullName(ns2.Name, "Page"), prov, dt); + + Assert.IsNull(prov.GetDraft(page), "GetDraft should return null"); + PageContent content = prov.GetDraft(page2); + + AssertPageContentsAreEqual(new PageContent(page2, "Title", "NUnit", dt, "Comment", "Content", new string[0], null), content); + } + + [Test] + public void ModifyPage_Draft_RemoveNamespace() { + IPagesStorageProviderV30 prov = GetProvider(); + + DateTime dt = DateTime.Now; + NamespaceInfo ns = prov.AddNamespace("NS"); + PageInfo page = prov.AddPage("NS", "Page", dt); + prov.ModifyPage(page, "Title", "NUnit", dt, "", "", new string[0], "", SaveMode.Normal); + prov.ModifyPage(page, "Title", "NUnit", dt, "Comment", "Content", new string[0], "", SaveMode.Draft); + + prov.RemoveNamespace(ns); + + Assert.IsNull(prov.GetDraft(page), "GetDraft should return null"); + } + + [Test] + public void ModifyPage_Draft_MovePage_Root2Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + DateTime dt = DateTime.Now; + PageInfo page = prov.AddPage(null, "Page", dt); + prov.ModifyPage(page, "Title", "NUnit", dt, "", "", new string[0], "", SaveMode.Normal); + prov.ModifyPage(page, "Title", "NUnit", dt, "Comment", "Content", new string[0], "", SaveMode.Draft); + + NamespaceInfo ns2 = prov.AddNamespace("NS2"); + + PageInfo page2 = prov.MovePage(page, ns2, false); + + Assert.IsNull(prov.GetDraft(page), "GetDraft should return null"); + PageContent content = prov.GetDraft(page2); + + AssertPageContentsAreEqual(new PageContent(page2, "Title", "NUnit", dt, "Comment", "Content", new string[0], null), content); + } + + [Test] + public void ModifyPage_Draft_MovePage_Sub2Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + DateTime dt = DateTime.Now; + NamespaceInfo ns = prov.AddNamespace("NS"); + PageInfo page = prov.AddPage("NS", "Page", dt); + prov.ModifyPage(page, "Title", "NUnit", dt, "", "", new string[0], "", SaveMode.Normal); + prov.ModifyPage(page, "Title", "NUnit", dt, "Comment", "Content", new string[0], "", SaveMode.Draft); + + PageInfo page2 = prov.MovePage(page, null, false); + + Assert.IsNull(prov.GetDraft(page), "GetDraft should return null"); + PageContent content = prov.GetDraft(page2); + + AssertPageContentsAreEqual(new PageContent(page2, "Title", "NUnit", dt, "Comment", "Content", new string[0], null), content); + } + + [Test] + public void ModifyPage_Draft_MovePage_Sub2Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + DateTime dt = DateTime.Now; + NamespaceInfo ns = prov.AddNamespace("NS"); + PageInfo page = prov.AddPage("NS", "Page", dt); + prov.ModifyPage(page, "Title", "NUnit", dt, "", "", new string[0], "", SaveMode.Normal); + prov.ModifyPage(page, "Title", "NUnit", dt, "Comment", "Content", new string[0], "", SaveMode.Draft); + + NamespaceInfo ns2 = prov.AddNamespace("NS2"); + PageInfo page2 = prov.MovePage(page, ns2, false); + + Assert.IsNull(prov.GetDraft(page), "GetDraft should return null"); + PageContent content = prov.GetDraft(page2); + + AssertPageContentsAreEqual(new PageContent(page2, "Title", "NUnit", dt, "Comment", "Content", new string[0], null), content); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void GetDraft_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.GetDraft(null); + } + + [Test] + public void GetDraft_Inexistent() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.IsNull(prov.GetDraft(new PageInfo("Page", null, DateTime.Now)), "GetDraft should return null"); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + Assert.IsNull(prov.GetDraft(page), "GetDraft should return null"); + + prov.ModifyPage(page, "Title", "NUnit", DateTime.Now, "", "Content", new string[0], "", SaveMode.Normal); + Assert.IsNull(prov.GetDraft(page), "GetDraft should return null"); + + prov.ModifyPage(page, "Title", "NUnit", DateTime.Now, "", "Content", new string[0], "", SaveMode.Backup); + Assert.IsNull(prov.GetDraft(page), "GetDraft should return null"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void DeleteDraft_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.DeleteDraft(null); + } + + [Test] + public void DeleteDraft_Inexistent() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.IsFalse(prov.DeleteDraft(new PageInfo("Page", null, DateTime.Now)), "DeleteDraft should return false"); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + Assert.IsFalse(prov.DeleteDraft(page), "DeleteDraft should return false"); + + prov.ModifyPage(page, "Title", "NUnit", DateTime.Now, "", "Content", new string[0], "", SaveMode.Normal); + Assert.IsFalse(prov.DeleteDraft(page), "DeleteDraft should return false"); + + DateTime dt = DateTime.Now; + prov.ModifyPage(page, "Title2", "NUnit2", dt, "", "Content2", new string[0], "", SaveMode.Backup); + Assert.IsFalse(prov.DeleteDraft(page), "DeleteDraft should return false"); + + AssertPageContentsAreEqual(new PageContent(page, "Title2", "NUnit2", dt, "", "Content2", new string[0], null), + prov.GetContent(page)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void GetBackups_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + prov.GetBackups(null); + } + + [Test] + public void GetBackups_InexistentPage_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + Assert.IsNull(prov.GetBackups(new PageInfo("PPP", prov, DateTime.Now)), "GetBackups should return null"); + } + + [Test] + public void GetBackups_InexistentPage_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + NamespaceInfo ns = prov.AddNamespace("Namespace"); + Assert.IsNull(prov.GetBackups(new PageInfo(NameTools.GetFullName(ns.Name, "PPP"), prov, DateTime.Now)), "GetBackups should return null"); + } + + [Test] + public void SetBackupContent_GetBackupContent_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + + PageContent content = new PageContent(p, "Title100", "NUnit100", DateTime.Now.AddDays(-1), "Comment100", "Content100", null, null); + PageContent contentInexistent = new PageContent(new PageInfo("PPP", prov, DateTime.Now), + "Title100", "NUnit100", DateTime.Now.AddDays(-1), "Comment100", "Content100", null, null); + + Assert.IsFalse(prov.SetBackupContent(contentInexistent, 0), "SetBackupContent should return "); + + Assert.IsTrue(prov.SetBackupContent(content, 0), "SetBackupContent should return true"); + + PageContent contentOutput = prov.GetBackupContent(p, 0); + + AssertPageContentsAreEqual(content, contentOutput); + } + + [Test] + public void SetBackupContent_GetBackupContent_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("Namespace"); + + PageInfo p = prov.AddPage(ns.Name, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + + PageContent content = new PageContent(p, "Title100", "NUnit100", DateTime.Now.AddDays(-1), "Comment100", "Content100", null, null); + PageContent contentInexistent = new PageContent(new PageInfo(NameTools.GetFullName(ns.Name, "PPP"), prov, DateTime.Now), + "Title100", "NUnit100", DateTime.Now.AddDays(-1), "Comment100", "Content100", null, null); + + Assert.IsFalse(prov.SetBackupContent(contentInexistent, 0), "SetBackupContent should return "); + + Assert.IsTrue(prov.SetBackupContent(content, 0), "SetBackupContent should return true"); + + PageContent contentOutput = prov.GetBackupContent(p, 0); + + AssertPageContentsAreEqual(content, contentOutput); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void GetBackupContent_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + prov.GetBackupContent(null, 0); + } + + [Test] + public void GetBackupContent_InexistentPage_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + Assert.IsNull(prov.GetBackupContent(new PageInfo("P", prov, DateTime.Now), 0), "GetBackupContent should return null"); + } + + [Test] + public void GetBackupContent_InexistentPage_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + NamespaceInfo ns = prov.AddNamespace("Namespace"); + Assert.IsNull(prov.GetBackupContent(new PageInfo(NameTools.GetFullName(ns.Name, "P"), prov, DateTime.Now), 0), + "GetBackupContent should return null"); + } + + [Test] + public void GetBackupContent_InexistentRevision_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + + Assert.IsNull(prov.GetBackupContent(p, 1), "GetBackupContent should return null"); + } + + [Test] + public void GetBackupContent_InexistentRevision_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo p = prov.AddPage(ns.Name, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + + Assert.IsNull(prov.GetBackupContent(p, 1), "GetBackupContent should return null"); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void GetBackupContent_InvalidRevision() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + + prov.GetBackupContent(p, -1); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetBackupContent_NullContent() { + IPagesStorageProviderV30 prov = GetProvider(); + prov.SetBackupContent(null, 0); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void SetBackupContent_InvalidRevision() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + + prov.SetBackupContent(new PageContent(p, "Title100", "NUnit100", DateTime.Now, "Comment100", "Content100", null, null), -1); + } + + [Test] + public void SetBackupContent_InexistentRevision_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + + PageContent testContent = new PageContent(p, "Title100", "NUnit100", DateTime.Now, "Comment100", "Content100", null, null); + + prov.SetBackupContent(testContent, 5); + + PageContent backup = prov.GetBackupContent(p, 5); + + AssertPageContentsAreEqual(testContent, backup); + + int[] baks = prov.GetBackups(p); + Assert.AreEqual(2, baks.Length, "Wrong backup count"); + Assert.AreEqual(0, baks[0], "Wrong backup number"); + Assert.AreEqual(5, baks[1], "Wrong backup number"); + } + + [Test] + public void SetBackupContent_InexistentRevision_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo p = prov.AddPage(ns.Name, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + + PageContent testContent = new PageContent(p, "Title100", "NUnit100", DateTime.Now, "Comment100", "Content100", null, null); + + prov.SetBackupContent(testContent, 5); + + PageContent backup = prov.GetBackupContent(p, 5); + + AssertPageContentsAreEqual(testContent, backup); + + int[] baks = prov.GetBackups(p); + Assert.AreEqual(2, baks.Length, "Wrong backup count"); + Assert.AreEqual(0, baks[0], "Wrong backup number"); + Assert.AreEqual(5, baks[1], "Wrong backup number"); + } + + [Test] + public void RenamePage_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.AddPage(null, "Page2", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + CategoryInfo cat1 = prov.AddCategory(null, "Cat1"); + CategoryInfo cat2 = prov.AddCategory(null, "Cat2"); + CategoryInfo cat3 = prov.AddCategory(null, "Cat3"); + prov.RebindPage(p, new string[] { cat1.FullName, cat3.FullName }); + PageContent content = prov.GetContent(p); + + Assert.IsTrue(prov.AddMessage(p, "NUnit", "Test", DateTime.Now, "Test message.", -1), "AddMessage should return true"); + + Assert.IsNull(prov.RenamePage(new PageInfo("Inexistent", prov, DateTime.Now), "RenamedPage"), "RenamePage should return null"); + Assert.IsNull(prov.RenamePage(p, "Page2"), "RenamePage should return null"); + + PageInfo renamed = prov.RenamePage(p, "Renamed"); + Assert.IsNotNull(renamed, "RenamePage should return something"); + AssertPageInfosAreEqual(new PageInfo("Renamed", prov, p.CreationDateTime), renamed, true); + + Assert.IsNull(prov.GetContent(p), "GetContent should return null"); + + AssertPageContentsAreEqual(new PageContent(renamed, content.Title, content.User, content.LastModified, + content.Comment, content.Content, null, null), + prov.GetContent(renamed)); + + Assert.IsNull(prov.GetBackups(p), "GetBackups should return null"); + Assert.AreEqual(1, prov.GetBackups(renamed).Length, "Wrong backup count"); + + CategoryInfo[] categories = prov.GetCategories(null); + Assert.AreEqual(3, categories.Length, "Wrong category count"); + Array.Sort(categories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + + Assert.AreEqual(1, categories[0].Pages.Length, "Wrong page count"); + Assert.AreEqual(renamed.FullName, categories[0].Pages[0], "Wrong page"); + Assert.AreEqual(0, categories[1].Pages.Length, "Wrong page count"); + Assert.AreEqual(1, categories[2].Pages.Length, "Wrong page count"); + Assert.AreEqual(renamed.FullName, categories[2].Pages[0], "Wrong page"); + + Assert.IsNull(prov.GetMessages(p), "GetMessages should return null"); + Message[] messages = prov.GetMessages(renamed); + Assert.AreEqual(1, messages.Length, "Wrong message count"); + Assert.AreEqual("Test", messages[0].Subject, "Wrong message subject"); + + } + + [Test] + public void RenamePage_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo p = prov.AddPage(ns.Name, "Page", DateTime.Now); + prov.AddPage(ns.Name, "Page2", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + CategoryInfo cat1 = prov.AddCategory(ns.Name, "Cat1"); + CategoryInfo cat2 = prov.AddCategory(ns.Name, "Cat2"); + CategoryInfo cat3 = prov.AddCategory(ns.Name, "Cat3"); + prov.RebindPage(p, new string[] { cat1.FullName, cat3.FullName }); + PageContent content = prov.GetContent(p); + + Assert.IsTrue(prov.AddMessage(p, "NUnit", "Test", DateTime.Now, "Test message.", -1), "AddMessage should return true"); + + Assert.IsNull(prov.RenamePage(new PageInfo(NameTools.GetFullName(ns.Name, "Inexistent"), prov, DateTime.Now), "RenamedPage"), "RenamePage should return null"); + Assert.IsNull(prov.RenamePage(p, "Page2"), "RenamePage should return null"); + + PageInfo renamed = prov.RenamePage(p, "Renamed"); + Assert.IsNotNull(renamed, "RenamePage should return something"); + AssertPageInfosAreEqual(new PageInfo(NameTools.GetFullName(ns.Name, "Renamed"), prov, p.CreationDateTime), renamed, true); + + Assert.IsNull(prov.GetContent(p), "GetContent should return null"); + + AssertPageContentsAreEqual(new PageContent(renamed, content.Title, content.User, content.LastModified, content.Comment, content.Content, null, null), + prov.GetContent(renamed)); + + Assert.IsNull(prov.GetBackups(p), "GetBackups should return null"); + Assert.AreEqual(1, prov.GetBackups(renamed).Length, "Wrong backup count"); + + CategoryInfo[] categories = prov.GetCategories(ns); + Assert.AreEqual(3, categories.Length, "Wrong category count"); + Array.Sort(categories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + + Assert.AreEqual(1, categories[0].Pages.Length, "Wrong page count"); + Assert.AreEqual(renamed.FullName, categories[0].Pages[0], "Wrong page"); + Assert.AreEqual(0, categories[1].Pages.Length, "Wrong page count"); + Assert.AreEqual(1, categories[2].Pages.Length, "Wrong page count"); + Assert.AreEqual(renamed.FullName, categories[2].Pages[0], "Wrong page"); + + Assert.IsNull(prov.GetMessages(p), "GetMessages should return null"); + Message[] messages = prov.GetMessages(renamed); + Assert.AreEqual(1, messages.Length, "Wrong message count"); + Assert.AreEqual("Test", messages[0].Subject, "Wrong message subject"); + } + + [Test] + public void RenamePage_DefaultPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo page = prov.AddPage("NS", "MainPage", DateTime.Now); + + prov.SetNamespaceDefaultPage(ns, page); + + Assert.IsNull(prov.RenamePage(page, "NewName"), "Cannot rename the default page"); + } + + [Test] + public void RenamePage_PerformSearch() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.AddMessage(p, "NUnit", "Message1", DateTime.Now, "Body1", -1); + prov.AddMessage(p, "NUnit", "Message2", DateTime.Now, "Body2", prov.GetMessages(p)[0].ID); + + DateTime dt = DateTime.Now; + + Assert.IsTrue(prov.ModifyPage(p, "TitleOld", "NUnitOld", dt, "CommentOld", "ContentOld", null, null, SaveMode.Normal), + "ModifyPage should return true"); + Assert.IsTrue(prov.ModifyPage(p, "Title", "NUnit", dt, "Comment", "Content", null, null, SaveMode.Backup), + "ModifyPage should return true"); + prov.RenamePage(p, "Page2"); + + SearchResultCollection results = prov.PerformSearch(new SearchParameters("content")); + + Assert.AreEqual(1, results.Count, "Wrong search result count"); + Assert.AreEqual("Page2", PageDocument.GetPageName(results[0].Document.Name), "Wrong document name"); + + results = prov.PerformSearch(new SearchParameters("title")); + + Assert.AreEqual(1, results.Count, "Wrong search result count"); + Assert.AreEqual("Page2", PageDocument.GetPageName(results[0].Document.Name), "Wrong document name"); + + results = prov.PerformSearch(new SearchParameters("message1 body1 message2 body2")); + Assert.AreEqual(2, results.Count, "Wrong result count"); + Assert.AreEqual(2, results[0].Matches.Count, "Wrong match count"); + Assert.AreEqual(2, results[1].Matches.Count, "Wrong match count"); + + string page; + int id; + + MessageDocument.GetMessageDetails(results[0].Document.Name, out page, out id); + Assert.AreEqual("Page2", page, "Wrong document name"); + + MessageDocument.GetMessageDetails(results[1].Document.Name, out page, out id); + Assert.AreEqual("Page2", page, "Wrong document name"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RenamePage_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + prov.RenamePage(null, "New Name"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RenamePage_InvalidName(string n) { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + + prov.RenamePage(p, n); + } + + [Test] + public void RollbackPage_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", new string[] { "kold", "k2old" }, "DescrOld", SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", new string[] { "k1", "k2" }, "Descr", SaveMode.Backup); + + PageContent content = prov.GetContent(p); + + prov.ModifyPage(p, "Title2", "NUnit2", DateTime.Now, "Comment2", "Content2", new string[] { "k4", "k5" }, "DescrNew", SaveMode.Backup); + + Assert.AreEqual(2, prov.GetBackups(p).Length, "Wrong backup count"); + + Assert.IsFalse(prov.RollbackPage(new PageInfo("Inexistent", prov, DateTime.Now), 0), "RollbackPage should return false"); + Assert.IsFalse(prov.RollbackPage(p, 5), "RollbackPage should return false"); + + Assert.IsTrue(prov.RollbackPage(p, 1), "RollbackPage should return true"); + + Assert.AreEqual(3, prov.GetBackups(p).Length, "Wrong backup count"); + + AssertPageContentsAreEqual(content, prov.GetContent(p)); + } + + [Test] + public void RollbackPage_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo p = prov.AddPage(ns.Name, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", new string[] { "kold", "k2old" }, "DescrOld", SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", new string[] { "k1", "k2" }, "Descr", SaveMode.Backup); + + PageContent content = prov.GetContent(p); + + prov.ModifyPage(p, "Title2", "NUnit2", DateTime.Now, "Comment2", "Content2", new string[] { "k4", "k5" }, "DescrNew", SaveMode.Backup); + + Assert.AreEqual(2, prov.GetBackups(p).Length, "Wrong backup count"); + + Assert.IsFalse(prov.RollbackPage(new PageInfo(NameTools.GetFullName(ns.Name, "Inexistent"), prov, DateTime.Now), 0), + "RollbackPage should return false"); + + Assert.IsFalse(prov.RollbackPage(p, 5), "RollbackPage should return false"); + + Assert.IsTrue(prov.RollbackPage(p, 1), "RollbackPage should return true"); + + Assert.AreEqual(3, prov.GetBackups(p).Length, "Wrong backup count"); + + AssertPageContentsAreEqual(content, prov.GetContent(p)); + } + + [Test] + public void RollbackPage_PerformSearch() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + + DateTime dt = DateTime.Now; + + Assert.IsTrue(prov.ModifyPage(p, "Title", "NUnit", dt, "Comment", "Content", new string[] { "k1" }, "descr", SaveMode.Backup), "ModifyPage should return true"); + Assert.IsTrue(prov.ModifyPage(p, "TitleMod", "NUnit", dt, "Comment", "ContentMod", new string[] { "k2" }, "descr2", SaveMode.Backup), "ModifyPage should return true"); + // Depending on the implementation, providers might start backups numbers from 0 or 1, or even don't perform a backup if the page has no content (as in this case) + int[] baks = prov.GetBackups(p); + prov.RollbackPage(p, baks[baks.Length - 1]); + + Assert.AreEqual(0, prov.PerformSearch(new SearchParameters("contentmod")).Count, "Wrong search result count"); + Assert.AreEqual(1, prov.PerformSearch(new SearchParameters("content")).Count, "Wrong search result count"); + + Assert.AreEqual(0, prov.PerformSearch(new SearchParameters("k2")).Count, "Wrong search result count"); + Assert.AreEqual(1, prov.PerformSearch(new SearchParameters("k1")).Count, "Wrong search result count"); + + Assert.AreEqual(0, prov.PerformSearch(new SearchParameters("titlemod")).Count, "Wrong search result count"); + Assert.AreEqual(1, prov.PerformSearch(new SearchParameters("title")).Count, "Wrong search result count"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RollbackPage_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.RollbackPage(null, 0); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void RollbackPage_InvalidRevision() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + + prov.RollbackPage(p, -1); + } + + [Test] + public void DeleteBackups_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + prov.ModifyPage(p, "Title2", "NUnit2", DateTime.Now, "Comment2", "Content2", new string[] { "k1", "k2" }, "Descr", SaveMode.Backup); + + PageContent content = prov.GetContent(p); + + Assert.IsFalse(prov.DeleteBackups(new PageInfo("Inexistent", prov, DateTime.Now), -1), "DeleteBackups should return false"); + + Assert.IsTrue(prov.DeleteBackups(p, 5), "DeleteBackups should return true"); + Assert.AreEqual(2, prov.GetBackups(p).Length, "Wrong backup count"); + AssertPageContentsAreEqual(content, prov.GetContent(p)); + + Assert.IsTrue(prov.DeleteBackups(p, 0), "DeleteBackups should return true"); + Assert.AreEqual(1, prov.GetBackups(p).Length, "Wrong backup count"); + AssertPageContentsAreEqual(content, prov.GetContent(p)); + + Assert.IsTrue(prov.DeleteBackups(p, -1), "DeleteBackups should return true"); + Assert.AreEqual(0, prov.GetBackups(p).Length, "Wrong backup count"); + AssertPageContentsAreEqual(content, prov.GetContent(p)); + } + + [Test] + public void DeleteBackups_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo p = prov.AddPage(ns.Name, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + prov.ModifyPage(p, "Title2", "NUnit2", DateTime.Now, "Comment2", "Content2", new string[] { "k1", "k2" }, "Descr", SaveMode.Backup); + + PageContent content = prov.GetContent(p); + + Assert.IsFalse(prov.DeleteBackups(new PageInfo(NameTools.GetFullName(ns.Name, "Inexistent"), + prov, DateTime.Now), -1), "DeleteBackups should return false"); + + Assert.IsTrue(prov.DeleteBackups(p, 5), "DeleteBackups should return true"); + Assert.AreEqual(2, prov.GetBackups(p).Length, "Wrong backup count"); + AssertPageContentsAreEqual(content, prov.GetContent(p)); + + Assert.IsTrue(prov.DeleteBackups(p, 0), "DeleteBackups should return true"); + Assert.AreEqual(1, prov.GetBackups(p).Length, "Wrong backup count"); + AssertPageContentsAreEqual(content, prov.GetContent(p)); + + Assert.IsTrue(prov.DeleteBackups(p, -1), "DeleteBackups should return true"); + Assert.AreEqual(0, prov.GetBackups(p).Length, "Wrong backup count"); + AssertPageContentsAreEqual(content, prov.GetContent(p)); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void DeleteBackups_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.DeleteBackups(null, -1); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void DeleteBackups_InvalidRevision() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + prov.ModifyPage(p, "Title1", "NUnit1", DateTime.Now, "Comment1", "Content1", null, null, SaveMode.Backup); + prov.ModifyPage(p, "Title2", "NUnit2", DateTime.Now, "Comment2", "Content2", null, null, SaveMode.Backup); + + prov.DeleteBackups(p, -2); + } + + [Test] + public void RemovePage_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + + Assert.IsFalse(prov.RemovePage(new PageInfo("Inexistent", prov, DateTime.Now)), + "RemovePage should return false"); + + Assert.IsTrue(prov.RemovePage(p), "RemovePage should return true"); + + Assert.AreEqual(0, prov.GetPages(null).Length, "Wrong page count"); + Assert.IsNull(prov.GetContent(p), "GetContent should return null"); + Assert.IsNull(prov.GetBackups(p), "GetBackups should return null"); + } + + [Test] + public void RemovePage_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo p = prov.AddPage(ns.Name, "Page", DateTime.Now); + prov.ModifyPage(p, "Title", "NUnit", DateTime.Now, "Comment", "Content", null, null, SaveMode.Normal); + + Assert.IsFalse(prov.RemovePage(new PageInfo(NameTools.GetFullName(ns.Name, "Inexistent"), + prov, DateTime.Now)), "RemovePage should return false"); + + Assert.IsTrue(prov.RemovePage(p), "RemovePage should return true"); + + Assert.AreEqual(0, prov.GetPages(ns).Length, "Wrong page count"); + Assert.IsNull(prov.GetContent(p), "GetContent should return null"); + Assert.IsNull(prov.GetBackups(p), "GetBackups should return null"); + } + + [Test] + public void RemovePage_DefaultPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo page = prov.AddPage("NS", "MainPage", DateTime.Now); + + prov.SetNamespaceDefaultPage(ns, page); + + Assert.IsFalse(prov.RemovePage(page), "Cannot remove default page"); + } + + [Test] + public void RemovePage_PerformSearch() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo p = prov.AddPage(null, "Page", DateTime.Now); + + prov.AddMessage(p, "NUnit", "Test1", DateTime.Now, "Body1", -1); + prov.AddMessage(p, "NUnit", "Test2", DateTime.Now, "Body2", prov.GetMessages(p)[0].ID); + + DateTime dt = DateTime.Now; + + Assert.IsTrue(prov.ModifyPage(p, "Title", "NUnit", dt, "Comment", "Content", null, null, SaveMode.Normal), + "ModifyPage should return true"); + + prov.RemovePage(p); + + Assert.AreEqual(0, prov.PerformSearch(new SearchParameters("content")).Count, "Wrong search result count"); + Assert.AreEqual(0, prov.PerformSearch(new SearchParameters("title")).Count, "Wrong search result count"); + + Assert.AreEqual(0, prov.PerformSearch(new SearchParameters("test1 test2 body1 body2")).Count, "Wrong result count"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemovePage_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.RemovePage(null); + } + + [Test] + public void RebindPage_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + CategoryInfo cat1 = prov.AddCategory(null, "Cat1"); + CategoryInfo cat2 = prov.AddCategory(null, "Cat2"); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + Assert.IsFalse(prov.RebindPage(new PageInfo("Inexistent", prov, DateTime.Now), new string[] { "Cat1" }), "Rebind should return false"); + + Assert.IsTrue(prov.RebindPage(page, new string[] { "Cat1" }), "Rebind should return true"); + + CategoryInfo[] categories = prov.GetCategories(null); + Array.Sort(categories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + Assert.AreEqual(1, categories[0].Pages.Length, "Wrong page count"); + Assert.AreEqual("Page", categories[0].Pages[0], "Wrong page name"); + + Assert.IsTrue(prov.RebindPage(page, new string[0]), "Rebind should return true"); + + categories = prov.GetCategories(null); + Array.Sort(categories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + Assert.AreEqual(0, categories[0].Pages.Length, "Wrong page count"); + } + + [Test] + public void RebindPage_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + CategoryInfo cat1 = prov.AddCategory(ns.Name, "Cat1"); + CategoryInfo cat2 = prov.AddCategory(ns.Name, "Cat2"); + + PageInfo page = prov.AddPage(ns.Name, "Page", DateTime.Now); + + Assert.IsFalse(prov.RebindPage(new PageInfo(NameTools.GetFullName(ns.Name, "Inexistent"), prov, DateTime.Now), new string[] { "Cat1" }), "Rebind should return false"); + + Assert.IsTrue(prov.RebindPage(page, new string[] { cat1.FullName }), "Rebind should return true"); + + CategoryInfo[] categories = prov.GetCategories(ns); + Array.Sort(categories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + Assert.AreEqual(1, categories[0].Pages.Length, "Wrong page count"); + Assert.AreEqual(page.FullName, categories[0].Pages[0], "Wrong page name"); + + Assert.IsTrue(prov.RebindPage(page, new string[0]), "Rebind should return true"); + + categories = prov.GetCategories(ns); + Array.Sort(categories, delegate(CategoryInfo x, CategoryInfo y) { return x.FullName.CompareTo(y.FullName); }); + Assert.AreEqual(0, categories[0].Pages.Length, "Wrong page count"); + } + + [Test] + public void RebindPage_SameNames() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + CategoryInfo cat1 = prov.AddCategory(null, "Category"); + CategoryInfo cat2 = prov.AddCategory(ns.Name, "Category"); + + PageInfo page1 = prov.AddPage(null, "Page", DateTime.Now); + PageInfo page2 = prov.AddPage(ns.Name, "Page", DateTime.Now); + + bool done1 = prov.RebindPage(page1, new string[] { cat1.FullName }); + bool done2 = prov.RebindPage(page2, new string[] { cat2.FullName }); + + CategoryInfo[] categories1 = prov.GetCategories(null); + Assert.AreEqual(1, categories1.Length, "Wrong category count"); + Assert.AreEqual(cat1.FullName, categories1[0].FullName, "Wrong category"); + Assert.AreEqual(1, categories1[0].Pages.Length, "Wrong page count"); + Assert.AreEqual(page1.FullName, categories1[0].Pages[0], "Wrong page"); + + CategoryInfo[] categories2 = prov.GetCategories(ns); + Assert.AreEqual(1, categories2.Length, "Wrong length"); + Assert.AreEqual(cat2.FullName, categories2[0].FullName, "Wrong category"); + Assert.AreEqual(1, categories2[0].Pages.Length, "Wrong page count"); + Assert.AreEqual(page2.FullName, categories2[0].Pages[0], "Wrong page"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RebindPage_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.RebindPage(null, new string[0]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RebindPage_NullCategories() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + prov.RebindPage(page, null); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RebindPage_InvalidCategoryElement(string e) { + IPagesStorageProviderV30 prov = GetProvider(); + + CategoryInfo cat1 = prov.AddCategory(null, "Cat1"); + CategoryInfo cat2 = prov.AddCategory(null, "Cat2"); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + prov.RebindPage(page, new string[] { "Cat1", e }); + } + + [Test] + public void RebindPage_InexistentCategoryElement_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + CategoryInfo cat1 = prov.AddCategory(null, "Cat1"); + CategoryInfo cat2 = prov.AddCategory(null, "Cat2"); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + Assert.IsFalse(prov.RebindPage(page, new string[] { "Cat1", "Cat222" }), "Rebind should return false"); + } + + [Test] + public void RebindPage_InexistentCategoryElement_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + CategoryInfo cat1 = prov.AddCategory(ns.Name, "Cat1"); + CategoryInfo cat2 = prov.AddCategory(ns.Name, "Cat2"); + + PageInfo page = prov.AddPage(ns.Name, "Page", DateTime.Now); + + Assert.IsFalse(prov.RebindPage(page, new string[] { "Cat1", "Cat222" }), "Rebind should return false"); + } + + [Test] + public void BulkStoreMessages_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.IsFalse(prov.BulkStoreMessages(new PageInfo("Inexistent", prov, DateTime.Now), new Message[0]), "BulkStoreMessages should return false"); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + prov.AddMessage(page, "NUnit", "Test", DateTime.Now, "Message", -1); + prov.AddMessage(page, "NUnit", "Test-1", DateTime.Now, "Message-1", prov.GetMessages(page)[0].ID); + prov.AddMessage(page, "NUnit", "Test400", DateTime.Now, "Message400", -1); + prov.AddMessage(page, "NUnit", "Test500", DateTime.Now, "Message500", -1); + + DateTime dt = DateTime.Now; + + List newMessages = new List(); + newMessages.Add(new Message(1, "NUnit", "New1", dt, "Body1")); + newMessages[0].Replies = new Message[] { new Message(2, "NUnit", "New11", dt.AddDays(2), "Body11") }; + newMessages.Add(new Message(3, "NUnit", "New2", dt.AddDays(1), "Body2")); + + Assert.IsTrue(prov.BulkStoreMessages(page, newMessages.ToArray()), "BulkStoreMessages should return true"); + + List result = new List(prov.GetMessages(page)); + + Assert.AreEqual(2, result.Count, "Wrong root message count"); + Assert.AreEqual(1, result[0].Replies.Length, "Wrong reply count"); + + Assert.AreEqual(1, result[0].ID, "Wrong ID"); + Assert.AreEqual("NUnit", result[0].Username, "Wrong username"); + Assert.AreEqual("New1", result[0].Subject, "Wrong subject"); + Tools.AssertDateTimesAreEqual(dt, result[0].DateTime); + Assert.AreEqual("Body1", result[0].Body, "Wrong body"); + + Assert.AreEqual(2, result[0].Replies[0].ID, "Wrong ID"); + Assert.AreEqual("NUnit", result[0].Replies[0].Username, "Wrong username"); + Assert.AreEqual("New11", result[0].Replies[0].Subject, "Wrong subject"); + Tools.AssertDateTimesAreEqual(dt.AddDays(2), result[0].Replies[0].DateTime); + Assert.AreEqual("Body11", result[0].Replies[0].Body, "Wrong body"); + + Assert.AreEqual(3, result[1].ID, "Wrong ID"); + Assert.AreEqual("NUnit", result[1].Username, "Wrong username"); + Assert.AreEqual("New2", result[1].Subject, "Wrong subject"); + Tools.AssertDateTimesAreEqual(dt.AddDays(1), result[1].DateTime); + Assert.AreEqual("Body2", result[1].Body, "Wrong body"); + } + + [Test] + public void BulkStoreMessages_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + Assert.IsFalse(prov.BulkStoreMessages(new PageInfo(NameTools.GetFullName(ns.Name, "Inexistent"), + prov, DateTime.Now), new Message[0]), "BulkStoreMessages should return false"); + + PageInfo page = prov.AddPage(ns.Name, "Page", DateTime.Now); + + prov.AddMessage(page, "NUnit", "Test", DateTime.Now, "Message", -1); + prov.AddMessage(page, "NUnit", "Test-1", DateTime.Now, "Message-1", prov.GetMessages(page)[0].ID); + prov.AddMessage(page, "NUnit", "Test400", DateTime.Now, "Message400", -1); + prov.AddMessage(page, "NUnit", "Test500", DateTime.Now, "Message500", -1); + + DateTime dt = DateTime.Now; + + List newMessages = new List(); + newMessages.Add(new Message(1, "NUnit", "New1", dt, "Body1")); + newMessages[0].Replies = new Message[] { new Message(2, "NUnit", "New11", dt.AddDays(2), "Body11") }; + newMessages.Add(new Message(3, "NUnit", "New2", dt.AddDays(1), "Body2")); + + Assert.IsTrue(prov.BulkStoreMessages(page, newMessages.ToArray()), "BulkStoreMessages should return true"); + + List result = new List(prov.GetMessages(page)); + + Assert.AreEqual(2, result.Count, "Wrong root message count"); + Assert.AreEqual(1, result[0].Replies.Length, "Wrong reply count"); + + Assert.AreEqual(1, result[0].ID, "Wrong ID"); + Assert.AreEqual("NUnit", result[0].Username, "Wrong username"); + Assert.AreEqual("New1", result[0].Subject, "Wrong subject"); + Tools.AssertDateTimesAreEqual(dt, result[0].DateTime); + Assert.AreEqual("Body1", result[0].Body, "Wrong body"); + + Assert.AreEqual(2, result[0].Replies[0].ID, "Wrong ID"); + Assert.AreEqual("NUnit", result[0].Replies[0].Username, "Wrong username"); + Assert.AreEqual("New11", result[0].Replies[0].Subject, "Wrong subject"); + Tools.AssertDateTimesAreEqual(dt.AddDays(2), result[0].Replies[0].DateTime); + Assert.AreEqual("Body11", result[0].Replies[0].Body, "Wrong body"); + + Assert.AreEqual(3, result[1].ID, "Wrong ID"); + Assert.AreEqual("NUnit", result[1].Username, "Wrong username"); + Assert.AreEqual("New2", result[1].Subject, "Wrong subject"); + Tools.AssertDateTimesAreEqual(dt.AddDays(1), result[1].DateTime); + Assert.AreEqual("Body2", result[1].Body, "Wrong body"); + } + + [Test] + public void BulkStoreMessages_DuplicateID() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + DateTime dt = DateTime.Now; + + List newMessages = new List(); + newMessages.Add(new Message(1, "NUnit", "New1", dt, "Body1")); + newMessages[0].Replies = new Message[] { new Message(1, "NUnit", "New11", dt.AddDays(2), "Body11") }; + newMessages.Add(new Message(3, "NUnit", "New2", dt.AddDays(1), "Body2")); + + Assert.IsFalse(prov.BulkStoreMessages(page, newMessages.ToArray()), "BulkStoreMessages should return false"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void BulkStoreMessages_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + prov.BulkStoreMessages(null, new Message[0]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void BulkStoreMessages_NullMessages() { + IPagesStorageProviderV30 prov = GetProvider(); + prov.BulkStoreMessages(new PageInfo("Page", prov, DateTime.Now), null); + } + + [Test] + public void BulkStoreMessages_PerformSearch() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + prov.AddMessage(page, "NUnit", "Blah", DateTime.Now, "Blah", -1); + + List newMessages = new List(); + newMessages.Add(new Message(1, "NUnit", "New1", DateTime.Now, "Body1")); + newMessages[0].Replies = new Message[] { new Message(2, "NUnit", "New11", DateTime.Now, "Body11") }; + newMessages.Add(new Message(3, "NUnit", "New2", DateTime.Now, "Body2")); + + prov.BulkStoreMessages(page, newMessages.ToArray()); + + SearchResultCollection result = prov.PerformSearch(new SearchParameters("new1 new11 new2 blah")); + Assert.AreEqual(3, result.Count, "Wrong result count"); + foreach(SearchResult res in result) { + foreach(WordInfo info in res.Matches) { + Assert.AreNotEqual("blah", info.Text, "Invalid search macth"); + } + } + } + + [Test] + public void AddMessage_GetMessages_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + Assert.AreEqual(-1, prov.GetMessageCount(new PageInfo("Inexistent", prov, DateTime.Now)), "GetMessageCount should return -1"); + Assert.IsNull(prov.GetMessages(new PageInfo("Inexistent", prov, DateTime.Now)), "GetMessages should return null"); + + Assert.AreEqual(0, prov.GetMessageCount(page), "Wrong initial message count"); + Assert.AreEqual(0, prov.GetMessages(page).Length, "Wrong initial message count"); + + Assert.IsFalse(prov.AddMessage(new PageInfo("Inexistent", prov, DateTime.Now), "NUnit", "Subject", DateTime.Now, "Body", -1), "AddMessage should return false"); + + DateTime dt = DateTime.Now; + + Assert.IsTrue(prov.AddMessage(page, "NUnit", "Subject", dt, "Body", -1), "AddMessage should return true"); + Assert.AreEqual(1, prov.GetMessageCount(page), "Wrong message count"); + Assert.IsTrue(prov.AddMessage(page, "NUnit1", "Subject1", dt.AddDays(1), "Body1", prov.GetMessages(page)[0].ID), "AddMessage should return true"); + Assert.AreEqual(2, prov.GetMessageCount(page), "Wrong message count"); + + Message[] messages = prov.GetMessages(page); + + Assert.AreEqual(1, messages.Length, "Wrong message count"); + Assert.AreEqual("NUnit", messages[0].Username, "Wrong username"); + Assert.AreEqual("Subject", messages[0].Subject, "Wrong subject"); + Tools.AssertDateTimesAreEqual(dt, messages[0].DateTime); + Assert.AreEqual("Body", messages[0].Body, "Wrong body"); + + messages = messages[0].Replies; + Assert.AreEqual(1, messages.Length, "Wrong reply count"); + Assert.AreEqual(0, messages[0].Replies.Length, "Wrong reply count"); + + Assert.AreEqual("NUnit1", messages[0].Username, "Wrong username"); + Assert.AreEqual("Subject1", messages[0].Subject, "Wrong subject"); + Tools.AssertDateTimesAreEqual(dt.AddDays(1), messages[0].DateTime); + Assert.AreEqual("Body1", messages[0].Body, "Wrong body"); + } + + [Test] + public void AddMessage_GetMessages_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo page = prov.AddPage(ns.Name, "Page", DateTime.Now); + + Assert.AreEqual(-1, prov.GetMessageCount(new PageInfo(NameTools.GetFullName(ns.Name, "Inexistent"), + prov, DateTime.Now)), "GetMessageCount should return -1"); + + Assert.IsNull(prov.GetMessages(new PageInfo(NameTools.GetFullName(ns.Name, "Inexistent"), + prov, DateTime.Now)), "GetMessages should return null"); + + Assert.AreEqual(0, prov.GetMessageCount(page), "Wrong initial message count"); + Assert.AreEqual(0, prov.GetMessages(page).Length, "Wrong initial message count"); + + Assert.IsFalse(prov.AddMessage(new PageInfo(NameTools.GetFullName(ns.Name, "Inexistent"), + prov, DateTime.Now), "NUnit", "Subject", DateTime.Now, "Body", -1), "AddMessage should return false"); + + DateTime dt = DateTime.Now; + + Assert.IsTrue(prov.AddMessage(page, "NUnit", "Subject", dt, "Body", -1), "AddMessage should return true"); + Assert.AreEqual(1, prov.GetMessageCount(page), "Wrong message count"); + Assert.IsTrue(prov.AddMessage(page, "NUnit1", "Subject1", dt.AddDays(1), "Body1", prov.GetMessages(page)[0].ID), "AddMessage should return true"); + Assert.AreEqual(2, prov.GetMessageCount(page), "Wrong message count"); + + Message[] messages = prov.GetMessages(page); + + Assert.AreEqual(1, messages.Length, "Wrong message count"); + Assert.AreEqual("NUnit", messages[0].Username, "Wrong username"); + Assert.AreEqual("Subject", messages[0].Subject, "Wrong subject"); + Tools.AssertDateTimesAreEqual(dt, messages[0].DateTime); + Assert.AreEqual("Body", messages[0].Body, "Wrong body"); + + messages = messages[0].Replies; + Assert.AreEqual(1, messages.Length, "Wrong reply count"); + Assert.AreEqual(0, messages[0].Replies.Length, "Wrong reply count"); + + Assert.AreEqual("NUnit1", messages[0].Username, "Wrong username"); + Assert.AreEqual("Subject1", messages[0].Subject, "Wrong subject"); + Tools.AssertDateTimesAreEqual(dt.AddDays(1), messages[0].DateTime); + Assert.AreEqual("Body1", messages[0].Body, "Wrong body"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void GetMessages_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.GetMessages(null); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void GetMessageCount_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.GetMessageCount(null); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void AddMessage_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.AddMessage(null, "NUnit", "Subject", DateTime.Now, "Body", -1); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AddMessage_InvalidUsername(string u) { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + prov.AddMessage(page, u, "Subject", DateTime.Now, "Body", -1); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AddMessage_InvalidSubject(string s) { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + prov.AddMessage(page, "NUnit", s, DateTime.Now, "Body", -1); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void AddMessage_NullBody() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + prov.AddMessage(page, "NUnit", "Subject", DateTime.Now, null, -1); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddMessage_InvalidParent() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + prov.AddMessage(page, "NUnit", "Subject", DateTime.Now, "Body", -2); + } + + [Test] + public void AddMessage_InexistentParent() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + Assert.IsFalse(prov.AddMessage(page, "NUnit", "Subject", DateTime.Now, "Body", 5), "AddMessage should return false"); + } + + [Test] + public void AddMessage_PerformSearch() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + prov.AddMessage(page, "NUnit", "Message", DateTime.Now, "Blah, Test.", -1); + prov.AddMessage(page, "NUnit", "Re: Message2", DateTime.Now, "Dummy.", prov.GetMessages(page)[0].ID); + + SearchResultCollection result = prov.PerformSearch(new SearchParameters("dummy message")); + Assert.AreEqual(2, result.Count, "Wrong result count"); + + bool found1 = false, found2 = false; + foreach(SearchResult res in result) { + Assert.AreEqual(MessageDocument.StandardTypeTag, res.Document.TypeTag, "Wrong type tag"); + if(res.Matches[0].Text == "dummy") found1 = true; + if(res.Matches[0].Text == "message") found2 = true; + } + + Assert.IsTrue(found1, "First word not found"); + Assert.IsTrue(found2, "Second word not found"); + } + + [Test] + public void RemoveMessage_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + // Subject0 + // Subject00 + // Subject1 + // Subject11 + + prov.AddMessage(page, "NUnit0", "Subject0", DateTime.Now, "Body0", -1); + prov.AddMessage(page, "NUnit00", "Subject00", DateTime.Now.AddHours(1), "Body00", prov.GetMessages(page)[0].ID); + prov.AddMessage(page, "NUnit1", "Subject1", DateTime.Now.AddHours(2), "Body1", -1); + prov.AddMessage(page, "NUnit11", "Subject11", DateTime.Now.AddHours(3), "Body11", prov.GetMessages(page)[1].ID); + + Message[] messages = prov.GetMessages(page); + + Assert.IsFalse(prov.RemoveMessage(page, 5, true), "RemoveMessage should return false"); + + Assert.IsFalse(prov.RemoveMessage(new PageInfo("Inexistent", prov, DateTime.Now), 1, true), + "RemoveMessage should return false"); + + Assert.IsTrue(prov.RemoveMessage(page, messages[0].ID, false), "RemoveMessage should return true"); + + Assert.AreEqual(3, prov.GetMessageCount(page), "Wrong message count"); + Assert.AreEqual("Subject00", prov.GetMessages(page)[0].Subject, "Wrong message"); + Assert.AreEqual("Subject1", prov.GetMessages(page)[1].Subject, "Wrong message"); + + Assert.IsTrue(prov.RemoveMessage(page, messages[1].ID, true), "RemoveMessages should return true"); + Assert.AreEqual(1, prov.GetMessageCount(page), "Wrong message count"); + Assert.AreEqual("Subject00", prov.GetMessages(page)[0].Subject, "Wrong message"); + } + + [Test] + public void RemoveMessage_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo page = prov.AddPage(ns.Name, "Page", DateTime.Now); + + // Subject0 + // Subject00 + // Subject1 + // Subject11 + + prov.AddMessage(page, "NUnit0", "Subject0", DateTime.Now, "Body0", -1); + prov.AddMessage(page, "NUnit00", "Subject00", DateTime.Now.AddHours(1), "Body00", prov.GetMessages(page)[0].ID); + prov.AddMessage(page, "NUnit1", "Subject1", DateTime.Now.AddHours(2), "Body1", -1); + prov.AddMessage(page, "NUnit11", "Subject11", DateTime.Now.AddHours(3), "Body11", prov.GetMessages(page)[1].ID); + + Message[] messages = prov.GetMessages(page); + + Assert.IsFalse(prov.RemoveMessage(page, 5, true), "RemoveMessage should return false"); + Assert.IsFalse(prov.RemoveMessage(new PageInfo(NameTools.GetFullName(ns.Name, "Inexistent"), + prov, DateTime.Now), 1, true), "RemoveMessage should return false"); + + Assert.IsTrue(prov.RemoveMessage(page, messages[0].ID, false), "RemoveMessage should return true"); + + Assert.AreEqual(3, prov.GetMessageCount(page), "Wrong message count"); + Assert.AreEqual("Subject00", prov.GetMessages(page)[0].Subject, "Wrong message"); + Assert.AreEqual("Subject1", prov.GetMessages(page)[1].Subject, "Wrong message"); + + Assert.IsTrue(prov.RemoveMessage(page, messages[1].ID, true), "RemoveMessages should return true"); + Assert.AreEqual(1, prov.GetMessageCount(page), "Wrong message count"); + Assert.AreEqual("Subject00", prov.GetMessages(page)[0].Subject, "Wrong message"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveMessage_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.RemoveMessage(null, 1, true); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void RemoveMessage_InvalidId() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + prov.RemoveMessage(page, -1, true); + } + + [Test] + public void RemoveMessage_KeepReplies_PerformSearch() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + prov.AddMessage(page, "NUnit", "Test", DateTime.Now, "Blah", -1); + prov.AddMessage(page, "NUnit", "RE: Test2", DateTime.Now, "Blah2", prov.GetMessages(page)[0].ID); + + prov.RemoveMessage(page, prov.GetMessages(page)[0].ID, false); + + SearchResultCollection result = prov.PerformSearch(new SearchParameters("test blah test2 blah2")); + Assert.AreEqual(1, result.Count, "Wrong result count"); + Assert.AreEqual(2, result[0].Matches.Count, "Wrong match count"); + + bool found1 = false, found2 = false; + foreach(WordInfo info in result[0].Matches) { + if(info.Text == "test2") found1 = true; + if(info.Text == "blah2") found2 = true; + } + + Assert.IsTrue(found1, "First word not found"); + Assert.IsTrue(found2, "Second word not found"); + } + + [Test] + public void RemoveMessage_RemoveReplies_PerformSearch() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + prov.AddMessage(page, "NUnit", "Test", DateTime.Now, "Blah", -1); + prov.AddMessage(page, "NUnit", "RE: Test2", DateTime.Now, "Blah2", prov.GetMessages(page)[0].ID); + + prov.RemoveMessage(page, prov.GetMessages(page)[0].ID, true); + + SearchResultCollection result = prov.PerformSearch(new SearchParameters("test blah test2 blah2")); + Assert.AreEqual(0, result.Count, "Wrong result count"); + } + + [Test] + public void ModifyMessage_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + DateTime dt = DateTime.Now; + + prov.AddMessage(page, "NUnit", "Subject", dt, "Body", -1); + prov.AddMessage(page, "NUnit", "Subject", dt, "Body", -1); + + Assert.IsFalse(prov.ModifyMessage(new PageInfo("Inexistent", prov, DateTime.Now), 1, + "NUnit", "Subject", DateTime.Now, "Body"), "ModifyMessage should return false"); + + Assert.IsFalse(prov.ModifyMessage(page, 5, "NUnit", "Subject", DateTime.Now, "Body"), "ModifyMessage should return false"); + + Assert.IsTrue(prov.ModifyMessage(page, prov.GetMessages(page)[0].ID, "NUnit1", "Subject1", dt.AddDays(1), "Body1"), "ModifyMessage should return true"); + + Assert.AreEqual(2, prov.GetMessageCount(page), "Wrong message count"); + + Message[] messages = prov.GetMessages(page); + + Assert.AreEqual("NUnit", messages[0].Username, "Wrong username"); + Assert.AreEqual("Subject", messages[0].Subject, "Wrong subject"); + Tools.AssertDateTimesAreEqual(dt, messages[0].DateTime); + Assert.AreEqual("Body", messages[0].Body, "Wrong body"); + + Assert.AreEqual("NUnit1", messages[1].Username, "Wrong username"); + Assert.AreEqual("Subject1", messages[1].Subject, "Wrong subject"); + Tools.AssertDateTimesAreEqual(dt.AddDays(1), messages[1].DateTime); + Assert.AreEqual("Body1", messages[1].Body, "Wrong body"); + } + + [Test] + public void ModifyMessage_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo page = prov.AddPage(ns.Name, "Page", DateTime.Now); + + DateTime dt = DateTime.Now; + + prov.AddMessage(page, "NUnit", "Subject", dt, "Body", -1); + prov.AddMessage(page, "NUnit", "Subject", dt, "Body", -1); + + Assert.IsFalse(prov.ModifyMessage(new PageInfo(NameTools.GetFullName(ns.Name, "Inexistent"), + prov, DateTime.Now), 1, "NUnit", "Subject", DateTime.Now, "Body"), "ModifyMessage should return false"); + + Assert.IsFalse(prov.ModifyMessage(page, 5, "NUnit", "Subject", DateTime.Now, "Body"), "ModifyMessage should return false"); + + Assert.IsTrue(prov.ModifyMessage(page, prov.GetMessages(page)[0].ID, "NUnit1", "Subject1", dt.AddDays(1), "Body1"), "ModifyMessage should return true"); + + Assert.AreEqual(2, prov.GetMessageCount(page), "Wrong message count"); + + Message[] messages = prov.GetMessages(page); + + Assert.AreEqual("NUnit", messages[0].Username, "Wrong username"); + Assert.AreEqual("Subject", messages[0].Subject, "Wrong subject"); + Tools.AssertDateTimesAreEqual(dt, messages[0].DateTime); + Assert.AreEqual("Body", messages[0].Body, "Wrong body"); + + Assert.AreEqual("NUnit1", messages[1].Username, "Wrong username"); + Assert.AreEqual("Subject1", messages[1].Subject, "Wrong subject"); + Tools.AssertDateTimesAreEqual(dt.AddDays(1), messages[1].DateTime); + Assert.AreEqual("Body1", messages[1].Body, "Wrong body"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ModifyMessage_NullPage() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.IsFalse(prov.ModifyMessage(null, 1, "NUnit", "Subject", DateTime.Now, "Body"), "ModifyMessage should return false"); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ModifyMessage_InvalidId() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + Assert.IsFalse(prov.ModifyMessage(page, -1, "NUnit", "Subject", DateTime.Now, "Body"), "ModifyMessage should return false"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ModifyMessage_InvalidUsername(string u) { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + prov.AddMessage(page, "NUnit", "Subject", DateTime.Now, "Body", -1); + + Assert.IsFalse(prov.ModifyMessage(page, prov.GetMessages(page)[0].ID, u, "Subject", DateTime.Now, "Body"), "ModifyMessage should return false"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ModifyMessage_InvalidSubject(string s) { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + prov.AddMessage(page, "NUnit", "Subject", DateTime.Now, "Body", -1); + + Assert.IsFalse(prov.ModifyMessage(page, prov.GetMessages(page)[0].ID, "NUnit", s, DateTime.Now, "Body"), "ModifyMessage should return false"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ModifyMessage_NullBody() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + prov.AddMessage(page, "NUnit", "Subject", DateTime.Now, "Body", -1); + + Assert.IsFalse(prov.ModifyMessage(page, prov.GetMessages(page)[0].ID, "NUnit", "Subject", DateTime.Now, null), "ModifyMessage should return false"); + } + + [Test] + public void ModifyMessage_PerformSearch() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page = prov.AddPage(null, "Page", DateTime.Now); + + prov.AddMessage(page, "NUnit", "Message", DateTime.Now, "Blah, Test.", -1); + prov.ModifyMessage(page, prov.GetMessages(page)[0].ID, "NUnit", "MessageMod", DateTime.Now, "Modified"); + + SearchResultCollection result = prov.PerformSearch(new SearchParameters("message modified")); + Assert.AreEqual(1, result.Count, "Wrong result count"); + + Assert.AreEqual(1, result[0].Matches.Count, "Wrong match count"); + Assert.AreEqual("modified", result[0].Matches[0].Text, "Wrong match"); + } + + [Test] + public void AddNavigationPath_GetNavigationPaths_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page1 = prov.AddPage(null, "Page1", DateTime.Now); + PageInfo page2 = prov.AddPage(null, "Page2", DateTime.Now); + + Assert.AreEqual(0, prov.GetNavigationPaths(null).Length, "Wrong initial navigation path count"); + + NavigationPath path1 = prov.AddNavigationPath(null, "Path1", new PageInfo[] { page1, page2 }); + Assert.IsNotNull(path1, "AddNavigationPath should return something"); + Assert.AreEqual("Path1", path1.FullName, "Wrong name"); + Assert.AreEqual(2, path1.Pages.Length, "Wrong page count"); + Assert.AreEqual(page1.FullName, path1.Pages[0], "Wrong page at position 0"); + Assert.AreEqual(page2.FullName, path1.Pages[1], "Wrong page at position 1"); + + NavigationPath path2 = prov.AddNavigationPath(null, "Path2", new PageInfo[] { page1 }); + Assert.IsNotNull(path2, "AddNavigationPath should return something"); + Assert.AreEqual("Path2", path2.FullName, "Wrong name"); + Assert.AreEqual(1, path2.Pages.Length, "Wrong page count"); + Assert.AreEqual(page1.FullName, path2.Pages[0], "Wrong page at position 0"); + + Assert.IsNull(prov.AddNavigationPath(null, "Path1", new PageInfo[] { page2, page1 }), "AddNavigationPath should return null"); + + NavigationPath[] paths = prov.GetNavigationPaths(null); + Assert.AreEqual(2, paths.Length, "Wrong navigation path count"); + Assert.AreEqual("Path1", paths[0].FullName, "Wrong name"); + Assert.AreEqual(2, paths[0].Pages.Length, "Wrong page count"); + Assert.AreEqual(page1.FullName, paths[0].Pages[0], "Wrong page at position 0"); + Assert.AreEqual(page2.FullName, paths[0].Pages[1], "Wrong page at position 1"); + Assert.AreEqual("Path2", paths[1].FullName, "Wrong name"); + Assert.AreEqual(1, paths[1].Pages.Length, "Wrong page count"); + Assert.AreEqual(page1.FullName, paths[1].Pages[0], "Wrong page at position 0"); + } + + [Test] + public void AddNavigationPath_GetNavigationPaths_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo page1 = prov.AddPage(ns.Name, "Page1", DateTime.Now); + PageInfo page2 = prov.AddPage(ns.Name, "Page2", DateTime.Now); + + Assert.AreEqual(0, prov.GetNavigationPaths(ns).Length, "Wrong initial navigation path count"); + + NavigationPath path1 = prov.AddNavigationPath(ns.Name, "Path1", new PageInfo[] { page1, page2 }); + Assert.IsNotNull(path1, "AddNavigationPath should return something"); + Assert.AreEqual(NameTools.GetFullName(ns.Name, "Path1"), path1.FullName, "Wrong name"); + Assert.AreEqual(2, path1.Pages.Length, "Wrong page count"); + Assert.AreEqual(page1.FullName, path1.Pages[0], "Wrong page at position 0"); + Assert.AreEqual(page2.FullName, path1.Pages[1], "Wrong page at position 1"); + + NavigationPath path2 = prov.AddNavigationPath(ns.Name, "Path2", new PageInfo[] { page1 }); + Assert.IsNotNull(path2, "AddNavigationPath should return something"); + Assert.AreEqual(NameTools.GetFullName(ns.Name, "Path2"), path2.FullName, "Wrong name"); + Assert.AreEqual(1, path2.Pages.Length, "Wrong page count"); + Assert.AreEqual(page1.FullName, path2.Pages[0], "Wrong page at position 0"); + + Assert.IsNull(prov.AddNavigationPath(ns.Name, "Path1", new PageInfo[] { page2, page1 }), "AddNavigationPath should return null"); + + NavigationPath[] paths = prov.GetNavigationPaths(ns); + Assert.AreEqual(2, paths.Length, "Wrong navigation path count"); + Assert.AreEqual(NameTools.GetFullName(ns.Name, "Path1"), paths[0].FullName, "Wrong name"); + Assert.AreEqual(2, paths[0].Pages.Length, "Wrong page count"); + Assert.AreEqual(page1.FullName, paths[0].Pages[0], "Wrong page at position 0"); + Assert.AreEqual(page2.FullName, paths[0].Pages[1], "Wrong page at position 1"); + Assert.AreEqual(NameTools.GetFullName(ns.Name, "Path2"), paths[1].FullName, "Wrong name"); + Assert.AreEqual(1, paths[1].Pages.Length, "Wrong page count"); + Assert.AreEqual(page1.FullName, paths[1].Pages[0], "Wrong page at position 0"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AddNavigationPath_InvalidName(string n) { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page1 = prov.AddPage(null, "Page1", DateTime.Now); + PageInfo page2 = prov.AddPage(null, "Page2", DateTime.Now); + + prov.AddNavigationPath(null, n, new PageInfo[] { page1, page2 }); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void AddNavigationPath_NullPages() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.AddNavigationPath(null, "Path", null); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddNavigationPath_EmptyPages() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.AddNavigationPath(null, "Path", new PageInfo[0]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void AddNavigationPath_NullPageElement() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page1 = prov.AddPage(null, "Page1", DateTime.Now); + + prov.AddNavigationPath(null, "Path", new PageInfo[] { page1, null }); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void AddNavigationPath_InexistentPageElement() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page1 = prov.AddPage(null, "Page1", DateTime.Now); + + prov.AddNavigationPath(null, "Path", new PageInfo[] { page1, new PageInfo("Inexistent", prov, DateTime.Now) }); + } + + [Test] + public void ModifyNavigationPath_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page1 = prov.AddPage(null, "Page1", DateTime.Now); + PageInfo page2 = prov.AddPage(null, "Page2", DateTime.Now); + PageInfo page3 = prov.AddPage(null, "Page3", DateTime.Now); + + NavigationPath path = prov.AddNavigationPath(null, "Path", new PageInfo[] { page1, page2 }); + + Assert.IsNull(prov.ModifyNavigationPath(new NavigationPath("Inexistent", prov), new PageInfo[] { page1, page3 }), "ModifyNavigationPath should return null"); + + NavigationPath output = prov.ModifyNavigationPath(path, new PageInfo[] { page1, page3 }); + + Assert.IsNotNull(output, "ModifyNavigationPath should return something"); + Assert.AreEqual("Path", path.FullName, "Wrong name"); + Assert.AreEqual(page1.FullName, output.Pages[0], "Wrong page at position 0"); + Assert.AreEqual(page3.FullName, output.Pages[1], "Wrong page at position 1"); + + NavigationPath[] paths = prov.GetNavigationPaths(null); + Assert.AreEqual(1, paths.Length, "Wrong navigation path count"); + Assert.AreEqual("Path", paths[0].FullName, "Wrong name"); + Assert.AreEqual(page1.FullName, paths[0].Pages[0], "Wrong page at position 0"); + Assert.AreEqual(page3.FullName, paths[0].Pages[1], "Wrong page at position 1"); + } + + [Test] + public void ModifyNavigationPath_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo page1 = prov.AddPage(ns.Name, "Page1", DateTime.Now); + PageInfo page2 = prov.AddPage(ns.Name, "Page2", DateTime.Now); + PageInfo page3 = prov.AddPage(ns.Name, "Page3", DateTime.Now); + + NavigationPath path = prov.AddNavigationPath(ns.Name, "Path", new PageInfo[] { page1, page2 }); + + Assert.IsNull(prov.ModifyNavigationPath(new NavigationPath(NameTools.GetFullName(ns.Name, "Inexistent"), prov), new PageInfo[] { page1, page3 }), "ModifyNavigationPath should return null"); + + NavigationPath output = prov.ModifyNavigationPath(path, new PageInfo[] { page1, page3 }); + + Assert.IsNotNull(output, "ModifyNavigationPath should return something"); + Assert.AreEqual(NameTools.GetFullName(ns.Name, "Path"), path.FullName, "Wrong name"); + Assert.AreEqual(page1.FullName, output.Pages[0], "Wrong page at position 0"); + Assert.AreEqual(page3.FullName, output.Pages[1], "Wrong page at position 1"); + + NavigationPath[] paths = prov.GetNavigationPaths(ns); + Assert.AreEqual(1, paths.Length, "Wrong navigation path count"); + Assert.AreEqual(NameTools.GetFullName(ns.Name, "Path"), paths[0].FullName, "Wrong name"); + Assert.AreEqual(page1.FullName, paths[0].Pages[0], "Wrong page at position 0"); + Assert.AreEqual(page3.FullName, paths[0].Pages[1], "Wrong page at position 1"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ModifyNavigationPath_NullPath() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page1 = prov.AddPage(null, "Page1", DateTime.Now); + PageInfo page2 = prov.AddPage(null, "Page2", DateTime.Now); + + prov.ModifyNavigationPath(null, new PageInfo[] { page1, page2 }); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ModifyNavigationPath_NullPages() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page1 = prov.AddPage(null, "Page1", DateTime.Now); + PageInfo page2 = prov.AddPage(null, "Page2", DateTime.Now); + + NavigationPath path = prov.AddNavigationPath(null, "Path", new PageInfo[] { page1, page2 }); + + NavigationPath output = prov.ModifyNavigationPath(path, null); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void ModifyNavigationPath_EmptyPages() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page1 = prov.AddPage(null, "Page1", DateTime.Now); + PageInfo page2 = prov.AddPage(null, "Page2", DateTime.Now); + + NavigationPath path = prov.AddNavigationPath(null, "Path", new PageInfo[] { page1, page2 }); + + NavigationPath output = prov.ModifyNavigationPath(path, new PageInfo[0]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ModifyNavigationPath_NullPageElement() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page1 = prov.AddPage(null, "Page1", DateTime.Now); + PageInfo page2 = prov.AddPage(null, "Page2", DateTime.Now); + + NavigationPath path = prov.AddNavigationPath(null, "Path", new PageInfo[] { page1, page2 }); + + NavigationPath output = prov.ModifyNavigationPath(path, new PageInfo[] { page1, null }); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void ModifyNavigationPath_InexistentPageElement() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page1 = prov.AddPage(null, "Page1", DateTime.Now); + PageInfo page2 = prov.AddPage(null, "Page2", DateTime.Now); + + NavigationPath path = prov.AddNavigationPath(null, "Path", new PageInfo[] { page1, page2 }); + + NavigationPath output = prov.ModifyNavigationPath(path, + new PageInfo[] { page1, new PageInfo("Inexistent", prov, DateTime.Now) }); + } + + [Test] + public void RemoveNavigationPath_Root() { + IPagesStorageProviderV30 prov = GetProvider(); + + PageInfo page1 = prov.AddPage(null, "Page1", DateTime.Now); + PageInfo page2 = prov.AddPage(null, "Page2", DateTime.Now); + + NavigationPath path1 = prov.AddNavigationPath(null, "Path1", new PageInfo[] { page1, page2 }); + NavigationPath path2 = prov.AddNavigationPath(null, "Path2", new PageInfo[] { page2, page1 }); + + Assert.IsFalse(prov.RemoveNavigationPath(new NavigationPath("Inexistent", prov)), "RemoveNavigationPath should return false"); + + Assert.IsTrue(prov.RemoveNavigationPath(path2), "RemoveNavigationPath should return true"); + + NavigationPath[] paths = prov.GetNavigationPaths(null); + Assert.AreEqual(1, paths.Length, "Wrong navigation path count"); + Assert.AreEqual("Path1", paths[0].FullName, "Wrong name"); + Assert.AreEqual(page1.FullName, paths[0].Pages[0], "Wrong page at position 0"); + Assert.AreEqual(page2.FullName, paths[0].Pages[1], "Wrong page at position 1"); + } + + [Test] + public void RemoveNavigationPath_Sub() { + IPagesStorageProviderV30 prov = GetProvider(); + + NamespaceInfo ns = prov.AddNamespace("NS"); + + PageInfo page1 = prov.AddPage(ns.Name, "Page1", DateTime.Now); + PageInfo page2 = prov.AddPage(ns.Name, "Page2", DateTime.Now); + + NavigationPath path1 = prov.AddNavigationPath(ns.Name, "Path1", new PageInfo[] { page1, page2 }); + NavigationPath path2 = prov.AddNavigationPath(ns.Name, "Path2", new PageInfo[] { page2, page1 }); + + Assert.IsFalse(prov.RemoveNavigationPath(new NavigationPath(NameTools.GetFullName(ns.Name, "Inexistent"), prov)), "RemoveNavigationPath should return false"); + + Assert.IsTrue(prov.RemoveNavigationPath(path2), "RemoveNavigationPath should return true"); + + NavigationPath[] paths = prov.GetNavigationPaths(ns); + Assert.AreEqual(1, paths.Length, "Wrong navigation path count"); + Assert.AreEqual(NameTools.GetFullName(ns.Name, "Path1"), paths[0].FullName, "Wrong name"); + Assert.AreEqual(page1.FullName, paths[0].Pages[0], "Wrong page at position 0"); + Assert.AreEqual(page2.FullName, paths[0].Pages[1], "Wrong page at position 1"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveNavigationPath_NullPath() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.RemoveNavigationPath(null); + } + + [Test] + public void AddSnippet_GetSnippets() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.AreEqual(0, prov.GetSnippets().Length, "Wrong snippet count"); + + Snippet snippet1 = prov.AddSnippet("Snippet1", "Content1"); + Snippet snippet2 = prov.AddSnippet("Snippet2", "Content2"); + + Assert.IsNull(prov.AddSnippet("Snippet1", "Content"), "AddSnippet should return null"); + + Assert.AreEqual("Snippet1", snippet1.Name, "Wrong name"); + Assert.AreEqual("Content1", snippet1.Content, "Wrong content"); + Assert.AreEqual("Snippet2", snippet2.Name, "Wrong name"); + Assert.AreEqual("Content2", snippet2.Content, "Wrong content"); + + Snippet[] snippets = prov.GetSnippets(); + Assert.AreEqual(2, snippets.Length, "Wrong snippet count"); + + Array.Sort(snippets, delegate(Snippet x, Snippet y) { return x.Name.CompareTo(y.Name); }); + Assert.AreEqual("Snippet1", snippets[0].Name, "Wrong name"); + Assert.AreEqual("Content1", snippets[0].Content, "Wrong content"); + Assert.AreEqual("Snippet2", snippets[1].Name, "Wrong name"); + Assert.AreEqual("Content2", snippets[1].Content, "Wrong content"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AddSnippet_InvalidName(string n) { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.AddSnippet(n, "Content"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void AddSnippet_NullContent() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.AddSnippet("Snippet", null); + } + + [Test] + public void ModifySnippet() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.IsNull(prov.ModifySnippet("Inexistent", "Content"), "ModifySnippet should return null"); + + prov.AddSnippet("Snippet1", "Content"); + prov.AddSnippet("Snippet2", "Content2"); + + Snippet output = prov.ModifySnippet("Snippet1", "Content1"); + Assert.IsNotNull(output, "ModifySnippet should return something"); + + Assert.AreEqual("Snippet1", output.Name, "Wrong name"); + Assert.AreEqual("Content1", output.Content, "Wrong content"); + + Snippet[] snippets = prov.GetSnippets(); + Assert.AreEqual(2, snippets.Length, "Wrong snippet count"); + + Array.Sort(snippets, delegate(Snippet x, Snippet y) { return x.Name.CompareTo(y.Name); }); + Assert.AreEqual("Snippet1", snippets[0].Name, "Wrong name"); + Assert.AreEqual("Content1", snippets[0].Content, "Wrong content"); + Assert.AreEqual("Snippet2", snippets[1].Name, "Wrong name"); + Assert.AreEqual("Content2", snippets[1].Content, "Wrong content"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ModifySnippet_InvalidName(string n) { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.ModifySnippet(n, "Content"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ModifySnippet_NullContent() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.AddSnippet("Snippet", "Blah"); + + prov.AddSnippet("Snippet", null); + } + + [Test] + public void RemoveSnippet() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.IsFalse(prov.RemoveSnippet("Inexistent"), "RemoveSnippet should return false"); + + prov.AddSnippet("Snippet1", "Content1"); + prov.AddSnippet("Snippet2", "Content2"); + + Assert.IsTrue(prov.RemoveSnippet("Snippet2"), "RemoveSnippet should return true"); + + Snippet[] snippets = prov.GetSnippets(); + Assert.AreEqual(1, snippets.Length, "Wrong snippet count"); + + Assert.AreEqual("Snippet1", snippets[0].Name, "Wrong name"); + Assert.AreEqual("Content1", snippets[0].Content, "Wrong content"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RemoveSnippet_InvalidName(string n) { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.RemoveSnippet(n); + } + + [Test] + public void AddContentTemplate_GetContentTemplates() { + IPagesStorageProviderV30 prov = GetProvider(); + + ContentTemplate temp1 = prov.AddContentTemplate("T1", "Template1"); + Assert.AreEqual("T1", temp1.Name, "Wrong name"); + Assert.AreEqual("Template1", temp1.Content, "Wrong content"); + + Assert.IsNull(prov.AddContentTemplate("T1", "Blah"), "AddContentTemplate should return null"); + + ContentTemplate temp2 = prov.AddContentTemplate("T2", "Template2"); + Assert.AreEqual("T2", temp2.Name, "Wrong name"); + Assert.AreEqual("Template2", temp2.Content, "Wrong content"); + + ContentTemplate[] templates = prov.GetContentTemplates(); + Assert.AreEqual(2, templates.Length, "Wrong template count"); + + Array.Sort(templates, (x, y) => { return x.Name.CompareTo(y.Name); }); + + Assert.AreEqual("T1", templates[0].Name, "Wrong name"); + Assert.AreEqual("Template1", templates[0].Content, "Wrong content"); + Assert.AreEqual("T2", templates[1].Name, "Wrong name"); + Assert.AreEqual("Template2", templates[1].Content, "Wrong content"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AddContentTemplate_InvalidName(string n) { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.AddContentTemplate(n, "Content"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void AddContentTemplate_NullContent() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.AddContentTemplate("T", null); + } + + [Test] + public void ModifyContentTemplate() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.IsNull(prov.ModifyContentTemplate("T", "Content"), "ModifyContentTemplate should return null"); + + prov.AddContentTemplate("T", "Content"); + prov.AddContentTemplate("T2", "Blah"); + + ContentTemplate temp = prov.ModifyContentTemplate("T", "Mod"); + Assert.AreEqual("T", temp.Name, "Wrong name"); + Assert.AreEqual("Mod", temp.Content, "Wrong content"); + + ContentTemplate[] templates = prov.GetContentTemplates(); + Assert.AreEqual(2, templates.Length, "Wrong template count"); + + Array.Sort(templates, (x, y) => { return x.Name.CompareTo(y.Name); }); + + Assert.AreEqual("T", templates[0].Name, "Wrong name"); + Assert.AreEqual("Mod", templates[0].Content, "Wrong content"); + Assert.AreEqual("T2", templates[1].Name, "Wrong name"); + Assert.AreEqual("Blah", templates[1].Content, "Wrong content"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ModifyContentTemplate_InvalidName(string n) { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.ModifyContentTemplate(n, "Content"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ModifyContentTemplate_NullContent() { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.ModifyContentTemplate("T", null); + } + + [Test] + public void RemoveContentTemplate() { + IPagesStorageProviderV30 prov = GetProvider(); + + Assert.IsFalse(prov.RemoveContentTemplate("T"), "RemoveContentTemplate should return false"); + + prov.AddContentTemplate("T", "Content"); + prov.AddContentTemplate("T2", "Blah"); + + Assert.IsTrue(prov.RemoveContentTemplate("T"), "RemoveContentTemplate should return true"); + + ContentTemplate[] templates = prov.GetContentTemplates(); + Assert.AreEqual(1, templates.Length, "Wrong template count"); + + Assert.AreEqual("T2", templates[0].Name, "Wrong name"); + Assert.AreEqual("Blah", templates[0].Content, "Wrong content"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RemoveContentTemplate_InvalidName(string n) { + IPagesStorageProviderV30 prov = GetProvider(); + + prov.RemoveContentTemplate(n); + } + + [Test] + public void RebuildIndex_ManyPages() { + IPagesStorageProviderV30 prov = GetProvider(); + + for(int i = 0; i < PagesContent.Length; i++) { + PageInfo page = prov.AddPage(null, "The Longest Page Name Ever Seen In The Whole Universe (Maybe) - " + i.ToString(), DateTime.Now); + Assert.IsNotNull(page, "AddPage should return something"); + + bool done = prov.ModifyPage(page, "Page " + i.ToString(), "NUnit", DateTime.Now, "Comment " + i.ToString(), + PagesContent[i], null, "Test Page " + i.ToString(), SaveMode.Normal); + Assert.IsTrue(done, "ModifyPage should return true"); + } + + DoChecksFor_RebuildIndex_ManyPages(prov); + + prov.RebuildIndex(); + + DoChecksFor_RebuildIndex_ManyPages(prov); + } + + private void DoChecksFor_RebuildIndex_ManyPages(IPagesStorageProviderV30 prov) { + int docCount, wordCount, matchCount; + long size; + prov.GetIndexStats(out docCount, out wordCount, out matchCount, out size); + Assert.AreEqual(PagesContent.Length, docCount, "Wrong document count"); + Assert.IsTrue(wordCount > 0, "Wrong word count"); + Assert.IsTrue(matchCount > 0, "Wrong match count"); + Assert.IsTrue(size > 0, "Wrong size"); + + SearchResultCollection results = prov.PerformSearch(new SearchParameters("lorem")); + Assert.IsTrue(results.Count > 0, "No results returned"); + } + + private static readonly string[] PagesContent = + { + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis non massa eu erat imperdiet porta nec eu ipsum. Nulla ullamcorper massa et dui tincidunt eget volutpat velit pellentesque.", + "Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc venenatis vestibulum velit, at molestie dui blandit eget.", + "Praesent bibendum accumsan nulla quis convallis. Quisque eget est metus. Praesent cursus mauris at diam aliquam luctus sit amet sed odio.", + "Cras dignissim eros quis risus vehicula quis ultrices ipsum mattis. Praesent hendrerit sodales volutpat.", + "Sed laoreet, quam at tempus rhoncus, ipsum ipsum tempus libero, in sodales justo nisl a tortor. Mauris non enim libero, ac rutrum tortor. Quisque at lacus mauris. Aenean non tortor a eros fermentum fringilla. Mauris tincidunt scelerisque mattis.", + "Ut ut augue ut sapien dapibus ullamcorper a imperdiet lorem. Maecenas rhoncus nibh nec purus ullamcorper dapibus. Sed blandit, dui eget aliquet adipiscing, nunc orci semper leo, eu fringilla ipsum neque ut augue.", + "Vestibulum cursus lectus dolor, eget lobortis libero. Sed nulla lacus, vulputate at vestibulum sit amet, faucibus id sapien. Nunc egestas semper laoreet.", + "Nunc tempus molestie velit, eu imperdiet ante luctus ut. Praesent diam sapien, mattis nec feugiat a, gravida sit amet quam.", + "Sed eu erat sed nulla vulputate molestie vel ut justo. Cras vestibulum ultrices mauris in consectetur. In bibendum enim neque, id tempus erat.", + "Aenean blandit, justo et tempus dignissim, arcu odio vestibulum erat, sed venenatis odio turpis sed nulla. Aenean venenatis rhoncus sem, sed tincidunt est cursus id. Nam ut sem id dui varius porta.", + + "Suspendisse potenti. Duis non dui ac nulla cursus varius. Morbi auctor diam quis urna lobortis sit amet laoreet leo egestas. Integer velit ante, dictum id faucibus quis, pulvinar vitae ipsum. Ut sed lorem lacus. Morbi a enim purus, quis tincidunt risus.", + "Maecenas ac odio quis magna vehicula faucibus. Ut arcu est, volutpat fringilla gravida non, mattis a diam. Nullam dapibus, arcu eget sagittis mattis, tortor leo consectetur tortor, eget mollis libero elit vel metus.", + "Vivamus faucibus ante at urna adipiscing pulvinar. Pellentesque ligula ante, sollicitudin a iaculis sed, dictum quis leo. Quisque augue ipsum, ultrices vitae pretium vel, vulputate vel arcu.", + "Nullam semper luctus dui. Morbi gravida tortor odio, et condimentum velit. Integer semper dapibus turpis, ac suscipit mauris eleifend et. Cras a quam tortor. Mauris lorem mauris, ultricies sed tristique ac, sollicitudin sit amet nibh.", + "Praesent scelerisque convallis risus, a tincidunt turpis porta nec. Aenean tristique malesuada diam, ut fringilla tortor congue vel. Duis id feugiat sapien. In hendrerit, nisl id porttitor convallis, sem est pretium sem, a tincidunt lorem ligula eget massa.", + "Aliquam neque quam, cursus eu iaculis non, laoreet et eros. Maecenas a lacus arcu. Mauris et placerat erat. Pellentesque ut felis est, sit amet sollicitudin turpis. Etiam non odio orci.", + "Nulla purus orci, elementum nec convallis in, feugiat sit amet lectus. Aenean eu elit sem. Quisque sit amet ante nibh, sed elementum magna. Quisque non est odio.", + "Morbi porta metus at mi vehicula sit amet scelerisque nulla vehicula. Sed eleifend venenatis velit. Nulla augue mauris, dignissim sed rhoncus non, luctus nec nulla. In hac habitasse platea dictumst.", + "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In nunc est, euismod et ullamcorper vitae, aliquam quis nulla.", + "Morbi porttitor vehicula placerat. Nunc et lorem mauris. Morbi in nunc lorem. Integer aliquet sem vel magna scelerisque lobortis. Integer nec suscipit libero. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nam posuere cursus enim. Sed auctor semper vehicula. Phasellus volutpat est et sem ultricies ornare." + }; + + } + +} diff --git a/TestScaffolding/Properties/AssemblyInfo.cs b/TestScaffolding/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..20a7648 --- /dev/null +++ b/TestScaffolding/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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("ScrewTurn Wiki Test Scaffolding")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// 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("bc6e9ba6-c9cd-42ec-aa66-4b1645254b62")] diff --git a/TestScaffolding/SettingsStorageProviderTestScaffolding.cs b/TestScaffolding/SettingsStorageProviderTestScaffolding.cs new file mode 100644 index 0000000..85da40b --- /dev/null +++ b/TestScaffolding/SettingsStorageProviderTestScaffolding.cs @@ -0,0 +1,787 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.AclEngine; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Tests { + + [TestFixture] + public abstract class SettingsStorageProviderTestScaffolding { + + private MockRepository mocks = new MockRepository(); + private string testDir = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Guid.NewGuid().ToString()); + + private const int MaxLogSize = 8; + private const int MaxRecentChanges = 20; + + protected IHostV30 MockHost() { + if(!Directory.Exists(testDir)) Directory.CreateDirectory(testDir); + //Console.WriteLine("Temp dir: " + testDir); + + IHostV30 host = mocks.DynamicMock(); + Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.AtLeastOnce(); + + Expect.Call(host.GetSettingValue(SettingName.LoggingLevel)).Return("3").Repeat.Any(); + Expect.Call(host.GetSettingValue(SettingName.MaxLogSize)).Return(MaxLogSize.ToString()).Repeat.Any(); + Expect.Call(host.GetSettingValue(SettingName.MaxRecentChanges)).Return(MaxRecentChanges.ToString()).Repeat.Any(); + + mocks.Replay(host); + + return host; + } + + [TearDown] + public void TearDown() { + try { + Directory.Delete(testDir, true); + } + catch { + //Console.WriteLine("Test: could not delete temp directory"); + } + } + + public abstract ISettingsStorageProviderV30 GetProvider(); + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Init_NullHost() { + ISettingsStorageProviderV30 prov = GetProvider(); + prov.Init(null, ""); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Init_NullConfig() { + ISettingsStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), null); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void SetSetting_InvalidName(string n) { + ISettingsStorageProviderV30 prov = GetProvider(); + + prov.SetSetting(n, "blah"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void GetSetting_InvalidName(string n) { + ISettingsStorageProviderV30 prov = GetProvider(); + + prov.GetSetting(n); + } + + [TestCase(null, "")] + [TestCase("", "")] + [TestCase("blah", "blah")] + [TestCase("with\nnew\nline", "with\nnew\nline")] + [TestCase("with|pipe", "with|pipe")] + [TestCase("withangbrack", "with>angbrack")] + public void SetSetting_GetSetting(string c, string r) { + ISettingsStorageProviderV30 prov = GetProvider(); + + Collectors.SettingsProvider = prov; + + Assert.IsTrue(prov.SetSetting("TS", c), "SetSetting should return true"); + Assert.AreEqual(r, prov.GetSetting("TS"), "Wrong return value"); + } + + [Test] + public void SetSetting_GetAllSettings() { + ISettingsStorageProviderV30 prov = GetProvider(); + + Collectors.SettingsProvider = prov; + + Assert.IsTrue(prov.SetSetting("TS1", "Value1"), "SetSetting should return true"); + Assert.IsTrue(prov.SetSetting("TS2", "Value2"), "SetSetting should return true"); + Assert.IsTrue(prov.SetSetting("TS3", "Value3"), "SetSetting should return true"); + + IDictionary settings = prov.GetAllSettings(); + Assert.AreEqual(3, settings.Count, "Wrong setting count"); + Assert.AreEqual("Value1", settings["TS1"], "Wrong setting value"); + Assert.AreEqual("Value2", settings["TS2"], "Wrong setting value"); + Assert.AreEqual("Value3", settings["TS3"], "Wrong setting value"); + } + + [TestCase("Message", EntryType.General, "User")] + [TestCase("Message\nblah", EntryType.Error, "User\nggg")] + [TestCase("Message|ppp", EntryType.Warning, "User|ghghgh")] + public void LogEntry_GetLogEntries(string m, EntryType t, string u) { + ISettingsStorageProviderV30 prov = GetProvider(); + + Collectors.SettingsProvider = prov; + + prov.LogEntry(m, t, u); + + LogEntry[] entries = prov.GetLogEntries(); + Assert.AreEqual(m, entries[entries.Length - 1].Message, "Wrong message"); + Assert.AreEqual(t, entries[entries.Length - 1].EntryType, "Wrong entry type"); + Assert.AreEqual(u, entries[entries.Length - 1].User, "Wrong user"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void LogEntry_InvalidMessage(string m) { + ISettingsStorageProviderV30 prov = GetProvider(); + + Collectors.SettingsProvider = prov; + + prov.LogEntry(m, EntryType.General, "NUnit"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void LogEntry_InvalidUser(string u) { + ISettingsStorageProviderV30 prov = GetProvider(); + + Collectors.SettingsProvider = prov; + + prov.LogEntry("Test", EntryType.General, u); + } + + [Test] + public void ClearLog() { + ISettingsStorageProviderV30 prov = GetProvider(); + + Collectors.SettingsProvider = prov; + + prov.LogEntry("Test", EntryType.General, "User"); + prov.LogEntry("Test", EntryType.Error, "User"); + prov.LogEntry("Test", EntryType.Warning, "User"); + + Assert.AreEqual(3, prov.GetLogEntries().Length, "Wrong log entry count"); + + prov.ClearLog(); + + Assert.AreEqual(0, prov.GetLogEntries().Length, "Wrong log entry count"); + } + + [Test] + public void CutLog_LogSize() { + ISettingsStorageProviderV30 prov = GetProvider(); + + Collectors.SettingsProvider = prov; + + for(int i = 0; i < 100; i++) { + prov.LogEntry("Test", EntryType.General, "User"); + prov.LogEntry("Test", EntryType.Error, "User"); + prov.LogEntry("Test", EntryType.Warning, "User"); + } + + Assert.IsTrue(prov.LogSize > 0 && prov.LogSize < MaxLogSize, "Wrong size"); + + Assert.IsTrue(prov.GetLogEntries().Length < 300, "Wrong log entry count"); + } + + [TestCase(MetaDataItem.AccountActivationMessage, "Activation mod")] + [TestCase(MetaDataItem.EditNotice, "Edit notice mod")] + [TestCase(MetaDataItem.Footer, "Footer mod")] + [TestCase(MetaDataItem.Header, "Header mod")] + [TestCase(MetaDataItem.HtmlHead, "HTML head mod")] + [TestCase(MetaDataItem.LoginNotice, "LN mod")] + [TestCase(MetaDataItem.AccessDeniedNotice, "AD mod")] + [TestCase(MetaDataItem.PageFooter, "PF mod")] + [TestCase(MetaDataItem.PageHeader, "PH mod")] + [TestCase(MetaDataItem.PasswordResetProcedureMessage, "Password reset mod")] + [TestCase(MetaDataItem.Sidebar, "Sidebar mod")] + [TestCase(MetaDataItem.PageChangeMessage, "Page change mod")] + [TestCase(MetaDataItem.DiscussionChangeMessage, "Discussion change mod")] + [TestCase(MetaDataItem.ApproveDraftMessage, "Approve draft mod")] + [TestCase(MetaDataItem.RegisterNotice, "RN mod")] + public void SetMetaDataItem_GetMetaDataItem(MetaDataItem item, string newContent) { + ISettingsStorageProviderV30 prov = GetProvider(); + + Assert.IsTrue(prov.SetMetaDataItem(item, null, newContent), "SetMetaDataItem should return true"); + Assert.AreEqual(newContent, prov.GetMetaDataItem(item, null), "Wrong content"); + + Assert.IsTrue(prov.SetMetaDataItem(item, "Tag", newContent + "Mod"), "SetMetaDataItem should return true"); + Assert.AreEqual(newContent + "Mod", prov.GetMetaDataItem(item, "Tag"), "Wrong content"); + } + + [Test] + public void SetMetaDataItem_NullContent() { + ISettingsStorageProviderV30 prov = GetProvider(); + + Assert.IsTrue(prov.SetMetaDataItem(MetaDataItem.Header, null, null), "SetMetaDataItem should return true"); + Assert.AreEqual("", prov.GetMetaDataItem(MetaDataItem.Header, null), "Wrong content"); + + Assert.IsTrue(prov.SetMetaDataItem(MetaDataItem.Header, "Tag", null), "SetMetaDataItem should return true"); + Assert.AreEqual("", prov.GetMetaDataItem(MetaDataItem.Header, "Tag"), "Wrong content"); + } + + [Test] + public void GetMetaDataItem_Inexistent() { + ISettingsStorageProviderV30 prov = GetProvider(); + + Assert.AreEqual("", prov.GetMetaDataItem(MetaDataItem.AccountActivationMessage, null), "GetMetaDataItem should return an empty string"); + Assert.AreEqual("", prov.GetMetaDataItem(MetaDataItem.AccountActivationMessage, "BLAH"), "GetMetaDataItem should return an empty string"); + } + + [Test] + public void AddRecentChange_GetRecentChanges() { + ISettingsStorageProviderV30 prov = GetProvider(); + + Collectors.SettingsProvider = prov; + + DateTime dt = DateTime.Now; + Assert.IsTrue(prov.AddRecentChange("MainPage", "Main Page", null, dt, Log.SystemUsername, ScrewTurn.Wiki.PluginFramework.Change.PageUpdated, ""), "AddRecentChange should return true"); + Assert.IsTrue(prov.AddRecentChange("MainPage", "Home Page", null, dt.AddHours(1), "admin", ScrewTurn.Wiki.PluginFramework.Change.PageUpdated, "Added info"), "AddRecentChange should return true"); + + Assert.IsTrue(prov.AddRecentChange("MainPage", "Home Page", null, dt.AddHours(5), "admin", ScrewTurn.Wiki.PluginFramework.Change.PageRenamed, ""), "AddRecentChange should return true"); + Assert.IsTrue(prov.AddRecentChange("MainPage", "Main Page", null, dt.AddHours(6), "admin", ScrewTurn.Wiki.PluginFramework.Change.PageRolledBack, ""), "AddRecentChange should return true"); + Assert.IsTrue(prov.AddRecentChange("MainPage", "Main Page", null, dt.AddHours(7), "admin", ScrewTurn.Wiki.PluginFramework.Change.PageDeleted, ""), "AddRecentChange should return true"); + + Assert.IsTrue(prov.AddRecentChange("MainPage", "Main Page", "Subject", dt.AddHours(2), "admin", ScrewTurn.Wiki.PluginFramework.Change.MessagePosted, ""), "AddRecentChange should return true"); + Assert.IsTrue(prov.AddRecentChange("MainPage", "Main Page", "Subject", dt.AddHours(3), "admin", ScrewTurn.Wiki.PluginFramework.Change.MessageEdited, ""), "AddRecentChange should return true"); + Assert.IsTrue(prov.AddRecentChange("MainPage", "Main Page", "Subject", dt.AddHours(4), "admin", ScrewTurn.Wiki.PluginFramework.Change.MessageDeleted, ""), "AddRecentChange should return true"); + + RecentChange[] changes = prov.GetRecentChanges(); + + Assert.AreEqual(8, changes.Length, "Wrong recent change count"); + + Assert.AreEqual("MainPage", changes[0].Page, "Wrong page"); + Assert.AreEqual("Main Page", changes[0].Title, "Wrong title"); + Assert.AreEqual("", changes[0].MessageSubject, "Wrong message subject"); + Tools.AssertDateTimesAreEqual(dt, changes[0].DateTime); + Assert.AreEqual(Log.SystemUsername, changes[0].User, "Wrong user"); + Assert.AreEqual(ScrewTurn.Wiki.PluginFramework.Change.PageUpdated, changes[0].Change, "Wrong change"); + Assert.AreEqual("", changes[0].Description, "Wrong description"); + + Assert.AreEqual("MainPage", changes[1].Page, "Wrong page"); + Assert.AreEqual("Home Page", changes[1].Title, "Wrong title"); + Assert.AreEqual("", changes[1].MessageSubject, "Wrong message subject"); + Tools.AssertDateTimesAreEqual(dt.AddHours(1), changes[1].DateTime); + Assert.AreEqual("admin", changes[1].User, "Wrong user"); + Assert.AreEqual(ScrewTurn.Wiki.PluginFramework.Change.PageUpdated, changes[1].Change, "Wrong change"); + Assert.AreEqual("Added info", changes[1].Description, "Wrong description"); + + Assert.AreEqual("MainPage", changes[2].Page, "Wrong page"); + Assert.AreEqual("Main Page", changes[2].Title, "Wrong title"); + Assert.AreEqual("Subject", changes[2].MessageSubject, "Wrong message subject"); + Tools.AssertDateTimesAreEqual(dt.AddHours(2), changes[2].DateTime); + Assert.AreEqual("admin", changes[2].User, "Wrong user"); + Assert.AreEqual(ScrewTurn.Wiki.PluginFramework.Change.MessagePosted, changes[2].Change, "Wrong change"); + Assert.AreEqual("", changes[2].Description, "Wrong description"); + + Assert.AreEqual("MainPage", changes[3].Page, "Wrong page"); + Assert.AreEqual("Main Page", changes[3].Title, "Wrong title"); + Assert.AreEqual("Subject", changes[3].MessageSubject, "Wrong message subject"); + Tools.AssertDateTimesAreEqual(dt.AddHours(3), changes[3].DateTime); + Assert.AreEqual("admin", changes[3].User, "Wrong user"); + Assert.AreEqual(ScrewTurn.Wiki.PluginFramework.Change.MessageEdited, changes[3].Change, "Wrong change"); + Assert.AreEqual("", changes[3].Description, "Wrong description"); + + Assert.AreEqual("MainPage", changes[4].Page, "Wrong page"); + Assert.AreEqual("Main Page", changes[4].Title, "Wrong title"); + Assert.AreEqual("Subject", changes[4].MessageSubject, "Wrong message subject"); + Tools.AssertDateTimesAreEqual(dt.AddHours(4), changes[4].DateTime); + Assert.AreEqual("admin", changes[4].User, "Wrong user"); + Assert.AreEqual(ScrewTurn.Wiki.PluginFramework.Change.MessageDeleted, changes[4].Change, "Wrong change"); + Assert.AreEqual("", changes[4].Description, "Wrong description"); + + Assert.AreEqual("MainPage", changes[5].Page, "Wrong page"); + Assert.AreEqual("Home Page", changes[5].Title, "Wrong title"); + Assert.AreEqual("", changes[5].MessageSubject, "Wrong message subject"); + Tools.AssertDateTimesAreEqual(dt.AddHours(5), changes[5].DateTime); + Assert.AreEqual("admin", changes[5].User, "Wrong user"); + Assert.AreEqual(ScrewTurn.Wiki.PluginFramework.Change.PageRenamed, changes[5].Change, "Wrong change"); + Assert.AreEqual("", changes[5].Description, "Wrong description"); + + Assert.AreEqual("MainPage", changes[6].Page, "Wrong page"); + Assert.AreEqual("Main Page", changes[6].Title, "Wrong title"); + Assert.AreEqual("", changes[6].MessageSubject, "Wrong message subject"); + Tools.AssertDateTimesAreEqual(dt.AddHours(6), changes[6].DateTime); + Assert.AreEqual("admin", changes[6].User, "Wrong user"); + Assert.AreEqual(ScrewTurn.Wiki.PluginFramework.Change.PageRolledBack, changes[6].Change, "Wrong change"); + Assert.AreEqual("", changes[6].Description, "Wrong description"); + + Assert.AreEqual("MainPage", changes[7].Page, "Wrong page"); + Assert.AreEqual("Main Page", changes[7].Title, "Wrong title"); + Assert.AreEqual("", changes[7].MessageSubject, "Wrong message subject"); + Tools.AssertDateTimesAreEqual(dt.AddHours(7), changes[7].DateTime); + Assert.AreEqual("admin", changes[7].User, "Wrong user"); + Assert.AreEqual(ScrewTurn.Wiki.PluginFramework.Change.PageDeleted, changes[7].Change, "Wrong change"); + Assert.AreEqual("", changes[7].Description, "Wrong description"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AddRecentChange_InvalidPage(string p) { + ISettingsStorageProviderV30 prov = GetProvider(); + + Collectors.SettingsProvider = prov; + + prov.AddRecentChange(p, "Title", null, DateTime.Now, "User", ScrewTurn.Wiki.PluginFramework.Change.PageDeleted, "Descr"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AddRecentChange_InvalidTitle(string t) { + ISettingsStorageProviderV30 prov = GetProvider(); + + Collectors.SettingsProvider = prov; + + prov.AddRecentChange("Page", t, null, DateTime.Now, "User", ScrewTurn.Wiki.PluginFramework.Change.PageDeleted, "Descr"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AddRecentChange_InvalidUser(string u) { + ISettingsStorageProviderV30 prov = GetProvider(); + + Collectors.SettingsProvider = prov; + + prov.AddRecentChange("Page", "Title", null, DateTime.Now, u, ScrewTurn.Wiki.PluginFramework.Change.PageDeleted, "Descr"); + } + + [Test] + public void AddRecentChange_NullMessageSubject_NullDescription() { + ISettingsStorageProviderV30 prov = GetProvider(); + + Collectors.SettingsProvider = prov; + + DateTime dt = DateTime.Now; + Assert.IsTrue(prov.AddRecentChange("Page", "Title", null, dt, "User", ScrewTurn.Wiki.PluginFramework.Change.PageUpdated, null), "AddRecentChange should return true"); + + RecentChange c = prov.GetRecentChanges()[0]; + + Assert.AreEqual("Page", c.Page, "Wrong page"); + Assert.AreEqual("Title", c.Title, "Wrong title"); + Assert.AreEqual("", c.MessageSubject, "Wrong message subject"); + Tools.AssertDateTimesAreEqual(dt, c.DateTime); + Assert.AreEqual("User", c.User, "Wrong user"); + Assert.AreEqual(ScrewTurn.Wiki.PluginFramework.Change.PageUpdated, c.Change, "Wrong change"); + Assert.AreEqual("", c.Description, "Wrong description"); + } + + [Test] + public void AddRecentChange_CutRecentChanges() { + ISettingsStorageProviderV30 prov = GetProvider(); + + Collectors.SettingsProvider = prov; + + for(int i = 0; i < MaxRecentChanges + 8; i++) { + Assert.IsTrue(prov.AddRecentChange("MainPage", "Main Page", null, DateTime.Now, Log.SystemUsername, ScrewTurn.Wiki.PluginFramework.Change.PageUpdated, ""), "AddRecentChange should return true"); + } + + RecentChange[] changes = prov.GetRecentChanges(); + + Assert.IsTrue(changes.Length > 0 && changes.Length <= MaxRecentChanges, "Wrong recent change count (" + changes.Length.ToString() + ")"); + } + + [Test] + public void StorePluginAssembly_RetrievePluginAssembly_ListPluginAssemblies() { + ISettingsStorageProviderV30 prov = GetProvider(); + Collectors.SettingsProvider = prov; + + byte[] stuff = new byte[50]; + for(int i = 0; i < stuff.Length; i++) stuff[i] = (byte)i; + + Assert.AreEqual(0, prov.ListPluginAssemblies().Length, "Wrong length"); + + Assert.IsTrue(prov.StorePluginAssembly("Plugin.dll", stuff), "StorePluginAssembly should return true"); + + string[] asms = prov.ListPluginAssemblies(); + Assert.AreEqual(1, asms.Length, "Wrong length"); + Assert.AreEqual("Plugin.dll", asms[0], "Wrong assembly name"); + + byte[] output = prov.RetrievePluginAssembly("Plugin.dll"); + Assert.AreEqual(stuff.Length, output.Length, "Wrong content length"); + for(int i = 0; i < stuff.Length; i++) Assert.AreEqual(stuff[i], output[i], "Wrong content"); + + stuff = new byte[30]; + for(int i = stuff.Length - 1; i >= 0; i--) stuff[i] = (byte)i; + + Assert.IsTrue(prov.StorePluginAssembly("Plugin.dll", stuff), "StorePluginAssembly should return true"); + + output = prov.RetrievePluginAssembly("Plugin.dll"); + Assert.AreEqual(stuff.Length, output.Length, "Wrong content length"); + for(int i = 0; i < stuff.Length; i++) Assert.AreEqual(stuff[i], output[i], "Wrong content"); + } + + [Test] + public void DeletePluginAssembly() { + ISettingsStorageProviderV30 prov = GetProvider(); + Collectors.SettingsProvider = prov; + + Assert.IsFalse(prov.DeletePluginAssembly("Assembly.dll"), "DeletePluginAssembly should return false"); + + byte[] stuff = new byte[50]; + for(int i = 0; i < stuff.Length; i++) stuff[i] = (byte)i; + + prov.StorePluginAssembly("Plugin.dll", stuff); + prov.StorePluginAssembly("Assembly.dll", stuff); + + Assert.IsTrue(prov.DeletePluginAssembly("Assembly.dll"), "DeletePluginAssembly should return true"); + + string[] asms = prov.ListPluginAssemblies(); + + Assert.AreEqual(1, asms.Length, "Wrong length"); + Assert.AreEqual("Plugin.dll", asms[0], "Wrong assembly"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void DeletePluginAssembly_InvalidName(string n) { + ISettingsStorageProviderV30 prov = GetProvider(); + Collectors.SettingsProvider = prov; + + prov.DeletePluginAssembly(n); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void StorePluginAssembly_InvalidFilename(string fn) { + ISettingsStorageProviderV30 prov = GetProvider(); + Collectors.SettingsProvider = prov; + + prov.StorePluginAssembly(fn, new byte[10]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void StorePluginAssembly_NullAssembly() { + ISettingsStorageProviderV30 prov = GetProvider(); + Collectors.SettingsProvider = prov; + + prov.StorePluginAssembly("Test.dll", null); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void StorePluginAssembly_EmptyAssembly() { + ISettingsStorageProviderV30 prov = GetProvider(); + Collectors.SettingsProvider = prov; + + prov.StorePluginAssembly("Test.dll", new byte[0]); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RetrievePluginAssembly_InvalidFilename(string fn) { + ISettingsStorageProviderV30 prov = GetProvider(); + Collectors.SettingsProvider = prov; + + prov.RetrievePluginAssembly(fn); + } + + [Test] + public void RetrievePluginAssembly_InexistentFilename() { + ISettingsStorageProviderV30 prov = GetProvider(); + Collectors.SettingsProvider = prov; + + Assert.IsNull(prov.RetrievePluginAssembly("Inexistent.dll"), "RetrievePluginAssembly should return null"); + } + + [Test] + public void SetPluginStatus_RetrievePluginStatus() { + ISettingsStorageProviderV30 prov = GetProvider(); + Collectors.SettingsProvider = prov; + + Assert.IsTrue(prov.GetPluginStatus("My.Test.Plugin"), "GetPluginStatus should return true"); + + Assert.IsTrue(prov.SetPluginStatus("My.Test.Plugin", true), "SetPluginStatus should return true"); + + Assert.IsTrue(prov.GetPluginStatus("My.Test.Plugin"), "GetPluginStatus should return true"); + + Assert.IsTrue(prov.SetPluginStatus("My.Test.Plugin", false), "SetPluginStatus should return true"); + + Assert.IsFalse(prov.GetPluginStatus("My.Test.Plugin"), "GetPluginStatus should return false"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void SetPluginStatus_InvalidTypeName(string tn) { + ISettingsStorageProviderV30 prov = GetProvider(); + Collectors.SettingsProvider = prov; + + prov.SetPluginStatus(tn, false); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void GetPluginStatus_InvalidTypeName(string tn) { + ISettingsStorageProviderV30 prov = GetProvider(); + Collectors.SettingsProvider = prov; + + prov.GetPluginStatus(tn); + } + + [Test] + public void SetPluginConfiguration_GetPluginConfiguration() { + ISettingsStorageProviderV30 prov = GetProvider(); + Collectors.SettingsProvider = prov; + + Assert.IsEmpty(prov.GetPluginConfiguration("My.Test.Plugin"), "GetPluginConfiguration should return an empty string"); + + Assert.IsTrue(prov.SetPluginConfiguration("My.Test.Plugin", "config"), "SetPluginConfiguration should return true"); + + Assert.AreEqual("config", prov.GetPluginConfiguration("My.Test.Plugin"), "Wrong config"); + + Assert.IsTrue(prov.SetPluginConfiguration("My.Test.Plugin", "config222"), "SetPluginConfiguration should return true"); + + Assert.AreEqual("config222", prov.GetPluginConfiguration("My.Test.Plugin"), "Wrong config"); + + Assert.IsTrue(prov.SetPluginConfiguration("My.Test.Plugin", ""), "SetPluginConfiguration should return true"); + + Assert.AreEqual("", prov.GetPluginConfiguration("My.Test.Plugin"), "Wrong config"); + + Assert.IsTrue(prov.SetPluginConfiguration("My.Test.Plugin", null), "SetPluginConfiguration should return true"); + + Assert.AreEqual("", prov.GetPluginConfiguration("My.Test.Plugin"), "Wrong config"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void SetPluginConfiguration_InvalidTypeName(string tn) { + ISettingsStorageProviderV30 prov = GetProvider(); + Collectors.SettingsProvider = prov; + + prov.SetPluginConfiguration(tn, "config"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void GetPluginConfiguration_InvalidTypeName(string tn) { + ISettingsStorageProviderV30 prov = GetProvider(); + Collectors.SettingsProvider = prov; + + prov.GetPluginConfiguration(tn); + } + + [Test] + public void AclManager_StoreEntry_RetrieveAllEntries_DeleteEntry() { + ISettingsStorageProviderV30 prov = GetProvider(); + Collectors.SettingsProvider = prov; + + Assert.IsTrue(prov.AclManager.StoreEntry("Res", "Action", "U.User", Value.Grant), "StoreEntry should return true"); + + prov = null; + prov = GetProvider(); + Collectors.SettingsProvider = prov; + + AclEntry[] entries = prov.AclManager.RetrieveAllEntries(); + Assert.AreEqual(1, entries.Length, "Wrong entry count"); + + Assert.AreEqual("Res", entries[0].Resource, "Wrong resource"); + Assert.AreEqual("Action", entries[0].Action, "Wrong action"); + Assert.AreEqual("U.User", entries[0].Subject, "Wrong subject"); + Assert.AreEqual(Value.Grant, entries[0].Value, "Wrong value"); + + prov = null; + prov = GetProvider(); + Collectors.SettingsProvider = prov; + + prov.AclManager.DeleteEntry("Res", "Action", "U.User"); + + prov = null; + prov = GetProvider(); + Collectors.SettingsProvider = prov; + + Assert.AreEqual(0, prov.AclManager.RetrieveAllEntries().Length, "Wrong entry count"); + } + + [Test] + public void StoreOutgoingLinks_GetOutgoingLinks() { + ISettingsStorageProviderV30 prov = GetProvider(); + + Assert.AreEqual(0, prov.GetOutgoingLinks("Page").Length, "Wrong initial link count"); + + Assert.IsTrue(prov.StoreOutgoingLinks("Page", new string[] { "Page2", "Sub.Page", "Page3" }), "StoreOutgoingLinks should return true"); + + string[] links = prov.GetOutgoingLinks("Page"); + + Assert.AreEqual(3, links.Length, "Wrong link count"); + + Array.Sort(links); + Assert.AreEqual("Page2", links[0], "Wrong link"); + Assert.AreEqual("Page3", links[1], "Wrong link"); + Assert.AreEqual("Sub.Page", links[2], "Wrong link"); + } + + [Test] + public void StoreOutgoingLinks_GetOutgoingLinks_Overwrite() { + ISettingsStorageProviderV30 prov = GetProvider(); + + Assert.AreEqual(0, prov.GetOutgoingLinks("Page").Length, "Wrong initial link count"); + + Assert.IsTrue(prov.StoreOutgoingLinks("Page", new string[] { "Page1", "Sub.Page1", "Page5" }), "StoreOutgoingLinks should return true"); + Assert.IsTrue(prov.StoreOutgoingLinks("Page", new string[] { "Page2", "Sub.Page", "Page3" }), "StoreOutgoingLinks should return true"); + + string[] links = prov.GetOutgoingLinks("Page"); + + Assert.AreEqual(3, links.Length, "Wrong link count"); + + Array.Sort(links); + Assert.AreEqual("Page2", links[0], "Wrong link"); + Assert.AreEqual("Page3", links[1], "Wrong link"); + Assert.AreEqual("Sub.Page", links[2], "Wrong link"); + } + + [Test] + public void StoreOutgoingLinks_GetOutgoingLinks_EmptyLinks() { + ISettingsStorageProviderV30 prov = GetProvider(); + + Assert.IsTrue(prov.StoreOutgoingLinks("Page", new string[0]), "StoreOutgoingLinks should return true even if outgoingLinks is empty"); + Assert.AreEqual(0, prov.GetOutgoingLinks("Page").Length, "Wrong link count"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void StoreOutgoingLinks_InvalidPage(string p) { + ISettingsStorageProviderV30 prov = GetProvider(); + + prov.StoreOutgoingLinks(p, new string[] { "P1", "P2" }); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void StoreOutgoingLinks_NullLinks() { + ISettingsStorageProviderV30 prov = GetProvider(); + + prov.StoreOutgoingLinks("Page", null); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void StoreOutgoingLinks_InvalidLinksEntry(string e) { + ISettingsStorageProviderV30 prov = GetProvider(); + + prov.StoreOutgoingLinks("Page", new string[] { "P1", e, "P3" }); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void GetOutgoingLinks_InvalidPage(string p) { + ISettingsStorageProviderV30 prov = GetProvider(); + + prov.GetOutgoingLinks(p); + } + + [Test] + public void GetAllOutgoingLinks() { + ISettingsStorageProviderV30 prov = GetProvider(); + + prov.StoreOutgoingLinks("Page1", new string[] { "Page2", "Page3" }); + prov.StoreOutgoingLinks("Page2", new string[] { "Page4", "Page5", "Page6" }); + prov.StoreOutgoingLinks("Page3", new string[] { "Page2", "Page5" }); + + IDictionary links = prov.GetAllOutgoingLinks(); + + Assert.AreEqual(3, links.Count, "Wrong source page count"); + + Assert.AreEqual(2, links["Page1"].Length, "Wrong link count"); + Array.Sort(links["Page1"]); + Assert.AreEqual("Page2", links["Page1"][0], "Wrong link"); + Assert.AreEqual("Page3", links["Page1"][1], "Wrong link"); + + Assert.AreEqual(3, links["Page2"].Length, "Wrong link count"); + Array.Sort(links["Page2"]); + Assert.AreEqual("Page4", links["Page2"][0], "Wrong link"); + Assert.AreEqual("Page5", links["Page2"][1], "Wrong link"); + Assert.AreEqual("Page6", links["Page2"][2], "Wrong link"); + + Assert.AreEqual(2, links["Page3"].Length, "Wrong link count"); + Array.Sort(links["Page3"]); + Assert.AreEqual("Page2", links["Page3"][0], "Wrong link"); + Assert.AreEqual("Page5", links["Page3"][1], "Wrong link"); + } + + [Test] + public void DeleteOutgoingLinks() { + ISettingsStorageProviderV30 prov = GetProvider(); + + prov.StoreOutgoingLinks("Page1", new string[] { "Page2", "Page3", "Page100" }); + prov.StoreOutgoingLinks("Page2", new string[] { "Page4", "Page5", "Page6" }); + prov.StoreOutgoingLinks("Page3", new string[] { "Page1", "Page6" }); + + Assert.IsFalse(prov.DeleteOutgoingLinks("Page21"), "DeleteOutgoingLinks should return false"); + Assert.IsTrue(prov.DeleteOutgoingLinks("Page100"), "DeleteOutgoingLinks should return true"); + + Assert.IsTrue(prov.DeleteOutgoingLinks("Page1"), "DeleteOutgoingLinks should return true"); + + Assert.AreEqual(0, prov.GetOutgoingLinks("Page1").Length, "Links not deleted"); + + Assert.AreEqual(3, prov.GetOutgoingLinks("Page2").Length, "Wrong link count"); + Assert.AreEqual(1, prov.GetOutgoingLinks("Page3").Length, "Wrong link count"); + + IDictionary links = prov.GetAllOutgoingLinks(); + Assert.AreEqual(2, links.Count, "Wrong source page count"); + + Assert.AreEqual(3, links["Page2"].Length, "Wrong link count"); + Array.Sort(links["Page2"]); + Assert.AreEqual("Page4", links["Page2"][0], "Wrong link"); + Assert.AreEqual("Page5", links["Page2"][1], "Wrong link"); + Assert.AreEqual("Page6", links["Page2"][2], "Wrong link"); + + Assert.AreEqual(1, links["Page3"].Length, "Wrong link count"); + Assert.AreEqual("Page6", links["Page3"][0], "Wrong link"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void DeleteOutgoingLinks_InvalidPage(string p) { + ISettingsStorageProviderV30 prov = GetProvider(); + + prov.DeleteOutgoingLinks(p); + } + + [Test] + public void UpdateOutgoingLinksForRename() { + ISettingsStorageProviderV30 prov = GetProvider(); + + prov.StoreOutgoingLinks("Page1", new string[] { "Page2", "OldPage" }); + prov.StoreOutgoingLinks("Page2", new string[] { "Page4", "Page5", "Page6" }); + prov.StoreOutgoingLinks("OldPage", new string[] { "Page2", "Page5" }); + + Assert.IsFalse(prov.UpdateOutgoingLinksForRename("Inexistent", "NewName"), "UpdateOutgoingLinksForRename should return false"); + + Assert.IsTrue(prov.UpdateOutgoingLinksForRename("OldPage", "Page3"), "UpdateOutgoingLinksForRename should return true"); + + IDictionary links = prov.GetAllOutgoingLinks(); + + Assert.AreEqual(3, links.Count, "Wrong source page count"); + + Assert.AreEqual(2, links["Page1"].Length, "Wrong link count"); + Array.Sort(links["Page1"]); + Assert.AreEqual("Page2", links["Page1"][0], "Wrong link"); + Assert.AreEqual("Page3", links["Page1"][1], "Wrong link"); + + Assert.AreEqual(3, links["Page2"].Length, "Wrong link count"); + Array.Sort(links["Page2"]); + Assert.AreEqual("Page4", links["Page2"][0], "Wrong link"); + Assert.AreEqual("Page5", links["Page2"][1], "Wrong link"); + Assert.AreEqual("Page6", links["Page2"][2], "Wrong link"); + + Assert.AreEqual(2, links["Page3"].Length, "Wrong link count"); + Array.Sort(links["Page3"]); + Assert.AreEqual("Page2", links["Page3"][0], "Wrong link"); + Assert.AreEqual("Page5", links["Page3"][1], "Wrong link"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void UpdateOutgoingLinksForRename_InvalidOldName(string n) { + ISettingsStorageProviderV30 prov = GetProvider(); + + prov.UpdateOutgoingLinksForRename(n, "NewName"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void UpdateOutgoingLinksForRename_InvalidNewName(string n) { + ISettingsStorageProviderV30 prov = GetProvider(); + + prov.UpdateOutgoingLinksForRename("OldName", n); + } + + } + +} diff --git a/TestScaffolding/TestScaffolding.csproj b/TestScaffolding/TestScaffolding.csproj new file mode 100644 index 0000000..71da275 --- /dev/null +++ b/TestScaffolding/TestScaffolding.csproj @@ -0,0 +1,88 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {F865670A-DEDE-41B5-B426-48D73C3B5B1C} + Library + Properties + ScrewTurn.Wiki.Tests + ScrewTurn.Wiki.Tests + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + none + true + bin\Release\ + TRACE + prompt + 4 + true + + + + False + ..\References\Tools\NUnit\framework\nunit.framework.dll + + + False + ..\References\Tools\Rhino.Mocks\Rhino.Mocks.dll + + + + 3.5 + + + + + + AssemblyVersion.cs + + + + + + + + + + + + {44B0F4C1-8CDC-4272-B2A2-C0AF689CEB81} + AclEngine + + + {C353A35C-86D0-4154-9500-4F88CAAB29C3} + Core + + + {531A83D6-76F9-4014-91C5-295818E2D948} + PluginFramework + + + {2DF980A6-4742-49B1-A090-DE79314644D0} + SearchEngine + + + + + \ No newline at end of file diff --git a/TestScaffolding/Tools.cs b/TestScaffolding/Tools.cs new file mode 100644 index 0000000..6f98a7c --- /dev/null +++ b/TestScaffolding/Tools.cs @@ -0,0 +1,56 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace ScrewTurn.Wiki.Tests { + + /// + /// Implement some useful testing tool.s + /// + public static class Tools { + + private const string DateTimeFormat = "yyyy-MM-dd-HH-mm-ss"; + + /// + /// Prints a date/time in the "yyyy/MM/dd HH:mm:ss" format. + /// + /// The date/time to print. + /// The string value. + private static string PrintDateTime(DateTime dt) { + return dt.ToString(DateTimeFormat); + } + + /// + /// Asserts that two date/time values are equal. + /// + /// The expected date/time value. + /// The actual date/time value. + /// A value indicating whether to ignore a difference up to 10 seconds. + public static void AssertDateTimesAreEqual(DateTime expected, DateTime actual, bool ignoreUpToTenSecondsDifference) { + if(ignoreUpToTenSecondsDifference) { + TimeSpan span = expected - actual; + Assert.IsTrue(Math.Abs(span.TotalSeconds) <= 10, "Wrong date/time value"); + /*Assert.AreEqual( + PrintDateTime(expected).Substring(0, DateTimeFormat.Length - 1), + PrintDateTime(actual).Substring(0, DateTimeFormat.Length - 1), + "Wrong date/time value");*/ + } + else { + Assert.AreEqual(PrintDateTime(expected), PrintDateTime(actual), "Wrong date/time value"); + } + } + + /// + /// Asserts that two date/time values are equal. + /// + /// The expected date/time value. + /// The actual date/time value. + public static void AssertDateTimesAreEqual(DateTime expected, DateTime actual) { + AssertDateTimesAreEqual(expected, actual, false); + } + + } + +} diff --git a/TestScaffolding/UsersStorageProviderTestScaffolding.cs b/TestScaffolding/UsersStorageProviderTestScaffolding.cs new file mode 100644 index 0000000..29b5539 --- /dev/null +++ b/TestScaffolding/UsersStorageProviderTestScaffolding.cs @@ -0,0 +1,714 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using NUnit.Framework; +using Rhino.Mocks; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki.Tests { + + [TestFixture] + public abstract class UsersStorageProviderTestScaffolding { + + private MockRepository mocks = new MockRepository(); + private string testDir = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Guid.NewGuid().ToString()); + + protected IHostV30 MockHost() { + if(!Directory.Exists(testDir)) Directory.CreateDirectory(testDir); + + IHostV30 host = mocks.DynamicMock(); + Expect.Call(host.GetSettingValue(SettingName.PublicDirectory)).Return(testDir).Repeat.AtLeastOnce(); + + mocks.Replay(host); + + return host; + } + + [TearDown] + public void TearDown() { + try { + Directory.Delete(testDir, true); + } + catch { } + } + + public abstract IUsersStorageProviderV30 GetProvider(); + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Init_NullConfig() { + IUsersStorageProviderV30 prov = GetProvider(); + prov.Init(MockHost(), null); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void Init_NullHost() { + IUsersStorageProviderV30 prov = GetProvider(); + prov.Init(null, ""); + } + + [Test] + public void AddUser_GetUsers() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo u1 = new UserInfo("user", "User", "email@server.com", true, DateTime.Now.AddDays(-1), prov); + UserInfo u2 = new UserInfo("john", null, "john@john.com", false, DateTime.Now, prov); + + UserInfo u1Out = prov.AddUser(u1.Username, u1.DisplayName, "password", u1.Email, u1.Active, u1.DateTime); + Assert.IsNotNull(u1Out, "AddUser should return something"); + AssertUserInfosAreEqual(u1, u1Out, true); + + UserInfo u2Out = prov.AddUser(u2.Username, u2.DisplayName, "password", u2.Email, u2.Active, u2.DateTime); + Assert.IsNotNull(u2Out, "AddUser should return something"); + AssertUserInfosAreEqual(u2, u2Out, true); + + Assert.IsNull(prov.AddUser("user", null, "pwd999", "dummy@server.com", false, DateTime.Now), "AddUser should return false"); + + UserInfo[] users = prov.GetUsers(); + Array.Sort(users, delegate(UserInfo x, UserInfo y) { return x.Username.CompareTo(y.Username); }); + + Assert.AreEqual(2, users.Length, "Wrong user count"); + + AssertUserInfosAreEqual(u2, users[0], true); + AssertUserInfosAreEqual(u1, users[1], true); + } + + private void AssertUserInfosAreEqual(UserInfo expected, UserInfo actual, bool checkProvider) { + Assert.AreEqual(expected.Username, actual.Username, "Wrong username"); + Assert.AreEqual(expected.DisplayName, actual.DisplayName, "Wrong display name"); + Assert.AreEqual(expected.Email, actual.Email, "Wrong email"); + Assert.AreEqual(expected.Active, actual.Active, "Wrong activation status"); + Tools.AssertDateTimesAreEqual(expected.DateTime, actual.DateTime, true); + if(checkProvider) Assert.AreSame(expected.Provider, actual.Provider, "Different provider instances"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AddUser_InvalidUsername(string u) { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.AddUser(u, null, "pass", "email@server.com", true, DateTime.Now); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AddUser_InvalidPassword(string p) { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.AddUser("user", null, p, "email@server.com", true, DateTime.Now); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AddUser_InvalidEmail(string e) { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.AddUser("user", null, "pass", e, true, DateTime.Now); + } + + [Test] + public void TestAccount() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo u1 = prov.AddUser("user1", null, "password", "email1@server.com", true, DateTime.Now); + UserInfo u2 = prov.AddUser("user2", "User", "password", "email2@server.com", false, DateTime.Now); + + Assert.IsTrue(prov.TestAccount(u1, "password"), "TestAccount should return true"); + Assert.IsFalse(prov.TestAccount(new UserInfo(u1.Username.ToUpperInvariant(), null, "email1@server.com", true, DateTime.Now, prov), "password"), "TestAccount should return false"); + Assert.IsFalse(prov.TestAccount(u2, "password"), "TestAccount should return false because the account is disabled"); + Assert.IsFalse(prov.TestAccount(new UserInfo("blah", null, "email30@server.com", true, DateTime.Now, prov), "blah"), "TestAccount should return false"); + Assert.IsFalse(prov.TestAccount(u1, "password222"), "TestAccount should return false"); + Assert.IsFalse(prov.TestAccount(u1, ""), "TestAccount should return false"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void TestAccount_NullUser() { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.TestAccount(null, "pass"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void TestAccount_NullPassword() { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.TestAccount(new UserInfo("blah", null, "email30@server.com", true, DateTime.Now, prov), null); + } + + [Test] + public void ModifyUser() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo user = new UserInfo("username", null, "email@server.com", false, DateTime.Now, prov); + prov.AddUser(user.Username, user.DisplayName, "password", user.Email, user.Active, user.DateTime); + prov.AddUser("zzzz", null, "password2", "email@server2.com", false, DateTime.Now); + + // Set new password + UserInfo expected = new UserInfo(user.Username, "New Display", "new@server.com", true, user.DateTime, prov); + UserInfo result = prov.ModifyUser(user, "New Display", "newpass", "new@server.com", true); + AssertUserInfosAreEqual(expected, result, true); + + UserInfo[] allUsers = prov.GetUsers(); + Assert.AreEqual(2, allUsers.Length, "Wrong user count"); + Array.Sort(allUsers, delegate(UserInfo x, UserInfo y) { return x.Username.CompareTo(y.Username); }); + AssertUserInfosAreEqual(expected, allUsers[0], true); + + Assert.IsTrue(prov.TestAccount(user, "newpass"), "TestAccount should return true"); + + // Set null display name + expected = new UserInfo(user.Username, null, "new@server.com", true, user.DateTime, prov); + result = prov.ModifyUser(user, null, null, "new@server.com", true); + AssertUserInfosAreEqual(expected, result, true); + + allUsers = prov.GetUsers(); + Assert.AreEqual(2, allUsers.Length, "Wrong user count"); + Array.Sort(allUsers, delegate(UserInfo x, UserInfo y) { return x.Username.CompareTo(y.Username); }); + AssertUserInfosAreEqual(expected, allUsers[0], true); + + Assert.IsTrue(prov.TestAccount(user, "newpass"), "TestAccount should return true"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ModifyUser_NullUser() { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.ModifyUser(null, "Display Name", null, "email@server.com", true); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void ModifyUser_InvalidNewEmail(string e) { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo user = new UserInfo("username", null, "email@server.com", true, DateTime.Now, prov); + prov.AddUser(user.Username, user.DisplayName, "password", user.Email, user.Active, user.DateTime); + + prov.ModifyUser(user, "Display Name", null, e, false); + } + + [Test] + public void RemoveUser() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo user = prov.AddUser("user", null, "password", "email@server.com", false, DateTime.Now); + + Assert.IsFalse(prov.RemoveUser(new UserInfo("user1", "Joe", "email1@server.com", false, DateTime.Now, prov)), "RemoveUser should return false"); + + Assert.IsTrue(prov.RemoveUser(user), "RemoveUser should return true"); + + Assert.AreEqual(0, prov.GetUsers().Length, "Wrong user count"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveUser_NullUser() { + IUsersStorageProviderV30 prov = GetProvider(); + prov.RemoveUser(null); + } + + private void AssertUserGroupsAreEqual(UserGroup expected, UserGroup actual, bool checkProvider) { + Assert.AreEqual(expected.Name, actual.Name, "Wrong name"); + Assert.AreEqual(expected.Description, actual.Description, "Wrong description"); + Assert.AreEqual(expected.Users.Length, actual.Users.Length, "Wrong user count"); + for(int i = 0; i < expected.Users.Length; i++) { + Assert.AreEqual(expected.Users[i], actual.Users[i], "Wrong user"); + } + if(checkProvider) { + Assert.AreSame(expected.Provider, actual.Provider, "Wrong provider"); + } + } + + [Test] + public void AddUserGroup_GetUserGroups() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserGroup group1 = prov.AddUserGroup("Group1", "Test1"); + UserGroup expected1 = new UserGroup("Group1", "Test1", prov); + UserGroup group2 = prov.AddUserGroup("Group2", "Test2"); + UserGroup expected2 = new UserGroup("Group2", "Test2", prov); + + Assert.IsNull(prov.AddUserGroup("Group1", "Test"), "AddUserGroup should return null"); + + AssertUserGroupsAreEqual(expected1, group1, true); + AssertUserGroupsAreEqual(expected2, group2, true); + + UserGroup[] allGroups = prov.GetUserGroups(); + Assert.AreEqual(2, allGroups.Length, "Wrong group count"); + Array.Sort(allGroups, delegate(UserGroup x, UserGroup y) { return x.Name.CompareTo(y.Name); }); + + AssertUserGroupsAreEqual(expected1, allGroups[0], true); + AssertUserGroupsAreEqual(expected2, allGroups[1], true); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void AddUserGroup_InvalidName(string n) { + IUsersStorageProviderV30 prov = GetProvider(); + prov.AddUserGroup(n, "Description"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void AddUserGroup_NullDescription() { + IUsersStorageProviderV30 prov = GetProvider(); + prov.AddUserGroup("Group", null); + } + + [Test] + public void ModifyUserGroup() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserGroup group1 = prov.AddUserGroup("Group1", "Description1"); + UserGroup group2 = prov.AddUserGroup("Group2", "Description2"); + + Assert.IsNull(prov.ModifyUserGroup(new UserGroup("Inexistent", "Descr", prov), "New"), "ModifyUserGroup should return null"); + + prov.SetUserMembership(prov.AddUser("user", "user", "pass", "user@server.com", true, DateTime.Now), new string[] { "Group2" }); + + UserGroup group2Out = prov.ModifyUserGroup(new UserGroup("Group2", "Description2", prov), "Mod"); + + UserGroup expected = new UserGroup("Group2", "Mod", prov); + expected.Users = new string[] { "user" }; + + AssertUserGroupsAreEqual(expected, group2Out, true); + + UserGroup[] allGroups = prov.GetUserGroups(); + Assert.AreEqual(2, allGroups.Length, "Wrong group count"); + Array.Sort(allGroups, delegate(UserGroup x, UserGroup y) { return x.Name.CompareTo(y.Name); }); + + AssertUserGroupsAreEqual(new UserGroup("Group1", "Description1", prov), allGroups[0], true); + AssertUserGroupsAreEqual(expected, allGroups[1], true); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ModifyUserGroup_NullGroup() { + IUsersStorageProviderV30 prov = GetProvider(); + prov.ModifyUserGroup(null, "Description"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ModifyUserGroup_NullDescription() { + IUsersStorageProviderV30 prov = GetProvider(); + prov.ModifyUserGroup(prov.AddUserGroup("Group", "Description"), null); + } + + [Test] + public void RemoveUserGroup() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserGroup group1 = prov.AddUserGroup("Group1", "Description1"); + UserGroup group2 = prov.AddUserGroup("Group2", "Description2"); + + Assert.IsFalse(prov.RemoveUserGroup(new UserGroup("Inexistent", "Descr", prov)), "RemoveUserGroup should return false"); + + Assert.IsTrue(prov.RemoveUserGroup(new UserGroup("Group1", "Desc", prov)), "RemoveUser should return true"); + + UserGroup[] allGroups = prov.GetUserGroups(); + Assert.AreEqual(1, allGroups.Length, "Wrong group count"); + + AssertUserGroupsAreEqual(group2, allGroups[0], true); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RemoveUserGroup_NullGroup() { + IUsersStorageProviderV30 prov = GetProvider(); + prov.RemoveUserGroup(null); + } + + [Test] + public void SetUserMembership() { + IUsersStorageProviderV30 prov = GetProvider(); + + DateTime dt = DateTime.Now; + + UserInfo user = prov.AddUser("user", "user", "pass", "user@server.com", true, dt); + UserGroup group1 = prov.AddUserGroup("Group1", ""); + UserGroup group2 = prov.AddUserGroup("Group2", ""); + + Assert.IsNull(prov.SetUserMembership(new UserInfo("user222", "user222", "user222@server.com", true, DateTime.Now, prov), new string[0]), + "SetUserMembership should return null"); + + Assert.IsNull(prov.SetUserMembership(user, new string[] { "Group2", "Inexistent" }), "SetUserMembership should return null"); + + UserInfo output = prov.SetUserMembership(user, new string[] { "Group2", "Group1" }); + AssertUserInfosAreEqual(new UserInfo("user", "user", "user@server.com", true, dt, prov), output, true); + Assert.AreEqual(2, output.Groups.Length, "Wrong group count"); + Array.Sort(output.Groups); + Assert.AreEqual("Group1", output.Groups[0], "Wrong group"); + Assert.AreEqual("Group2", output.Groups[1], "Wrong group"); + + UserInfo[] allUsers = prov.GetUsers(); + Assert.AreEqual(2, allUsers[0].Groups.Length, "Wrong group count"); + Array.Sort(allUsers[0].Groups); + Assert.AreEqual("Group1", allUsers[0].Groups[0], "Wrong group"); + Assert.AreEqual("Group2", allUsers[0].Groups[1], "Wrong group"); + + // Also test ModifyUser + UserInfo info = prov.ModifyUser(output, output.Username, "Pass", output.Email, output.Active); + Array.Sort(allUsers[0].Groups); + Assert.AreEqual("Group1", info.Groups[0], "Wrong group"); + Assert.AreEqual("Group2", info.Groups[1], "Wrong group"); + + UserGroup[] allGroups = prov.GetUserGroups(); + + Assert.AreEqual(2, allGroups.Length, "Wrong group count"); + + UserGroup expected1 = new UserGroup("Group1", "", prov); + expected1.Users = new string[] { "user" }; + UserGroup expected2 = new UserGroup("Group2", "", prov); + expected2.Users = new string[] { "user" }; + + Array.Sort(allGroups, delegate(UserGroup x, UserGroup y) { return x.Name.CompareTo(y.Name); }); + AssertUserGroupsAreEqual(expected1, allGroups[0], true); + AssertUserGroupsAreEqual(expected2, allGroups[1], true); + + output = prov.SetUserMembership(user, new string[0]); + AssertUserInfosAreEqual(new UserInfo("user", "user", "user@server.com", true, dt, prov), output, true); + Assert.AreEqual(0, output.Groups.Length, "Wrong group count"); + + allGroups = prov.GetUserGroups(); + + Assert.AreEqual(2, allGroups.Length, "Wrong group count"); + + expected1 = new UserGroup("Group1", "", prov); + expected2 = new UserGroup("Group2", "", prov); + + Array.Sort(allGroups, delegate(UserGroup x, UserGroup y) { return x.Name.CompareTo(y.Name); }); + AssertUserGroupsAreEqual(expected1, allGroups[0], true); + AssertUserGroupsAreEqual(expected2, allGroups[1], true); + + allUsers = prov.GetUsers(); + Assert.AreEqual(0, allUsers[0].Groups.Length, "Wrong group count"); + + // Also test ModifyUser + info = prov.ModifyUser(output, output.Username, "Pass", output.Email, output.Active); + Assert.AreEqual(0, info.Groups.Length, "Wrong group count"); + + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetUserMembership_NullUser() { + IUsersStorageProviderV30 prov = GetProvider(); + prov.SetUserMembership(null, new string[0]); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SetUserMembership_NullGroups() { + IUsersStorageProviderV30 prov = GetProvider(); + prov.SetUserMembership(prov.AddUser("user", "user", "pass", "user@server.com", true, DateTime.Now), null); + } + + [Test] + public void TryManualLogin() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo user = prov.AddUser("user", null, "password", "email@server.com", true, DateTime.Now); + prov.AddUser("user2", null, "password", "email2@server.com", false, DateTime.Now); + + UserInfo output = prov.TryManualLogin("inexistent", "password"); + Assert.IsNull(output, "TryManualLogin should return null"); + + output = prov.TryManualLogin("inexistent", ""); + Assert.IsNull(output, "TryManualLogin should return null"); + + output = prov.TryManualLogin("", "password"); + Assert.IsNull(output, "TryManualLogin should return null"); + + output = prov.TryManualLogin("user2", "password"); + Assert.IsNull(output, "TryManualLogin should return null because the account is inactive"); + + output = prov.TryManualLogin("user", "password"); + AssertUserInfosAreEqual(user, output, true); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void TryManualLogin_NullUsername() { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.TryManualLogin(null, "password"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void TryManualLogin_NullPassword() { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.AddUser("user", null, "password", "email@server.com", true, DateTime.Now); + prov.TryManualLogin("user", null); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void TryAutoLogin_NullContext() { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.TryAutoLogin(null); + } + + [Test] + public void GetUser() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo user = prov.AddUser("user", null, "password", "email@server.com", true, DateTime.Now); + + Assert.IsNull(prov.GetUser("inexistent"), "TryGetUser should return null"); + + UserInfo output = prov.GetUser("user"); + + AssertUserInfosAreEqual(user, output, true); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void GetUser_InvalidUsername(string u) { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.GetUser(u); + } + + [Test] + public void GetUserByEmail() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo user1 = prov.AddUser("user1", null, "password", "email1@server.com", true, DateTime.Now); + UserInfo user2 = prov.AddUser("user2", null, "password", "email2@server.com", true, DateTime.Now); + + Assert.IsNull(prov.GetUserByEmail("inexistent@server.com"), "TryGetUserByEmail should return null"); + + UserInfo output = prov.GetUserByEmail("email1@server.com"); + + AssertUserInfosAreEqual(user1, output, true); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void GetUserByEmail_InvalidEmail(string e) { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.GetUserByEmail(e); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void NotifyCookieLogin_NullUser() { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.NotifyCookieLogin(null); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void NotifyLogout_NullUser() { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.NotifyLogout(null); + } + + [Test] + public void StoreUserData_RetrieveUserData() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo user = new UserInfo("User", "User", "user@users.com", true, DateTime.Now, prov); + + Assert.IsFalse(prov.StoreUserData(user, "Key", "Value"), "StoreUserData should return false"); + + user = prov.AddUser("User", "User", "password", "user@users.com", true, DateTime.Now); + + Assert.IsTrue(prov.StoreUserData(user, "Key", "Value"), "StoreUserData should return true"); + Assert.IsTrue(prov.StoreUserData(user, "Key2", "Value2"), "StoreUserData should return true"); + string value = prov.RetrieveUserData(user, "Key"); + Assert.AreEqual("Value", value, "Wrong value"); + string value2 = prov.RetrieveUserData(user, "Key2"); + Assert.AreEqual("Value2", value2, "Wrong value"); + } + + [Test] + public void StoreUserData_RetrieveUserData_Overwrite() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo user = prov.AddUser("User", "User", "password", "user@users.com", true, DateTime.Now); + + Assert.IsTrue(prov.StoreUserData(user, "Key", "Value1"), "StoreUserData should return true"); + Assert.IsTrue(prov.StoreUserData(user, "Key2", "Value2"), "StoreUserData should return true"); + Assert.IsTrue(prov.StoreUserData(user, "Key", "Value"), "StoreUserData should return true"); + string value = prov.RetrieveUserData(user, "Key"); + Assert.AreEqual("Value", value, "Wrong value"); + string value2 = prov.RetrieveUserData(user, "Key2"); + Assert.AreEqual("Value2", value2, "Wrong value"); + } + + [Test] + public void StoreUserData_RetrieveUserData_NullValue() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo user = prov.AddUser("User", "User", "password", "user@users.com", true, DateTime.Now); + + Assert.IsTrue(prov.StoreUserData(user, "Key", "Value"), "StoreUserData should return true"); + Assert.IsTrue(prov.StoreUserData(user, "Key2", "Value2"), "StoreUserData should return true"); + Assert.IsTrue(prov.StoreUserData(user, "Key", null), "StoreUserData should return true"); + + string value = prov.RetrieveUserData(user, "Key"); + Assert.IsNull(value, "Wrong value"); + string value2 = prov.RetrieveUserData(user, "Key2"); + Assert.AreEqual("Value2", value2, "Wrong value"); + } + + [Test] + public void StoreUserData_RetrieveUserData_EmptyValue() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo user = prov.AddUser("User", "User", "password", "user@users.com", true, DateTime.Now); + + Assert.IsTrue(prov.StoreUserData(user, "Key", "Value"), "StoreUserData should return true"); + Assert.IsTrue(prov.StoreUserData(user, "Key2", "Value2"), "StoreUserData should return true"); + Assert.IsTrue(prov.StoreUserData(user, "Key", ""), "StoreUserData should return true"); + + string value = prov.RetrieveUserData(user, "Key"); + Assert.AreEqual("", value, "Wrong value"); + string value2 = prov.RetrieveUserData(user, "Key2"); + Assert.AreEqual("Value2", value2, "Wrong value"); + } + + [Test] + public void StoreUserData_RetrieveUserData_RemoveUser() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo user = prov.AddUser("User", "User", "password", "user@users.com", true, DateTime.Now); + UserInfo user2 = prov.AddUser("User2", "User2", "password2", "user2@users.com", true, DateTime.Now); + + Assert.IsTrue(prov.StoreUserData(user, "Key", "Value"), "StoreUserData should return true"); + Assert.IsTrue(prov.StoreUserData(user2, "Key", "Value"), "StoreUserData should return true"); + prov.RemoveUser(user); + + string value = prov.RetrieveUserData(user, "Key"); + Assert.IsNull(value, "Wrong value"); + string value2 = prov.RetrieveUserData(user2, "Key"); + Assert.AreEqual(value2, "Value", "Wrong value"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void StoreUserData_NullUser() { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.StoreUserData(null, "Key", "Value"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void StoreUserData_InvalidKey(string k) { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo user = new UserInfo("User", "User", "user@users.com", true, DateTime.Now, prov); + + prov.StoreUserData(user, k, "Value"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveUserData_NullUser() { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.RetrieveUserData(null, "Key"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void RetrieveUserData_InvalidKey(string k) { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo user = prov.AddUser("User", "User", "password", "user@users.com", true, DateTime.Now); + + prov.RetrieveUserData(user, k); + } + + [Test] + public void RetrieveUserData_InexistentKey() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo user = prov.AddUser("User", "User", "password", "user@users.com", true, DateTime.Now); + + Assert.IsNull(prov.RetrieveUserData(user, "Inexistent"), "RetrieveUserData should return null"); + } + + [Test] + public void GetUsersWithData() { + IUsersStorageProviderV30 prov = GetProvider(); + + UserInfo user1 = prov.AddUser("user1", "User1", "password", "user1@users.com", true, DateTime.Now); + UserInfo user2 = prov.AddUser("user2", "User2", "password", "user2@users.com", true, DateTime.Now); + UserInfo user3 = prov.AddUser("user3", "User3", "password", "user3@users.com", true, DateTime.Now); + UserInfo user4 = prov.AddUser("user4", "User4", "password", "user4@users.com", true, DateTime.Now); + + Assert.AreEqual(0, prov.GetUsersWithData("Key").Count, "Wrong user count"); + + prov.StoreUserData(user1, "Key", "Value"); + prov.StoreUserData(user2, "Key2", "Value"); + prov.StoreUserData(user4, "Key", "Value2"); + + IDictionary data = prov.GetUsersWithData("Key"); + + Assert.AreEqual(2, data.Count, "Wrong user count"); + + UserInfo[] users = new UserInfo[data.Count]; + data.Keys.CopyTo(users, 0); + + AssertUserInfosAreEqual(user1, users[0], true); + AssertUserInfosAreEqual(user4, users[1], true); + + Assert.AreEqual("Value", data[users[0]], "Wrong data"); + Assert.AreEqual("Value2", data[users[1]], "Wrong data"); + } + + [TestCase(null, ExpectedException = typeof(ArgumentNullException))] + [TestCase("", ExpectedException = typeof(ArgumentException))] + public void GetUsersWithData_InvalidKey(string k) { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.GetUsersWithData(k); + } + + [Test] + public void RetrieveAllUserData() { + IUsersStorageProviderV30 prov = GetProvider(); + + Assert.AreEqual(0, prov.RetrieveAllUserData(new UserInfo("Inexistent", "Inex", "inex@users.com", true, DateTime.Now, prov)).Count, "Wrong data count"); + + UserInfo user1 = prov.AddUser("user1", "User1", "password", "user1@users.com", true, DateTime.Now); + UserInfo user2 = prov.AddUser("user2", "User2", "password", "user2@users.com", true, DateTime.Now); + + Assert.AreEqual(0, prov.RetrieveAllUserData(user1).Count, "Wrong data count"); + + prov.StoreUserData(user1, "Key", "Value"); + prov.StoreUserData(user1, "Key2", "Value2"); + prov.StoreUserData(user2, "Key", "Value3"); + + IDictionary data = prov.RetrieveAllUserData(user1); + Assert.AreEqual(2, data.Count, "Wrong data count"); + Assert.AreEqual("Value", data["Key"], "Wrong data"); + Assert.AreEqual("Value2", data["Key2"], "Wrong data"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void RetrieveAllUserData_NullUser() { + IUsersStorageProviderV30 prov = GetProvider(); + + prov.RetrieveAllUserData(null); + } + + } + +} diff --git a/Tests.nunit b/Tests.nunit new file mode 100644 index 0000000..9b73a7a --- /dev/null +++ b/Tests.nunit @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests.xlsx b/Tests.xlsx new file mode 100644 index 0000000..7b6546b Binary files /dev/null and b/Tests.xlsx differ diff --git a/WebApplication/AccessDenied.aspx b/WebApplication/AccessDenied.aspx new file mode 100644 index 0000000..2df8207 --- /dev/null +++ b/WebApplication/AccessDenied.aspx @@ -0,0 +1,8 @@ +<%@ Page Language="C#" MasterPageFile="~/MasterPageSA.master" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.AccessDenied" Title="Untitled Page" Culture="auto" meta:resourcekey="PageResource1" UICulture="auto" Codebehind="AccessDenied.aspx.cs" %> + + + +

    + + +
    diff --git a/WebApplication/AccessDenied.aspx.cs b/WebApplication/AccessDenied.aspx.cs new file mode 100644 index 0000000..c47db19 --- /dev/null +++ b/WebApplication/AccessDenied.aspx.cs @@ -0,0 +1,34 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class AccessDenied : BasePage { + + protected void Page_Load(object sender, EventArgs e) { + Page.Title = Properties.Messages.AccessDeniedTitle + " - " + Settings.WikiTitle; + + string n = Content.GetPseudoCacheValue("AccessDeniedNotice"); + if(n == null) { + n = Settings.Provider.GetMetaDataItem(MetaDataItem.AccessDeniedNotice, null); + if(!string.IsNullOrEmpty(n)) { + n = FormattingPipeline.FormatWithPhase1And2(n, false, FormattingContext.Other, null); + Content.SetPseudoCacheValue("AccessDeniedNotice", n); + } + } + if(!string.IsNullOrEmpty(n)) lblDescription.Text = FormattingPipeline.FormatWithPhase3(n, FormattingContext.Other, null); + } + + } + +} diff --git a/WebApplication/AccessDenied.aspx.designer.cs b/WebApplication/AccessDenied.aspx.designer.cs new file mode 100644 index 0000000..da867a8 --- /dev/null +++ b/WebApplication/AccessDenied.aspx.designer.cs @@ -0,0 +1,34 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.3074 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AccessDenied { + + /// + /// lblTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblTitle; + + /// + /// lblDescription control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDescription; + } +} diff --git a/WebApplication/AclActionsSelector.ascx b/WebApplication/AclActionsSelector.ascx new file mode 100644 index 0000000..3b5bd92 --- /dev/null +++ b/WebApplication/AclActionsSelector.ascx @@ -0,0 +1,24 @@ +<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="AclActionsSelector.ascx.cs" Inherits="ScrewTurn.Wiki.AclActionsSelector" %> + + + + + + + + + + +
    + + + +
    + + + +
    diff --git a/WebApplication/AclActionsSelector.ascx.cs b/WebApplication/AclActionsSelector.ascx.cs new file mode 100644 index 0000000..846c860 --- /dev/null +++ b/WebApplication/AclActionsSelector.ascx.cs @@ -0,0 +1,207 @@ + +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; + +namespace ScrewTurn.Wiki { + + public partial class AclActionsSelector : System.Web.UI.UserControl { + + protected void Page_Load(object sender, EventArgs e) { + } + + /// + /// Gets or sets the name of the resource of which to display the actions. + /// + public AclResources CurrentResource { + get { + object temp = ViewState["CR"]; + if(temp == null) return AclResources.Globals; + else return (AclResources)temp; + } + set { + ViewState["CR"] = value; + Render(); + } + } + + /// + /// Renders the items in the list. + /// + private void Render() { + AclResources res = CurrentResource; + + string[] temp = null; + switch(res) { + case AclResources.Globals: + temp = Actions.ForGlobals.All; + break; + case AclResources.Namespaces: + temp = Actions.ForNamespaces.All; + break; + case AclResources.Pages: + temp = Actions.ForPages.All; + break; + case AclResources.Directories: + temp = Actions.ForDirectories.All; + break; + default: + throw new NotSupportedException("ACL Resource not supported"); + } + + // Add full-control action + string[] actions = new string[temp.Length + 1]; + actions[0] = Actions.FullControl; + Array.Copy(temp, 0, actions, 1, temp.Length); + + lstActionsGrant.Items.Clear(); + lstActionsDeny.Items.Clear(); + foreach(string action in actions) { + ListItem item = new ListItem(GetName(res, action), action); + lstActionsGrant.Items.Add(item); + ListItem itemBlank = new ListItem(" ", action); + lstActionsDeny.Items.Add(itemBlank); + } + } + + private string GetName(AclResources res, string action) { + switch(res) { + case AclResources.Globals: + return Actions.ForGlobals.GetFullName(action); + case AclResources.Namespaces: + return Actions.ForNamespaces.GetFullName(action); + case AclResources.Pages: + return Actions.ForPages.GetFullName(action); + case AclResources.Directories: + return Actions.ForDirectories.GetFullName(action); + default: + throw new NotSupportedException("ACL Resource not supported"); + } + } + + /// + /// Gets or sets the list of the granted actions. + /// + public string[] GrantedActions { + get { + List actions = new List(); + foreach(ListItem item in lstActionsGrant.Items) { + if(item.Selected) actions.Add(item.Value); + } + return actions.ToArray(); + } + set { + if(value == null) throw new ArgumentNullException("value"); + + SelectActions(value, DeniedActions); + } + } + + /// + /// Gets or sets the list of the denied actions. + /// + public string[] DeniedActions { + get { + List actions = new List(); + foreach(ListItem item in lstActionsDeny.Items) { + if(item.Selected) actions.Add(item.Value); + } + return actions.ToArray(); + } + set { + if(value == null) throw new ArgumentNullException("value"); + + SelectActions(GrantedActions, value); + } + } + + private void SelectActions(string[] granted, string[] denied) { + // Deselect and enable all items first + foreach(ListItem item in lstActionsGrant.Items) { + item.Selected = false; + item.Enabled = true; + } + foreach(ListItem item in lstActionsDeny.Items) { + item.Selected = false; + item.Enabled = true; + } + + // Select specific ones + foreach(ListItem item in lstActionsGrant.Items) { + item.Selected = Array.Find(granted, delegate(string s) { return s == item.Value; }) != null; + } + foreach(ListItem item in lstActionsDeny.Items) { + item.Selected = Array.Find(denied, delegate(string s) { return s == item.Value; }) != null; + } + + SetupCheckBoxes(null); + } + + protected void lstActions_SelectedIndexChanged(object sender, EventArgs e) { + SetupCheckBoxes(sender as Anthem.CheckBoxList); + } + + private void SetupCheckBoxes(Anthem.CheckBoxList list) { + // Setup the checkboxes so that full-control takes over the others, + // and there cannot be an action that is both granted and denied + // The list parameter determines the last checkbox list that changed status, + // allowing to switch the proper checkbox pair + if(lstActionsGrant.Items.Count > 0) { + if(list == null) list = lstActionsGrant; + Anthem.CheckBoxList other = list == lstActionsGrant ? lstActionsDeny : lstActionsGrant; + + // Verify whether full-control is checked + // If so, disable all other checkboxes + for(int i = 1; i < list.Items.Count; i++) { + if(list.Items[0].Selected) { + list.Items[i].Selected = false; + list.Items[i].Enabled = false; + other.Items[i].Enabled = true; + } + else { + list.Items[i].Enabled = true; + } + } + + // Switch status of other list checkboxes + for(int i = 0; i < other.Items.Count; i++) { + if(i > 0 && other.Items[0].Selected) { + other.Items[i].Selected = false; + other.Items[i].Enabled = false; + } + else { + if(other.Items[i].Enabled && list.Items[i].Enabled && list.Items[i].Selected) { + other.Items[i].Selected = false; + } + } + } + } + } + + } + + /// + /// Lists legal ACL resources. + /// + public enum AclResources { + /// + /// Global resources. + /// + Globals, + /// + /// Namespaces. + /// + Namespaces, + /// + /// Pages. + /// + Pages, + /// + /// Directories. + /// + Directories + } + +} diff --git a/WebApplication/AclActionsSelector.ascx.designer.cs b/WebApplication/AclActionsSelector.ascx.designer.cs new file mode 100644 index 0000000..944059b --- /dev/null +++ b/WebApplication/AclActionsSelector.ascx.designer.cs @@ -0,0 +1,52 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.3074 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AclActionsSelector { + + /// + /// lblGrant control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblGrant; + + /// + /// lblDeny control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDeny; + + /// + /// lstActionsGrant control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBoxList lstActionsGrant; + + /// + /// lstActionsDeny control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBoxList lstActionsDeny; + } +} diff --git a/WebApplication/Admin.aspx b/WebApplication/Admin.aspx new file mode 100644 index 0000000..d035a13 --- /dev/null +++ b/WebApplication/Admin.aspx @@ -0,0 +1 @@ +<%@ Page Language="C#" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.Admin" Title="Admin Redirect" Codebehind="Admin.aspx.cs" %> diff --git a/WebApplication/Admin.aspx.cs b/WebApplication/Admin.aspx.cs new file mode 100644 index 0000000..d6a2598 --- /dev/null +++ b/WebApplication/Admin.aspx.cs @@ -0,0 +1,32 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text.RegularExpressions; +using System.Threading; +using System.Web; +using System.Web.Configuration; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using System.Net; +using ScrewTurn.Wiki.PluginFramework; +using System.Text; + +namespace ScrewTurn.Wiki { + + public partial class Admin : BasePage { + + protected void Page_Load(object sender, EventArgs e) { + Response.Redirect("AdminHome.aspx"); + } + + } + +} diff --git a/WebApplication/Admin.aspx.designer.cs b/WebApplication/Admin.aspx.designer.cs new file mode 100644 index 0000000..1005e1d --- /dev/null +++ b/WebApplication/Admin.aspx.designer.cs @@ -0,0 +1,16 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.3053 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class Admin { + } +} diff --git a/WebApplication/Admin.master b/WebApplication/Admin.master new file mode 100644 index 0000000..60b5bd2 --- /dev/null +++ b/WebApplication/Admin.master @@ -0,0 +1,79 @@ +<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Admin.master.cs" Inherits="ScrewTurn.Wiki.AdminMaster" %> + + + + + + Administration + + + + + + + +
    +
    + + + + +

    + <% Response.Write(ScrewTurn.Wiki.Properties.Messages.Refresh); %> + - + » +

    + +
    + +
    + + + + + + + + + + + + +
    + + +


    + +
    +

    GNU General Public License v2

    +

    This Program is released under the GNU General Public License v2. You cannot remove this statement.
    + View the GNU General Public License v2 or visit the GNU website.

    +
    + + + + +
    +
    + + diff --git a/WebApplication/Admin.master.cs b/WebApplication/Admin.master.cs new file mode 100644 index 0000000..f204f41 --- /dev/null +++ b/WebApplication/Admin.master.cs @@ -0,0 +1,249 @@ + +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class AdminMaster : System.Web.UI.MasterPage { + + protected void Page_Load(object sender, EventArgs e) { + StringBuilder sb = new StringBuilder(100); + sb.Append(""); + lblStrings.Text = sb.ToString(); + + Page.Title = Properties.Messages.AdminTitle + " - " + Settings.WikiTitle; + + if(!string.IsNullOrEmpty(Request.UserAgent) && Request.UserAgent.ToLowerInvariant().Contains("konqueror") || + Request.UserAgent.ToLowerInvariant().Contains("safari")) { + lblBrowserSupport.Visible = true; + } + + lblJS.Text = Tools.GetJavaScriptIncludes(); + + SetupButtons(); + + SetupButtonsVisibility(); + } + + /// + /// Redirects to the login page if needed. + /// + public static void RedirectToLoginIfNeeded() { + if(SessionFacade.LoginKey == null) { + UrlTools.Redirect("Login.aspx?Redirect=" + Tools.UrlEncode(HttpContext.Current.Request.Url.ToString())); + } + } + + private readonly Dictionary HyperlinkMap = new Dictionary() { + { "admingroups", "lnkSelectGroups" }, + { "adminusers", "lnkSelectAccounts" }, + { "adminnamespaces", "lnkSelectNamespaces" }, + { "adminpages", "lnkSelectPages" }, + { "admincontent", "lnkSelectContent" }, + { "adminlog", "lnkSelectLog" }, + { "adminconfig", "lnkSelectConfig" }, + { "adminsnippets", "lnkSelectSnippets" }, + { "admincategories", "lnkSelectCategories" }, + { "adminhome", "lnkSelectAdminHome" }, + { "adminnavpaths", "lnkSelectNavPaths" }, + { "adminproviders", "lnkSelectProviders" } + }; + + /// + /// Sets up the buttons state. + /// + private void SetupButtons() { + string selectedPage = System.IO.Path.GetFileNameWithoutExtension(Request.PhysicalPath).ToLowerInvariant(); + + HyperLink hyperLink = (HyperLink)FindControl(HyperlinkMap[selectedPage]); + + lnkSelectGroups.CssClass = "tab"; + lnkSelectAccounts.CssClass = "tab"; + lnkSelectNamespaces.CssClass = "tab"; + lnkSelectPages.CssClass = "tab"; + lnkSelectContent.CssClass = "tab"; + lnkSelectLog.CssClass = "tab"; + lnkSelectConfig.CssClass = "tab"; + lnkSelectSnippets.CssClass = "tab"; + lnkSelectCategories.CssClass = "tab"; + lnkSelectAdminHome.CssClass = "tab"; + lnkSelectNavPaths.CssClass = "tab"; + lnkSelectProviders.CssClass = "tab"; + + hyperLink.CssClass = "tabselected"; + } + + /// + /// Sets up the buttons visibility based on the current user's permissions. + /// + private void SetupButtonsVisibility() { + string currentUser = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + + // Categories (can manage categories in at least one NS) + lnkSelectCategories.Visible = CanManageCategories(currentUser, currentGroups); + + // Configuration (can manage config) + lnkSelectConfig.Visible = CanManageConfiguration(currentUser, currentGroups); + + // Content (can manage config) + lnkSelectContent.Visible = CanManageConfiguration(currentUser, currentGroups); + + // Groups (can manage groups) + lnkSelectGroups.Visible = CanManageGroups(currentUser, currentGroups); + + // Home (can manage config) + lnkSelectAdminHome.Visible = CanManageConfiguration(currentUser, currentGroups); + + // Log (can manage config) + lnkSelectLog.Visible = CanManageConfiguration(currentUser, currentGroups); + + // Namespaces (can manage namespaces) + lnkSelectNamespaces.Visible = CanManageNamespaces(currentUser, currentGroups); + + // Nav. Paths (can manage pages in at least one NS) + lnkSelectNavPaths.Visible = CanManagePages(currentUser, currentGroups); + + // Pages + // Always displayed because checking every page can take too much time + + // Providers (can manage providers) + lnkSelectProviders.Visible = CanManageProviders(currentUser, currentGroups); + + // Snippets (can manage snippets) + lnkSelectSnippets.Visible = CanManageSnippetsAndTemplates(currentUser, currentGroups); + + // Accounts (can manage user accounts) + lnkSelectAccounts.Visible = CanManageUsers(currentUser, currentGroups); + } + + /// + /// Determines whether a user can manage the configuration. + /// + /// The username. + /// The groups. + /// true if the user can manage the configuration, false otherwise. + public static bool CanManageConfiguration(string username, string[] groups) { + bool canManageConfiguration = AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageConfiguration, username, groups); + return canManageConfiguration; + } + + /// + /// Determines whether a user can manage categories in at least one namespace. + /// + /// The username. + /// The groups. + /// true if the user can manage categories in at least one namespace, false otherwise. + public static bool CanManageCategories(string username, string[] groups) { + if(AuthChecker.CheckActionForNamespace(null, Actions.ForNamespaces.ManageCategories, username, groups)) return true; + + foreach(NamespaceInfo ns in Pages.GetNamespaces()) { + if(AuthChecker.CheckActionForNamespace(ns, Actions.ForNamespaces.ManageCategories, username, groups)) return true; + } + + return false; + } + + /// + /// Determines whether a user can manage user groups. + /// + /// The username. + /// The groups. + /// true if the user can manage groups, false otherwise. + public static bool CanManageGroups(string username, string[] groups) { + bool canManageGroups = AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageGroups, username, groups); + return canManageGroups; + } + + /// + /// Determines whether a user can manage permissions. + /// + /// The username. + /// The groups. + /// true if the user can manage permissions, false otherwise. + public static bool CanManagePermissions(string username, string[] groups) { + bool canManagePermissions = AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManagePermissions, username, groups); + return canManagePermissions; + } + + /// + /// Determines whether a user can manage namespaces. + /// + /// The username. + /// The groups. + /// true if the user can manage namespace, false otherwise. + public static bool CanManageNamespaces(string username, string[] groups) { + bool canManageNamespaces = AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageNamespaces, username, groups); + return canManageNamespaces; + } + + /// + /// Determines whether a user can manage pages in at least one namespace. + /// + /// The username. + /// The groups. + /// true if the the user can manage pages in at least one namespace, false otherwise. + public static bool CanManagePages(string username, string[] groups) { + if(AuthChecker.CheckActionForNamespace(null, Actions.ForNamespaces.ManagePages, username, groups)) return true; + + foreach(NamespaceInfo ns in Pages.GetNamespaces()) { + if(AuthChecker.CheckActionForNamespace(ns, Actions.ForNamespaces.ManagePages, username, groups)) return true; + } + + return false; + } + + /// + /// Determines whether a user can manage providers. + /// + /// The username. + /// The groups. + /// true if the user can manage providers, false otherwise. + public static bool CanManageProviders(string username, string[] groups) { + bool canManageProviders = AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageProviders, username, groups); + return canManageProviders; + } + + /// + /// Determines whether a user can manage snippets and templates. + /// + /// The username. + /// The groups. + /// true if the user can manage snippets and templates, false otherwise. + public static bool CanManageSnippetsAndTemplates(string username, string[] groups) { + bool canManageSnippets = AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageSnippetsAndTemplates, username, groups); + return canManageSnippets; + } + + /// + /// Determines whether a user can manager user accounts. + /// + /// The username. + /// The groups. + /// true if the user can manage user accounts, false otherwise. + public static bool CanManageUsers(string username, string[] groups) { + bool canManageUsers = AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManageAccounts, username, groups); + return canManageUsers; + } + + /// + /// Determines whether a user can approve/reject a draft of a page. + /// + /// The page. + /// The username. + /// The groups. + /// true if the user can approve/reject a draft of the page, false otherwise. + public static bool CanApproveDraft(PageInfo page, string username, string[] groups) { + return Pages.CanApproveDraft(page, username, groups); + } + + } + +} diff --git a/WebApplication/Admin.master.designer.cs b/WebApplication/Admin.master.designer.cs new file mode 100644 index 0000000..019de22 --- /dev/null +++ b/WebApplication/Admin.master.designer.cs @@ -0,0 +1,196 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AdminMaster { + + /// + /// lblJS control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblJS; + + /// + /// head control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.ContentPlaceHolder head; + + /// + /// frmAdmin control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.HtmlControls.HtmlForm frmAdmin; + + /// + /// lblStrings control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblStrings; + + /// + /// lblAdminTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblAdminTitle; + + /// + /// lblHomeLink control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblHomeLink; + + /// + /// lblBrowserSupport control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblBrowserSupport; + + /// + /// lnkSelectAdminHome control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkSelectAdminHome; + + /// + /// lnkSelectGroups control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkSelectGroups; + + /// + /// lnkSelectAccounts control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkSelectAccounts; + + /// + /// lnkSelectNamespaces control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkSelectNamespaces; + + /// + /// lnkSelectPages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkSelectPages; + + /// + /// lnkSelectCategories control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkSelectCategories; + + /// + /// lnkSelectSnippets control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkSelectSnippets; + + /// + /// lnkSelectNavPaths control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkSelectNavPaths; + + /// + /// lnkSelectContent control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkSelectContent; + + /// + /// lnkSelectLog control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkSelectLog; + + /// + /// lnkSelectProviders control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkSelectProviders; + + /// + /// lnkSelectConfig control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkSelectConfig; + + /// + /// cphAdmin control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.ContentPlaceHolder cphAdmin; + } +} diff --git a/WebApplication/AdminCategories.aspx b/WebApplication/AdminCategories.aspx new file mode 100644 index 0000000..d56c320 --- /dev/null +++ b/WebApplication/AdminCategories.aspx @@ -0,0 +1,171 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Admin.master" AutoEventWireup="true" CodeBehind="AdminCategories.aspx.cs" Inherits="ScrewTurn.Wiki.AdminCategories" culture="auto" meta:resourcekey="PageResource1" uiculture="auto" %> + +<%@ Register TagPrefix="st" TagName="ProviderSelector" Src="~/ProviderSelector.ascx" %> +<%@ Register TagPrefix="st" TagName="PageListBuilder" Src="~/PageListBuilder.ascx" %> + + + + + +

    + + +
    +

    + +
    + + + + +

    + +
    + +
    +
    + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +  
    <%# ScrewTurn.Wiki.PluginFramework.NameTools.GetLocalName((string)Eval("FullName")) %><%# Eval("PageCount") %><%# Eval("Provider") %>
    <%# ScrewTurn.Wiki.PluginFramework.NameTools.GetLocalName((string)Eval("FullName")) %><%# Eval("PageCount") %><%# Eval("Provider") %>
    + +
    +
    +
    + + +
    +

    + ()

    +
    + +
    +

    +
    +
    + +

    + +
    + + + +
    + +
    +

    +
    +
    + +

    + +
    + +
    + +
    +

    +
    + +
    + +
    + +
    + +
    + +
    +
    + + +
    +
    +

    + + +

    + + +
    + +
    +

    +
    + +
    +
    + +
    +

    + +

    + +

    + +
    + +
    + +
    + +
    +
    +
    + + + +
    + +
    diff --git a/WebApplication/AdminCategories.aspx.cs b/WebApplication/AdminCategories.aspx.cs new file mode 100644 index 0000000..fe65aec --- /dev/null +++ b/WebApplication/AdminCategories.aspx.cs @@ -0,0 +1,429 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class AdminCategories : BasePage { + + protected void Page_Load(object sender, EventArgs e) { + AdminMaster.RedirectToLoginIfNeeded(); + + bool canManageCategories = AdminMaster.CanManageCategories(SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames()); + if(!canManageCategories) UrlTools.Redirect("AccessDenied.aspx"); + + if(!Page.IsPostBack) { + // Load namespaces + + // Add root namespace + lstNamespace.Items.Add(new ListItem("", "")); + + List namespaces = Pages.GetNamespaces(); + + foreach(NamespaceInfo ns in namespaces) { + lstNamespace.Items.Add(new ListItem(ns.Name, ns.Name)); + } + + // Load pages + rptCategories.DataBind(); + } + + btnNewCategory.Enabled = CanManageCategoriesInCurrentNamespace(); + btnBulkManage.Enabled = btnNewCategory.Enabled; + } + + protected void cvNewCategory_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = Pages.IsValidName(txtNewCategory.Text); + } + + protected void btnNewCategory_Click(object sender, EventArgs e) { + if(!CanManageCategoriesInCurrentNamespace()) return; + + lblNewCategoryResult.CssClass = ""; + lblNewCategoryResult.Text = ""; + + if(!Page.IsValid) return; + + txtNewCategory.Text = txtNewCategory.Text.Trim(); + if(txtNewCategory.Text.Length == 0) { + return; + } + + if(Pages.FindCategory(NameTools.GetFullName(lstNamespace.SelectedValue, txtNewCategory.Text)) != null) { + lblNewCategoryResult.CssClass = "resulterror"; + lblNewCategoryResult.Text = Properties.Messages.CategoryAlreadyExists; + return; + } + else { + Log.LogEntry("Category creation requested for " + txtNewCategory.Text, EntryType.General, Log.SystemUsername); + + if(Pages.CreateCategory(lstNamespace.SelectedValue, txtNewCategory.Text)) { + txtNewCategory.Text = ""; + lblNewCategoryResult.CssClass = "resultok"; + lblNewCategoryResult.Text = Properties.Messages.CategoryCreated; + RefreshList(); + } + else { + lblNewCategoryResult.CssClass = "resulterror"; + lblNewCategoryResult.Text = Properties.Messages.CouldNotCreateCategory; + } + } + } + + protected void lstNamespace_SelectedIndexChanged(object sender, EventArgs e) { + rptCategories.DataBind(); + btnNewCategory.Enabled = CanManageCategoriesInCurrentNamespace(); + } + + /// + /// Returns a value indicating whether the current user can manage categories in the selected namespace. + /// + /// true if the user can manage categories, false otherwise. + private bool CanManageCategoriesInCurrentNamespace() { + NamespaceInfo nspace = Pages.FindNamespace(lstNamespace.SelectedValue); + bool canManageCategories = AuthChecker.CheckActionForNamespace(nspace, Actions.ForNamespaces.ManageCategories, + SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames()); + return canManageCategories; + } + + protected void rptCategories_DataBinding(object sender, EventArgs e) { + NamespaceInfo nspace = Pages.FindNamespace(lstNamespace.SelectedValue); + bool canManageCategories = CanManageCategoriesInCurrentNamespace(); + List categories = Pages.GetCategories(nspace); + + List result = new List(categories.Count); + + foreach(CategoryInfo cat in categories) { + result.Add(new CategoryRow(cat, canManageCategories, txtCurrentCategory.Value == cat.FullName)); + } + + rptCategories.DataSource = result; + } + + /// + /// Refreshes the pages list. + /// + private void RefreshList() { + txtCurrentCategory.Value = ""; + ResetEditor(); + ResetBulkEditor(); + rptCategories.DataBind(); + } + + /// + /// Returns to the group list. + /// + private void ReturnToList() { + pnlEditCategory.Visible = false; + pnlBulkManage.Visible = false; + pnlList.Visible = true; + } + + /// + /// Resets the editor. + /// + private void ResetEditor() { + btnRename.Enabled = true; + txtNewName.Text = ""; + lstDestinationCategory.Items.Clear(); + btnMerge.Enabled = true; + } + + /// + /// Clears all the result labels. + /// + private void ClearResultLabels() { + lblNewCategoryResult.CssClass = ""; + lblNewCategoryResult.Text = ""; + lblRenameResult.CssClass = ""; + lblRenameResult.Text = ""; + lblMergeResult.CssClass = ""; + lblMergeResult.Text = ""; + } + + protected void rptCategories_ItemCommand(object sender, CommandEventArgs e) { + if(e.CommandName == "Select") { + if(!CanManageCategoriesInCurrentNamespace()) return; + + txtCurrentCategory.Value = e.CommandArgument as string; + lblCurrentCategory.Text = txtCurrentCategory.Value; + + txtNewName.Text = NameTools.GetLocalName(txtCurrentCategory.Value); + + // Load target directories for merge function + lstDestinationCategory.Items.Clear(); + List categories = Pages.GetCategories(Pages.FindNamespace(lstNamespace.SelectedValue)); + foreach(CategoryInfo cat in categories) { + if(cat.FullName != txtCurrentCategory.Value) { + string name = NameTools.GetLocalName(cat.FullName); + lstDestinationCategory.Items.Add(new ListItem(name, cat.FullName)); + } + } + btnMerge.Enabled = lstDestinationCategory.Items.Count > 0; + + pnlEditCategory.Visible = true; + pnlList.Visible = false; + + ClearResultLabels(); + } + } + + protected void cvNewName_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = Pages.IsValidName(txtNewName.Text); + } + + protected void btnRename_Click(object sender, EventArgs e) { + if(!CanManageCategoriesInCurrentNamespace()) return; + + lblRenameResult.CssClass = ""; + lblRenameResult.Text = ""; + + if(!Page.IsValid) return; + + txtNewName.Text = txtNewName.Text.Trim(); + + if(txtNewName.Text.ToLowerInvariant() == txtCurrentCategory.Value.ToLowerInvariant()) { + return; + } + + if(Pages.FindCategory(NameTools.GetFullName(lstNamespace.SelectedValue, txtNewName.Text)) != null) { + lblRenameResult.CssClass = "resulterror"; + lblRenameResult.Text = Properties.Messages.CategoryAlreadyExists; + return; + } + + Log.LogEntry("Category rename requested for " + txtCurrentCategory.Value + " to " + txtNewName.Text, EntryType.General, Log.SystemUsername); + + if(Pages.RenameCategory(Pages.FindCategory(txtCurrentCategory.Value), txtNewName.Text)) { + RefreshList(); + lblRenameResult.CssClass = "resultok"; + lblRenameResult.Text = Properties.Messages.CategoryRenamed; + ReturnToList(); + } + else { + lblRenameResult.CssClass = "resulterror"; + lblRenameResult.Text = Properties.Messages.CouldNotRenameCategory; + } + } + + protected void btnMerge_Click(object sender, EventArgs e) { + if(!CanManageCategoriesInCurrentNamespace()) return; + + CategoryInfo source = Pages.FindCategory(txtCurrentCategory.Value); + CategoryInfo dest = Pages.FindCategory(lstDestinationCategory.SelectedValue); + + Log.LogEntry("Category merge requested for " + txtCurrentCategory.Value + " into " + lstDestinationCategory.SelectedValue, EntryType.General, Log.SystemUsername); + + if(Pages.MergeCategories(source, dest)) { + RefreshList(); + lblMergeResult.CssClass = "resultok"; + lblMergeResult.Text = Properties.Messages.CategoriesMerged; + ReturnToList(); + } + else { + lblMergeResult.CssClass = "resulterror"; + lblMergeResult.Text = Properties.Messages.CouldNotMergeCategories; + } + } + + protected void btnDelete_Click(object sender, EventArgs e) { + if(!CanManageCategoriesInCurrentNamespace()) return; + + Log.LogEntry("Category deletion requested for " + txtCurrentCategory.Value, EntryType.General, Log.SystemUsername); + + if(Pages.RemoveCategory(Pages.FindCategory(txtCurrentCategory.Value))) { + RefreshList(); + lblDeleteResult.CssClass = "resultok"; + lblDeleteResult.Text = Properties.Messages.CategoryDeleted; + ReturnToList(); + } + else { + lblDeleteResult.CssClass = "resulterror"; + lblDeleteResult.Text = Properties.Messages.CouldNotDeleteCategory; + } + } + + protected void btnBack_Click(object sender, EventArgs e) { + RefreshList(); + ReturnToList(); + } + + /// + /// Resets the bulk management editor. + /// + private void ResetBulkEditor() { + pageListBuilder.ResetControl(); + lstBulkCategories.Items.Clear(); + + rdoBulkAdd.Checked = true; + rdoBulkReplace.Checked = false; + + lblBulkResult.CssClass = ""; + lblBulkResult.Text = ""; + } + + protected void providerSelector_SelectedProviderChanged(object sender, EventArgs e) { + pageListBuilder.CurrentProvider = providerSelector.SelectedProvider; + RefreshBulkCategoryList(); + } + + protected void btnBulkBack_Click(object sender, EventArgs e) { + RefreshList(); + ReturnToList(); + } + + protected void btnBulkManage_Click(object sender, EventArgs e) { + ResetBulkEditor(); + pageListBuilder.CurrentNamespace = lstNamespace.SelectedValue; + pageListBuilder.CurrentProvider = providerSelector.SelectedProvider; + RefreshBulkCategoryList(); + pnlBulkManage.Visible = true; + pnlList.Visible = false; + } + + /// + /// Refreshes the bulk category list. + /// + private void RefreshBulkCategoryList() { + lstBulkCategories.Items.Clear(); + + string cp = providerSelector.SelectedProvider; + NamespaceInfo nspace = Pages.FindNamespace(lstNamespace.SelectedValue); + var categories = + from c in Pages.GetCategories(nspace) + where c.Provider.GetType().FullName == cp + select c; + + foreach(CategoryInfo cat in categories) { + ListItem item = new ListItem(NameTools.GetLocalName(cat.FullName), cat.FullName); + lstBulkCategories.Items.Add(item); + } + } + + protected void btnBulkSave_Click(object sender, EventArgs e) { + lblBulkResult.CssClass = ""; + lblBulkResult.Text = ""; + + List selectedPages = new List(20); + foreach(string pg in pageListBuilder.SelectedPages) { + PageInfo page = Pages.FindPage(pg); + if(page != null) selectedPages.Add(page); + } + + List selectedCategories = new List(lstBulkCategories.Items.Count); + foreach(ListItem item in lstBulkCategories.Items) { + if(item.Selected) { + CategoryInfo cat = Pages.FindCategory(item.Value); + if(cat != null) selectedCategories.Add(cat); + } + } + + if(selectedPages.Count == 0) { + lblBulkResult.CssClass = "resulterror"; + lblBulkResult.Text = Properties.Messages.NoPages; + return; + } + + if(rdoBulkAdd.Checked && selectedCategories.Count == 0) { + lblBulkResult.CssClass = "resulterror"; + lblBulkResult.Text = Properties.Messages.NoCategories; + return; + } + + Log.LogEntry("Bulk rebind requested", EntryType.General, SessionFacade.CurrentUsername); + + foreach(PageInfo page in selectedPages) { + CategoryInfo[] cats = null; + if(rdoBulkAdd.Checked) { + // Merge selected categories with previous ones + List existing = new List(Pages.GetCategoriesForPage(page)); + + foreach(CategoryInfo newCat in selectedCategories) { + if(existing.Find((c) => { return c.FullName == newCat.FullName; }) == null) { + existing.Add(newCat); + } + } + + existing.Sort(new CategoryNameComparer()); + + cats = existing.ToArray(); + } + else { + // Replace old binding + cats = selectedCategories.ToArray(); + } + + Pages.Rebind(page, cats); + } + + lblBulkResult.CssClass = "resultok"; + lblBulkResult.Text = Properties.Messages.BulkRebindCompleted; + } + + } + + /// + /// Represents a category for display purposes. + /// + public class CategoryRow { + + private string fullName, pageCount, provider, additionalClass; + private bool canSelect; + + /// + /// Initializes a new instance of the class. + /// + /// The original category. + /// A value indicating whether the category can be selected. + /// A value indicating whether the category is selected. + public CategoryRow(CategoryInfo category, bool canSelect, bool selected) { + fullName = category.FullName; + pageCount = category.Pages.Length.ToString(); + provider = category.Provider.Information.Name; + this.canSelect = canSelect; + additionalClass = selected ? " selected" : ""; + } + + /// + /// Gets the full name. + /// + public string FullName { + get { return fullName; } + } + + /// + /// Gets the page count. + /// + public string PageCount { + get { return pageCount; } + } + + /// + /// Gets the provider. + /// + public string Provider { + get { return provider; } + } + + /// + /// Gets a value indicating whether the current category can be selected. + /// + public bool CanSelect { + get { return canSelect; } + } + + /// + /// Gets the CSS additional class. + /// + public string AdditionalClass { + get { return additionalClass; } + } + + } + +} diff --git a/WebApplication/AdminCategories.aspx.designer.cs b/WebApplication/AdminCategories.aspx.designer.cs new file mode 100644 index 0000000..29527da --- /dev/null +++ b/WebApplication/AdminCategories.aspx.designer.cs @@ -0,0 +1,412 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4016 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AdminCategories { + + /// + /// lblCategories control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCategories; + + /// + /// pnlList control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlList; + + /// + /// lblNewCategory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNewCategory; + + /// + /// txtNewCategory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtNewCategory; + + /// + /// btnNewCategory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnNewCategory; + + /// + /// rfvNewCategory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RequiredFieldValidator rfvNewCategory; + + /// + /// cvNewCategory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CustomValidator cvNewCategory; + + /// + /// lblNewCategoryResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblNewCategoryResult; + + /// + /// btnBulkManage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnBulkManage; + + /// + /// lblNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNamespace; + + /// + /// lstNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstNamespace; + + /// + /// rptCategories control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Repeater rptCategories; + + /// + /// pnlEditCategory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlEditCategory; + + /// + /// lblEditTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblEditTitle; + + /// + /// lblCurrentCategory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCurrentCategory; + + /// + /// lblRenameCategoryTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRenameCategoryTitle; + + /// + /// lblNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNewName; + + /// + /// txtNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtNewName; + + /// + /// btnRename control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnRename; + + /// + /// rfvNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RequiredFieldValidator rfvNewName; + + /// + /// cvNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CustomValidator cvNewName; + + /// + /// lblRenameResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblRenameResult; + + /// + /// lblMergeCategoryTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblMergeCategoryTitle; + + /// + /// lblMergeInto control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblMergeInto; + + /// + /// lstDestinationCategory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstDestinationCategory; + + /// + /// btnMerge control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnMerge; + + /// + /// lblMergeResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblMergeResult; + + /// + /// lblDeleteCategory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDeleteCategory; + + /// + /// btnDeleteCategory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnDeleteCategory; + + /// + /// lblDeleteResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblDeleteResult; + + /// + /// btnBack control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnBack; + + /// + /// pnlBulkManage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlBulkManage; + + /// + /// lblBulkStep1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblBulkStep1; + + /// + /// providerSelector control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.ProviderSelector providerSelector; + + /// + /// pageListBuilder control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.PageListBuilder pageListBuilder; + + /// + /// lblBulkStep2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblBulkStep2; + + /// + /// lstBulkCategories control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBoxList lstBulkCategories; + + /// + /// lblBulkStep3 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblBulkStep3; + + /// + /// rdoBulkAdd control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoBulkAdd; + + /// + /// rdoBulkReplace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoBulkReplace; + + /// + /// btnBulkSave control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnBulkSave; + + /// + /// lblBulkResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblBulkResult; + + /// + /// btnBulkBack control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnBulkBack; + + /// + /// txtCurrentCategory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.HiddenField txtCurrentCategory; + } +} diff --git a/WebApplication/AdminConfig.aspx b/WebApplication/AdminConfig.aspx new file mode 100644 index 0000000..5642531 --- /dev/null +++ b/WebApplication/AdminConfig.aspx @@ -0,0 +1,688 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Admin.master" AutoEventWireup="true" CodeBehind="AdminConfig.aspx.cs" Inherits="ScrewTurn.Wiki.AdminConfig" culture="auto" meta:resourcekey="PageResource2" uiculture="auto" %> + + + + + + + + + +

    + +
    +
    +

    +
    + +
    +
    + + + + +
    + +
    + + ( - + )
    + + + + +
    + +
    +
    + + + + +
    + +
    +
    + + + + +
    + +
    +
    + + + +
    + +
    +
    + + + + + + +
    + +
    +
    + + + + + + +
    +
    + +
    +
    +

    +
    + +
    + + ( + + )
    + +
    + +
    + + ( + + )
    + +
    + +
    +
    + + + + + +
    + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + +
    + +
    +
    + + + + + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + + + + + +
    + +
    + +
    +
    + +
    +
    +

    +
    + +
    + +
    + +
    +
    + + + + + +
    + +
    +
    + +
    + +
    +
    + +
    + +
    +
    + +
    + +
    + +
    + +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    + + +
    + +
    + + + + +
    + +
    + +
    +
    + KB + + + +
    + +
    + +
    + +
    +
    + + + + +
    + +
    +
    + KB + + + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + + +
    + +
    +
    + + +
    + +
    +

    +
    + +
    + +
    + +
    + +
    + +
    +
    + + + + + +
    + +
    +
    + + + + + +
    + +
    + +
    + +
    + +
    +
    + +
    + + +
    + +
    +
    diff --git a/WebApplication/AdminConfig.aspx.cs b/WebApplication/AdminConfig.aspx.cs new file mode 100644 index 0000000..8ff8465 --- /dev/null +++ b/WebApplication/AdminConfig.aspx.cs @@ -0,0 +1,529 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.Linq; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class AdminConfig : BasePage { + + protected void Page_Load(object sender, EventArgs e) { + AdminMaster.RedirectToLoginIfNeeded(); + + if(!AdminMaster.CanManageConfiguration(SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) UrlTools.Redirect("AccessDenied.aspx"); + + StringBuilder sb = new StringBuilder(200); + sb.Append(""); + lblStrings.Text = sb.ToString(); + + if(!Page.IsPostBack) { + // Setup validation regular expressions + revMainUrl.ValidationExpression = Settings.MainUrlRegex; + revWikiTitle.ValidationExpression = Settings.WikiTitleRegex; + revContactEmail.ValidationExpression = Settings.EmailRegex; + revSenderEmail.ValidationExpression = Settings.EmailRegex; + revSmtpServer.ValidationExpression = Settings.SmtpServerRegex; + + // Load current values + LoadGeneralConfig(); + LoadContentConfig(); + LoadSecurityConfig(); + LoadAdvancedConfig(); + } + } + + /// + /// Loads the general configuration. + /// + private void LoadGeneralConfig() { + txtWikiTitle.Text = Settings.WikiTitle; + txtMainUrl.Text = Settings.MainUrl; + txtContactEmail.Text = Settings.ContactEmail; + txtSenderEmail.Text = Settings.SenderEmail; + txtErrorsEmails.Text = string.Join(", ", Settings.ErrorsEmails); + txtSmtpServer.Text = Settings.SmtpServer; + int port = Settings.SmtpPort; + txtSmtpPort.Text = port != -1 ? port.ToString() : ""; + txtUsername.Text = Settings.SmtpUsername; + txtPassword.Attributes.Add("value", Settings.SmtpPassword); + chkEnableSslForSmtp.Checked = Settings.SmtpSsl; + } + + /// + /// Populates the Themes list selecting the current one. + /// + /// The current theme. + private void PopulateThemes(string current) { + current = current.ToLowerInvariant(); + + string[] themes = Tools.AvailableThemes; + lstRootTheme.Items.Clear(); + foreach(string theme in themes) { + lstRootTheme.Items.Add(new ListItem(theme, theme)); + if(theme.ToLowerInvariant() == current) lstRootTheme.Items[lstRootTheme.Items.Count - 1].Selected = true; + } + } + + /// + /// Populates the main pages list selecting the current one. + /// + /// The current page. + private void PopulateMainPages(string current) { + current = current.ToLowerInvariant(); + + List pages = Pages.GetPages(null); + lstMainPage.Items.Clear(); + foreach(PageInfo page in pages) { + lstMainPage.Items.Add(new ListItem(page.FullName, page.FullName)); + if(page.FullName.ToLowerInvariant() == current) { + lstMainPage.SelectedIndex = -1; + lstMainPage.Items[lstMainPage.Items.Count - 1].Selected = true; + } + } + } + + /// + /// Populates the languages list selecting the current one. + /// + /// The current language. + private void PopulateLanguages(string current) { + current = current.ToLowerInvariant(); + + string[] langs = Tools.AvailableCultures; + lstDefaultLanguage.Items.Clear(); + foreach(string lang in langs) { + string[] fields = lang.Split('|'); + lstDefaultLanguage.Items.Add(new ListItem(fields[1], fields[0])); + if(fields[0].ToLowerInvariant() == current) lstDefaultLanguage.Items[lstDefaultLanguage.Items.Count - 1].Selected = true; + } + } + + /// + /// Populates the time zones list selecting the current one. + /// + /// The current time zone. + private void PopulateTimeZones(string current) { + for(int i = 0; i < lstDefaultTimeZone.Items.Count; i++) { + if(lstDefaultTimeZone.Items[i].Value == current) lstDefaultTimeZone.Items[i].Selected = true; + else lstDefaultTimeZone.Items[i].Selected = false; + } + } + + /// + /// Populates the date/time format templates list. + /// + private void PopulateDateTimeFormats() { + StringBuilder sb = new StringBuilder(500); + DateTime test = DateTime.Now; + sb.Append(@""); + sb.Append(@""); + sb.Append(@""); + sb.Append(@""); + sb.Append(@""); + + sb.Append(@""); + sb.Append(@""); + sb.Append(@""); + sb.Append(@""); + sb.Append(@""); + + lblDateTimeFormatTemplates.Text = sb.ToString(); + } + + /// + /// Loads the content configuration. + /// + private void LoadContentConfig() { + PopulateThemes(Settings.GetTheme(null)); + PopulateMainPages(Settings.DefaultPage); + txtDateTimeFormat.Text = Settings.DateTimeFormat; + PopulateDateTimeFormats(); + PopulateLanguages(Settings.DefaultLanguage); + PopulateTimeZones(Settings.DefaultTimezone.ToString()); + txtMaxRecentChangesToDisplay.Text = Settings.MaxRecentChangesToDisplay.ToString(); + + lstRssFeedsMode.SelectedIndex = -1; + switch(Settings.RssFeedsMode) { + case RssFeedsMode.FullText: + lstRssFeedsMode.SelectedIndex = 0; + break; + case RssFeedsMode.Summary: + lstRssFeedsMode.SelectedIndex = 1; + break; + case RssFeedsMode.Disabled: + lstRssFeedsMode.SelectedIndex = 2; + break; + } + + chkEnableDoubleClickEditing.Checked = Settings.EnableDoubleClickEditing; + chkEnableSectionEditing.Checked = Settings.EnableSectionEditing; + chkEnablePageToolbar.Checked = Settings.EnablePageToolbar; + chkEnableViewPageCode.Checked = Settings.EnableViewPageCodeFeature; + chkEnablePageInfoDiv.Checked = Settings.EnablePageInfoDiv; + chkEnableBreadcrumbsTrail.Checked = !Settings.DisableBreadcrumbsTrail; + chkAutoGeneratePageNames.Checked = Settings.AutoGeneratePageNames; + chkProcessSingleLineBreaks.Checked = Settings.ProcessSingleLineBreaks; + chkUseVisualEditorAsDefault.Checked = Settings.UseVisualEditorAsDefault; + if(Settings.KeptBackupNumber == -1) txtKeptBackupNumber.Text = ""; + else txtKeptBackupNumber.Text = Settings.KeptBackupNumber.ToString(); + chkDisplayGravatars.Checked = Settings.DisplayGravatars; + } + + /// + /// Populates the activation mode list selecting the current one. + /// + /// The current account activation mode. + private void PopulateAccountActivationMode(AccountActivationMode current) { + if(current == AccountActivationMode.Email) lstAccountActivationMode.SelectedIndex = 0; + else if(current == AccountActivationMode.Administrator) lstAccountActivationMode.SelectedIndex = 1; + else lstAccountActivationMode.SelectedIndex = 2; + } + + /// + /// Populates the default groups lists, selecting the current ones. + /// + /// The current default users group. + /// The current default administrators group. + /// The current default anonymous users group. + private void PopulateDefaultGroups(string users, string admins, string anonymous) { + users = users.ToLowerInvariant(); + admins = admins.ToLowerInvariant(); + anonymous = anonymous.ToLowerInvariant(); + + lstDefaultUsersGroup.Items.Clear(); + lstDefaultAdministratorsGroup.Items.Clear(); + lstDefaultAnonymousGroup.Items.Clear(); + foreach(UserGroup group in Users.GetUserGroups()) { + string lowerName = group.Name.ToLowerInvariant(); + + lstDefaultUsersGroup.Items.Add(new ListItem(group.Name, group.Name)); + if(lowerName == users) { + lstDefaultUsersGroup.SelectedIndex = -1; + lstDefaultUsersGroup.Items[lstDefaultUsersGroup.Items.Count - 1].Selected = true; + } + + lstDefaultAdministratorsGroup.Items.Add(new ListItem(group.Name, group.Name)); + if(lowerName == admins) { + lstDefaultAdministratorsGroup.SelectedIndex = -1; + lstDefaultAdministratorsGroup.Items[lstDefaultAdministratorsGroup.Items.Count - 1].Selected = true; + } + + lstDefaultAnonymousGroup.Items.Add(new ListItem(group.Name, group.Name)); + if(lowerName == anonymous) { + lstDefaultAnonymousGroup.SelectedIndex = -1; + lstDefaultAnonymousGroup.Items[lstDefaultAnonymousGroup.Items.Count - 1].Selected = true; + } + } + } + + /// + /// Loads the security configuration. + /// + private void LoadSecurityConfig() { + chkAllowUsersToRegister.Checked = Settings.UsersCanRegister; + txtPasswordRegEx.Text = Settings.PasswordRegex; + txtUsernameRegEx.Text = Settings.UsernameRegex; + PopulateAccountActivationMode(Settings.AccountActivationMode); + PopulateDefaultGroups(Settings.UsersGroup, + Settings.AdministratorsGroup, + Settings.AnonymousGroup); + chkEnableCaptchaControl.Checked = !Settings.DisableCaptchaControl; + chkPreventConcurrentEditing.Checked = Settings.DisableConcurrentEditing; + + switch(Settings.ChangeModerationMode) { + case ChangeModerationMode.None: + rdoNoModeration.Checked = true; + break; + case ChangeModerationMode.RequirePageViewingPermissions: + rdoRequirePageViewingPermissions.Checked = true; + break; + case ChangeModerationMode.RequirePageEditingPermissions: + rdoRequirePageEditingPermissions.Checked = true; + break; + } + + txtExtensionsAllowed.Text = string.Join(", ", Settings.AllowedFileTypes); + + lstFileDownloadCountFilterMode.SelectedIndex = -1; + switch(Settings.FileDownloadCountFilterMode) { + case FileDownloadCountFilterMode.CountAll: + lstFileDownloadCountFilterMode.SelectedIndex = 0; + txtFileDownloadCountFilter.Enabled = false; + break; + case FileDownloadCountFilterMode.CountSpecifiedExtensions: + lstFileDownloadCountFilterMode.SelectedIndex = 1; + txtFileDownloadCountFilter.Enabled = true; + txtFileDownloadCountFilter.Text = string.Join(", ", Settings.FileDownloadCountFilter); + break; + case FileDownloadCountFilterMode.ExcludeSpecifiedExtensions: + txtFileDownloadCountFilter.Text = string.Join(", ", Settings.FileDownloadCountFilter); + txtFileDownloadCountFilter.Enabled = true; + lstFileDownloadCountFilterMode.SelectedIndex = 2; + break; + default: + throw new NotSupportedException(); + } + + txtMaxFileSize.Text = Settings.MaxFileSize.ToString(); + chkAllowScriptTags.Checked = Settings.ScriptTagsAllowed; + txtMaxLogSize.Text = Settings.MaxLogSize.ToString(); + txtIpHostFilter.Text = Settings.IpHostFilter; + switch(Settings.LoggingLevel) { + case LoggingLevel.DisableLog: + rdoDisableLog.Checked = true; + break; + case LoggingLevel.ErrorsOnly: + rdoErrorsOnly.Checked = true; + break; + case LoggingLevel.WarningsAndErrors: + rdoWarningsAndErrors.Checked = true; + break; + case LoggingLevel.AllMessages: + rdoAllMessages.Checked = true; + break; + } + } + + /// + /// Loads the advanced configuration. + /// + private void LoadAdvancedConfig() { + chkEnableAutomaticUpdateChecks.Checked = !Settings.DisableAutomaticVersionCheck; + chkDisableCache.Checked = Settings.DisableCache; + txtCacheSize.Text = Settings.CacheSize.ToString(); + txtCacheCutSize.Text = Settings.CacheCutSize.ToString(); + chkEnableViewStateCompression.Checked = Settings.EnableViewStateCompression; + chkEnableHttpCompression.Checked = Settings.EnableHttpCompression; + } + + protected void btnAutoWikiUrl_Click(object sender, EventArgs e) { + string url = Request.Url.ToString(); + // Assume the URL contains AdminConfig.aspx + url = url.Substring(0, url.ToLowerInvariant().IndexOf("adminconfig.aspx")); + txtMainUrl.Text = url; + } + + protected void cvUsername_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = txtUsername.Text.Length == 0 || + (txtUsername.Text.Length > 0 && txtPassword.Text.Length > 0); + } + + protected void cvPassword_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = txtPassword.Text.Length == 0 || + (txtUsername.Text.Length > 0 && txtPassword.Text.Length > 0); + } + + /// + /// Gets the errors emails, properly trimmed. + /// + /// The emails. + private string[] GetErrorsEmails() { + string[] emails = txtErrorsEmails.Text.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + for(int i = 0; i < emails.Length; i++) { + emails[i] = emails[i].Trim(); + } + + return emails; + } + + protected void cvErrorsEmails_ServerValidate(object sender, ServerValidateEventArgs e) { + string[] emails = GetErrorsEmails(); + + Regex regex = new Regex(Settings.EmailRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + foreach(string email in emails) { + if(!regex.Match(email).Success) { + e.IsValid = false; + return; + } + } + + e.IsValid = true; + } + + protected void cvUsernameRegEx_ServerValidate(object sender, ServerValidateEventArgs e) { + try { + var r = new Regex(txtUsernameRegEx.Text); + r.IsMatch("Test String to validate Regular Expression"); + e.IsValid = true; + } + catch { + e.IsValid = false; + } + } + + protected void cvPasswordRegEx_ServerValidate(object sender, ServerValidateEventArgs e) { + try { + var r = new Regex(txtPasswordRegEx.Text); + r.IsMatch("Test String to validate Regular Expression"); + e.IsValid = true; + } + catch { + e.IsValid = false; + } + } + + protected void cvDateTimeFormat_ServerValidate(object sender, ServerValidateEventArgs e) { + try { + DateTime.Now.ToString(txtDateTimeFormat.Text); + e.IsValid = true; + } + catch { + e.IsValid = false; + } + } + + /// + /// Gets the extensions allowed for upload from the input control. + /// + /// The extensions. + private string[] GetAllowedFileExtensions() { + return txtExtensionsAllowed.Text.Replace(" ", "").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + } + + protected void cvExtensionsAllowed_ServerValidate(object sender, ServerValidateEventArgs e) { + string[] allowed = GetAllowedFileExtensions(); + + bool wildcardFound = + (from s in allowed + where s == "*" + select s).Any(); + + e.IsValid = allowed.Length <= 1 || allowed.Length > 1 && !wildcardFound; + } + + protected void lstFileDownloadCountFilterMode_SelectedIndexChanged(object sender, EventArgs e) { + if(lstFileDownloadCountFilterMode.SelectedValue == FileDownloadCountFilterMode.CountAll.ToString()) { + txtFileDownloadCountFilter.Enabled = false; + } + else { + txtFileDownloadCountFilter.Enabled = true; + } + } + + protected void btnSave_Click(object sender, EventArgs e) { + lblResult.CssClass = ""; + lblResult.Text = ""; + + Page.Validate(); + + if(!Page.IsValid) return; + + Log.LogEntry("Wiki Configuration change requested", EntryType.General, SessionFacade.CurrentUsername); + + Settings.BeginBulkUpdate(); + + // Save general configuration + Settings.WikiTitle = txtWikiTitle.Text; + Settings.MainUrl = txtMainUrl.Text; + Settings.ContactEmail = txtContactEmail.Text; + Settings.SenderEmail = txtSenderEmail.Text; + Settings.ErrorsEmails = GetErrorsEmails(); + Settings.SmtpServer = txtSmtpServer.Text; + + txtSmtpPort.Text = txtSmtpPort.Text.Trim(); + if(txtSmtpPort.Text.Length > 0) Settings.SmtpPort = int.Parse(txtSmtpPort.Text); + else Settings.SmtpPort = -1; + if(txtUsername.Text.Length > 0) { + Settings.SmtpUsername = txtUsername.Text; + Settings.SmtpPassword = txtPassword.Text; + } + else { + Settings.SmtpUsername = ""; + Settings.SmtpPassword = ""; + } + Settings.SmtpSsl = chkEnableSslForSmtp.Checked; + + // Save content configuration + Settings.SetTheme(null, lstRootTheme.SelectedValue); + Settings.DefaultPage = lstMainPage.SelectedValue; + Settings.DateTimeFormat = txtDateTimeFormat.Text; + Settings.DefaultLanguage = lstDefaultLanguage.SelectedValue; + Settings.DefaultTimezone = int.Parse(lstDefaultTimeZone.SelectedValue); + Settings.MaxRecentChangesToDisplay = int.Parse(txtMaxRecentChangesToDisplay.Text); + Settings.RssFeedsMode = (RssFeedsMode)Enum.Parse(typeof(RssFeedsMode), lstRssFeedsMode.SelectedValue); + Settings.EnableDoubleClickEditing = chkEnableDoubleClickEditing.Checked; + Settings.EnableSectionEditing = chkEnableSectionEditing.Checked; + Settings.EnablePageToolbar = chkEnablePageToolbar.Checked; + Settings.EnableViewPageCodeFeature = chkEnableViewPageCode.Checked; + Settings.EnablePageInfoDiv = chkEnablePageInfoDiv.Checked; + Settings.DisableBreadcrumbsTrail = !chkEnableBreadcrumbsTrail.Checked; + Settings.AutoGeneratePageNames = chkAutoGeneratePageNames.Checked; + Settings.ProcessSingleLineBreaks = chkProcessSingleLineBreaks.Checked; + Settings.UseVisualEditorAsDefault = chkUseVisualEditorAsDefault.Checked; + if(txtKeptBackupNumber.Text == "") Settings.KeptBackupNumber = -1; + else Settings.KeptBackupNumber = int.Parse(txtKeptBackupNumber.Text); + Settings.DisplayGravatars = chkDisplayGravatars.Checked; + + // Save security configuration + Settings.UsersCanRegister = chkAllowUsersToRegister.Checked; + Settings.UsernameRegex = txtUsernameRegEx.Text; + Settings.PasswordRegex = txtPasswordRegEx.Text; + AccountActivationMode mode = AccountActivationMode.Email; + switch(lstAccountActivationMode.SelectedValue.ToLowerInvariant()) { + case "email": + mode = AccountActivationMode.Email; + break; + case "admin": + mode = AccountActivationMode.Administrator; + break; + case "auto": + mode = AccountActivationMode.Auto; + break; + } + Settings.AccountActivationMode = mode; + Settings.UsersGroup = lstDefaultUsersGroup.SelectedValue; + Settings.AdministratorsGroup = lstDefaultAdministratorsGroup.SelectedValue; + Settings.AnonymousGroup = lstDefaultAnonymousGroup.SelectedValue; + Settings.DisableCaptchaControl = !chkEnableCaptchaControl.Checked; + Settings.DisableConcurrentEditing = chkPreventConcurrentEditing.Checked; + + if(rdoNoModeration.Checked) Settings.ChangeModerationMode = ChangeModerationMode.None; + else if(rdoRequirePageViewingPermissions.Checked) Settings.ChangeModerationMode = ChangeModerationMode.RequirePageViewingPermissions; + else if(rdoRequirePageEditingPermissions.Checked) Settings.ChangeModerationMode = ChangeModerationMode.RequirePageEditingPermissions; + + Settings.AllowedFileTypes = GetAllowedFileExtensions(); + + Settings.FileDownloadCountFilterMode = (FileDownloadCountFilterMode)Enum.Parse(typeof(FileDownloadCountFilterMode), lstFileDownloadCountFilterMode.SelectedValue); + Settings.FileDownloadCountFilter = txtFileDownloadCountFilter.Text.Replace(" ", "").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + Settings.MaxFileSize = int.Parse(txtMaxFileSize.Text); + Settings.ScriptTagsAllowed = chkAllowScriptTags.Checked; + LoggingLevel level = LoggingLevel.AllMessages; + if(rdoAllMessages.Checked) level = LoggingLevel.AllMessages; + else if(rdoWarningsAndErrors.Checked) level = LoggingLevel.WarningsAndErrors; + else if(rdoErrorsOnly.Checked) level = LoggingLevel.ErrorsOnly; + else level = LoggingLevel.DisableLog; + Settings.LoggingLevel = level; + Settings.MaxLogSize = int.Parse(txtMaxLogSize.Text); + Settings.IpHostFilter = txtIpHostFilter.Text; + + // Save advanced configuration + Settings.DisableAutomaticVersionCheck = !chkEnableAutomaticUpdateChecks.Checked; + Settings.DisableCache = chkDisableCache.Checked; + Settings.CacheSize = int.Parse(txtCacheSize.Text); + Settings.CacheCutSize = int.Parse(txtCacheCutSize.Text); + Settings.EnableViewStateCompression = chkEnableViewStateCompression.Checked; + Settings.EnableHttpCompression = chkEnableHttpCompression.Checked; + + Settings.EndBulkUpdate(); + + Content.InvalidateAllPages(); + Content.ClearPseudoCache(); + + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.ConfigSaved; + } + + } + +} diff --git a/WebApplication/AdminConfig.aspx.designer.cs b/WebApplication/AdminConfig.aspx.designer.cs new file mode 100644 index 0000000..c554a59 --- /dev/null +++ b/WebApplication/AdminConfig.aspx.designer.cs @@ -0,0 +1,1240 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AdminConfig { + + /// + /// lblStrings control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblStrings; + + /// + /// lblConfig control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblConfig; + + /// + /// lblGeneralConfig control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblGeneralConfig; + + /// + /// lblWikiTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblWikiTitle; + + /// + /// txtWikiTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtWikiTitle; + + /// + /// rfvWikiTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RequiredFieldValidator rfvWikiTitle; + + /// + /// revWikiTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RegularExpressionValidator revWikiTitle; + + /// + /// lblWikiUrl control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblWikiUrl; + + /// + /// lblUsedForEmailCommunications control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblUsedForEmailCommunications; + + /// + /// btnAutoWikiUrl control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.LinkButton btnAutoWikiUrl; + + /// + /// txtMainUrl control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtMainUrl; + + /// + /// rfvMainUrl control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RequiredFieldValidator rfvMainUrl; + + /// + /// revMainUrl control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RegularExpressionValidator revMainUrl; + + /// + /// lblContactEmail control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblContactEmail; + + /// + /// txtContactEmail control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtContactEmail; + + /// + /// rfvContactEmail control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RequiredFieldValidator rfvContactEmail; + + /// + /// revContactEmail control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RegularExpressionValidator revContactEmail; + + /// + /// lblSenderEmail control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblSenderEmail; + + /// + /// txtSenderEmail control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtSenderEmail; + + /// + /// rfvSenderEmail control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RequiredFieldValidator rfvSenderEmail; + + /// + /// revSenderEmail control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RegularExpressionValidator revSenderEmail; + + /// + /// lblErrorsEmails control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblErrorsEmails; + + /// + /// txtErrorsEmails control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtErrorsEmails; + + /// + /// cvErrorsEmails control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CustomValidator cvErrorsEmails; + + /// + /// lblSmtpServer control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblSmtpServer; + + /// + /// txtSmtpServer control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtSmtpServer; + + /// + /// txtSmtpPort control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtSmtpPort; + + /// + /// rfvSmtpServer control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RequiredFieldValidator rfvSmtpServer; + + /// + /// revSmtpServer control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RegularExpressionValidator revSmtpServer; + + /// + /// rvSmtpPort control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RangeValidator rvSmtpPort; + + /// + /// lblSmtpAuthentication control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblSmtpAuthentication; + + /// + /// txtUsername control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtUsername; + + /// + /// txtPassword control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtPassword; + + /// + /// chkEnableSslForSmtp control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkEnableSslForSmtp; + + /// + /// cvUsername control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CustomValidator cvUsername; + + /// + /// cvPassword control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CustomValidator cvPassword; + + /// + /// lblContentConfig control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblContentConfig; + + /// + /// lblRootTheme control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRootTheme; + + /// + /// lblSeeAlsoNamespaces1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblSeeAlsoNamespaces1; + + /// + /// lblNamespaces1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNamespaces1; + + /// + /// lstRootTheme control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstRootTheme; + + /// + /// lblRootMainPage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRootMainPage; + + /// + /// lblSeeAlsoNamespaces2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblSeeAlsoNamespaces2; + + /// + /// lblNamespaces2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNamespaces2; + + /// + /// lstMainPage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstMainPage; + + /// + /// lblDateTimeFormat control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDateTimeFormat; + + /// + /// txtDateTimeFormat control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtDateTimeFormat; + + /// + /// lblSelectFormat control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblSelectFormat; + + /// + /// lblDateTimeFormatTemplates control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDateTimeFormatTemplates; + + /// + /// rfvDateTimeFormat control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RequiredFieldValidator rfvDateTimeFormat; + + /// + /// cvDateTimeFormat control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CustomValidator cvDateTimeFormat; + + /// + /// lblDefaultLanguage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDefaultLanguage; + + /// + /// lstDefaultLanguage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstDefaultLanguage; + + /// + /// lblDefaultTimeZone control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDefaultTimeZone; + + /// + /// lstDefaultTimeZone control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstDefaultTimeZone; + + /// + /// lblMaxRecentChangesToDisplayPre control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblMaxRecentChangesToDisplayPre; + + /// + /// txtMaxRecentChangesToDisplay control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtMaxRecentChangesToDisplay; + + /// + /// lblMaxRecentChangesToDisplayPost control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblMaxRecentChangesToDisplayPost; + + /// + /// rfvMaxRecentChangesToDisplay control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RequiredFieldValidator rfvMaxRecentChangesToDisplay; + + /// + /// rvMaxRecentChangesToDisplay control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RangeValidator rvMaxRecentChangesToDisplay; + + /// + /// lblRssFeedsMode control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRssFeedsMode; + + /// + /// lstRssFeedsMode control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstRssFeedsMode; + + /// + /// chkEnableDoubleClickEditing control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkEnableDoubleClickEditing; + + /// + /// chkEnableSectionEditing control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkEnableSectionEditing; + + /// + /// chkEnablePageToolbar control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkEnablePageToolbar; + + /// + /// chkEnableViewPageCode control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkEnableViewPageCode; + + /// + /// chkEnablePageInfoDiv control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkEnablePageInfoDiv; + + /// + /// chkEnableBreadcrumbsTrail control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkEnableBreadcrumbsTrail; + + /// + /// chkAutoGeneratePageNames control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkAutoGeneratePageNames; + + /// + /// chkProcessSingleLineBreaks control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkProcessSingleLineBreaks; + + /// + /// chkUseVisualEditorAsDefault control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkUseVisualEditorAsDefault; + + /// + /// lblKeptBackupNumberPre control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblKeptBackupNumberPre; + + /// + /// txtKeptBackupNumber control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtKeptBackupNumber; + + /// + /// lblKeptBackupNumberPost control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblKeptBackupNumberPost; + + /// + /// rvKeptBackupNumber control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RangeValidator rvKeptBackupNumber; + + /// + /// chkDisplayGravatars control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkDisplayGravatars; + + /// + /// lblSecurityConfig control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblSecurityConfig; + + /// + /// chkAllowUsersToRegister control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkAllowUsersToRegister; + + /// + /// lblActivationMode control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblActivationMode; + + /// + /// lstAccountActivationMode control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstAccountActivationMode; + + /// + /// lblDefaultUsersGroup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDefaultUsersGroup; + + /// + /// lstDefaultUsersGroup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstDefaultUsersGroup; + + /// + /// lblDefaultAdministratorsGroup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDefaultAdministratorsGroup; + + /// + /// lstDefaultAdministratorsGroup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstDefaultAdministratorsGroup; + + /// + /// lblDefaultAnonymousGroup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDefaultAnonymousGroup; + + /// + /// lstDefaultAnonymousGroup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstDefaultAnonymousGroup; + + /// + /// chkEnableCaptchaControl control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkEnableCaptchaControl; + + /// + /// chkPreventConcurrentEditing control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkPreventConcurrentEditing; + + /// + /// lblChangeModerationMode control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblChangeModerationMode; + + /// + /// rdoNoModeration control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RadioButton rdoNoModeration; + + /// + /// rdoRequirePageViewingPermissions control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RadioButton rdoRequirePageViewingPermissions; + + /// + /// rdoRequirePageEditingPermissions control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RadioButton rdoRequirePageEditingPermissions; + + /// + /// lblExtensionsAllowedForUpload control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblExtensionsAllowedForUpload; + + /// + /// txtExtensionsAllowed control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtExtensionsAllowed; + + /// + /// cvExtensionsAllowed control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CustomValidator cvExtensionsAllowed; + + /// + /// lstFileDownloadCountFilterMode control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstFileDownloadCountFilterMode; + + /// + /// txtFileDownloadCountFilter control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtFileDownloadCountFilter; + + /// + /// lblMaxFileSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblMaxFileSize; + + /// + /// txtMaxFileSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtMaxFileSize; + + /// + /// rfvMaxFileSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RequiredFieldValidator rfvMaxFileSize; + + /// + /// rvMaxFileSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RangeValidator rvMaxFileSize; + + /// + /// chkAllowScriptTags control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkAllowScriptTags; + + /// + /// lblLoggingLevel control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblLoggingLevel; + + /// + /// rdoAllMessages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoAllMessages; + + /// + /// rdoWarningsAndErrors control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoWarningsAndErrors; + + /// + /// rdoErrorsOnly control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoErrorsOnly; + + /// + /// rdoDisableLog control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoDisableLog; + + /// + /// lblMaxLogSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblMaxLogSize; + + /// + /// txtMaxLogSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtMaxLogSize; + + /// + /// rfvMaxLogSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RequiredFieldValidator rfvMaxLogSize; + + /// + /// rvMaxLogSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RangeValidator rvMaxLogSize; + + /// + /// lblIpHostFilter control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblIpHostFilter; + + /// + /// txtIpHostFilter control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtIpHostFilter; + + /// + /// lblUsernameRegEx control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblUsernameRegEx; + + /// + /// txtUsernameRegEx control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtUsernameRegEx; + + /// + /// cvUsernameRegEx control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CustomValidator cvUsernameRegEx; + + /// + /// lblPasswordRegEx control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPasswordRegEx; + + /// + /// txtPasswordRegEx control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtPasswordRegEx; + + /// + /// cvPasswordRegEx control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CustomValidator cvPasswordRegEx; + + /// + /// lblAdvancedConfig control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblAdvancedConfig; + + /// + /// chkEnableAutomaticUpdateChecks control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkEnableAutomaticUpdateChecks; + + /// + /// chkDisableCache control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkDisableCache; + + /// + /// lblCacheSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCacheSize; + + /// + /// txtCacheSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtCacheSize; + + /// + /// lblPages1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPages1; + + /// + /// rfvCacheSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RequiredFieldValidator rfvCacheSize; + + /// + /// rvCacheSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RangeValidator rvCacheSize; + + /// + /// lblCacheCutSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCacheCutSize; + + /// + /// txtCacheCutSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtCacheCutSize; + + /// + /// lblPages2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPages2; + + /// + /// rfvCacheCutSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RequiredFieldValidator rfvCacheCutSize; + + /// + /// rvCacheCutSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RangeValidator rvCacheCutSize; + + /// + /// chkEnableViewStateCompression control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkEnableViewStateCompression; + + /// + /// chkEnableHttpCompression control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkEnableHttpCompression; + + /// + /// btnSave control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnSave; + + /// + /// lblResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblResult; + } +} diff --git a/WebApplication/AdminContent.aspx b/WebApplication/AdminContent.aspx new file mode 100644 index 0000000..a14a817 --- /dev/null +++ b/WebApplication/AdminContent.aspx @@ -0,0 +1,98 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Admin.master" AutoEventWireup="true" CodeBehind="AdminContent.aspx.cs" Inherits="ScrewTurn.Wiki.AdminContent" ValidateRequest="false" culture="auto" meta:resourcekey="PageResource1" uiculture="auto" %> + +<%@ Register TagPrefix="st" TagName="Editor" Src="~/Editor.ascx" %> + + + + + +

    + + + + +

    + +
    +

    +
    + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + +
    + +
    +

    +
    +
      +

    • +
    • +

    • +
    • +

    • +
    • +

    • +
    • +

    • +
    • +

    • +
    • +

    • +
    • +

    • +
    • +

    • +
    • +
    +
    +
    + + + +
    + + + + +
    +
    + + + +
    + + +
    +
    + + + +
    + +
    diff --git a/WebApplication/AdminContent.aspx.cs b/WebApplication/AdminContent.aspx.cs new file mode 100644 index 0000000..9190301 --- /dev/null +++ b/WebApplication/AdminContent.aspx.cs @@ -0,0 +1,126 @@ + +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class AdminContent : BasePage { + + protected void Page_Load(object sender, EventArgs e) { + AdminMaster.RedirectToLoginIfNeeded(); + + if(!AdminMaster.CanManageConfiguration(SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) UrlTools.Redirect("AccessDenied.aspx"); + + if(!Page.IsPostBack) { + // Load namespaces + + // Add root namespace + lstNamespace.Items.Add(new ListItem("", "")); + List namespaces = Pages.GetNamespaces(); + foreach(NamespaceInfo ns in namespaces) { + lstNamespace.Items.Add(new ListItem(ns.Name, ns.Name)); + } + } + } + + private static readonly Dictionary ButtonMetaDataItemMapping = new Dictionary() { + { "btnHtmlHead", MetaDataItem.HtmlHead }, + { "btnHeader", MetaDataItem.Header }, + { "btnSidebar", MetaDataItem.Sidebar }, + { "btnPageHeader", MetaDataItem.PageHeader }, + { "btnPageFooter", MetaDataItem.PageFooter }, + { "btnFooter", MetaDataItem.Footer }, + { "btnAccountActivationMessage", MetaDataItem.AccountActivationMessage }, + { "btnPasswordResetProcedureMessage", MetaDataItem.PasswordResetProcedureMessage }, + { "btnEditingPageNotice", MetaDataItem.EditNotice }, + { "btnLoginNotice", MetaDataItem.LoginNotice }, + { "btnAccessDeniedNotice", MetaDataItem.AccessDeniedNotice }, + { "btnRegisterNotice", MetaDataItem.RegisterNotice }, + { "btnPageChangeMessage", MetaDataItem.PageChangeMessage }, + { "btnDiscussionChangeMessage", MetaDataItem.DiscussionChangeMessage }, + { "btnApproveDraftMessage", MetaDataItem.ApproveDraftMessage } + }; + + private static List WikiMarkupOnlyItems = new List() { + MetaDataItem.AccountActivationMessage, + MetaDataItem.ApproveDraftMessage, + MetaDataItem.DiscussionChangeMessage, + MetaDataItem.PageChangeMessage, + MetaDataItem.PasswordResetProcedureMessage + }; + + protected void btn_Click(object sender, EventArgs e) { + Control senderControl = sender as Control; + txtCurrentButton.Value = senderControl.ID; + + MetaDataItem item = ButtonMetaDataItemMapping[senderControl.ID]; + + string content = Settings.Provider.GetMetaDataItem(item, lstNamespace.SelectedValue); + editor.SetContent(content, Settings.UseVisualEditorAsDefault); + + if(WikiMarkupOnlyItems.Contains(item)) { + editor.VisualVisible = false; + editor.PreviewVisible = false; + editor.ToolbarVisible = false; + } + else { + editor.VisualVisible = true; + editor.PreviewVisible = true; + editor.ToolbarVisible = true; + } + + pnlList.Visible = false; + pnlEditor.Visible = true; + + // Load namespaces for content copying + lstCopyFromNamespace.Items.Clear(); + string currentNamespace = lstNamespace.SelectedValue; + if(!string.IsNullOrEmpty(currentNamespace)) lstCopyFromNamespace.Items.Add(new ListItem("", "")); + List namespaces = Pages.GetNamespaces(); + foreach(NamespaceInfo ns in namespaces) { + if(currentNamespace != ns.Name) lstCopyFromNamespace.Items.Add(new ListItem(ns.Name, ns.Name)); + } + pnlInlineTools.Visible = lstCopyFromNamespace.Items.Count > 0 && !Settings.IsMetaDataItemGlobal(item); + } + + protected void btnCopyFrom_Click(object sender, EventArgs e) { + MetaDataItem item = ButtonMetaDataItemMapping[txtCurrentButton.Value]; + + if(Settings.IsMetaDataItemGlobal(item)) return; + + string newValue = Settings.Provider.GetMetaDataItem(item, lstCopyFromNamespace.SelectedValue); + + editor.SetContent(newValue, Settings.UseVisualEditorAsDefault); + } + + protected void btnSave_Click(object sender, EventArgs e) { + MetaDataItem item = ButtonMetaDataItemMapping[txtCurrentButton.Value]; + + string tag = null; + // These elements are global, all others are are namespace-specific + if(!Settings.IsMetaDataItemGlobal(item)) { + tag = lstNamespace.SelectedValue; + } + + Log.LogEntry("Metadata file change requested for " + item.ToString() + + (tag != null ? ", ns: " + tag : "") + lstNamespace.SelectedValue, EntryType.General, SessionFacade.CurrentUsername); + + Settings.Provider.SetMetaDataItem(item, tag, editor.GetContent()); + Content.ClearPseudoCache(); + + pnlEditor.Visible = false; + pnlList.Visible = true; + } + + protected void btnCancel_Click(object sender, EventArgs e) { + pnlEditor.Visible = false; + pnlList.Visible = true; + } + + } + +} diff --git a/WebApplication/AdminContent.aspx.designer.cs b/WebApplication/AdminContent.aspx.designer.cs new file mode 100644 index 0000000..096e972 --- /dev/null +++ b/WebApplication/AdminContent.aspx.designer.cs @@ -0,0 +1,385 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AdminContent { + + /// + /// lblContent control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblContent; + + /// + /// pnlList control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlList; + + /// + /// lblNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNamespace; + + /// + /// lstNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.DropDownList lstNamespace; + + /// + /// lblPageLayout control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPageLayout; + + /// + /// btnHtmlHead control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnHtmlHead; + + /// + /// btnHeader control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnHeader; + + /// + /// btnSidebar control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnSidebar; + + /// + /// btnPageHeader control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnPageHeader; + + /// + /// lblPageContent control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPageContent; + + /// + /// btnPageFooter control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnPageFooter; + + /// + /// btnFooter control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnFooter; + + /// + /// lblInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblInfo; + + /// + /// lblOther control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblOther; + + /// + /// btnEditingPageNotice control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnEditingPageNotice; + + /// + /// lblEditingPageNoticeInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblEditingPageNoticeInfo; + + /// + /// btnAccountActivationMessage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnAccountActivationMessage; + + /// + /// lblAccountActivationMessageInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblAccountActivationMessageInfo; + + /// + /// btnPasswordResetProcedureMessage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnPasswordResetProcedureMessage; + + /// + /// lblPasswordResetProcedureMessageInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPasswordResetProcedureMessageInfo; + + /// + /// btnLoginNotice control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnLoginNotice; + + /// + /// lblLoginNoticeInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblLoginNoticeInfo; + + /// + /// btnAccessDeniedNotice control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnAccessDeniedNotice; + + /// + /// lblAccessDeniedNoticeInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblAccessDeniedNoticeInfo; + + /// + /// btnRegisterNotice control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnRegisterNotice; + + /// + /// lblRegisterNoticeInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRegisterNoticeInfo; + + /// + /// btnPageChangeMessage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnPageChangeMessage; + + /// + /// lblPageChangeMessageInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPageChangeMessageInfo; + + /// + /// btnDiscussionChangeMessage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnDiscussionChangeMessage; + + /// + /// lblDiscussionChangeMessageInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDiscussionChangeMessageInfo; + + /// + /// btnApproveDraftMessage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnApproveDraftMessage; + + /// + /// lblApproveDraftMessageInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblApproveDraftMessageInfo; + + /// + /// pnlEditor control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlEditor; + + /// + /// pnlInlineTools control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlInlineTools; + + /// + /// lblCopyFrom control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCopyFrom; + + /// + /// lstCopyFromNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.DropDownList lstCopyFromNamespace; + + /// + /// btnCopyFrom control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnCopyFrom; + + /// + /// editor control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.Editor editor; + + /// + /// btnSave control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnSave; + + /// + /// btnCancel control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnCancel; + + /// + /// txtCurrentButton control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HiddenField txtCurrentButton; + } +} diff --git a/WebApplication/AdminGroups.aspx b/WebApplication/AdminGroups.aspx new file mode 100644 index 0000000..d511c5d --- /dev/null +++ b/WebApplication/AdminGroups.aspx @@ -0,0 +1,111 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Admin.master" AutoEventWireup="true" CodeBehind="AdminGroups.aspx.cs" Inherits="ScrewTurn.Wiki.AdminGroups" culture="auto" meta:resourcekey="PageResource1" uiculture="auto" %> + +<%@ Register TagPrefix="st" TagName="ProviderSelector" Src="~/ProviderSelector.ascx" %> +<%@ Register TagPrefix="st" TagName="AclActionsSelector" Src="~/AclActionsSelector.ascx" %> + + + + + +

    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     
    <%# Eval("Name") %><%# Eval("Description") %><%# Eval("Provider") %><%# Eval("Users") %>
    <%# Eval("Name") %><%# Eval("Description") %><%# Eval("Provider") %><%# Eval("Users") %>
    + +
    +
    +
    + + +
    +

    + +
    +
    + + + +
    + + + +
    + +
    +
    + +
    + +
    +

    + +
    + +
    + + + + + + +
    + +
    +
    + + + +
    + +
    diff --git a/WebApplication/AdminGroups.aspx.cs b/WebApplication/AdminGroups.aspx.cs new file mode 100644 index 0000000..30285e9 --- /dev/null +++ b/WebApplication/AdminGroups.aspx.cs @@ -0,0 +1,338 @@ + +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; +using ScrewTurn.Wiki.PluginFramework; +using ScrewTurn.Wiki.AclEngine; + +namespace ScrewTurn.Wiki { + + public partial class AdminGroups : BasePage { + + protected void Page_Load(object sender, EventArgs e) { + AdminMaster.RedirectToLoginIfNeeded(); + + string currentUser = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + + if(!AdminMaster.CanManageGroups(currentUser, currentGroups)) UrlTools.Redirect("AccessDenied.aspx"); + aclActionsSelector.Visible = AdminMaster.CanManagePermissions(currentUser, currentGroups); + + revName.ValidationExpression = Settings.UsernameRegex; + + if(!Page.IsPostBack) { + rptGroups.DataBind(); + providerSelector.Reload(); + btnNewGroup.Enabled = providerSelector.HasProviders; + } + } + + protected void rptGroups_DataBinding(object sender, EventArgs e) { + List allGroups = Users.GetUserGroups(); + + List result = new List(allGroups.Count); + + foreach(UserGroup group in allGroups) { + result.Add(new UserGroupRow(group, group.Name == txtCurrentName.Value)); + } + + rptGroups.DataSource = result; + } + + protected void rptGroups_ItemCommand(object sender, RepeaterCommandEventArgs e) { + if(e.CommandName == "Select") { + txtCurrentName.Value = e.CommandArgument as string; + //rptGroups.DataBind(); Not needed because the list is hidden on select + + UserGroup group = Users.FindUserGroup(txtCurrentName.Value); + + txtName.Text = group.Name; + txtName.Enabled = false; + txtDescription.Text = group.Description; + providerSelector.SelectedProvider = group.Provider.GetType().FullName; + providerSelector.Enabled = false; + + // Select group's global permissions + aclActionsSelector.GrantedActions = AuthReader.RetrieveGrantsForGlobals(group); + aclActionsSelector.DeniedActions = AuthReader.RetrieveDenialsForGlobals(group); + + btnCreate.Visible = false; + btnSave.Visible = true; + btnDelete.Visible = true; + bool isDefaultGroup = + group.Name == Settings.AdministratorsGroup || + group.Name == Settings.UsersGroup || + group.Name == Settings.AnonymousGroup; + + pnlEditGroup.Visible = true; + pnlList.Visible = false; + + // Enable/disable interface sections based on provider read-only settings + pnlGroupDetails.Enabled = !group.Provider.UserGroupsReadOnly; + btnDelete.Enabled = !group.Provider.UserGroupsReadOnly && !isDefaultGroup; + + lblResult.CssClass = ""; + lblResult.Text = ""; + } + } + + /// + /// Resets the group editor's status. + /// + private void ResetEditor() { + txtName.Text = ""; + txtName.Enabled = true; + txtDescription.Text = ""; + providerSelector.Enabled = true; + providerSelector.Reload(); + pnlGroupDetails.Enabled = true; + + aclActionsSelector.GrantedActions = new string[0]; + aclActionsSelector.DeniedActions = new string[0]; + + btnCreate.Visible = true; + btnSave.Visible = false; + btnDelete.Visible = false; + lblResult.Text = ""; + } + + /// + /// Refreshes the group list. + /// + private void RefreshList() { + txtCurrentName.Value = ""; + ResetEditor(); + rptGroups.DataBind(); + } + + protected void cvName_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = Users.FindUser(txtName.Text) == null; + } + + protected void btnNewGroup_Click(object sender, EventArgs e) { + pnlList.Visible = false; + pnlEditGroup.Visible = true; + + lblResult.Text = ""; + lblResult.CssClass = ""; + } + + protected void btnCreate_Click(object sender, EventArgs e) { + if(!Page.IsValid) return; + + lblResult.CssClass = ""; + lblResult.Text = ""; + + Log.LogEntry("Group creation requested for " + txtName.Text, EntryType.General, SessionFacade.CurrentUsername); + + // Add the new group then set its global permissions + bool done = Users.AddUserGroup(txtName.Text, txtDescription.Text, + Collectors.UsersProviderCollector.GetProvider(providerSelector.SelectedProvider)); + + UserGroup currentGroup = null; + if(done) { + currentGroup = Users.FindUserGroup(txtName.Text); + done = AddAclEntries(currentGroup, aclActionsSelector.GrantedActions, aclActionsSelector.DeniedActions); + + if(done) { + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.GroupCreated; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.GroupCreatedCouldNotStorePermissions; + } + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotCreateGroup; + } + } + + protected void btnSave_Click(object sender, EventArgs e) { + if(!Page.IsValid) return; + + lblResult.CssClass = ""; + lblResult.Text = ""; + + Log.LogEntry("Group update requested for " + txtCurrentName.Value, EntryType.General, SessionFacade.CurrentUsername); + + UserGroup currentGroup = Users.FindUserGroup(txtCurrentName.Value); + + // Perform proper actions based on provider read-only settings + // 1. If possible, modify group + // 2. Update ACLs + + bool done = true; + + if(!currentGroup.Provider.UserGroupsReadOnly) { + done = Users.ModifyUserGroup(currentGroup, txtDescription.Text); + } + + if(!done) { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotUpdateGroup; + return; + } + + done = RemoveAllAclEntries(currentGroup); + if(done) { + done = AddAclEntries(currentGroup, aclActionsSelector.GrantedActions, aclActionsSelector.DeniedActions); + + if(done) { + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.GroupUpdated; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.GroupSavedCouldNotStoreNewPermissions; + } + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.GroupSavedCouldNotDeleteOldPermissions; + } + } + + protected void btnDelete_Click(object sender, EventArgs e) { + lblResult.Text = ""; + lblResult.CssClass = ""; + + Log.LogEntry("Group deletion requested for " + txtCurrentName.Value, EntryType.General, SessionFacade.CurrentUsername); + + UserGroup currentGroup = Users.FindUserGroup(txtCurrentName.Value); + + if(currentGroup.Provider.UserGroupsReadOnly) return; + + // Remove all global permissions for the group then delete it + bool done = RemoveAllAclEntries(currentGroup); + if(done) { + done = Users.RemoveUserGroup(currentGroup); + + if(done) { + RefreshList(); + lblResult.Text = Properties.Messages.GroupDeleted; + lblResult.CssClass = "resultok"; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.PermissionsDeletedCouldNotDeleteGroup; + } + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotDeletePermissions; + } + } + + protected void btnCancel_Click(object sender, EventArgs e) { + RefreshList(); + ReturnToList(); + } + + /// + /// Removes all the ACL entries for a group. + /// + /// The group. + /// true if the operation succeeded, false otherwise. + private bool RemoveAllAclEntries(UserGroup group) { + return AuthWriter.RemoveEntriesForGlobals(group); + } + + /// + /// Adds some ACL entries for a group. + /// + /// The group. + /// The granted actions. + /// The denied actions. + /// true if the operation succeeded, false otherwise. + private bool AddAclEntries(UserGroup group, string[] grants, string[] denials) { + foreach(string action in grants) { + bool done = AuthWriter.SetPermissionForGlobals(AuthStatus.Grant, action, group); + if(!done) return false; + } + + foreach(string action in denials) { + bool done = AuthWriter.SetPermissionForGlobals(AuthStatus.Deny, action, group); + if(!done) return false; + } + + return true; + } + + /// + /// Returns to the group list. + /// + private void ReturnToList() { + pnlEditGroup.Visible = false; + pnlList.Visible = true; + } + + } + + /// + /// Represents a User Group for display purposes. + /// + public class UserGroupRow { + + private string name, description, provider, additionalClass; + private int users; + + /// + /// Initializes a new instance of the class. + /// + /// The original group. + /// A value indicating whether the user group is selected. + public UserGroupRow(UserGroup group, bool selected) { + name = group.Name; + description = group.Description; + provider = group.Provider.Information.Name; + additionalClass = selected ? " selected" : ""; + users = group.Users.Length; + } + + /// + /// Gets the name. + /// + public string Name { + get { return name; } + } + + /// + /// Gets the description. + /// + public string Description { + get { return description; } + } + + /// + /// Gets the provider. + /// + public string Provider { + get { return provider; } + } + + /// + /// Gets the additional CSS class. + /// + public string AdditionalClass { + get { return additionalClass; } + } + + /// + /// Gets the users. + /// + public int Users { + get { return users; } + } + + } + +} diff --git a/WebApplication/AdminGroups.aspx.designer.cs b/WebApplication/AdminGroups.aspx.designer.cs new file mode 100644 index 0000000..3fa8b7b --- /dev/null +++ b/WebApplication/AdminGroups.aspx.designer.cs @@ -0,0 +1,232 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.3082 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AdminGroups { + + /// + /// lblGroups control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblGroups; + + /// + /// pnlList control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlList; + + /// + /// btnNewGroup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnNewGroup; + + /// + /// rptGroups control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Repeater rptGroups; + + /// + /// pnlEditGroup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlEditGroup; + + /// + /// lblEditTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblEditTitle; + + /// + /// lblProvider control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblProvider; + + /// + /// providerSelector control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.ProviderSelector providerSelector; + + /// + /// pnlGroupDetails control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlGroupDetails; + + /// + /// lblName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblName; + + /// + /// txtName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtName; + + /// + /// rfvName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RequiredFieldValidator rfvName; + + /// + /// revName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RegularExpressionValidator revName; + + /// + /// cvName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CustomValidator cvName; + + /// + /// lblDescription control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDescription; + + /// + /// txtDescription control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtDescription; + + /// + /// lblGlobalPermissions control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblGlobalPermissions; + + /// + /// aclActionsSelector control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.AclActionsSelector aclActionsSelector; + + /// + /// btnSave control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnSave; + + /// + /// btnCreate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnCreate; + + /// + /// btnDelete control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnDelete; + + /// + /// btnCancel control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnCancel; + + /// + /// lblResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblResult; + + /// + /// txtCurrentName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.HiddenField txtCurrentName; + } +} diff --git a/WebApplication/AdminHome.aspx b/WebApplication/AdminHome.aspx new file mode 100644 index 0000000..39073e4 --- /dev/null +++ b/WebApplication/AdminHome.aspx @@ -0,0 +1,173 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Admin.master" AutoEventWireup="true" CodeBehind="AdminHome.aspx.cs" Inherits="ScrewTurn.Wiki.AdminHome" culture="auto" meta:resourcekey="PageResource2" uiculture="auto" %> + + + + + + + +

    +

    + +

    + +

    +

    + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    <%# Eval("Nspace") %><%# ScrewTurn.Wiki.PluginFramework.NameTools.GetLocalName(Eval("Name") as string) %><%# Eval("LinkingPages") %>
    <%# Eval("Nspace") %><%# ScrewTurn.Wiki.PluginFramework.NameTools.GetLocalName(Eval("Name") as string) %><%# Eval("LinkingPages") %>
    + +
    +

    + +

    + + + + (). +
    + +

    + + + +

    + + +

    + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     
    <%# Eval("Provider") %><%# Eval("Documents") %><%# Eval("Words") %><%# Eval("Occurrences") %><%# Eval("Size") %><%# ((bool)Eval("IsOK") ? ScrewTurn.Wiki.Properties.Messages.OK : "" + ScrewTurn.Wiki.Properties.Messages.Corrupted + "")%>
    <%# Eval("Provider") %><%# Eval("Documents") %><%# Eval("Words") %><%# Eval("Occurrences") %><%# Eval("Size") %><%# ((bool)Eval("IsOK") ? ScrewTurn.Wiki.Properties.Messages.OK : "" + ScrewTurn.Wiki.Properties.Messages.Corrupted + "")%>
    + +
    +
    + + + + +


    + +

    + +

    +
    + +
    + +
    +
    diff --git a/WebApplication/AdminHome.aspx.cs b/WebApplication/AdminHome.aspx.cs new file mode 100644 index 0000000..9a824d0 --- /dev/null +++ b/WebApplication/AdminHome.aspx.cs @@ -0,0 +1,311 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; +using ScrewTurn.Wiki.PluginFramework; +using System.Globalization; + +namespace ScrewTurn.Wiki { + + public partial class AdminHome : BasePage { + + protected void Page_Load(object sender, EventArgs e) { + AdminMaster.RedirectToLoginIfNeeded(); + + if(!AdminMaster.CanManageConfiguration(SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) UrlTools.Redirect("AccessDenied.aspx"); + + PrintSystemStatus(); + + if(!Page.IsPostBack) { + rptPages.DataBind(); + rptIndex.DataBind(); + + DisplayOrphansCount(); + } + } + + /// + /// Displays the orphan pages count. + /// + private void DisplayOrphansCount() { + int orphans = Pages.GetOrphanedPages(null).Length; + foreach(NamespaceInfo nspace in Pages.GetNamespaces()) { + orphans += Pages.GetOrphanedPages(nspace).Length; + } + lblOrphanPagesCount.Text = orphans.ToString(); + } + + protected void btnClearCache_Click(object sender, EventArgs e) { + Redirections.Clear(); + Content.ClearPseudoCache(); + Content.InvalidateAllPages(); + PrintSystemStatus(); + } + + protected void rptPages_DataBinding(object sender, EventArgs e) { + List result = new List(50); + + Dictionary> links = Pages.GetWantedPages(null); + foreach(KeyValuePair> pair in links) { + result.Add(new WantedPageRow("<root>", "", pair.Key, pair.Value)); + } + + foreach(NamespaceInfo nspace in Pages.GetNamespaces()) { + links = Pages.GetWantedPages(nspace.Name); + foreach(KeyValuePair> pair in links) { + result.Add(new WantedPageRow(nspace.Name, nspace.Name + ".", pair.Key, pair.Value)); + } + } + + rptPages.DataSource = result; + } + + protected void btnRebuildPageLinks_Click(object sender, EventArgs e) { + RebuildPageLinks(Pages.GetPages(null)); + foreach(NamespaceInfo nspace in Pages.GetNamespaces()) { + RebuildPageLinks(Pages.GetPages(nspace)); + } + + DisplayOrphansCount(); + } + + /// + /// Rebuilds the page links for the specified pages. + /// + /// The pages. + private void RebuildPageLinks(IList pages) { + foreach(PageInfo page in pages) { + PageContent content = Content.GetPageContent(page, false); + Pages.StorePageOutgoingLinks(page, content.Content); + } + } + + protected void rptIndex_DataBinding(object sender, EventArgs e) { + List result = new List(5); + + foreach(IPagesStorageProviderV30 prov in Collectors.PagesProviderCollector.AllProviders) { + result.Add(new IndexRow(prov)); + } + + rptIndex.DataSource = result; + } + + protected void rptIndex_ItemCommand(object sender, CommandEventArgs e) { + Log.LogEntry("Index rebuild requested for " + e.CommandArgument as string, EntryType.General, SessionFacade.GetCurrentUsername()); + + IPagesStorageProviderV30 provider = Collectors.PagesProviderCollector.GetProvider(e.CommandArgument as string); + provider.RebuildIndex(); + + Log.LogEntry("Index rebuild completed for " + e.CommandArgument as string, EntryType.General, Log.SystemUsername); + + rptIndex.DataBind(); + } + + protected void btnShutdownConfirm_Click(object sender, EventArgs e) { + Log.LogEntry("WebApp shutdown requested", EntryType.General, SessionFacade.CurrentUsername); + Response.Clear(); + Response.Write(@"Web Application has been shut down, please go to the home page." + "\n\n"); + Response.Flush(); + Response.Close(); + Log.LogEntry("Executing WebApp shutdown", EntryType.General, Log.SystemUsername); + HttpRuntime.UnloadAppDomain(); + } + + public void PrintSystemStatus() { + StringBuilder sb = new StringBuilder(500); + sb.Append(Properties.Messages.OnlineUsers + ": " + + ScrewTurn.Wiki.Cache.OnlineUsers.ToString() + "
    " + "\n"); + int inactive = 0; + + List users = Users.GetUsers(); + for(int i = 0; i < users.Count; i++) { + if(!users[i].Active) inactive++; + } + sb.Append(Properties.Messages.UserCount + ": " + users.Count.ToString() + " (" + inactive.ToString() + " " + Properties.Messages.InactiveUsers + ")
    " + "\n"); + sb.Append(Properties.Messages.CachedPages + ": " + ScrewTurn.Wiki.Cache.PageCacheUsage.ToString() + "/" + Pages.GetGlobalPageCount().ToString() + " (" + ScrewTurn.Wiki.Cache.FormattedPageCacheUsage.ToString() + " " + Properties.Messages.Formatted + ")
    " + "\n"); + sb.Append(Properties.Messages.WikiVersion + ": " + Settings.WikiVersion + "" + "\n"); + if(!Page.IsPostBack) { + sb.Append(CheckVersion()); + } + sb.Append("
    "); + sb.Append(Properties.Messages.ServerUptime + ": " + Tools.TimeSpanToString(Tools.SystemUptime) + " (" + + Properties.Messages.MayBeInaccurate + ")"); + + lblSystemStatusContent.Text = sb.ToString(); + } + + private string CheckVersion() { + if(Settings.DisableAutomaticVersionCheck) return ""; + + StringBuilder sb = new StringBuilder(100); + sb.Append("("); + + string newVersion = null; + string ignored = null; + UpdateStatus status = Tools.GetUpdateStatus("http://www.screwturn.eu/Version/Wiki/3.htm", + Settings.WikiVersion, out newVersion, out ignored); + + if(status == UpdateStatus.Error) { + sb.Append(@"" + Properties.Messages.VersionCheckError + ""); + } + else if(status == UpdateStatus.NewVersionFound) { + sb.Append(@"" + Properties.Messages.NewVersionFound + ": " + newVersion + ""); + } + else if(status == UpdateStatus.UpToDate) { + sb.Append(@"" + Properties.Messages.WikiUpToDate + ""); + } + else throw new NotSupportedException(); + + sb.Append(")"); + return sb.ToString(); + } + + } + + /// + /// Represents a missing or orphaned page. + /// + public class WantedPageRow { + + private string nspace, nspacePrefix, name, linkingPages; + + /// + /// Initializes a new instance of the class. + /// + /// The namespace. + /// The namespace prefix. + /// The full name. + /// The pages that link the wanted page. + public WantedPageRow(string nspace, string nspacePrefix, string name, List linkingPages) { + this.nspace = nspace; + this.nspacePrefix = nspacePrefix; + this.name = name; + + StringBuilder sb = new StringBuilder(100); + for(int i = 0; i < linkingPages.Count; i++) { + PageInfo page = Pages.FindPage(linkingPages[i]); + if(page != null) { + PageContent content = Content.GetPageContent(page, false); + + sb.AppendFormat(@"{2}, ", page.FullName, Settings.PageExtension, + FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.Other, page)); + } + } + this.linkingPages = sb.ToString().TrimEnd(' ', ','); + } + + /// + /// Gets the namespace. + /// + public string Nspace { + get { return nspace; } + } + + /// + /// Gets the namespace prefix. + /// + public string NspacePrefix { + get { return nspacePrefix; } + } + + /// + /// Gets the full name. + /// + public string Name { + get { return name; } + } + + /// + /// Gets the linker pages. + /// + public string LinkingPages { + get { return linkingPages; } + } + + } + + /// + /// Represents the status of a search engine index. + /// + public class IndexRow { + + private string provider, providerType, documents, words, occurrences, size; + private bool isOk; + + /// + /// Initializes a new instance of the class. + /// + /// The original provider. + public IndexRow(IPagesStorageProviderV30 provider) { + this.provider = provider.Information.Name; + providerType = provider.GetType().FullName; + + int docCount, wordCount, matchCount; + long size; + provider.GetIndexStats(out docCount, out wordCount, out matchCount, out size); + + this.documents = docCount.ToString(); + this.words = wordCount.ToString(); + this.occurrences = matchCount.ToString(); + this.size = Tools.BytesToString(size); + + this.isOk = !provider.IsIndexCorrupted; + } + + /// + /// Gets the provider. + /// + public string Provider { + get { return provider; } + } + + /// + /// Gets the provider type. + /// + public string ProviderType { + get { return providerType; } + } + + /// + /// Gets the number of documents. + /// + public string Documents { + get { return documents; } + } + + /// + /// Gets the number of words. + /// + public string Words { + get { return words; } + } + + /// + /// Gets the number of occurrences. + /// + public string Occurrences { + get { return occurrences; } + } + + /// + /// Gets the size of the index. + /// + public string Size { + get { return size; } + } + + /// + /// Gets a value indicating whether the index is OK. + /// + public bool IsOK { + get { return isOk; } + } + + } + +} diff --git a/WebApplication/AdminHome.aspx.designer.cs b/WebApplication/AdminHome.aspx.designer.cs new file mode 100644 index 0000000..f6334a4 --- /dev/null +++ b/WebApplication/AdminHome.aspx.designer.cs @@ -0,0 +1,187 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4016 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AdminHome { + + /// + /// lblAdminHome control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblAdminHome; + + /// + /// lblSystemStatusContent control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblSystemStatusContent; + + /// + /// btnClearCache control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnClearCache; + + /// + /// lblMissingPages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblMissingPages; + + /// + /// rptPages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Repeater rptPages; + + /// + /// lblOrphanPages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblOrphanPages; + + /// + /// lblOrphanPagesInfoPre control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblOrphanPagesInfoPre; + + /// + /// lblOrphanPagesCount control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblOrphanPagesCount; + + /// + /// lblOrphanPagesInfoPost control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblOrphanPagesInfoPost; + + /// + /// lnkPages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkPages; + + /// + /// lblOrphanPagesInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblOrphanPagesInfo; + + /// + /// btnRebuildPageLinks control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnRebuildPageLinks; + + /// + /// lblRebuildPageLinksInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRebuildPageLinksInfo; + + /// + /// lblIndexStatus control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblIndexStatus; + + /// + /// rptIndex control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Repeater rptIndex; + + /// + /// lblRebuildIndexInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRebuildIndexInfo; + + /// + /// lblAppShutdown control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblAppShutdown; + + /// + /// lblAppShutdownInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblAppShutdownInfo; + + /// + /// btnShutdownConfirm control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnShutdownConfirm; + } +} diff --git a/WebApplication/AdminLog.aspx b/WebApplication/AdminLog.aspx new file mode 100644 index 0000000..f2f10fb --- /dev/null +++ b/WebApplication/AdminLog.aspx @@ -0,0 +1,69 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Admin.master" AutoEventWireup="true" CodeBehind="AdminLog.aspx.cs" Inherits="ScrewTurn.Wiki.AdminLog" culture="auto" meta:resourcekey="PageResource1" uiculture="auto" %> + + + + + +

    + +
    +
    + +
    + + + + + + +          + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     
    Log<%# Eval("DateTime") %><%# Eval("User") %><%# Eval("Message") %>
    Log<%# Eval("DateTime") %><%# Eval("User") %><%# Eval("Message") %>
    + +
    +
    + +
    + +
    diff --git a/WebApplication/AdminLog.aspx.cs b/WebApplication/AdminLog.aspx.cs new file mode 100644 index 0000000..a851588 --- /dev/null +++ b/WebApplication/AdminLog.aspx.cs @@ -0,0 +1,137 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class AdminLog : BasePage { + + private const int MaxEntries = 100; + + protected void Page_Load(object sender, EventArgs e) { + AdminMaster.RedirectToLoginIfNeeded(); + + if(!AdminMaster.CanManageConfiguration(SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) UrlTools.Redirect("AccessDenied.aspx"); + + if(!Page.IsPostBack) { + // Load log entries + rptLog.DataBind(); + } + } + + protected void chkFilter_CheckedChanged(object sender, EventArgs e) { + rptLog.DataBind(); + } + + protected void btnNoLimit_Click(object sender, EventArgs e) { + lblLimit.Visible = false; + btnNoLimit.Visible = false; + rptLog.DataBind(); + } + + protected void rptLog_DataBinding(object sender, EventArgs e) { + List entries = Log.ReadEntries(); + + List result = new List(entries.Count); + + int maxEntries = btnNoLimit.Visible ? MaxEntries : int.MaxValue; + + foreach(LogEntry entry in entries) { + if(IsEntryIncluded(entry.EntryType)) { + result.Add(new LogEntryRow(entry)); + } + } + + if(!Page.IsPostBack) { + if(result.Count <= MaxEntries) { + lblLimit.Visible = false; + btnNoLimit.Visible = false; + } + } + + rptLog.DataSource = result.Take(maxEntries); + } + + /// + /// Decides whether a log entry must be displayed based on the current filtering options. + /// + /// The log entry type. + /// true if the entry must be displayed, false otherwise. + private bool IsEntryIncluded(EntryType type) { + return type == EntryType.General && chkMessages.Checked || + type == EntryType.Warning && chkWarnings.Checked || + type == EntryType.Error && chkErrors.Checked; + } + + protected void btnClearLog_Click(object sender, EventArgs e) { + Log.ClearLog(); + rptLog.DataBind(); + } + + } + + /// + /// Represents a log entry for display purposes. + /// + public class LogEntryRow { + + private string imageTag, dateTime, user, message, additionalClass; + + /// + /// Initializes a new instance of the class. + /// + /// The original log entry. + public LogEntryRow(LogEntry entry) { + imageTag = entry.EntryType.ToString(); + dateTime = Preferences.AlignWithTimezone(entry.DateTime).ToString(Settings.DateTimeFormat).Replace(" ", " "); + user = entry.User.Replace(" ", " "); + message = entry.Message.Replace("&", "&"); + + if(entry.EntryType == EntryType.Error) additionalClass = " error"; + else if(entry.EntryType == EntryType.Warning) additionalClass = " warning"; + else additionalClass = ""; + } + + /// + /// Gets the image tag. + /// + public string ImageTag { + get { return imageTag; } + } + + /// + /// Gets the date/time. + /// + public string DateTime { + get { return dateTime; } + } + + /// + /// Gets the user. + /// + public string User { + get { return user; } + } + + /// + /// Gets the message. + /// + public string Message { + get { return message; } + } + + /// + /// Gets the additional CSS class. + /// + public string AdditionalClass { + get { return additionalClass; } + } + + } + +} diff --git a/WebApplication/AdminLog.aspx.designer.cs b/WebApplication/AdminLog.aspx.designer.cs new file mode 100644 index 0000000..de9493b --- /dev/null +++ b/WebApplication/AdminLog.aspx.designer.cs @@ -0,0 +1,97 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AdminLog { + + /// + /// lblLog control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblLog; + + /// + /// btnClearLog control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnClearLog; + + /// + /// lblFilter control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblFilter; + + /// + /// chkMessages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkMessages; + + /// + /// chkWarnings control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkWarnings; + + /// + /// chkErrors control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkErrors; + + /// + /// lblLimit control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblLimit; + + /// + /// btnNoLimit control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.LinkButton btnNoLimit; + + /// + /// rptLog control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Repeater rptLog; + } +} diff --git a/WebApplication/AdminNamespaces.aspx b/WebApplication/AdminNamespaces.aspx new file mode 100644 index 0000000..f8131e5 --- /dev/null +++ b/WebApplication/AdminNamespaces.aspx @@ -0,0 +1,213 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Admin.master" AutoEventWireup="true" CodeBehind="AdminNamespaces.aspx.cs" Inherits="ScrewTurn.Wiki.AdminNamespaces" culture="auto" meta:resourcekey="PageResource3" uiculture="auto" %> + +<%@ Register TagPrefix="st" TagName="ProviderSelector" Src="~/ProviderSelector.ascx" %> +<%@ Register TagPrefix="st" TagName="PermissionsManager" Src="~/PermissionsManager.ascx" %> + + + + + +

    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     
    <%# Eval("Name") %><%# ScrewTurn.Wiki.PluginFramework.NameTools.GetLocalName((string)Eval("DefaultPage")) %><%# Eval("PageCount") %><%# Eval("CategoryCount") %><%# Eval("Theme") %><%# Eval("Provider") %> + + • + +
    <%# Eval("Name") %><%# ScrewTurn.Wiki.PluginFramework.NameTools.GetLocalName((string)Eval("DefaultPage")) %><%# Eval("PageCount") %><%# Eval("CategoryCount") %><%# Eval("Theme") %><%# Eval("Provider") %> + + • + +
    + +
    +
    +
    + + +
    +

    + +
    +
    + +
    + + + +
    + +
    +
    + + +
    +
    + +
    + + + + + + + +

    + +

    + +
    +
    + +

    + +

    + +
    + + + + +
    + + +
    +
    + + + +

    + + () +

    + + + +
    + + ... + + +
    + + + +
    + +
    + +
    + + + +
    + +
    diff --git a/WebApplication/AdminNamespaces.aspx.cs b/WebApplication/AdminNamespaces.aspx.cs new file mode 100644 index 0000000..e240f2c --- /dev/null +++ b/WebApplication/AdminNamespaces.aspx.cs @@ -0,0 +1,518 @@ + +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class AdminNamespaces : BasePage { + + private const string RootName = "<root>"; + private const string RootNameUnescaped = "-- root --"; + + protected void Page_Load(object sender, EventArgs e) { + AdminMaster.RedirectToLoginIfNeeded(); + + if(!AdminMaster.CanManageNamespaces(SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) UrlTools.Redirect("AccessDenied.aspx"); + + if(!Page.IsPostBack) { + rptNamespaces.DataBind(); + + // Populate themes + string[] themes = Tools.AvailableThemes; + foreach(string theme in themes) { + lstTheme.Items.Add(new ListItem(theme, theme)); + } + } + } + + protected void rptNamespaces_DataBinding(object sender, EventArgs e) { + List namespaces = Pages.GetNamespaces(); + + List result = new List(namespaces.Count); + + bool canSetPermissions = AdminMaster.CanManagePermissions(SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames()); + + PageInfo defaultPage = Pages.FindPage(Settings.DefaultPage); + + // Inject the root namespace as first entry, retrieving the default page in Settings + result.Add(new NamespaceRow(new NamespaceInfo(RootName, defaultPage.Provider, defaultPage), + Settings.GetTheme(null), + Pages.GetPages(null).Count, Pages.GetCategories(null).Count, + canSetPermissions, txtCurrentNamespace.Value == RootName)); + + foreach(NamespaceInfo ns in namespaces) { + result.Add(new NamespaceRow(ns, Settings.GetTheme(ns.Name), + Pages.GetPages(ns).Count, Pages.GetCategories(ns).Count, + canSetPermissions, txtCurrentNamespace.Value == ns.Name)); + } + + rptNamespaces.DataSource = result; + } + + protected void rptNamespaces_ItemCommand(object sender, RepeaterCommandEventArgs e) { + txtCurrentNamespace.Value = e.CommandArgument as string; + + NamespaceInfo nspace = txtCurrentNamespace.Value != RootName ? + Pages.FindNamespace(txtCurrentNamespace.Value) : null; + + if(e.CommandName == "Select") { + // rptNamespaces.DataBind(); Not needed because the list is hidden on select + + string theme = Settings.GetTheme(nspace != null ? nspace.Name : null); + + txtName.Enabled = false; + txtName.Text = nspace != null ? nspace.Name : RootNameUnescaped; + txtNewName.Text = ""; + cvName.Enabled = false; + cvName2.Enabled = false; + LoadDefaultPages(); + lstTheme.SelectedIndex = -1; + foreach(ListItem item in lstTheme.Items) { + if(item.Value == theme) { + item.Selected = true; + break; + } + } + providerSelector.SelectedProvider = nspace != null ? nspace.Provider.ToString() : Settings.DefaultPagesProvider; + providerSelector.Enabled = false; + + btnCreate.Visible = false; + btnSave.Visible = true; + btnDelete.Visible = true; + // Cannot delete root namespace + btnDelete.Enabled = nspace != null; + // Cannot rename root namespace + btnRename.Enabled = nspace != null; + + pnlList.Visible = false; + pnlEditNamespace.Visible = true; + + lblResult.Text = ""; + lblResult.CssClass = ""; + } + else if(e.CommandName == "Perms") { + if(!AdminMaster.CanManagePermissions(SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) return; + + permissionsManager.CurrentResourceName = nspace != null ? nspace.Name : null; + + lblNamespaceName.Text = nspace != null ? nspace.Name : RootName; + + pnlList.Visible = false; + pnlPermissions.Visible = true; + + lblResult.Text = ""; + lblResult.CssClass = ""; + } + } + + protected void btnPublic_Click(object sender, EventArgs e) { + NamespaceInfo nspace = Pages.FindNamespace(txtCurrentNamespace.Value); + + RemoveAllPermissions(nspace); + + // Set permissions + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.FullControl, + Users.FindUserGroup(Settings.AdministratorsGroup)); + + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.CreatePages, + Users.FindUserGroup(Settings.UsersGroup)); + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.ManageCategories, + Users.FindUserGroup(Settings.UsersGroup)); + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.PostDiscussion, + Users.FindUserGroup(Settings.UsersGroup)); + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.DownloadAttachments, + Users.FindUserGroup(Settings.UsersGroup)); + + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.ModifyPages, + Users.FindUserGroup(Settings.AnonymousGroup)); + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.PostDiscussion, + Users.FindUserGroup(Settings.AnonymousGroup)); + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.DownloadAttachments, + Users.FindUserGroup(Settings.AnonymousGroup)); + + RefreshPermissionsManager(); + } + + protected void btnNormal_Click(object sender, EventArgs e) { + NamespaceInfo nspace = Pages.FindNamespace(txtCurrentNamespace.Value); + + RemoveAllPermissions(nspace); + + // Set permissions + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.FullControl, + Users.FindUserGroup(Settings.AdministratorsGroup)); + + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.CreatePages, + Users.FindUserGroup(Settings.UsersGroup)); + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.ManageCategories, + Users.FindUserGroup(Settings.UsersGroup)); + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.PostDiscussion, + Users.FindUserGroup(Settings.UsersGroup)); + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.DownloadAttachments, + Users.FindUserGroup(Settings.UsersGroup)); + + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.ReadPages, + Users.FindUserGroup(Settings.AnonymousGroup)); + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.ReadDiscussion, + Users.FindUserGroup(Settings.AnonymousGroup)); + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.DownloadAttachments, + Users.FindUserGroup(Settings.AnonymousGroup)); + + RefreshPermissionsManager(); + } + + protected void btnPrivate_Click(object sender, EventArgs e) { + NamespaceInfo nspace = Pages.FindNamespace(txtCurrentNamespace.Value); + + RemoveAllPermissions(nspace); + + // Set permissions + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.FullControl, + Users.FindUserGroup(Settings.AdministratorsGroup)); + + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.CreatePages, + Users.FindUserGroup(Settings.UsersGroup)); + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.ManageCategories, + Users.FindUserGroup(Settings.UsersGroup)); + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.PostDiscussion, + Users.FindUserGroup(Settings.UsersGroup)); + AuthWriter.SetPermissionForNamespace(AuthStatus.Grant, nspace, Actions.ForNamespaces.DownloadAttachments, + Users.FindUserGroup(Settings.UsersGroup)); + + RefreshPermissionsManager(); + } + + /// + /// Refreshes the permissions manager. + /// + private void RefreshPermissionsManager() { + string r = permissionsManager.CurrentResourceName; + permissionsManager.CurrentResourceName = r; + } + + /// + /// Removes all the permissions for a namespace. + /// + /// The namespace (null for the root). + private void RemoveAllPermissions(NamespaceInfo nspace) { + AuthWriter.RemoveEntriesForNamespace(Users.FindUserGroup(Settings.AnonymousGroup), nspace); + AuthWriter.RemoveEntriesForNamespace(Users.FindUserGroup(Settings.UsersGroup), nspace); + AuthWriter.RemoveEntriesForNamespace(Users.FindUserGroup(Settings.AdministratorsGroup), nspace); + } + + protected void cvName_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = Pages.FindNamespace(txtName.Text) == null; + } + + protected void cvName2_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = Pages.IsValidName(txtName.Text); + } + + protected void btnCreate_Click(object sender, EventArgs e) { + Page.Validate("namespace"); + if(!Page.IsValid) return; + + // Create new namespace and the default page (MainPage) + bool done = Pages.CreateNamespace(txtName.Text, + Collectors.PagesProviderCollector.GetProvider(providerSelector.SelectedProvider)); + + if(done) { + NamespaceInfo nspace = Pages.FindNamespace(txtName.Text); + done = Pages.CreatePage(nspace, "MainPage"); + PageInfo page = Pages.FindPage(NameTools.GetFullName(nspace.Name, "MainPage")); + + if(done) { + done = Pages.ModifyPage(page, "Main Page", Log.SystemUsername, + DateTime.Now, "", Defaults.MainPageContentForSubNamespace, + new string[0], "", SaveMode.Normal); + + if(done) { + done = Pages.SetNamespaceDefaultPage(nspace, page); + + if(done) { + Settings.SetTheme(nspace.Name, lstTheme.SelectedValue); + + if(done) { + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.NamespaceCreated; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.NamespaceCreatedCouldNotSetTheme; + } + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.NamespaceCreatedCouldNotSetDefaultPage; + } + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.NamespaceCreatedCouldNotStoreDefaultPageContent; + } + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.NamespaceCreatedCouldNotCreateDefaultPage; + } + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotCreateNamespace; + } + } + + protected void btnSave_Click(object sender, EventArgs e) { + // This can rarely occur + if(string.IsNullOrEmpty(lstDefaultPage.SelectedValue)) return; + if(string.IsNullOrEmpty(lstTheme.SelectedValue)) return; + + NamespaceInfo nspace = txtCurrentNamespace.Value != RootName ? + Pages.FindNamespace(txtCurrentNamespace.Value) : null; + + bool done = Pages.SetNamespaceDefaultPage(nspace, Pages.FindPage(lstDefaultPage.SelectedValue)); + + if(done) { + Settings.SetTheme(nspace != null ? nspace.Name : null, lstTheme.SelectedValue); + + if(done) { + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.NamespaceSaved; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotSetNamespaceTheme; + } + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotSetNamespaceDefaultPage; + } + } + + protected void btnDelete_Click(object sender, EventArgs e) { + pnlDelete.Visible = true; + } + + protected void btnConfirmDeletion_Click(object sender, EventArgs e) { + bool done = Pages.RemoveNamespace(Pages.FindNamespace(txtCurrentNamespace.Value)); + + if(done) { + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.NamespaceDeleted; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotDeleteNamespace; + } + } + + protected void btnCancel_Click(object sender, EventArgs e) { + RefreshList(); + ReturnToList(); + } + + protected void btnBack_Click(object sender, EventArgs e) { + RefreshList(); + ReturnToList(); + } + + protected void btnNewNamespace_Click(object sender, EventArgs e) { + pnlList.Visible = false; + pnlEditNamespace.Visible = true; + lstDefaultPage.Enabled = false; + lblDefaultPageInfo.Visible = true; + btnRename.Enabled = false; + + lblResult.Text = ""; + lblResult.CssClass = ""; + } + + /// + /// Refreshes the namespace list. + /// + private void RefreshList() { + txtCurrentNamespace.Value = ""; + ResetEditor(); + rptNamespaces.DataBind(); + } + + /// + /// Loads the available pages and selects the current one. + /// + private void LoadDefaultPages() { + // Populate default page, if there is a namespace selected + if(!string.IsNullOrEmpty(txtCurrentNamespace.Value)) { + NamespaceInfo nspace = Pages.FindNamespace( + txtCurrentNamespace.Value != RootName ? txtCurrentNamespace.Value : null); + + List pages = Pages.GetPages(nspace); + + string currentDefaultPage = nspace != null ? nspace.DefaultPage.FullName : Settings.DefaultPage; + + lstDefaultPage.Items.Clear(); + foreach(PageInfo page in pages) { + ListItem item = new ListItem(NameTools.GetLocalName(page.FullName), page.FullName); + if(page.FullName == currentDefaultPage) item.Selected = true; + lstDefaultPage.Items.Add(item); + } + } + } + + /// + /// Resets the namespace editor. + /// + private void ResetEditor() { + txtName.Text = ""; + txtName.Enabled = true; + cvName.Enabled = true; + cvName2.Enabled = true; + providerSelector.Enabled = true; + providerSelector.Reload(); + lstDefaultPage.Enabled = true; + lstDefaultPage.Items.Clear(); + lblDefaultPageInfo.Visible = false; + lstTheme.SelectedIndex = 0; + btnCreate.Visible = true; + btnSave.Visible = false; + btnDelete.Visible = false; + btnDelete.Enabled = true; + pnlDelete.Visible = false; + btnRename.Enabled = true; + } + + /// + /// Returns to the group list. + /// + private void ReturnToList() { + pnlEditNamespace.Visible = false; + pnlPermissions.Visible = false; + pnlList.Visible = true; + } + + protected void cvNewName_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = Pages.FindNamespace(txtNewName.Text) == null; + } + + protected void cvNewName2_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = Pages.IsValidName(txtNewName.Text); + } + + protected void btnRename_Click(object sender, EventArgs e) { + lblRenameResult.CssClass = ""; + lblRenameResult.Text = ""; + + Page.Validate("rename"); + if(!Page.IsValid) return; + + if(Pages.RenameNamespace(Pages.FindNamespace(txtCurrentNamespace.Value), txtNewName.Text)) { + RefreshList(); + lblRenameResult.CssClass = "resultok"; + lblRenameResult.Text = Properties.Messages.NamespaceRenamed; + ReturnToList(); + } + else { + lblRenameResult.CssClass = "resulterror"; + lblRenameResult.Text = Properties.Messages.CouldNotRenameNamespace; + } + } + + } + + /// + /// Represents a Namespace for display purposes. + /// + public class NamespaceRow { + + private string name, defaultPage, theme, pageCount, categoryCount, provider, additionalClass; + private bool canSetPermissions; + + /// + /// Initializes a new instance of the class. + /// + /// The original namespace. + /// The theme for the namespace. + /// The page count. + /// The category count. + /// A value indicating whether the current user can set namespace permissions. + /// A value indicating whether the namespace is selected. + public NamespaceRow(NamespaceInfo nspace, string theme, int pageCount, int categoryCount, bool canSetPermissions, bool selected) { + name = nspace.Name; + defaultPage = nspace.DefaultPage.FullName; + this.theme = theme; + this.pageCount = pageCount.ToString(); + this.categoryCount = categoryCount.ToString(); + provider = nspace.Provider.Information.Name; + this.canSetPermissions = canSetPermissions; + additionalClass = selected ? " selected" : ""; + } + + /// + /// Gets the name. + /// + public string Name { + get { return name; } + } + + /// + /// Gets the default page. + /// + public string DefaultPage { + get { return defaultPage; } + } + + /// + /// Gets the theme. + /// + public string Theme { + get { return theme; } + } + + /// + /// Gets the page count. + /// + public string PageCount { + get { return pageCount; } + } + + /// + /// Gets the category count. + /// + public string CategoryCount { + get { return categoryCount; } + } + + /// + /// Gets the provider. + /// + public string Provider { + get { return provider; } + } + + /// + /// Gets a value indicating whether the current user can set namespace permissions. + /// + public bool CanSetPermissions { + get { return canSetPermissions; } + } + + /// + /// Gets the additional CSS class. + /// + public string AdditionalClass { + get { return additionalClass; } + } + + } + +} diff --git a/WebApplication/AdminNamespaces.aspx.designer.cs b/WebApplication/AdminNamespaces.aspx.designer.cs new file mode 100644 index 0000000..6a01e00 --- /dev/null +++ b/WebApplication/AdminNamespaces.aspx.designer.cs @@ -0,0 +1,466 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4016 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AdminNamespaces { + + /// + /// lblNamespaces control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNamespaces; + + /// + /// pnlList control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlList; + + /// + /// btnNewNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnNewNamespace; + + /// + /// rptNamespaces control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Repeater rptNamespaces; + + /// + /// pnlEditNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlEditNamespace; + + /// + /// lblEditTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblEditTitle; + + /// + /// lblProvider control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblProvider; + + /// + /// providerSelector control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.ProviderSelector providerSelector; + + /// + /// lblName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblName; + + /// + /// txtName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtName; + + /// + /// rfvName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RequiredFieldValidator rfvName; + + /// + /// cvName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CustomValidator cvName; + + /// + /// cvName2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CustomValidator cvName2; + + /// + /// lblDefaultPage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDefaultPage; + + /// + /// lstDefaultPage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.DropDownList lstDefaultPage; + + /// + /// lblDefaultPageInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblDefaultPageInfo; + + /// + /// lblTheme control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblTheme; + + /// + /// lstTheme control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.DropDownList lstTheme; + + /// + /// btnSave control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnSave; + + /// + /// btnCreate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnCreate; + + /// + /// btnDelete control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnDelete; + + /// + /// btnCancel control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnCancel; + + /// + /// lblResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblResult; + + /// + /// pnlDelete control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlDelete; + + /// + /// lblConfirmDeletion control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblConfirmDeletion; + + /// + /// btnConfirmDeletion control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnConfirmDeletion; + + /// + /// lblRename control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRename; + + /// + /// lblRenameInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRenameInfo; + + /// + /// lblNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNewName; + + /// + /// txtNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtNewName; + + /// + /// btnRename control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnRename; + + /// + /// rfvNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RequiredFieldValidator rfvNewName; + + /// + /// cvNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CustomValidator cvNewName; + + /// + /// cvNewName2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CustomValidator cvNewName2; + + /// + /// lblRenameResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblRenameResult; + + /// + /// pnlPermissions control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlPermissions; + + /// + /// lblNamespacePermissions control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNamespacePermissions; + + /// + /// lblNamespaceName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNamespaceName; + + /// + /// lblInheritanceInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblInheritanceInfo; + + /// + /// lblPermissionsTemplates control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPermissionsTemplates; + + /// + /// btnPublic control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.LinkButton btnPublic; + + /// + /// lblPublicInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPublicInfo; + + /// + /// btnNormal control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.LinkButton btnNormal; + + /// + /// lblNormalInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNormalInfo; + + /// + /// btnPrivate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.LinkButton btnPrivate; + + /// + /// lblPrivateInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPrivateInfo; + + /// + /// lblPermissionsTemplatesInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPermissionsTemplatesInfo; + + /// + /// permissionsManager control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.PermissionsManager permissionsManager; + + /// + /// btnBack control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnBack; + + /// + /// txtCurrentNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.HiddenField txtCurrentNamespace; + } +} diff --git a/WebApplication/AdminNavPaths.aspx b/WebApplication/AdminNavPaths.aspx new file mode 100644 index 0000000..2fdabd4 --- /dev/null +++ b/WebApplication/AdminNavPaths.aspx @@ -0,0 +1,114 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Admin.master" AutoEventWireup="true" CodeBehind="AdminNavPaths.aspx.cs" Inherits="ScrewTurn.Wiki.AdminNavPaths" culture="auto" meta:resourcekey="PageResource2" uiculture="auto" %> + + + + + +

    + + + + +
    +
    + +
    + +
    + + +
    +

    + +
    + + + +

    + + +
    + +
    + + +
    +
    + + +
    + + + + + + +
    +
    +
    + + + +
    + +
    diff --git a/WebApplication/AdminNavPaths.aspx.cs b/WebApplication/AdminNavPaths.aspx.cs new file mode 100644 index 0000000..1afa164 --- /dev/null +++ b/WebApplication/AdminNavPaths.aspx.cs @@ -0,0 +1,354 @@ + +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class AdminNavPaths : BasePage { + + protected void Page_Load(object sender, EventArgs e) { + AdminMaster.RedirectToLoginIfNeeded(); + + if(!AdminMaster.CanManagePages(SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) UrlTools.Redirect("AccessDenied.aspx"); + + if(!Page.IsPostBack) { + // Load namespaces + + // Add root namespace + lstNamespace.Items.Add(new ListItem("", "")); + + List namespaces = Pages.GetNamespaces(); + + foreach(NamespaceInfo ns in namespaces) { + lstNamespace.Items.Add(new ListItem(ns.Name, ns.Name)); + } + + // Load navigation paths + rptNavPaths.DataBind(); + } + + btnNewNavPath.Enabled = CanManagePagesInCurrentNamespace(); + } + + protected void lstNamespace_SelectedIndexChanged(object sender, EventArgs e) { + rptNavPaths.DataBind(); + btnNewNavPath.Enabled = CanManagePagesInCurrentNamespace(); + } + + /// + /// Gets a value indicating whether the current user can manage pages in the selected namespace. + /// + /// true if the user can manage pages, false otherwise. + private bool CanManagePagesInCurrentNamespace() { + NamespaceInfo nspace = Pages.FindNamespace(lstNamespace.SelectedValue); + bool canManagePages = AuthChecker.CheckActionForNamespace(nspace, Actions.ForNamespaces.ManagePages, + SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames()); + return canManagePages; + } + + protected void rptNavPaths_DataBinding(object sender, EventArgs e) { + bool canManagePages = CanManagePagesInCurrentNamespace(); + List paths = NavigationPaths.GetNavigationPaths(Pages.FindNamespace(lstNamespace.SelectedValue)); + + List result = new List(paths.Count); + + foreach(NavigationPath path in paths) { + result.Add(new NavigationPathRow(path, canManagePages, path.FullName == txtCurrentNavPath.Value)); + } + + rptNavPaths.DataSource = result; + } + + protected void rptNavPaths_ItemCommand(object sender, CommandEventArgs e) { + txtCurrentNavPath.Value = e.CommandArgument as string; + + if(e.CommandName == "Select") { + if(!CanManagePagesInCurrentNamespace()) return; + + txtName.Text = txtCurrentNavPath.Value; + txtName.Enabled = false; + + NavigationPath path = NavigationPaths.Find(txtCurrentNavPath.Value); + foreach(string page in path.Pages) { + PageInfo pageInfo = Pages.FindPage(page); + if(pageInfo != null) { + PageContent content = Content.GetPageContent(pageInfo, false); + lstPages.Items.Add(new ListItem(FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.Other, pageInfo), pageInfo.FullName)); + } + } + + cvName2.Enabled = false; + btnCreate.Visible = false; + btnSave.Visible = true; + btnDelete.Visible = true; + + pnlList.Visible = false; + pnlEditNavPath.Visible = true; + + lblResult.Text = ""; + } + } + + protected void btnNewNavPath_Click(object sender, EventArgs e) { + if(!CanManagePagesInCurrentNamespace()) return; + + pnlList.Visible = false; + pnlEditNavPath.Visible = true; + lblResult.Text = ""; + } + + protected void cvName1_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = Pages.IsValidName(txtName.Text); + } + + protected void cvName2_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = NavigationPaths.Find(NameTools.GetFullName(lstNamespace.SelectedValue, txtName.Text)) == null; + } + + /// + /// Resets the account editor. + /// + private void ResetEditor() { + txtName.Text = ""; + txtName.Enabled = true; + + txtPageName.Text = ""; + lstAvailablePage.Items.Clear(); + btnAdd.Enabled = false; + lstPages.Items.Clear(); + lstPages_SelectedIndexChanged(this, null); + + cvName2.Enabled = true; + btnCreate.Visible = true; + btnSave.Visible = false; + btnDelete.Visible = false; + lblResult.Text = ""; + } + + /// + /// Returns to the accounts list. + /// + private void ReturnToList() { + pnlEditNavPath.Visible = false; + pnlList.Visible = true; + } + + /// + /// Refreshes the users list. + /// + private void RefreshList() { + txtCurrentNavPath.Value = ""; + ResetEditor(); + rptNavPaths.DataBind(); + } + + protected void btnSearch_Click(object sender, EventArgs e) { + txtPageName.Text = txtPageName.Text.Trim(); + + if(txtPageName.Text.Length == 0) { + lstAvailablePage.Items.Clear(); + btnAdd.Enabled = false; + return; + } + + PageInfo[] pages = SearchTools.SearchSimilarPages(txtPageName.Text, lstNamespace.SelectedValue); + + lstAvailablePage.Items.Clear(); + + foreach(PageInfo page in pages) { + PageContent content = Content.GetPageContent(page, false); + lstAvailablePage.Items.Add(new ListItem(FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.Other, page), page.FullName)); + } + + btnAdd.Enabled = pages.Length > 0; + } + + protected void btnAdd_Click(object sender, EventArgs e) { + PageInfo page = Pages.FindPage(lstAvailablePage.SelectedValue); + PageContent content = Content.GetPageContent(page, false); + + lstPages.Items.Add(new ListItem(FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.Other, page), page.FullName)); + + txtPageName.Text = ""; + lstAvailablePage.Items.Clear(); + btnAdd.Enabled = false; + } + + protected void lstPages_SelectedIndexChanged(object sender, EventArgs e) { + btnUp.Enabled = lstPages.SelectedIndex > 0; + btnDown.Enabled = lstPages.SelectedIndex < lstPages.Items.Count - 1; + btnRemove.Enabled = lstPages.SelectedIndex >= 0;; + } + + protected void btnUp_Click(object sender, EventArgs e) { + int index = lstPages.SelectedIndex; + ListItem item = lstPages.Items[lstPages.SelectedIndex]; + lstPages.Items.RemoveAt(lstPages.SelectedIndex); + lstPages.Items.Insert(index - 1, item); + lstPages.SelectedIndex = index - 1; + lstPages_SelectedIndexChanged(sender, e); + } + + protected void btnDown_Click(object sender, EventArgs e) { + int index = lstPages.SelectedIndex; + ListItem item = lstPages.Items[lstPages.SelectedIndex]; + lstPages.Items.RemoveAt(lstPages.SelectedIndex); + lstPages.Items.Insert(index + 1, item); + lstPages.SelectedIndex = index + 1; + lstPages_SelectedIndexChanged(sender, e); + } + + protected void btnRemove_Click(object sender, EventArgs e) { + lstPages.Items.RemoveAt(lstPages.SelectedIndex); + lstPages.SelectedIndex = -1; + lstPages_SelectedIndexChanged(sender, e); + } + + protected void btnCancel_Click(object sender, EventArgs e) { + RefreshList(); + ReturnToList(); + } + + protected void btnCreate_Click(object sender, EventArgs e) { + if(!CanManagePagesInCurrentNamespace()) return; + + if(!Page.IsValid) return; + + if(lstPages.Items.Count == 0) { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.TheNavPathMustContain; + return; + } + + bool done = NavigationPaths.AddNavigationPath(Pages.FindNamespace(lstNamespace.SelectedValue), + txtName.Text, GetSelectedPages(), null); + + if(done) { + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.NavPathCreated; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotCreateNavPath; + } + } + + protected void btnDelete_Click(object sender, EventArgs e) { + if(!CanManagePagesInCurrentNamespace()) return; + + bool done = NavigationPaths.RemoveNavigationPath(txtCurrentNavPath.Value); + + if(done) { + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.NavPathDeleted; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotDeleteNavPath; + } + } + + protected void btnSave_Click(object sender, EventArgs e) { + if(!CanManagePagesInCurrentNamespace()) return; + + bool done = NavigationPaths.ModifyNavigationPath(txtCurrentNavPath.Value, GetSelectedPages()); + + if(done) { + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.NavPathSaved; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotSaveNavPath; + } + } + + /// + /// Gets the selected pages for the navigation path. + /// + /// The selected pages. + private List GetSelectedPages() { + List result = new List(lstPages.Items.Count); + + foreach(ListItem item in lstPages.Items) { + PageInfo page = Pages.FindPage(item.Value); + if(page != null) { + result.Add(page); + } + } + + return result; + } + + } + + /// + /// Represents a navigation path for display purposes. + /// + public class NavigationPathRow { + + private string fullName, pages, provider, additionalClass; + private bool canSelect; + + /// + /// Initializes a new instance of the class. + /// + /// The original navigation path. + /// A value indicating whether the path can be selected. + /// A value indicating whether the navigation path is selected. + public NavigationPathRow(NavigationPath path, bool canSelect, bool selected) { + fullName = path.FullName; + pages = path.Pages.Length.ToString(); + provider = path.Provider.Information.Name; + this.canSelect = canSelect; + additionalClass = selected ? " selected" : ""; + } + + /// + /// Gets the full name. + /// + public string FullName { + get { return fullName; } + } + + /// + /// Gets the pages. + /// + public string Pages { + get { return pages; } + } + + /// + /// Gets the provider. + /// + public string Provider { + get { return provider; } + } + + /// + /// Gets a value indicating whether the path can be selected. + /// + public bool CanSelect { + get { return canSelect; } + } + + /// + /// Gets the additional CSS class. + /// + public string AdditionalClass { + get { return additionalClass; } + } + + } + +} diff --git a/WebApplication/AdminNavPaths.aspx.designer.cs b/WebApplication/AdminNavPaths.aspx.designer.cs new file mode 100644 index 0000000..895f168 --- /dev/null +++ b/WebApplication/AdminNavPaths.aspx.designer.cs @@ -0,0 +1,259 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4016 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AdminNavPaths { + + /// + /// lblNavPaths control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNavPaths; + + /// + /// pnlList control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlList; + + /// + /// btnNewNavPath control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnNewNavPath; + + /// + /// lblNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNamespace; + + /// + /// lstNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstNamespace; + + /// + /// rptNavPaths control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Repeater rptNavPaths; + + /// + /// pnlEditNavPath control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlEditNavPath; + + /// + /// lblEditTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblEditTitle; + + /// + /// lblName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblName; + + /// + /// txtName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtName; + + /// + /// rfvName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RequiredFieldValidator rfvName; + + /// + /// cvName1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CustomValidator cvName1; + + /// + /// cvName2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CustomValidator cvName2; + + /// + /// txtPageName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtPageName; + + /// + /// btnSearch control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnSearch; + + /// + /// lstAvailablePage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstAvailablePage; + + /// + /// btnAdd control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnAdd; + + /// + /// lstPages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.ListBox lstPages; + + /// + /// btnUp control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnUp; + + /// + /// btnDown control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnDown; + + /// + /// btnRemove control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnRemove; + + /// + /// btnSave control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnSave; + + /// + /// btnCreate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnCreate; + + /// + /// btnDelete control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnDelete; + + /// + /// btnCancel control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnCancel; + + /// + /// lblResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblResult; + + /// + /// txtCurrentNavPath control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.HiddenField txtCurrentNavPath; + } +} diff --git a/WebApplication/AdminPages.aspx b/WebApplication/AdminPages.aspx new file mode 100644 index 0000000..8b1c310 --- /dev/null +++ b/WebApplication/AdminPages.aspx @@ -0,0 +1,372 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Admin.master" AutoEventWireup="true" CodeBehind="AdminPages.aspx.cs" Inherits="ScrewTurn.Wiki.AdminPages" culture="auto" meta:resourcekey="PageResource2" uiculture="auto" %> + +<%@ Register TagPrefix="st" TagName="PermissionsManager" Src="~/PermissionsManager.ascx" %> +<%@ Register TagPrefix="st" TagName="PageSelector" Src="~/PageSelector.ascx" %> +<%@ Register TagPrefix="st" TagName="ProviderSelector" Src="~/ProviderSelector.ascx" %> +<%@ Register TagPrefix="st" TagName="PageListBuilder" Src="~/PageListBuilder.ascx" %> + + + + + + + +

    + + +
    + +

    + +
    + +
    +
    + +
    +
    + +      + + +
    + +
    + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     
    <%# ScrewTurn.Wiki.PluginFramework.NameTools.GetLocalName((string)Eval("FullName")) %><%# Eval("Title") %><%# Eval("CreatedOn") %><%# Eval("CreatedBy") %><%# Eval("LastModifiedOn") %><%# Eval("LastModifiedBy") %><%# Eval("Discussion") %><%# Eval("Revisions") %><%# (bool)Eval("IsOrphan") ? "" + ScrewTurn.Wiki.Properties.Messages.Yes + "" : ""%><%# Eval("Provider") %> + • + + • + +
    <%# ScrewTurn.Wiki.PluginFramework.NameTools.GetLocalName((string)Eval("FullName")) %><%# Eval("Title") %><%# Eval("CreatedOn") %><%# Eval("CreatedBy") %><%# Eval("LastModifiedOn") %><%# Eval("LastModifiedBy") %><%# Eval("Discussion") %><%# Eval("Revisions") %><%# (bool)Eval("IsOrphan") ? "" + ScrewTurn.Wiki.Properties.Messages.Yes + "" : ""%><%# Eval("Provider") %> + + • + + • + +
    + +
    +
    +
    + + +
    +

    + ()

    +
    + + +
    +

    +
    + +
    + +

    + Preview • + • + +

    + + +
    + +
    +
    + + +
    +

    +
    +
    +
    + +

    + +
    + + + +
    +
    + + +
    +

    +
    + +
    + +
    + +

    + + +
    +
    + + +
    +

    +
    +
    + +

    + +
    + +
    +
    + + +
    +

    +
    +
    +
    + +

    + +
    + +
    +
    + + +
    +

    +
    + +
    + +
    +
    + + +
    +

    +
    + +
    + +
    +
    + +
    + +
    +
    +
    + + + +

    + + () +

    + +
    + + ... + + +
    + + + +
    + +
    + +
    + + +
    +
    +

    + + +

    + + +
    + +
    +

    + + +

    + + +
    + +
    +

    + +

    + + +

    + +
    + +
    + +
    + +
    +
    +
    + + + + + +
    + +
    diff --git a/WebApplication/AdminPages.aspx.cs b/WebApplication/AdminPages.aspx.cs new file mode 100644 index 0000000..d5f6fc6 --- /dev/null +++ b/WebApplication/AdminPages.aspx.cs @@ -0,0 +1,1035 @@ + +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class AdminPages : BasePage { + + /// + /// The numer of items in a page. + /// + public const int PageSize = 50; + + private IList currentPages = null; + + private int rangeBegin = 0; + private int rangeEnd = PageSize - 1; + private int selectedPage = 0; + + private PageInfo externallySelectedPage = null; + + protected void Page_Load(object sender, EventArgs e) { + AdminMaster.RedirectToLoginIfNeeded(); + + if(!Page.IsPostBack) { + // Load namespaces + + // Add root namespace + lstNamespace.Items.Add(new ListItem("", "")); + + List namespaces = Pages.GetNamespaces(); + + foreach(NamespaceInfo ns in namespaces) { + lstNamespace.Items.Add(new ListItem(ns.Name, ns.Name)); + } + + bool loaded = LoadExternallySelectedPage(); + + if(!loaded) { + // Load pages + ResetPageList(); + rptPages.DataBind(); + } + } + } + + /// + /// Resets the page list. + /// + private void ResetPageList() { + currentPages = GetPages(); + pageSelector.ItemCount = currentPages.Count; + pageSelector.SelectPage(0); + } + + /// + /// Gets the pages in the selected namespace. + /// + /// The pages. + private IList GetPages() { + NamespaceInfo nspace = Pages.FindNamespace(lstNamespace.SelectedValue); + List pages = Pages.GetPages(nspace); + + List result = new List(pages.Count); + + string filter = txtFilter.Text.Trim().ToLower(System.Globalization.CultureInfo.CurrentCulture); + + foreach(PageInfo page in pages) { + if(NameTools.GetLocalName(page.FullName).ToLower(System.Globalization.CultureInfo.CurrentCulture).Contains(filter)) { + result.Add(page); + } + } + + return result; + } + + /// + /// Loads the externally selected page. + /// + /// true if a page was loaded, false otherwise. + private bool LoadExternallySelectedPage() { + if(!string.IsNullOrEmpty(Request["Rollback"])) { + externallySelectedPage = Pages.FindPage(Request["Rollback"]); + if(externallySelectedPage != null) { + AutoRollback(); + return true; + } + } + + if(!string.IsNullOrEmpty(Request["Admin"])) { + externallySelectedPage = Pages.FindPage(Request["Admin"]); + if(externallySelectedPage != null) { + txtCurrentPage.Value = externallySelectedPage.FullName; + ActivatePageEditor(); + return true; + } + } + + if(!string.IsNullOrEmpty(Request["Perms"])) { + externallySelectedPage = Pages.FindPage(Request["Perms"]); + if(externallySelectedPage != null) { + txtCurrentPage.Value = externallySelectedPage.FullName; + ActivatePagePermissionsManager(); + return true; + } + } + + return false; + } + + /// + /// Rolls back the externally selected page to the previous version. + /// + private void AutoRollback() { + List backups = Pages.GetBackups(externallySelectedPage); + if(backups.Count > 0) { + int targetRevision = backups[backups.Count - 1]; + + Log.LogEntry("Page rollback requested for " + txtCurrentPage.Value + " to rev. " + targetRevision.ToString(), EntryType.General, SessionFacade.GetCurrentUsername()); + + Pages.Rollback(externallySelectedPage, targetRevision); + + UrlTools.Redirect(externallySelectedPage.FullName + Settings.PageExtension); + } + } + + protected void btnNewPage_Click(object sender, EventArgs e) { + // Redirect to the edit page, keeping the correct namespace + string currentNamespace = lstNamespace.SelectedValue; + if(!string.IsNullOrEmpty(currentNamespace)) currentNamespace += "."; + Response.Redirect(currentNamespace + "Edit.aspx"); + } + + protected void pageSelector_SelectedPageChanged(object sender, SelectedPageChangedEventArgs e) { + rangeBegin = e.SelectedPage * PageSize; + rangeEnd = rangeBegin + e.ItemCount - 1; + selectedPage = e.SelectedPage; + + rptPages.DataBind(); + } + + protected void lstNamespace_SelectedIndexChanged(object sender, EventArgs e) { + currentPages = GetPages(); + pageSelector.ItemCount = currentPages.Count; + pageSelector.SelectPage(0); + + rptPages.DataBind(); + + string currentUser = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + + bool canManageAllPages = AuthChecker.CheckActionForNamespace( + Pages.FindNamespace(lstNamespace.SelectedValue), + Actions.ForNamespaces.ManagePages, currentUser, currentGroups); + + btnBulkMigrate.Enabled = canManageAllPages; + } + + protected void btnFilter_Click(object sender, EventArgs e) { + currentPages = GetPages(); + pageSelector.ItemCount = currentPages.Count; + pageSelector.SelectPage(0); + + rptPages.DataBind(); + } + + protected void rdoFilter_CheckedChanged(object sender, EventArgs e) { + currentPages = GetPages(); + pageSelector.ItemCount = currentPages.Count; + pageSelector.SelectPage(0); + + rptPages.DataBind(); + } + + protected void rptPages_DataBinding(object sender, EventArgs e) { + if(currentPages == null) currentPages = GetPages(); + NamespaceInfo nspace = DetectNamespaceInfo(); + + List result = new List(PageSize); + + string currentUser = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + + bool canSetPermissions = AdminMaster.CanManagePermissions(currentUser, currentGroups); + bool canDeletePages = AuthChecker.CheckActionForNamespace(nspace, Actions.ForNamespaces.DeletePages, currentUser, currentGroups); + + for(int i = rangeBegin; i <= rangeEnd; i++) { + PageInfo page = currentPages[i]; + + PageContent currentContent = Content.GetPageContent(page, false); + + // The page can be selected if the user can either manage or delete the page or manage the discussion + // Repeat checks for enabling/disabling sections when a page is selected + bool canEdit = AuthChecker.CheckActionForPage(page, Actions.ForPages.ModifyPage, currentUser, currentGroups); + bool canManagePage = false; + bool canManageDiscussion = false; + if(!canDeletePages) canManagePage = AuthChecker.CheckActionForPage(page, Actions.ForPages.ManagePage, currentUser, currentGroups); + if(!canDeletePages && !canManagePage) canManageDiscussion = AuthChecker.CheckActionForPage(page, Actions.ForPages.ManageDiscussion, currentUser, currentGroups); + bool canSelect = canManagePage | canDeletePages | canManageDiscussion; + + int incomingLinks = Pages.GetPageIncomingLinks(page).Length; + + if(chkOrphansOnly.Checked && incomingLinks > 0) continue; + + PageContent firstContent = null; + List baks = Pages.GetBackups(page); + if(baks.Count == 0) firstContent = currentContent; + else firstContent = Pages.GetBackupContent(page, baks[0]); + + result.Add(new PageRow(page, currentContent, firstContent, + Pages.GetMessageCount(page), baks.Count, incomingLinks == 0, + canEdit, canSelect, canSetPermissions, txtCurrentPage.Value == page.FullName)); + } + + rptPages.DataSource = result; + } + + protected void rptPages_ItemCommand(object sender, RepeaterCommandEventArgs e) { + txtCurrentPage.Value = e.CommandArgument as string; + + if(e.CommandName == "Select") { + // Permissions are checked in ActivatePageEditor() + + ActivatePageEditor(); + } + else if(e.CommandName == "Perms") { + if(!AdminMaster.CanManagePermissions(SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) return; + + ActivatePagePermissionsManager(); + } + } + + protected void btnPublic_Click(object sender, EventArgs e) { + PageInfo page = Pages.FindPage(txtCurrentPage.Value); + + RemoveAllPermissions(page); + + // Set permissions + AuthWriter.SetPermissionForPage(AuthStatus.Grant, page, Actions.ForPages.ModifyPage, + Users.FindUserGroup(Settings.AnonymousGroup)); + AuthWriter.SetPermissionForPage(AuthStatus.Grant, page, Actions.ForPages.PostDiscussion, + Users.FindUserGroup(Settings.AnonymousGroup)); + + RefreshPermissionsManager(); + } + + protected void btnAsNamespace_Click(object sender, EventArgs e) { + PageInfo page = Pages.FindPage(txtCurrentPage.Value); + + RemoveAllPermissions(page); + + RefreshPermissionsManager(); + } + + protected void btnLocked_Click(object sender, EventArgs e) { + PageInfo page = Pages.FindPage(txtCurrentPage.Value); + + RemoveAllPermissions(page); + + // Set permissions + AuthWriter.SetPermissionForPage(AuthStatus.Deny, page, Actions.ForPages.ModifyPage, + Users.FindUserGroup(Settings.UsersGroup)); + AuthWriter.SetPermissionForPage(AuthStatus.Deny, page, Actions.ForPages.ModifyPage, + Users.FindUserGroup(Settings.AnonymousGroup)); + + RefreshPermissionsManager(); + } + + /// + /// Refreshes the permissions manager. + /// + private void RefreshPermissionsManager() { + string r = permissionsManager.CurrentResourceName; + permissionsManager.CurrentResourceName = r; + } + + /// + /// Removes all the permissions for a page. + /// + /// The page. + private void RemoveAllPermissions(PageInfo page) { + AuthWriter.RemoveEntriesForPage(Users.FindUserGroup(Settings.AnonymousGroup), page); + AuthWriter.RemoveEntriesForPage(Users.FindUserGroup(Settings.UsersGroup), page); + AuthWriter.RemoveEntriesForPage(Users.FindUserGroup(Settings.AdministratorsGroup), page); + } + + /// + /// Populates the namespaces list for migration. + /// + /// The selected page. + /// true if there is at least one valid target namespace, false otherwise. + private bool PopulateTargetNamespaces(PageInfo page) { + string currentUser = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + + lstTargetNamespace.Items.Clear(); + + NamespaceInfo pageNamespace = Pages.FindNamespace(NameTools.GetNamespace(page.FullName)); + if(pageNamespace != null) { + // Try adding Root as target namespace + bool canManagePages = AuthChecker.CheckActionForNamespace(null, Actions.ForNamespaces.ManagePages, currentUser, currentGroups); + if(canManagePages) lstTargetNamespace.Items.Add(new ListItem("", "")); + } + + // Try adding all other namespaces + foreach(NamespaceInfo nspace in Pages.GetNamespaces().FindAll(n => n.Provider == page.Provider)) { + if(pageNamespace == null || (pageNamespace != null && nspace.Name != pageNamespace.Name)) { + bool canManagePages = AuthChecker.CheckActionForNamespace(nspace, Actions.ForNamespaces.ManagePages, currentUser, currentGroups); + if(canManagePages) lstTargetNamespace.Items.Add(new ListItem(nspace.Name, nspace.Name)); + } + } + + return lstTargetNamespace.Items.Count > 0; + } + + /// + /// Activates the page editor. + /// + private void ActivatePageEditor() { + lblCurrentPage.Text = txtCurrentPage.Value; + txtNewName.Text = NameTools.GetLocalName(txtCurrentPage.Value); + + // Enable/disable page sections + PageInfo page = Pages.FindPage(txtCurrentPage.Value); + NamespaceInfo nspace = Pages.FindNamespace(NameTools.GetNamespace(page.FullName)); + string currentUser = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + + bool canApproveReject = AdminMaster.CanApproveDraft(page, currentUser, currentGroups); + bool canDeletePages = AuthChecker.CheckActionForNamespace(nspace, Actions.ForNamespaces.DeletePages, currentUser, currentGroups); + bool canManageAllPages = AuthChecker.CheckActionForNamespace(nspace, Actions.ForNamespaces.ManagePages, currentUser, currentGroups); + bool canManagePage = AuthChecker.CheckActionForPage(page, Actions.ForPages.ManagePage, currentUser, currentGroups); + bool canManageDiscussion = AuthChecker.CheckActionForPage(page, Actions.ForPages.ManageDiscussion, currentUser, currentGroups); + bool namespaceAvailable = PopulateTargetNamespaces(page); + + // Approve/reject + // Rename + // Migrate + // Rollback + // Delete Backups + // Clear discussion + // Delete + + pnlApproveRevision.Enabled = canApproveReject; + pnlRename.Enabled = canDeletePages; + pnlMigrate.Enabled = canManageAllPages && namespaceAvailable; + pnlRollback.Enabled = canManagePage; + pnlDeleteBackups.Enabled = canManagePage; + pnlClearDiscussion.Enabled = canManageDiscussion; + pnlDelete.Enabled = canDeletePages; + + // Disable rename, migrate, delete for default page + NamespaceInfo currentNamespace = Pages.FindNamespace(lstNamespace.SelectedValue); + string currentDefaultPage = currentNamespace != null ? currentNamespace.DefaultPage.FullName : Settings.DefaultPage; + if(txtCurrentPage.Value == currentDefaultPage) { + btnRename.Enabled = false; + btnMigrate.Enabled = false; + btnDeletePage.Enabled = false; + } + + LoadDraft(txtCurrentPage.Value); + + LoadBackups(txtCurrentPage.Value); + + btnRollback.Enabled = lstRevision.Items.Count > 0; + btnDeleteBackups.Enabled = lstBackup.Items.Count > 0; + + pnlList.Visible = false; + pnlEditPage.Visible = true; + + ClearResultLabels(); + } + + /// + /// Activates the permissions manager. + /// + private void ActivatePagePermissionsManager() { + lblPageName.Text = txtCurrentPage.Value; + + permissionsManager.CurrentResourceName = txtCurrentPage.Value; + + pnlList.Visible = false; + pnlPermissions.Visible = true; + } + + /// + /// Loads the draft information, if any. + /// + /// The current page name. + private void LoadDraft(string currentPage) { + PageInfo page = Pages.FindPage(currentPage); + PageContent draft = Pages.GetDraft(page); + + if(draft != null) { + pnlApproveRevision.Visible = true; + lblDateTime.Text = Preferences.AlignWithTimezone(draft.LastModified).ToString(Settings.DateTimeFormat); + lblUser.Text = Users.UserLink(draft.User, true); + + // Ampersands are escaped automatically + lnkEdit.NavigateUrl = "Edit.aspx?Page=" + Tools.UrlEncode(currentPage); + lnkDiff.NavigateUrl = "Diff.aspx?Page=" + Tools.UrlEncode(currentPage) + "&Rev1=Current&Rev2=Draft"; + lblDraftPreview.Text = FormattingPipeline.FormatWithPhase3( + FormattingPipeline.FormatWithPhase1And2(draft.Content, false, FormattingContext.PageContent, page), FormattingContext.PageContent, page); + } + else { + pnlApproveRevision.Visible = false; + } + } + + /// + /// Loads the backups for a page and populates the backups and revisions drop-down lists. + /// + /// The page. + private void LoadBackups(string pageName) { + PageInfo page = Pages.FindPage(pageName); + List backups = Pages.GetBackups(page); + + lstRevision.Items.Clear(); + lstBackup.Items.Clear(); + + foreach(int bak in backups) { + PageContent bakContent = Pages.GetBackupContent(page, bak); + + ListItem item = new ListItem(bak.ToString() + ": " + + Preferences.AlignWithTimezone(bakContent.LastModified).ToString(Settings.DateTimeFormat) + + " " + Properties.Messages.By + " " + bakContent.User, + bak.ToString()); + + // Add in reverse order - newer revisions at the top + lstRevision.Items.Insert(0, item); + lstBackup.Items.Insert(0, item); + } + } + + /// + /// Clears all the result labels. + /// + private void ClearResultLabels() { + lblApproveResult.CssClass = ""; + lblApproveResult.Text = ""; + lblRenameResult.CssClass = ""; + lblRenameResult.Text = ""; + lblRollbackResult.CssClass = ""; + lblRollbackResult.Text = ""; + lblBackupResult.CssClass = ""; + lblBackupResult.Text = ""; + lblDiscussionResult.CssClass = ""; + lblDiscussionResult.Text = ""; + lblDeleteResult.CssClass = ""; + lblDeleteResult.Text = ""; + } + + protected void rdoBackup_CheckedChanged(object sender, EventArgs e) { + lstBackup.Enabled = rdoUpTo.Checked; + } + + protected void btnBack_Click(object sender, EventArgs e) { + // If page was specified in query string, don't refresh list + ReturnToList(); + RefreshList(); + } + + /// + /// Refreshes the pages list. + /// + private void RefreshList() { + rangeBegin = pageSelector.SelectedPage * PageSize; + rangeEnd = rangeBegin + pageSelector.SelectedPageSize - 1; + selectedPage = pageSelector.SelectedPage; + + txtCurrentPage.Value = ""; + ResetEditor(); + rptPages.DataBind(); + } + + /// + /// Returns to the group list. + /// + private void ReturnToList() { + LoadExternallySelectedPage(); + if(externallySelectedPage != null) { + // Return to page + UrlTools.Redirect(externallySelectedPage.FullName + Settings.PageExtension); + } + else { + // Return to list + pnlEditPage.Visible = false; + pnlPermissions.Visible = false; + pnlBulkMigrate.Visible = false; + pnlList.Visible = true; + } + } + + /// + /// Resets the editor. + /// + private void ResetEditor() { + btnRename.Enabled = true; + btnMigrate.Enabled = true; + btnDeletePage.Enabled = true; + btnRollback.Enabled = true; + btnDeleteBackups.Enabled = true; + rdoAllBackups.Checked = false; + rdoUpTo.Checked = false; + lstBackup.Enabled = false; + } + + protected void cvNewName_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = Pages.IsValidName(txtNewName.Text); + } + + protected void btnApprove_Click(object sender, EventArgs e) { + PageInfo page = Pages.FindPage(txtCurrentPage.Value); + if(!AdminMaster.CanApproveDraft(page, SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) return; + + PageContent draft = Pages.GetDraft(page); + + Log.LogEntry("Page draft approval requested for " + draft.PageInfo.FullName, EntryType.General, SessionFacade.CurrentUsername); + + bool done = Pages.ModifyPage(draft.PageInfo, draft.Title, draft.User, draft.LastModified, draft.Comment, + draft.Content, draft.Keywords, draft.Description, SaveMode.Backup); + + if(done) { + Pages.DeleteDraft(draft.PageInfo); + + lblApproveResult.CssClass = "resultok"; + lblApproveResult.Text = Properties.Messages.DraftApproved; + lblDraftPreview.Text = ""; + + ReturnToList(); + } + else { + lblApproveResult.CssClass = "resulterror"; + lblApproveResult.Text = Properties.Messages.CouldNotApproveDraft; + } + } + + protected void btnReject_Click(object sender, EventArgs e) { + PageInfo page = Pages.FindPage(txtCurrentPage.Value); + if(!AdminMaster.CanApproveDraft(page, SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) return; + + Log.LogEntry("Page draft reject requested for " + page.FullName, EntryType.General, SessionFacade.CurrentUsername); + + Pages.DeleteDraft(page); + + lblApproveResult.CssClass = "resultok"; + lblApproveResult.Text = Properties.Messages.DraftRejected; + lblDraftPreview.Text = ""; + + ReturnToList(); + } + + protected void btnRename_Click(object sender, EventArgs e) { + // Check name for change, validity and existence of another page with same name + // Perform rename + // Create shadow page, if needed + + PageInfo page = Pages.FindPage(txtCurrentPage.Value); + if(!AuthChecker.CheckActionForNamespace(Pages.FindNamespace(NameTools.GetNamespace(page.FullName)), Actions.ForNamespaces.DeletePages, + SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) return; + + txtNewName.Text = txtNewName.Text.Trim(); + + string currentNamespace = NameTools.GetNamespace(txtCurrentPage.Value); + string currentPage = NameTools.GetLocalName(txtCurrentPage.Value); + + if(!Page.IsValid) return; + + if(txtNewName.Text.ToLowerInvariant() == currentPage.ToLowerInvariant()) { + return; + } + + if(Pages.FindPage(NameTools.GetFullName(currentNamespace, txtNewName.Text)) != null) { + lblRenameResult.CssClass = "resulterror"; + lblRenameResult.Text = Properties.Messages.PageAlreadyExists; + return; + } + + Log.LogEntry("Page rename requested for " + txtCurrentPage.Value, EntryType.General, Log.SystemUsername); + + PageInfo oldPage = Pages.FindPage(txtCurrentPage.Value); + PageContent oldContent = Content.GetPageContent(oldPage, false); + + bool done = Pages.RenamePage(oldPage, txtNewName.Text); + + if(done) { + if(chkShadowPage.Checked) { + done = Pages.CreatePage(currentNamespace, currentPage); + + if(done) { + done = Pages.ModifyPage(Pages.FindPage(txtCurrentPage.Value), + oldContent.Title, oldContent.User, oldContent.LastModified, + oldContent.Comment, ">>> [" + txtNewName.Text + "]", + new string[0], oldContent.Description, SaveMode.Normal); + + if(done) { + ResetPageList(); + + RefreshList(); + lblRenameResult.CssClass = "resultok"; + lblRenameResult.Text = Properties.Messages.PageRenamed; + ReturnToList(); + } + else { + lblRenameResult.CssClass = "resulterror"; + lblRenameResult.Text = Properties.Messages.PageRenamedCouldNotSetShadowPageContent; + } + } + else { + lblRenameResult.CssClass = "resulterror"; + lblRenameResult.Text = Properties.Messages.PageRenamedCouldNotCreateShadowPage; + } + } + else { + RefreshList(); + lblRenameResult.CssClass = "resultok"; + lblRenameResult.Text = Properties.Messages.PageRenamed; + ReturnToList(); + } + } + else { + lblRenameResult.CssClass = "resulterror"; + lblRenameResult.Text = Properties.Messages.CouldNotRenamePage; + } + } + + protected void btnMigrate_Click(object sender, EventArgs e) { + lblMigrateResult.CssClass = ""; + lblMigrateResult.Text = ""; + + string currentUser = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + + PageInfo page = Pages.FindPage(txtCurrentPage.Value); + NamespaceInfo targetNamespace = Pages.FindNamespace(lstTargetNamespace.SelectedValue); + bool canManageAllPages = AuthChecker.CheckActionForNamespace(Pages.FindNamespace(NameTools.GetNamespace(page.FullName)), + Actions.ForNamespaces.ManagePages, currentUser, currentGroups); + bool canManageAllPagesInTarget = AuthChecker.CheckActionForNamespace(targetNamespace, + Actions.ForNamespaces.ManagePages, currentUser, currentGroups); + + if(canManageAllPages && canManageAllPagesInTarget) { + bool done = Pages.MigratePage(page, targetNamespace, chkCopyCategories.Checked); + if(done) { + chkCopyCategories.Checked = false; + + ResetPageList(); + + RefreshList(); + lblRenameResult.CssClass = "resultok"; + lblRenameResult.Text = Properties.Messages.PageRenamed; + ReturnToList(); + } + else { + lblMigrateResult.CssClass = "resulterror"; + lblMigrateResult.Text = Properties.Messages.CouldNotMigratePage; + } + } + } + + protected void btnRollback_Click(object sender, EventArgs e) { + PageInfo page = Pages.FindPage(txtCurrentPage.Value); + if(!AuthChecker.CheckActionForPage(page, Actions.ForPages.ManagePage, SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) return; + + int targetRevision = -1; + + // This should never occur + if(!int.TryParse(lstRevision.SelectedValue, out targetRevision)) return; + + Log.LogEntry("Page rollback requested for " + txtCurrentPage.Value + " to rev. " + targetRevision.ToString(), EntryType.General, Log.SystemUsername); + + bool done = Pages.Rollback(page, targetRevision); + + if(done) { + RefreshList(); + lblRollbackResult.CssClass = "resultok"; + lblRollbackResult.Text = Properties.Messages.PageRolledBack; + ReturnToList(); + } + else { + lblRollbackResult.CssClass = "resulterror"; + lblRollbackResult.Text = Properties.Messages.CouldNotRollbackPage; + } + } + + protected void btnDeleteBackups_Click(object sender, EventArgs e) { + PageInfo page = Pages.FindPage(txtCurrentPage.Value); + if(!AuthChecker.CheckActionForPage(page, Actions.ForPages.ManagePage, SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) return; + + int targetRevision = -1; + + // This should never occur + if(!int.TryParse(lstBackup.SelectedValue, out targetRevision)) return; + + Log.LogEntry("Page backup deletion requested for " + txtCurrentPage.Value, EntryType.General, Log.SystemUsername); + + bool done = false; + if(rdoAllBackups.Checked) { + done = Pages.DeleteBackups(page); + } + else { + done = Pages.DeleteBackups(page, targetRevision); + } + + if(done) { + RefreshList(); + lblBackupResult.CssClass = "resultok"; + lblBackupResult.Text = Properties.Messages.PageBackupsDeleted; + ReturnToList(); + } + else { + lblBackupResult.CssClass = "resulterror"; + lblBackupResult.Text = Properties.Messages.CouldNotDeletePageBackups; + } + } + + protected void btnClearDiscussion_Click(object sender, EventArgs e) { + PageInfo page = Pages.FindPage(txtCurrentPage.Value); + if(!AuthChecker.CheckActionForPage(page, Actions.ForPages.ManageDiscussion, SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) return; + + Log.LogEntry("Page discussion cleanup requested for " + txtCurrentPage.Value, EntryType.General, Log.SystemUsername); + + bool done = Pages.RemoveAllMessages(page); + + if(done) { + RefreshList(); + lblDiscussionResult.CssClass = "resultok"; + lblDiscussionResult.Text = Properties.Messages.AllMessagesDeleted; + ReturnToList(); + } + else { + lblDiscussionResult.CssClass = "resulterror"; + lblDiscussionResult.Text = Properties.Messages.CouldNotDeleteOneOrMoreMessages; + } + } + + protected void btnDeletePage_Click(object sender, EventArgs e) { + PageInfo page = Pages.FindPage(txtCurrentPage.Value); + if(!AuthChecker.CheckActionForNamespace(Pages.FindNamespace(NameTools.GetNamespace(page.FullName)), Actions.ForNamespaces.DeletePages, + SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) return; + + Log.LogEntry("Page deletion requested for " + txtCurrentPage.Value, EntryType.General, Log.SystemUsername); + + bool done = Pages.DeletePage(page); + + if(done) { + ResetPageList(); + + RefreshList(); + lblDeleteResult.CssClass = "resultok"; + lblDeleteResult.Text = Properties.Messages.PageDeleted; + ReturnToList(); + } + else { + lblDeleteResult.CssClass = "resulterror"; + lblDeleteResult.Text = Properties.Messages.CouldNotDeletePage; + } + } + + /// + /// Resets the bulk management editor. + /// + private void ResetBulkEditor() { + pageListBuilder.ResetControl(); + + chkBulkMigrateCopyCategories.Checked = false; + + lblBulkMigrateResult.CssClass = ""; + lblBulkMigrateResult.Text = ""; + } + + /// + /// Loads target namespaces for bulk migration. + /// + private void LoadTargetNamespaces() { + // Load valid namespaces, filtering the current one + lstBulkMigrateTargetNamespace.Items.Clear(); + + bool canManageAllPages = false; + string currentUser = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + + if(!string.IsNullOrEmpty(lstNamespace.SelectedValue)) { + // Root namespace + canManageAllPages = AuthChecker.CheckActionForNamespace(null, + Actions.ForNamespaces.ManagePages, currentUser, currentGroups); + + if(canManageAllPages) { + lstBulkMigrateTargetNamespace.Items.Add(new ListItem("", ".")); + } + } + + foreach(NamespaceInfo ns in Pages.GetNamespaces().FindAll(n => n.Provider.GetType().FullName == providerSelector.SelectedProvider)) { + // All sub-namespaces + if(ns.Name != lstNamespace.SelectedValue) { + canManageAllPages = AuthChecker.CheckActionForNamespace(ns, + Actions.ForNamespaces.ManagePages, currentUser, currentGroups); + + if(canManageAllPages) { + lstBulkMigrateTargetNamespace.Items.Add(new ListItem(ns.Name, ns.Name)); + } + } + } + } + + protected void providerSelector_SelectedProviderChanged(object sender, EventArgs e) { + pageListBuilder.CurrentProvider = providerSelector.SelectedProvider; + LoadTargetNamespaces(); + } + + protected void btnBulkMigrateBack_Click(object sender, EventArgs e) { + RefreshList(); + ReturnToList(); + } + + protected void btnBulkMigrate_Click(object sender, EventArgs e) { + ResetBulkEditor(); + pageListBuilder.CurrentNamespace = lstNamespace.SelectedValue; + pageListBuilder.CurrentProvider = providerSelector.SelectedProvider; + pnlBulkMigrate.Visible = true; + pnlList.Visible = false; + + LoadTargetNamespaces(); + + btnBulkMigratePages.Enabled = lstBulkMigrateTargetNamespace.Items.Count > 0; + } + + /// + /// Determines whether a page is the default page of its namespace. + /// + /// The page. + /// true if the page is the default namespace of its namespace, false otherwise. + private bool IsDefaultPage(PageInfo page) { + string localName, nspace; + NameTools.ExpandFullName(page.FullName, out nspace, out localName); + + if(string.IsNullOrEmpty(nspace)) { + return localName.ToLowerInvariant() == Settings.DefaultPage.ToLowerInvariant(); + } + else { + NamespaceInfo ns = Pages.FindNamespace(nspace); + return ns.DefaultPage != null && ns.DefaultPage.FullName.ToLowerInvariant() == page.FullName.ToLowerInvariant(); + } + } + + protected void btnBulkMigratePages_Click(object sender, EventArgs e) { + lblBulkMigrateResult.CssClass = ""; + lblBulkMigrateResult.Text = ""; + + List selectedPages = new List(20); + foreach(string pg in pageListBuilder.SelectedPages) { + PageInfo page = Pages.FindPage(pg); + if(page != null && !IsDefaultPage(page)) selectedPages.Add(page); + } + + if(selectedPages.Count == 0) { + lblBulkMigrateResult.CssClass = "resulterror"; + lblBulkMigrateResult.Text = Properties.Messages.NoPagesToMigrate; + return; + } + + string nspaceName = lstBulkMigrateTargetNamespace.SelectedValue; + if(nspaceName == ".") nspaceName = null; + NamespaceInfo selectedNamespace = Pages.FindNamespace(nspaceName); + + Log.LogEntry("Bulk migration requested", EntryType.General, SessionFacade.CurrentUsername); + + bool allDone = true; + foreach(PageInfo pg in selectedPages) { + allDone &= Pages.MigratePage(pg, selectedNamespace, chkBulkMigrateCopyCategories.Checked); + } + + if(allDone) { + lblBulkMigrateResult.CssClass = "resultok"; + lblBulkMigrateResult.Text = Properties.Messages.BulkMigrationCompleted; + } + else { + lblBulkMigrateResult.CssClass = "resulterror"; + lblBulkMigrateResult.Text = Properties.Messages.BulkMigrationCompletedWithErrors; + } + + ResetPageList(); + rptPages.DataBind(); + + pageListBuilder.ResetControl(); + } + + } + + /// + /// Represents a Page for display purposes. + /// + public class PageRow { + + private string fullName, title, createdBy, createdOn, lastModifiedBy, lastModifiedOn, discussion, revisions, provider, additionalClass; + private bool isOrphan, canEdit, canSelect, canSetPermissions; + + /// + /// Initializes a new instance of the class. + /// + /// The original page. + /// The current content. + /// The first revision content. + /// The number of messages in the discussion. + /// The number of revisions. + /// A value indicating whether the page is orphan. + /// A value indicating whether the current user can edit the page. + /// A value indicating whether the current user can select the page. + /// A value indicating whether the current user can set permissions for the page. + /// A value indicating whether the page is selected. + public PageRow(PageInfo page, PageContent currentContent, PageContent firstContent, int discussionCount, int revisionCount, + bool isOrphan, bool canEdit, bool canSelect, bool canSetPermissions, bool selected) { + + fullName = page.FullName; + title = FormattingPipeline.PrepareTitle(currentContent.Title, false, FormattingContext.Other, page); + createdBy = firstContent.User; + createdOn = Preferences.AlignWithTimezone(page.CreationDateTime).ToString(Settings.DateTimeFormat); + lastModifiedBy = currentContent.User; + lastModifiedOn = Preferences.AlignWithTimezone(currentContent.LastModified).ToString(Settings.DateTimeFormat); + discussion = discussionCount.ToString(); + revisions = revisionCount.ToString(); + provider = page.Provider.Information.Name; + + this.isOrphan = isOrphan; + + this.canEdit = canEdit; + this.canSelect = canSelect; + this.canSetPermissions = canSetPermissions; + additionalClass = selected ? " selected" : ""; + } + + /// + /// Gets the full name. + /// + public string FullName { + get { return fullName; } + } + + /// + /// Gets the title. + /// + public string Title { + get { return title; } + } + + /// + /// Gets the original page creator. + /// + public string CreatedBy { + get { return createdBy; } + } + + /// + /// Gets the original creation date/time. + /// + public string CreatedOn { + get { return createdOn; } + } + + /// + /// Gets the user who last modified the page. + /// + public string LastModifiedBy { + get { return lastModifiedBy; } + } + + /// + /// Gets the last modification date/time. + /// + public string LastModifiedOn { + get { return lastModifiedOn; } + } + + /// + /// Gets the discussion message count. + /// + public string Discussion { + get { return discussion; } + } + + /// + /// Gets the number of revisions. + /// + public string Revisions { + get { return revisions; } + } + + /// + /// Gets the provider. + /// + public string Provider { + get { return provider; } + } + + /// + /// Gets a value indicating whether the page is orhpan. + /// + public bool IsOrphan { + get { return isOrphan; } + } + + /// + /// Gets a value indicating whether the current user can edit the page. + /// + public bool CanEdit { + get { return canEdit; } + } + + /// + /// Gets a value indicating whether the current user can select the page. + /// + public bool CanSelect { + get { return canSelect; } + } + + /// + /// Gets a value indicating whether the current user can set permissions for the page. + /// + public bool CanSetPermissions { + get { return canSetPermissions; } + } + + /// + /// Gets the additional CSS class. + /// + public string AdditionalClass { + get { return additionalClass; } + } + + } + +} diff --git a/WebApplication/AdminPages.aspx.designer.cs b/WebApplication/AdminPages.aspx.designer.cs new file mode 100644 index 0000000..548bbab --- /dev/null +++ b/WebApplication/AdminPages.aspx.designer.cs @@ -0,0 +1,835 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4016 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AdminPages { + + /// + /// lblPages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPages; + + /// + /// pnlList control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlList; + + /// + /// btnNewPage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnNewPage; + + /// + /// btnBulkMigrate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnBulkMigrate; + + /// + /// lblNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNamespace; + + /// + /// lstNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstNamespace; + + /// + /// chkOrphansOnly control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkOrphansOnly; + + /// + /// txtFilter control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtFilter; + + /// + /// btnFilter control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.ImageButton btnFilter; + + /// + /// pageSelector control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.PageSelector pageSelector; + + /// + /// rptPages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Repeater rptPages; + + /// + /// pnlEditPage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlEditPage; + + /// + /// lblEditTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblEditTitle; + + /// + /// lblCurrentPage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCurrentPage; + + /// + /// pnlApproveRevision control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlApproveRevision; + + /// + /// lblApproveTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblApproveTitle; + + /// + /// lblSavedOn control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblSavedOn; + + /// + /// lblDateTime control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDateTime; + + /// + /// lblBy control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblBy; + + /// + /// lblUser control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblUser; + + /// + /// lnkDiff control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkDiff; + + /// + /// lnkEdit control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkEdit; + + /// + /// btnApprove control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnApprove; + + /// + /// btnReject control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnReject; + + /// + /// lblApproveResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblApproveResult; + + /// + /// pnlRename control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlRename; + + /// + /// lblRenamePageTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRenamePageTitle; + + /// + /// lblNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNewName; + + /// + /// txtNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtNewName; + + /// + /// chkShadowPage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkShadowPage; + + /// + /// btnRename control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnRename; + + /// + /// rfvNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RequiredFieldValidator rfvNewName; + + /// + /// cvNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CustomValidator cvNewName; + + /// + /// lblRenameResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblRenameResult; + + /// + /// pnlMigrate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlMigrate; + + /// + /// lblMigratePageTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblMigratePageTitle; + + /// + /// lblTargetNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblTargetNamespace; + + /// + /// lstTargetNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstTargetNamespace; + + /// + /// chkCopyCategories control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkCopyCategories; + + /// + /// btnMigrate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnMigrate; + + /// + /// lblMigrateResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblMigrateResult; + + /// + /// pnlRollback control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlRollback; + + /// + /// lblRollbackPageTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRollbackPageTitle; + + /// + /// lblRevision control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRevision; + + /// + /// lstRevision control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstRevision; + + /// + /// btnRollback control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnRollback; + + /// + /// lblRollbackResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblRollbackResult; + + /// + /// pnlDeleteBackups control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlDeleteBackups; + + /// + /// lblDeleteBackupsTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDeleteBackupsTitle; + + /// + /// rdoAllBackups control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoAllBackups; + + /// + /// rdoUpTo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoUpTo; + + /// + /// lstBackup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstBackup; + + /// + /// btnDeleteBackups control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnDeleteBackups; + + /// + /// lblBackupResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblBackupResult; + + /// + /// pnlClearDiscussion control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlClearDiscussion; + + /// + /// lblClearDiscussionTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblClearDiscussionTitle; + + /// + /// btnClearDiscussion control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnClearDiscussion; + + /// + /// lblDiscussionResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblDiscussionResult; + + /// + /// pnlDelete control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlDelete; + + /// + /// lblDeletePage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDeletePage; + + /// + /// btnDeletePage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnDeletePage; + + /// + /// lblDeleteResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblDeleteResult; + + /// + /// btnBack control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnBack; + + /// + /// pnlPermissions control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlPermissions; + + /// + /// lblPagePermissions control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPagePermissions; + + /// + /// lblPageName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPageName; + + /// + /// lblPermissionsTemplates control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPermissionsTemplates; + + /// + /// btnPublic control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.LinkButton btnPublic; + + /// + /// lblPublicInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPublicInfo; + + /// + /// btnAsNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.LinkButton btnAsNamespace; + + /// + /// lblNormalInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNormalInfo; + + /// + /// btnLocked control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.LinkButton btnLocked; + + /// + /// lblLockedInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblLockedInfo; + + /// + /// lblPermissionsTemplatesInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPermissionsTemplatesInfo; + + /// + /// permissionsManager control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.PermissionsManager permissionsManager; + + /// + /// btnBack2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnBack2; + + /// + /// pnlBulkMigrate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlBulkMigrate; + + /// + /// lblBulkStep1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblBulkStep1; + + /// + /// providerSelector control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.ProviderSelector providerSelector; + + /// + /// pageListBuilder control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.PageListBuilder pageListBuilder; + + /// + /// lblBulkStep2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblBulkStep2; + + /// + /// lblBulkMigarateInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblBulkMigarateInfo; + + /// + /// lstBulkMigrateTargetNamespace control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstBulkMigrateTargetNamespace; + + /// + /// lblBulkStep3 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblBulkStep3; + + /// + /// chkBulkMigrateCopyCategories control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CheckBox chkBulkMigrateCopyCategories; + + /// + /// lblBulkMigrateInfo2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblBulkMigrateInfo2; + + /// + /// btnBulkMigratePages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnBulkMigratePages; + + /// + /// lblBulkMigrateResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblBulkMigrateResult; + + /// + /// btnBulkMigrateBack control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnBulkMigrateBack; + + /// + /// lblDraftPreview control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblDraftPreview; + + /// + /// txtCurrentPage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.HiddenField txtCurrentPage; + } +} diff --git a/WebApplication/AdminProviders.aspx b/WebApplication/AdminProviders.aspx new file mode 100644 index 0000000..2402e42 --- /dev/null +++ b/WebApplication/AdminProviders.aspx @@ -0,0 +1,254 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Admin.master" AutoEventWireup="true" CodeBehind="AdminProviders.aspx.cs" Inherits="ScrewTurn.Wiki.AdminProviders" culture="auto" meta:resourcekey="PageResource1" uiculture="auto" %> + +<%@ Register TagPrefix="st" TagName="ProviderSelector" Src="~/ProviderSelector.ascx" %> + + + + + + + +

    + + + : + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     
    <%# Eval("Name") %><%# Eval("Version") %><%# Eval("Author") %><%# Eval("UpdateStatus") %>
    <%# Eval("Name") %><%# Eval("Version") %><%# Eval("Author") %><%# Eval("UpdateStatus") %>
    + +
    +
    + + +
    +

    + +

    + + +       + + +
    + +
    + + + + + +

    +
    + + + +
    +
    + +
    + + + + + + + +
    + +
    + + + +
    + +
    +

    + +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    + + +
    + +
    + +
    +

    + +

    + + +
    + + +
    + + +
    + +
    + +
    + + + +
    + +
    + +
    +

    + +

    + +

    + + -> + + + +
    +
    + +

    + + -> + + + +
    + + + +

    + +

    + + -> + + + +

    + +

    + + -> + + +
    + + + +

    +
    +
    + +
    + +
    + +
    diff --git a/WebApplication/AdminProviders.aspx.cs b/WebApplication/AdminProviders.aspx.cs new file mode 100644 index 0000000..2d3a7af --- /dev/null +++ b/WebApplication/AdminProviders.aspx.cs @@ -0,0 +1,632 @@ + +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; +using ScrewTurn.Wiki.PluginFramework; +using System.Net; + +namespace ScrewTurn.Wiki { + + public partial class AdminProviders : BasePage { + + protected void Page_Load(object sender, EventArgs e) { + AdminMaster.RedirectToLoginIfNeeded(); + + if(!AdminMaster.CanManageProviders(SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) UrlTools.Redirect("AccessDenied.aspx"); + + if(!Page.IsPostBack) { + // Load providers and related data + rptProviders.DataBind(); + + LoadDlls(); + + LoadSourceProviders(); + } + } + + #region Providers List + + protected void rdo_CheckedChanged(object sender, EventArgs e) { + ResetEditor(); + rptProviders.DataBind(); + } + + /// + /// Resets the editor. + /// + private void ResetEditor() { + pnlProviderDetails.Visible = false; + btnAutoUpdateProviders.Visible = true; + txtCurrentProvider.Value = ""; + lblResult.CssClass = ""; + lblResult.Text = ""; + } + + protected void rptProviders_DataBinding(object sender, EventArgs e) { + List providers = new List(5); + + int enabledCount = 0; + + if(rdoPages.Checked) { + enabledCount = Collectors.PagesProviderCollector.AllProviders.Length; + providers.AddRange(Collectors.PagesProviderCollector.AllProviders); + providers.AddRange(Collectors.DisabledPagesProviderCollector.AllProviders); + } + else if(rdoUsers.Checked) { + enabledCount = Collectors.UsersProviderCollector.AllProviders.Length; + providers.AddRange(Collectors.UsersProviderCollector.AllProviders); + providers.AddRange(Collectors.DisabledUsersProviderCollector.AllProviders); + } + else if(rdoFiles.Checked) { + enabledCount = Collectors.FilesProviderCollector.AllProviders.Length; + providers.AddRange(Collectors.FilesProviderCollector.AllProviders); + providers.AddRange(Collectors.DisabledFilesProviderCollector.AllProviders); + } + else if(rdoCache.Checked) { + enabledCount = Collectors.CacheProviderCollector.AllProviders.Length; + providers.AddRange(Collectors.CacheProviderCollector.AllProviders); + providers.AddRange(Collectors.DisabledCacheProviderCollector.AllProviders); + } + else if(rdoFormatter.Checked) { + enabledCount = Collectors.FormatterProviderCollector.AllProviders.Length; + providers.AddRange(Collectors.FormatterProviderCollector.AllProviders); + providers.AddRange(Collectors.DisabledFormatterProviderCollector.AllProviders); + } + + List result = new List(providers.Count); + + for(int i = 0; i < providers.Count; i++) { + IProviderV30 prov = providers[i]; + result.Add(new ProviderRow(prov.Information, + prov.GetType().FullName, + GetUpdateStatus(prov.Information), + i > enabledCount - 1, + txtCurrentProvider.Value == prov.GetType().FullName)); + } + + rptProviders.DataSource = result; + } + + /// + /// Gets the update status of a provider. + /// + /// The component information. + /// The update status. + private string GetUpdateStatus(ComponentInformation info) { + if(!Settings.DisableAutomaticVersionCheck) { + if(string.IsNullOrEmpty(info.UpdateUrl)) return "n/a"; + else { + string newVersion = null; + string newAssemblyUrl = null; + UpdateStatus status = Tools.GetUpdateStatus(info.UpdateUrl, info.Version, out newVersion, out newAssemblyUrl); + + if(status == UpdateStatus.Error) { + return "" + Properties.Messages.Error + ""; + } + else if(status == UpdateStatus.NewVersionFound) { + return "" + Properties.Messages.NewVersion + " " + newVersion + "" + + (string.IsNullOrEmpty(newAssemblyUrl) ? "" : " (" + Properties.Messages.AutoUpdateAvailable + ")") + ""; + } + else if(status == UpdateStatus.UpToDate) { + return "" + Properties.Messages.UpToDate + ""; + } + else throw new NotSupportedException(); + } + } + else return "n/a"; + } + + /// + /// Gets the currently selected provider. + /// + /// The provider. + /// A value indicating whether the returned provider is enabled. + /// A value indicating whether the returned provider can be disabled. + private IProviderV30 GetCurrentProvider(out bool enabled, out bool canDisable) { + return Collectors.FindProvider(txtCurrentProvider.Value, out enabled, out canDisable); + } + + protected void rptProviders_ItemCommand(object sender, CommandEventArgs e) { + txtCurrentProvider.Value = e.CommandArgument as string; + + if(e.CommandName == "Select") { + bool enabled; + bool canDisable; + IProviderV30 provider = GetCurrentProvider(out enabled, out canDisable); + + // Cannot disable the provider that handles the default page of the root namespace + if(Pages.FindPage(Settings.DefaultPage).Provider == provider) canDisable = false; + + pnlProviderDetails.Visible = true; + lblProviderName.Text = provider.Information.Name + " (" + provider.Information.Version + ")"; + string dll = provider.GetType().Assembly.FullName; + lblProviderDll.Text = dll.Substring(0, dll.IndexOf(",")) + ".dll"; + txtConfigurationString.Text = ProviderLoader.LoadConfiguration(provider.GetType().FullName); + if(provider.ConfigHelpHtml != null) { + lblProviderConfigHelp.Text = provider.ConfigHelpHtml; + } + else { + lblProviderConfigHelp.Text = Properties.Messages.NoConfigurationRequired; + } + + btnEnable.Visible = !enabled; + btnAutoUpdateProviders.Visible = false; + btnDisable.Visible = enabled; + btnDisable.Enabled = canDisable; + lblCannotDisable.Visible = !canDisable; + btnUnload.Enabled = !enabled; + + rptProviders.DataBind(); + } + } + + /// + /// Performs all the actions that are needed after a provider status is changed. + /// + private void PerformPostProviderChangeActions() { + Content.InvalidateAllPages(); + Content.ClearPseudoCache(); + } + + protected void btnSave_Click(object sender, EventArgs e) { + bool enabled, canDisable; + IProviderV30 prov = GetCurrentProvider(out enabled, out canDisable); + Log.LogEntry("Configuration change requested for Provider " + prov.Information.Name, EntryType.General, SessionFacade.CurrentUsername); + + string error; + if(ProviderLoader.TryChangeConfiguration(txtCurrentProvider.Value, txtConfigurationString.Text, out error)) { + PerformPostProviderChangeActions(); + + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.ProviderConfigurationSaved; + + ResetEditor(); + rptProviders.DataBind(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.ProviderRejectedConfiguration + + (string.IsNullOrEmpty(error) ? "" : (": " + error)); + } + } + + protected void btnDisable_Click(object sender, EventArgs e) { + bool enabled, canDisable; + IProviderV30 prov = GetCurrentProvider(out enabled, out canDisable); + Log.LogEntry("Deactivation requested for Provider " + prov.Information.Name, EntryType.General, SessionFacade.CurrentUsername); + + ProviderLoader.DisableProvider(txtCurrentProvider.Value); + PerformPostProviderChangeActions(); + + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.ProviderDisabled; + + ResetEditor(); + rptProviders.DataBind(); + LoadSourceProviders(); + ReloadDefaultProviders(); + } + + protected void btnEnable_Click(object sender, EventArgs e) { + bool enabled, canDisable; + IProviderV30 prov = GetCurrentProvider(out enabled, out canDisable); + Log.LogEntry("Activation requested for provider provider " + prov.Information.Name, EntryType.General, SessionFacade.CurrentUsername); + + ProviderLoader.EnableProvider(txtCurrentProvider.Value); + PerformPostProviderChangeActions(); + + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.ProviderEnabled; + + ResetEditor(); + rptProviders.DataBind(); + LoadSourceProviders(); + ReloadDefaultProviders(); + } + + protected void btnUnload_Click(object sender, EventArgs e) { + bool enabled, canDisable; + IProviderV30 prov = GetCurrentProvider(out enabled, out canDisable); + Log.LogEntry("Unloading requested for provider provider " + prov.Information.Name, EntryType.General, SessionFacade.CurrentUsername); + + ProviderLoader.UnloadProvider(txtCurrentProvider.Value); + PerformPostProviderChangeActions(); + + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.ProviderUnloaded; + + ResetEditor(); + rptProviders.DataBind(); + LoadSourceProviders(); + ReloadDefaultProviders(); + } + + protected void btnCancel_Click(object sender, EventArgs e) { + ResetEditor(); + rptProviders.DataBind(); + } + + protected void btnAutoUpdateProviders_Click(object sender, EventArgs e) { + lblAutoUpdateResult.CssClass = ""; + lblAutoUpdateResult.Text = ""; + + Log.LogEntry("Providers auto-update requested", EntryType.General, SessionFacade.CurrentUsername); + + ProviderUpdater updater = new ProviderUpdater(Settings.Provider, + Collectors.FileNames, + Collectors.PagesProviderCollector.AllProviders, + Collectors.DisabledPagesProviderCollector.AllProviders, + Collectors.UsersProviderCollector.AllProviders, + Collectors.DisabledUsersProviderCollector.AllProviders, + Collectors.FilesProviderCollector.AllProviders, + Collectors.DisabledFilesProviderCollector.AllProviders, + Collectors.CacheProviderCollector.AllProviders, + Collectors.DisabledCacheProviderCollector.AllProviders, + Collectors.FormatterProviderCollector.AllProviders, + Collectors.DisabledFormatterProviderCollector.AllProviders); + + int count = updater.UpdateAll(); + + lblAutoUpdateResult.CssClass = "resultok"; + if(count > 0) lblAutoUpdateResult.Text = Properties.Messages.ProvidersUpdated; + else lblAutoUpdateResult.Text = Properties.Messages.NoProvidersToUpdate; + + rptProviders.DataBind(); + } + + #endregion + + #region Defaults + + /// + /// Reloads the default providers. + /// + private void ReloadDefaultProviders() { + lstPagesProvider.Reload(); + lstUsersProvider.Reload(); + lstFilesProvider.Reload(); + } + + protected void btnSaveDefaultProviders_Click(object sender, EventArgs e) { + Log.LogEntry("Default providers change requested", EntryType.General, SessionFacade.CurrentUsername); + + Settings.BeginBulkUpdate(); + Settings.DefaultPagesProvider = lstPagesProvider.SelectedProvider; + Settings.DefaultUsersProvider = lstUsersProvider.SelectedProvider; + Settings.DefaultFilesProvider = lstFilesProvider.SelectedProvider; + Settings.DefaultCacheProvider = lstCacheProvider.SelectedProvider; + Settings.EndBulkUpdate(); + + lblDefaultProvidersResult.CssClass = "resultok"; + lblDefaultProvidersResult.Text = Properties.Messages.DefaultProvidersSaved; + + ResetEditor(); + rptProviders.DataBind(); + } + + #endregion + + #region DLLs + + /// + /// Loads all the providers' DLLs. + /// + private void LoadDlls() { + string[] files = Settings.Provider.ListPluginAssemblies(); + lstDlls.Items.Clear(); + lstDlls.Items.Add(new ListItem("- " + Properties.Messages.SelectAndDelete + " -", "")); + for(int i = 0; i < files.Length; i++) { + lstDlls.Items.Add(new ListItem(files[i], files[i])); + } + } + + protected void lstDlls_SelectedIndexChanged(object sender, EventArgs e) { + btnDeleteDll.Enabled = lstDlls.SelectedIndex >= 0 && !string.IsNullOrEmpty(lstDlls.SelectedValue); + } + + protected void btnDeleteDll_Click(object sender, EventArgs e) { + if(Settings.Provider.DeletePluginAssembly(lstDlls.SelectedValue)) { + LoadDlls(); + lstDlls_SelectedIndexChanged(sender, e); + lblDllResult.CssClass = "resultok"; + lblDllResult.Text = Properties.Messages.DllDeleted; + } + else { + lblDllResult.CssClass = "resulterror"; + lblDllResult.Text = Properties.Messages.CouldNotDeleteDll; + } + } + + protected void btnUpload_Click(object sender, EventArgs e) { + string file = upDll.FileName; + + string ext = System.IO.Path.GetExtension(file); + if(ext != null) ext = ext.ToLowerInvariant(); + if(ext != ".dll") { + lblUploadResult.CssClass = "resulterror"; + lblUploadResult.Text = Properties.Messages.VoidOrInvalidFile; + return; + } + + Log.LogEntry("Provider DLL upload requested " + upDll.FileName, EntryType.General, SessionFacade.CurrentUsername); + + string[] asms = Settings.Provider.ListPluginAssemblies(); + if(Array.Find(asms, delegate(string v) { + if(v.Equals(file)) return true; + else return false; + }) != null) { + // DLL already exists + lblUploadResult.CssClass = "resulterror"; + lblUploadResult.Text = Properties.Messages.DllAlreadyExists; + return; + } + else { + Settings.Provider.StorePluginAssembly(file, upDll.FileBytes); + + int count = ProviderLoader.LoadFromAuto(file); + + lblUploadResult.CssClass = "resultok"; + lblUploadResult.Text = Properties.Messages.LoadedProviders.Replace("###", count.ToString()); + upDll.Attributes.Add("value", ""); + + PerformPostProviderChangeActions(); + + LoadDlls(); + ResetEditor(); + rptProviders.DataBind(); + LoadSourceProviders(); + } + } + + #endregion + + #region Data Migration + + /// + /// Loads source providers for data migration. + /// + private void LoadSourceProviders() { + lstPagesSource.Items.Clear(); + lstPagesSource.Items.Add(new ListItem("", "")); + foreach(IPagesStorageProviderV30 prov in Collectors.PagesProviderCollector.AllProviders) { + if(!prov.ReadOnly) { + lstPagesSource.Items.Add(new ListItem(prov.Information.Name, prov.GetType().ToString())); + } + } + + lstUsersSource.Items.Clear(); + lstUsersSource.Items.Add(new ListItem("", "")); + foreach(IUsersStorageProviderV30 prov in Collectors.UsersProviderCollector.AllProviders) { + if(IsUsersProviderFullWriteEnabled(prov)) { + lstUsersSource.Items.Add(new ListItem(prov.Information.Name, prov.GetType().ToString())); + } + } + + lstFilesSource.Items.Clear(); + lstFilesSource.Items.Add(new ListItem("", "")); + foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) { + if(!prov.ReadOnly) { + lstFilesSource.Items.Add(new ListItem(prov.Information.Name, prov.GetType().ToString())); + } + } + + lblSettingsSource.Text = Settings.Provider.Information.Name; + lstSettingsDestination.Items.Clear(); + lstSettingsDestination.Items.Add(new ListItem("", "")); + if(Settings.Provider.GetType().FullName != typeof(SettingsStorageProvider).FullName) { + lstSettingsDestination.Items.Add(new ListItem(SettingsStorageProvider.ProviderName, typeof(SettingsStorageProvider).FullName)); + } + foreach(ISettingsStorageProviderV30 prov in ProviderLoader.LoadAllSettingsStorageProviders(Settings.Provider)) { + if(prov.GetType().FullName != Settings.Provider.GetType().FullName) { + lstSettingsDestination.Items.Add(new ListItem(prov.Information.Name, prov.GetType().FullName)); + } + } + } + + protected void lstPagesSource_SelectedIndexChanged(object sender, EventArgs e) { + lstPagesDestination.Items.Clear(); + if(lstPagesSource.SelectedValue != "") { + foreach(IPagesStorageProviderV30 prov in Collectors.PagesProviderCollector.AllProviders) { + if(!prov.ReadOnly && lstPagesSource.SelectedValue != prov.GetType().ToString()) { + lstPagesDestination.Items.Add(new ListItem(prov.Information.Name, prov.GetType().ToString())); + } + } + } + btnMigratePages.Enabled = lstPagesDestination.Items.Count > 0; + } + + protected void lstUsersSource_SelectedIndexChanged(object sender, EventArgs e) { + lstUsersDestination.Items.Clear(); + if(lstUsersSource.SelectedValue != "") { + foreach(IUsersStorageProviderV30 prov in Collectors.UsersProviderCollector.AllProviders) { + if(IsUsersProviderFullWriteEnabled(prov) && lstUsersSource.SelectedValue != prov.GetType().ToString()) { + lstUsersDestination.Items.Add(new ListItem(prov.Information.Name, prov.GetType().ToString())); + } + } + } + btnMigrateUsers.Enabled = lstUsersDestination.Items.Count > 0; + } + + protected void lstFilesSource_SelectedIndexChanged(object sender, EventArgs e) { + lstFilesDestination.Items.Clear(); + if(lstFilesSource.SelectedValue != "") { + foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) { + if(!prov.ReadOnly && lstFilesSource.SelectedValue != prov.GetType().ToString()) { + lstFilesDestination.Items.Add(new ListItem(prov.Information.Name, prov.GetType().ToString())); + } + } + } + btnMigrateFiles.Enabled = lstFilesDestination.Items.Count > 0; + } + + protected void lstSettingsDestination_SelectedIndexChanged(object sender, EventArgs e) { + btnCopySettings.Enabled = lstSettingsDestination.SelectedValue != ""; + } + + protected void btnMigratePages_Click(object sender, EventArgs e) { + IPagesStorageProviderV30 from = Collectors.PagesProviderCollector.GetProvider(lstPagesSource.SelectedValue); + IPagesStorageProviderV30 to = Collectors.PagesProviderCollector.GetProvider(lstPagesDestination.SelectedValue); + + Log.LogEntry("Pages data migration requested from " + from.Information.Name + " to " + to.Information.Name, EntryType.General, SessionFacade.CurrentUsername); + + DataMigrator.MigratePagesStorageProviderData(from, to); + + lblMigratePagesResult.CssClass = "resultok"; + lblMigratePagesResult.Text = Properties.Messages.DataMigrated; + } + + protected void btnMigrateUsers_Click(object sender, EventArgs e) { + IUsersStorageProviderV30 from = Collectors.UsersProviderCollector.GetProvider(lstUsersSource.SelectedValue); + IUsersStorageProviderV30 to = Collectors.UsersProviderCollector.GetProvider(lstUsersDestination.SelectedValue); + + Log.LogEntry("Users data migration requested from " + from.Information.Name + " to " + to.Information.Name, EntryType.General, SessionFacade.CurrentUsername); + + DataMigrator.MigrateUsersStorageProviderData(from, to, true); + + lblMigrateUsersResult.CssClass = "resultok"; + lblMigrateUsersResult.Text = Properties.Messages.DataMigrated; + } + + protected void btnMigrateFiles_Click(object sender, EventArgs e) { + IFilesStorageProviderV30 from = Collectors.FilesProviderCollector.GetProvider(lstFilesSource.SelectedValue); + IFilesStorageProviderV30 to = Collectors.FilesProviderCollector.GetProvider(lstFilesDestination.SelectedValue); + + Log.LogEntry("Files data migration requested from " + from.Information.Name + " to " + to.Information.Name, EntryType.General, SessionFacade.CurrentUsername); + + DataMigrator.MigrateFilesStorageProviderData(from, to, Settings.Provider); + + lblMigrateFilesResult.CssClass = "resultok"; + lblMigrateFilesResult.Text = Properties.Messages.DataMigrated; + } + + protected void btnCopySettings_Click(object sender, EventArgs e) { + ISettingsStorageProviderV30 to = null; + + ISettingsStorageProviderV30[] allProviders = ProviderLoader.LoadAllSettingsStorageProviders(Settings.Provider); + foreach(ISettingsStorageProviderV30 prov in allProviders) { + if(prov.GetType().ToString() == lstSettingsDestination.SelectedValue) { + to = prov; + break; + } + } + + Log.LogEntry("Settings data copy requested to " + to.Information.Name, EntryType.General, SessionFacade.CurrentUsername); + + try { + to.Init(Host.Instance, txtSettingsDestinationConfig.Text); + } + catch(InvalidConfigurationException ex) { + Log.LogEntry("Provider rejected configuration: " + ex.ToString(), EntryType.Error, Log.SystemUsername); + lblCopySettingsResult.CssClass = "resulterror"; + lblCopySettingsResult.Text = Properties.Messages.ProviderRejectedConfiguration; + return; + } + + // Find namespaces + List namespaces = new List(5); + foreach(NamespaceInfo ns in Pages.GetNamespaces()) { + namespaces.Add(ns.Name); + } + + DataMigrator.CopySettingsStorageProviderData(Settings.Provider, to, namespaces.ToArray(), Collectors.GetAllProviders()); + + lblCopySettingsResult.CssClass = "resultok"; + lblCopySettingsResult.Text = Properties.Messages.DataCopied; + } + + #endregion + + /// + /// Detects whether a users storage provider fully supports writing to all managed data. + /// + /// The provider. + /// true if the provider fully supports writing all managed data, false otherwise. + private static bool IsUsersProviderFullWriteEnabled(IUsersStorageProviderV30 provider) { + return + !provider.UserAccountsReadOnly && + !provider.UserGroupsReadOnly && + !provider.GroupMembershipReadOnly && + !provider.UsersDataReadOnly; + } + + } + + /// + /// Represents a provider for display purposes. + /// + public class ProviderRow { + + private string name, typeName, version, author, authorUrl, updateStatus, additionalClass; + + /// + /// Initializes a new instance of the class. + /// + /// The original component information. + /// The type name. + /// A value indicating whether the provider is disabled. + /// A value indicating whether the provider is selected. + public ProviderRow(ComponentInformation info, string typeName, string updateStatus, bool disabled, bool selected) { + name = info.Name; + this.typeName = typeName; + version = info.Version; + author = info.Author; + authorUrl = info.Url; + this.updateStatus = updateStatus; + additionalClass = disabled ? " disabled" : ""; + additionalClass += selected ? " selected" : ""; + } + + /// + /// Gets the name. + /// + public string Name { + get { return name; } + } + + /// + /// Gets the type name. + /// + public string TypeName { + get { return typeName; } + } + + /// + /// Gets the version. + /// + public string Version { + get { return version; } + } + + /// + /// Gets the author. + /// + public string Author { + get { return author; } + } + + /// + /// Gets the author URL. + /// + public string AuthorUrl { + get { return authorUrl; } + } + + /// + /// Gets the provider update status. + /// + public string UpdateStatus { + get { return updateStatus; } + } + + /// + /// Gets the additional CSS class. + /// + public string AdditionalClass { + get { return additionalClass; } + } + + } + +} diff --git a/WebApplication/AdminProviders.aspx.designer.cs b/WebApplication/AdminProviders.aspx.designer.cs new file mode 100644 index 0000000..54a2752 --- /dev/null +++ b/WebApplication/AdminProviders.aspx.designer.cs @@ -0,0 +1,664 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AdminProviders { + + /// + /// lblProviders control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblProviders; + + /// + /// pnlList control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlList; + + /// + /// lblDisplay control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDisplay; + + /// + /// rdoPages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoPages; + + /// + /// rdoUsers control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoUsers; + + /// + /// rdoFiles control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoFiles; + + /// + /// rdoCache control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoCache; + + /// + /// rdoFormatter control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoFormatter; + + /// + /// rptProviders control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Repeater rptProviders; + + /// + /// pnlProviderDetails control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlProviderDetails; + + /// + /// lblProviderName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblProviderName; + + /// + /// lblProviderDll control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblProviderDll; + + /// + /// lblConfigurationStringTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblConfigurationStringTitle; + + /// + /// lblConfigHelp control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblConfigHelp; + + /// + /// txtConfigurationString control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtConfigurationString; + + /// + /// btnSave control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnSave; + + /// + /// btnDisable control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnDisable; + + /// + /// btnEnable control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnEnable; + + /// + /// btnUnload control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnUnload; + + /// + /// btnCancel control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnCancel; + + /// + /// lblCannotDisable control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblCannotDisable; + + /// + /// lblResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblResult; + + /// + /// lblProviderConfigHelp control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblProviderConfigHelp; + + /// + /// btnAutoUpdateProviders control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnAutoUpdateProviders; + + /// + /// lblAutoUpdateResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblAutoUpdateResult; + + /// + /// txtCurrentProvider control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.HiddenField txtCurrentProvider; + + /// + /// lblDefaultProvidersTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDefaultProvidersTitle; + + /// + /// lblDefaultProvPages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDefaultProvPages; + + /// + /// lstPagesProvider control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.ProviderSelector lstPagesProvider; + + /// + /// lblDefaultProvUsers control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDefaultProvUsers; + + /// + /// lstUsersProvider control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.ProviderSelector lstUsersProvider; + + /// + /// lblDefaultProvFiles control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDefaultProvFiles; + + /// + /// lstFilesProvider control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.ProviderSelector lstFilesProvider; + + /// + /// lblDefaultProvCache control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDefaultProvCache; + + /// + /// lstCacheProvider control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.ProviderSelector lstCacheProvider; + + /// + /// btnSaveDefaultProviders control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnSaveDefaultProviders; + + /// + /// lblDefaultProvidersResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblDefaultProvidersResult; + + /// + /// lblUploadProvidersTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblUploadProvidersTitle; + + /// + /// lblUploadNewDll control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblUploadNewDll; + + /// + /// upDll control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.FileUpload upDll; + + /// + /// btnUpload control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnUpload; + + /// + /// lblUploadResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblUploadResult; + + /// + /// lstDlls control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstDlls; + + /// + /// btnDeleteDll control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnDeleteDll; + + /// + /// lblDllResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblDllResult; + + /// + /// lblUploadInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblUploadInfo; + + /// + /// lblDataMigration control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDataMigration; + + /// + /// lblMigrationInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblMigrationInfo; + + /// + /// lblMigratePages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblMigratePages; + + /// + /// lstPagesSource control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstPagesSource; + + /// + /// lstPagesDestination control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstPagesDestination; + + /// + /// btnMigratePages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnMigratePages; + + /// + /// lblMigratePagesResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblMigratePagesResult; + + /// + /// lblMigrateUsers control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblMigrateUsers; + + /// + /// lstUsersSource control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstUsersSource; + + /// + /// lstUsersDestination control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstUsersDestination; + + /// + /// btnMigrateUsers control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnMigrateUsers; + + /// + /// lblMigrateUsersResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblMigrateUsersResult; + + /// + /// lblMigrateUsersInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblMigrateUsersInfo; + + /// + /// lblMigrateFiles control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblMigrateFiles; + + /// + /// lstFilesSource control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstFilesSource; + + /// + /// lstFilesDestination control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstFilesDestination; + + /// + /// btnMigrateFiles control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnMigrateFiles; + + /// + /// lblMigrateFilesResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblMigrateFilesResult; + + /// + /// lblCopySettings control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCopySettings; + + /// + /// lblSettingsSource control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblSettingsSource; + + /// + /// lstSettingsDestination control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstSettingsDestination; + + /// + /// btnCopySettings control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnCopySettings; + + /// + /// lblCopySettingsResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblCopySettingsResult; + + /// + /// lblCopySettingsInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCopySettingsInfo; + + /// + /// lblCopySettingsDestinationConfig control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCopySettingsDestinationConfig; + + /// + /// txtSettingsDestinationConfig control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtSettingsDestinationConfig; + } +} diff --git a/WebApplication/AdminSnippets.aspx b/WebApplication/AdminSnippets.aspx new file mode 100644 index 0000000..356793e --- /dev/null +++ b/WebApplication/AdminSnippets.aspx @@ -0,0 +1,99 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Admin.master" AutoEventWireup="true" CodeBehind="AdminSnippets.aspx.cs" Inherits="ScrewTurn.Wiki.AdminSnippets" ValidateRequest="false" culture="auto" meta:resourcekey="PageResource1" uiculture="auto" %> + +<%@ Register TagPrefix="st" TagName="ProviderSelector" Src="~/ProviderSelector.ascx" %> +<%@ Register TagPrefix="st" TagName="Editor" Src="~/Editor.ascx" %> + + + + + +

    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     
    <%# Eval("Type") %><%# Eval("Name") %><%# Eval("ParameterCount") %><%# Eval("Provider") %>
    <%# Eval("Type") %><%# Eval("Name") %><%# Eval("ParameterCount") %><%# Eval("Provider") %>
    + +
    +
    +
    + + +
    +

    + + +

    + +
    +
    + +
    + + + +

    + + + +
    + + + + + + +
    +
    +
    + + + +
    + +
    diff --git a/WebApplication/AdminSnippets.aspx.cs b/WebApplication/AdminSnippets.aspx.cs new file mode 100644 index 0000000..8348ea6 --- /dev/null +++ b/WebApplication/AdminSnippets.aspx.cs @@ -0,0 +1,397 @@ + +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class AdminSnippets : BasePage { + + protected void Page_Load(object sender, EventArgs e) { + AdminMaster.RedirectToLoginIfNeeded(); + + if(!AdminMaster.CanManageSnippetsAndTemplates(SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) UrlTools.Redirect("AccessDenied.aspx"); + + if(!Page.IsPostBack) { + // Load snippets + rptSnippetsTemplates.DataBind(); + } + } + + protected void rptSnippetsTemplates_DataBinding(object sender, EventArgs e) { + List snippets = Snippets.GetSnippets(); + List templates = Templates.GetTemplates(); + + List result = new List(snippets.Count + templates.Count); + + foreach(Snippet snip in snippets) { + result.Add(new SnippetTemplateRow(snip, "S." + snip.Name == txtCurrentElement.Value)); + } + foreach(ContentTemplate temp in templates) { + result.Add(new SnippetTemplateRow(temp, "T." + temp.Name == txtCurrentElement.Value)); + } + + rptSnippetsTemplates.DataSource = result; + } + + protected void rptSnippetsTemplates_ItemCommand(object sender, CommandEventArgs e) { + if(e.CommandName == "Select") { + txtCurrentElement.Value = e.CommandArgument as string; + + if(txtCurrentElement.Value.StartsWith("S.")) SelectSnippet(txtCurrentElement.Value.Substring(2)); + else SelectTemplate(txtCurrentElement.Value.Substring(2)); + + providerSelector.Enabled = false; + txtName.Text = txtCurrentElement.Value.Substring(2); + txtName.Enabled = false; + + btnCreate.Visible = false; + btnSave.Visible = true; + btnDelete.Visible = true; + pnlList.Visible = false; + pnlEditElement.Visible = true; + + lblResult.CssClass = ""; + lblResult.Text = ""; + } + } + + /// + /// Sets the editing area title for a snippet. + /// + private void SetTitleForSnippet() { + lblEditTitleSnippet.Visible = true; + lblEditTitleTemplate.Visible = false; + } + + /// + /// Sets the editing area title for a template. + /// + private void SetTitleForTemplate() { + lblEditTitleSnippet.Visible = false; + lblEditTitleTemplate.Visible = true; + } + + /// + /// Selects a snippet for editing. + /// + /// The name of the snippet. + private void SelectSnippet(string name) { + Snippet snippet = Snippets.Find(name); + providerSelector.SelectedProvider = snippet.Provider.GetType().FullName; + editor.SetContent(snippet.Content, Settings.UseVisualEditorAsDefault); + SetTitleForSnippet(); + } + + /// + /// Selects a template for editing. + /// + /// The name of the template. + private void SelectTemplate(string name) { + ContentTemplate template = Templates.Find(name); + providerSelector.SelectedProvider = template.Provider.GetType().FullName; + editor.SetContent(template.Content, Settings.UseVisualEditorAsDefault); + SetTitleForTemplate(); + } + + protected void btnNewSnippet_Click(object sender, EventArgs e) { + pnlList.Visible = false; + pnlEditElement.Visible = true; + + editor.SetContent("", Settings.UseVisualEditorAsDefault); + SetTitleForSnippet(); + txtCurrentElement.Value = "S"; + + lblResult.Text = ""; + lblResult.CssClass = ""; + } + + protected void btnNewTemplate_Click(object sender, EventArgs e) { + pnlList.Visible = false; + pnlEditElement.Visible = true; + + editor.SetContent("", Settings.UseVisualEditorAsDefault); + SetTitleForTemplate(); + txtCurrentElement.Value = "T"; + + lblResult.Text = ""; + lblResult.CssClass = ""; + } + + protected void cvName_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = Pages.IsValidName(txtName.Text); + } + + protected void btnCreate_Click(object sender, EventArgs e) { + lblResult.CssClass = ""; + lblResult.Text = ""; + + if(!Page.IsValid) return; + + if(txtCurrentElement.Value == "S") CreateSnippet(); + else CreateTemplate(); + } + + /// + /// Creates a snippet. + /// + private void CreateSnippet() { + Log.LogEntry("Snippet creation requested for " + txtName.Text, EntryType.General, Log.SystemUsername); + + if(Snippets.AddSnippet(txtName.Text, editor.GetContent(), + Collectors.PagesProviderCollector.GetProvider(providerSelector.SelectedProvider))) { + + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.SnippetCreated; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotCreateSnippet; + } + } + + /// + /// Creates a template. + /// + private void CreateTemplate() { + Log.LogEntry("Content Template creation requested for " + txtName.Text, EntryType.General, Log.SystemUsername); + + if(Templates.AddTemplate(txtName.Text, editor.GetContent(), + Collectors.PagesProviderCollector.GetProvider(providerSelector.SelectedProvider))) { + + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.TemplateCreated; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotCreateTemplate; + } + } + + protected void btnSave_Click(object sender, EventArgs e) { + lblResult.CssClass = ""; + lblResult.Text = ""; + + if(txtCurrentElement.Value.StartsWith("S.")) SaveSnippet(txtCurrentElement.Value.Substring(2)); + else SaveTemplate(txtCurrentElement.Value.Substring(2)); + } + + /// + /// Saves a snippet. + /// + /// The name of the snippet to save. + private void SaveSnippet(string name) { + Snippet snippet = Snippets.Find(name); + + Log.LogEntry("Snippet modification requested for " + name, EntryType.General, Log.SystemUsername); + + if(Snippets.ModifySnippet(snippet, editor.GetContent())) { + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.SnippetSaved; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotSaveSnippet; + } + } + + /// + /// Saves a template. + /// + /// The name of the template to save. + private void SaveTemplate(string name) { + ContentTemplate template = Templates.Find(name); + + Log.LogEntry("Content Template modification requested for " + name, EntryType.General, Log.SystemUsername); + + if(Templates.ModifyTemplate(template, editor.GetContent())) { + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.TemplateSaved; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotSaveTemplate; + } + } + + protected void btnDelete_Click(object sender, EventArgs e) { + lblResult.CssClass = ""; + lblResult.Text = ""; + + if(txtCurrentElement.Value.StartsWith("S.")) DeleteSnippet(txtCurrentElement.Value.Substring(2)); + else DeleteTemplate(txtCurrentElement.Value.Substring(2)); + } + + /// + /// Deletes a snippet. + /// + /// The name of the snippet to delete. + private void DeleteSnippet(string name) { + Snippet snippet = Snippets.Find(name); + + Log.LogEntry("Snippet deletion requested for " + name, EntryType.General, Log.SystemUsername); + + if(Snippets.RemoveSnippet(snippet)) { + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.SnippetDeleted; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotDeleteSnippet; + } + } + + /// + /// Deletes a template. + /// + /// The name of the template to delete. + private void DeleteTemplate(string name) { + ContentTemplate snippet = Templates.Find(name); + + Log.LogEntry("Content Template deletion requested for " + name, EntryType.General, Log.SystemUsername); + + if(Templates.RemoveTemplate(snippet)) { + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.TemplateDeleted; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotDeleteTemplate; + } + } + + protected void btnCancel_Click(object sender, EventArgs e) { + RefreshList(); + ReturnToList(); + } + + /// + /// Returns to the accounts list. + /// + private void ReturnToList() { + pnlEditElement.Visible = false; + pnlList.Visible = true; + } + + /// + /// Refreshes the users list. + /// + private void RefreshList() { + txtCurrentElement.Value = ""; + ResetEditor(); + rptSnippetsTemplates.DataBind(); + } + + /// + /// Resets the account editor. + /// + private void ResetEditor() { + providerSelector.Enabled = true; + txtName.Text = ""; + txtName.Enabled = true; + editor.SetContent("", Settings.UseVisualEditorAsDefault); + + btnCreate.Visible = true; + btnSave.Visible = false; + btnDelete.Visible = false; + lblResult.Text = ""; + lblResult.CssClass = ""; + } + + } + + /// + /// Represents a snippet for display purposes. + /// + public class SnippetTemplateRow { + + private string type, name, distinguishedName, parameterCount, provider, additionalClass; + + /// + /// Initializes a new instance of the class. + /// + /// The original snippet. + /// A value indicating whether the snippet is selected. + public SnippetTemplateRow(Snippet snippet, bool selected) { + type = Properties.Messages.Snippet; + name = snippet.Name; + distinguishedName = "S." + snippet.Name; + parameterCount = Snippets.CountParameters(snippet).ToString(); + provider = snippet.Provider.Information.Name; + additionalClass = selected ? " selected" : ""; + } + + /// + /// Initializes a new instance of the class. + /// + /// The original template. + /// A value indicating whether the template is selected. + public SnippetTemplateRow(ContentTemplate template, bool selected) { + type = Properties.Messages.Template; + name = template.Name; + distinguishedName = "T." + template.Name; + parameterCount = ""; + provider = template.Provider.Information.Name; + additionalClass = selected ? " selected" : ""; + } + + /// + /// Gets the type identifier. + /// + public string Type { + get { return type; } + } + + /// + /// Gets the name. + /// + public string Name { + get { return name; } + } + + /// + /// Gets the distinguished name. + /// + public string DistinguishedName { + get { return distinguishedName; } + } + + /// + /// Gets the parameter count. + /// + public string ParameterCount { + get { return parameterCount; } + } + + /// + /// Gets the provider. + /// + public string Provider { + get { return provider; } + } + + /// + /// Gets the additional CSS class. + /// + public string AdditionalClass { + get { return additionalClass; } + } + + } + +} diff --git a/WebApplication/AdminSnippets.aspx.designer.cs b/WebApplication/AdminSnippets.aspx.designer.cs new file mode 100644 index 0000000..e2a9792 --- /dev/null +++ b/WebApplication/AdminSnippets.aspx.designer.cs @@ -0,0 +1,205 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.3074 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AdminSnippets { + + /// + /// lblSnippetsTemplates control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblSnippetsTemplates; + + /// + /// pnlList control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlList; + + /// + /// btnNewTemplate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnNewTemplate; + + /// + /// btnNewSnippet control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnNewSnippet; + + /// + /// rptSnippetsTemplates control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Repeater rptSnippetsTemplates; + + /// + /// pnlEditElement control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlEditElement; + + /// + /// lblEditTitleSnippet control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblEditTitleSnippet; + + /// + /// lblEditTitleTemplate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblEditTitleTemplate; + + /// + /// lblProvider control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblProvider; + + /// + /// providerSelector control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.ProviderSelector providerSelector; + + /// + /// lblName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblName; + + /// + /// txtName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtName; + + /// + /// rfvName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RequiredFieldValidator rfvName; + + /// + /// cvName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CustomValidator cvName; + + /// + /// editor control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.Editor editor; + + /// + /// btnSave control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnSave; + + /// + /// btnCreate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnCreate; + + /// + /// btnDelete control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnDelete; + + /// + /// btnCancel control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnCancel; + + /// + /// lblResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblResult; + + /// + /// txtCurrentElement control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HiddenField txtCurrentElement; + } +} diff --git a/WebApplication/AdminUsers.aspx b/WebApplication/AdminUsers.aspx new file mode 100644 index 0000000..3dffb1c --- /dev/null +++ b/WebApplication/AdminUsers.aspx @@ -0,0 +1,192 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Admin.master" AutoEventWireup="true" CodeBehind="AdminUsers.aspx.cs" Inherits="ScrewTurn.Wiki.AdminUsers" culture="auto" meta:resourcekey="PageResource1" uiculture="auto" %> + +<%@ Register TagPrefix="st" TagName="ProviderSelector" Src="~/ProviderSelector.ascx" %> +<%@ Register TagPrefix="st" TagName="AclActionsSelector" Src="~/AclActionsSelector.ascx" %> +<%@ Register TagPrefix="st" TagName="PageSelector" Src="~/PageSelector.ascx" %> + + + + + + + +

    + + +
    + +



    +

    + +

    + + +
    + +
    + + + + + + + + +
    + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     
    <%# Eval("Username") %><%# Eval("DisplayName") %><%# Eval("Email") %><%# Eval("MemberOf") %><%# Eval("RegDateTime") %><%# Eval("Provider") %>
    <%# Eval("Username") %><%# Eval("DisplayName") %><%# Eval("Email") %><%# Eval("MemberOf") %><%# Eval("RegDateTime") %><%# Eval("Provider") %>
    + +
    +
    +
    + + +
    +

    + +
    +
    + + + +
    + + + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + +
    + + +
    + +
    + + + +
    + +
    +

    + +
    +
    +

    + +
    + +
    + + + + + + +
    +
    +
    + + + +
    + +
    diff --git a/WebApplication/AdminUsers.aspx.cs b/WebApplication/AdminUsers.aspx.cs new file mode 100644 index 0000000..e2aa6c8 --- /dev/null +++ b/WebApplication/AdminUsers.aspx.cs @@ -0,0 +1,585 @@ + +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; +using ScrewTurn.Wiki.PluginFramework; +using System.Text; + +namespace ScrewTurn.Wiki { + + public partial class AdminUsers : BasePage { + + /// + /// The numer of items in a page. + /// + public const int PageSize = 50; + + private IList currentUsers = null; + + private int rangeBegin = 0; + private int rangeEnd = PageSize - 1; + private int selectedPage = 0; + + protected void Page_Load(object sender, EventArgs e) { + AdminMaster.RedirectToLoginIfNeeded(); + + if(!AdminMaster.CanManageUsers(SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) UrlTools.Redirect("AccessDenied.aspx"); + aclActionsSelector.Visible = AdminMaster.CanManagePermissions(SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames()); + + revUsername.ValidationExpression = Settings.UsernameRegex; + revDisplayName.ValidationExpression = Settings.DisplayNameRegex; + revPassword1.ValidationExpression = Settings.UsernameRegex; + revEmail.ValidationExpression = Settings.EmailRegex; + + if(!Page.IsPostBack) { + ResetUserList(); + + RefreshList(); + + providerSelector.Reload(); + btnNewUser.Enabled = providerSelector.HasProviders; + } + + if(Page.IsPostBack) { + // Preserve password value (a bit insecure but much more usable) + txtPassword1.Attributes.Add("value", txtPassword1.Text); + txtPassword2.Attributes.Add("value", txtPassword2.Text); + } + } + + /// + /// Resets the user list. + /// + private void ResetUserList() { + currentUsers = GetUsers(); + pageSelector.ItemCount = currentUsers.Count; + pageSelector.SelectPage(0); + } + + protected void chkFilter_CheckedChanged(object sender, EventArgs e) { + currentUsers = GetUsers(); + pageSelector.ItemCount = currentUsers.Count; + pageSelector.SelectPage(0); + + RefreshList(); + } + + protected void btnFilter_Click(object sender, EventArgs e) { + currentUsers = GetUsers(); + pageSelector.ItemCount = currentUsers.Count; + pageSelector.SelectPage(0); + + RefreshList(); + } + + protected void pageSelector_SelectedPageChanged(object sender, SelectedPageChangedEventArgs e) { + rangeBegin = e.SelectedPage * PageSize; + rangeEnd = rangeBegin + e.ItemCount - 1; + selectedPage = e.SelectedPage; + + RefreshList(); + } + + /// + /// Gets the users. + /// + /// The users. + private IList GetUsers() { + List allUsers = Users.GetUsers(); + + // Apply filter + List result = new List(allUsers.Count); + + foreach(UserInfo user in allUsers) { + if(user.Active && chkActive.Checked) { + if(FilterUsername(user)) result.Add(user); + } + else if(!user.Active && chkInactive.Checked) { + if(FilterUsername(user)) result.Add(user); + } + } + + return result; + } + + /// + /// Refreshes the users list. + /// + private void RefreshList() { + rangeBegin = pageSelector.SelectedPage * PageSize; + rangeEnd = rangeBegin + pageSelector.SelectedPageSize - 1; + selectedPage = pageSelector.SelectedPage; + + txtCurrentUsername.Value = ""; + ResetEditor(); + rptAccounts.DataBind(); + } + + private bool FilterUsername(UserInfo user) { + if(txtFilter.Text.Length == 0) return true; + else return user.Username.ToLower(System.Globalization.CultureInfo.CurrentCulture).Contains(txtFilter.Text.ToLower(System.Globalization.CultureInfo.CurrentCulture)); + } + + protected void rptAccounts_DataBinding(object sender, EventArgs e) { + if(currentUsers == null) currentUsers = GetUsers(); + + List selectedRows = new List(PageSize); + + for(int i = rangeBegin; i <= rangeEnd; i++) { + selectedRows.Add(new UserRow(currentUsers[i], Users.GetUserGroupsForUser(currentUsers[i]), + currentUsers[i].Username == txtCurrentUsername.Value)); + } + + rptAccounts.DataSource = selectedRows; + } + + protected void rptAccounts_ItemCommand(object sender, RepeaterCommandEventArgs e) { + if(e.CommandName == "Select") { + txtCurrentUsername.Value = e.CommandArgument as string; + //rptAccounts.DataBind(); Not needed because the list is hidden on select + + UserInfo user = Users.FindUser(txtCurrentUsername.Value); + + txtUsername.Text = user.Username; + txtUsername.Enabled = false; + txtDisplayName.Text = user.DisplayName; + txtEmail.Text = user.Email; + chkSetActive.Checked = user.Active; + providerSelector.SelectedProvider = user.Provider.GetType().FullName; + providerSelector.Enabled = false; + btnCreate.Visible = false; + btnSave.Visible = true; + btnDelete.Visible = true; + rfvPassword1.Enabled = false; + cvUsername.Enabled = false; + lblPasswordInfo.Visible = true; + + pnlEditAccount.Visible = true; + pnlList.Visible = false; + PopulateGroups(); + + // Select user's groups + List groups = Users.GetUserGroupsForUser(user); + foreach(ListItem item in lstGroups.Items) { + if(groups.Find(delegate(UserGroup g) { return g.Name == item.Value; }) != null) { + item.Selected = true; + } + } + + // Select user's global permissions + aclActionsSelector.GrantedActions = + AuthReader.RetrieveGrantsForGlobals(user); + aclActionsSelector.DeniedActions = + AuthReader.RetrieveDenialsForGlobals(user); + + // Enable/disable interface sections based on provider read-only settings + lstGroups.Enabled = !user.Provider.GroupMembershipReadOnly; + pnlAccountDetails.Enabled = !user.Provider.UserAccountsReadOnly; + btnDelete.Enabled = !user.Provider.UserAccountsReadOnly; + + lblResult.CssClass = ""; + lblResult.Text = ""; + } + } + + /// + /// Resets the account editor. + /// + private void ResetEditor() { + txtUsername.Text = ""; + txtUsername.Enabled = true; + txtEmail.Text = ""; + chkSetActive.Checked = true; + providerSelector.Enabled = true; + providerSelector.Reload(); + lstGroups.Enabled = true; + pnlAccountDetails.Enabled = true; + + aclActionsSelector.GrantedActions = new string[0]; + aclActionsSelector.DeniedActions = new string[0]; + + foreach(ListItem item in lstGroups.Items) { + item.Selected = false; + } + + btnCreate.Visible = true; + btnSave.Visible = false; + btnDelete.Visible = false; + rfvPassword1.Enabled = true; + cvUsername.Enabled = true; + lblPasswordInfo.Visible = false; + lblResult.Text = ""; + } + + protected void providerSelector_SelectedProviderChanged(object sender, EventArgs e) { + PopulateGroups(); + } + + /// + /// Populates the groups list according to the currently selected provider. + /// + private void PopulateGroups() { + List groups = Users.GetUserGroups(Collectors.UsersProviderCollector.GetProvider(providerSelector.SelectedProvider)); + + lstGroups.Items.Clear(); + foreach(UserGroup group in groups) { + ListItem item = new ListItem(group.Name, group.Name); + lstGroups.Items.Add(item); + } + } + + protected void cvUsername_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = Users.FindUser(txtUsername.Text) == null; + } + + protected void cvPassword2_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = txtPassword1.Text == txtPassword2.Text; + } + + protected void btnBulkDelete_Click(object sender, EventArgs e) { + Log.LogEntry("Bulk account deletion requested", EntryType.General, SessionFacade.CurrentUsername); + + DateTime now = DateTime.Now; + List allUsers = Users.GetUsers(); + int count = 0; + + for(int i = 0; i < allUsers.Count; i++) { + if(!allUsers[i].Active && !allUsers[i].Provider.UserAccountsReadOnly && (now - allUsers[i].DateTime).TotalDays >= 31) { + RemoveAllAclEntries(allUsers[i]); + RemoveGroupMembership(allUsers[i]); + Users.RemoveUser(allUsers[i]); + count++; + } + } + + Log.LogEntry("Bulk account deletion completed - " + count.ToString() + " accounts deleted", EntryType.General, SessionFacade.CurrentUsername); + + ResetUserList(); + RefreshList(); + lblBulkDeleteResult.CssClass = "resultok"; + lblBulkDeleteResult.Text = Properties.Messages.NAccountsDeleted.Replace("$", count.ToString()); + } + + protected void btnCreate_Click(object sender, EventArgs e) { + if(!Page.IsValid) return; + + lblResult.CssClass = ""; + lblResult.Text = ""; + + Log.LogEntry("User creation requested for " + txtUsername.Text, EntryType.General, SessionFacade.CurrentUsername); + + // Add the new user, set its global permissions, set its membership + bool done = Users.AddUser(txtUsername.Text, txtDisplayName.Text, txtPassword1.Text, txtEmail.Text, + chkSetActive.Checked, + Collectors.UsersProviderCollector.GetProvider(providerSelector.SelectedProvider)); + + UserInfo currentUser = null; + if(done) { + currentUser = Users.FindUser(txtUsername.Text); + + // Wipe old data, if any + RemoveAllAclEntries(currentUser); + + done = AddAclEntries(currentUser, aclActionsSelector.GrantedActions, aclActionsSelector.DeniedActions); + + if(done) { + done = SetGroupMembership(currentUser, GetSelectedGroups()); + + if(done) { + ResetUserList(); + + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.UserCreated; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.UserCreatedCouldNotStoreGroupMembership; + } + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.UserCreatedCouldNotStorePermissions; + } + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotCreateUser; + } + } + + protected void btnSave_Click(object sender, EventArgs e) { + if(!Page.IsValid) return; + + // Perform proper actions based on provider read-only settings + // 1. If possible, modify user + // 2. Update ACLs + // 3. If possible, update group membership + + lblResult.CssClass = ""; + lblResult.Text = ""; + + Log.LogEntry("User update requested for " + txtCurrentUsername.Value, EntryType.General, SessionFacade.CurrentUsername); + + UserInfo currentUser = Users.FindUser(txtCurrentUsername.Value); + + bool done = true; + + if(!currentUser.Provider.UserAccountsReadOnly) { + done = Users.ModifyUser(currentUser, txtDisplayName.Text, txtPassword1.Text, txtEmail.Text, chkSetActive.Checked); + } + + if(!done) { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotUpdateUser; + return; + } + + done = RemoveAllAclEntries(currentUser); + if(done) { + done = AddAclEntries(currentUser, aclActionsSelector.GrantedActions, aclActionsSelector.DeniedActions); + + if(!done) { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.UserUpdatedCouldNotStoreNewPermissions; + } + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.UserUpdatedCouldNotDeleteOldPermissions; + return; + } + + if(!currentUser.Provider.GroupMembershipReadOnly) { + // This overwrites old membership data + done = SetGroupMembership(currentUser, GetSelectedGroups()); + + if(done) { + RefreshList(); + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.UserUpdated; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.UserUpdatedCouldNotStoreGroupMembership; + } + } + } + + protected void btnDelete_Click(object sender, EventArgs e) { + lblResult.Text = ""; + lblResult.CssClass = ""; + + Log.LogEntry("User deletion requested for " + txtCurrentUsername.Value, EntryType.General, SessionFacade.CurrentUsername); + + UserInfo currentUser = Users.FindUser(txtCurrentUsername.Value); + + if(currentUser.Provider.UserAccountsReadOnly) return; + + // Remove global permissions, remove group membership, remove user + bool done = RemoveAllAclEntries(currentUser); + if(done) { + done = RemoveGroupMembership(currentUser); + + if(done) { + done = Users.RemoveUser(currentUser); + + if(done) { + ResetUserList(); + + RefreshList(); + lblResult.Text = Properties.Messages.UserDeleted; + lblResult.CssClass = "resultok"; + ReturnToList(); + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.PermissionsAndGroupMembershipDeletedCouldNotDeleteUser; + } + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.PermissionsDeletedCouldNotDeleteUser; + } + } + else { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.CouldNotDeletePermissions; + } + } + + protected void btnCancel_Click(object sender, EventArgs e) { + rangeBegin = pageSelector.SelectedPage * PageSize; + rangeEnd = rangeBegin + pageSelector.SelectedPageSize - 1; + selectedPage = pageSelector.SelectedPage; + + RefreshList(); + ReturnToList(); + } + + protected void btnNewUser_Click(object sender, EventArgs e) { + pnlList.Visible = false; + pnlEditAccount.Visible = true; + PopulateGroups(); + + lblResult.Text = ""; + lblResult.CssClass = ""; + } + + /// + /// Gets the currently selected groups. + /// + /// + private string[] GetSelectedGroups() { + List selectedGroups = new List(5); + foreach(ListItem item in lstGroups.Items) { + if(item.Selected) selectedGroups.Add(item.Value); + } + return selectedGroups.ToArray(); + } + + /// + /// Removes all the ACL entries for a user. + /// + /// The user. + /// true if the operation succeeded, false otherwise. + private bool RemoveAllAclEntries(UserInfo user) { + return AuthWriter.RemoveEntriesForGlobals(user); + } + + /// + /// Adds some ACL entries for a user. + /// + /// The user. + /// The granted actions. + /// The denied actions. + /// true if the operation succeeded, false otherwise. + private bool AddAclEntries(UserInfo user, string[] grants, string[] denials) { + foreach(string action in grants) { + bool done = AuthWriter.SetPermissionForGlobals(AuthStatus.Grant, action, user); + if(!done) return false; + } + + foreach(string action in denials) { + bool done = AuthWriter.SetPermissionForGlobals(AuthStatus.Deny, action, user); + if(!done) return false; + } + + return true; + } + + /// + /// Removes all the group membership data for a user. + /// + /// The user. + /// true if the operation succeeded, false otherwise. + private bool RemoveGroupMembership(UserInfo user) { + return Users.SetUserMembership(user, new string[0]); + } + + /// + /// Sets the group membership data for a user, overwriting previous membership. + /// + /// The user. + /// The groups the user should be member of. + /// true if the operation succeded, false otherwise. + private bool SetGroupMembership(UserInfo user, string[] groups) { + return Users.SetUserMembership(user, groups); + } + + /// + /// Returns to the accounts list. + /// + private void ReturnToList() { + pnlEditAccount.Visible = false; + pnlList.Visible = true; + } + + } + + /// + /// Represents a User for display purposes. + /// + public class UserRow { + + private string username, displayName, email, memberOf, regDateTime, provider, additionalClass; + + /// + /// Initializes a new instance of the class. + /// + /// The original user. + /// The groups the user is member of. + /// A value indicating whether the user is selected. + public UserRow(UserInfo user, List groups, bool selected) { + username = user.Username; + displayName = Users.GetDisplayName(user); + email = user.Email; + + StringBuilder sb = new StringBuilder(50); + for(int i = 0; i < groups.Count; i++) { + sb.Append(groups[i].Name); + if(i != groups.Count - 1) sb.Append(", "); + } + memberOf = sb.ToString(); + + regDateTime = user.DateTime.ToString(Settings.DateTimeFormat); + provider = user.Provider.Information.Name; + additionalClass = (selected ? " selected" : "") + (!user.Active ? " inactive" : ""); + } + + /// + /// Gets the username. + /// + public string Username { + get { return username; } + } + + /// + /// Gets the display name. + /// + public string DisplayName { + get { return displayName; } + } + + /// + /// Gets the email. + /// + public string Email { + get { return email; } + } + + /// + /// Gets the user membership. + /// + public string MemberOf { + get { return memberOf; } + } + + /// + /// Gets the registration date/time, formatted. + /// + public string RegDateTime { + get { return regDateTime; } + } + + /// + /// Gets the provider name. + /// + public string Provider { + get { return provider; } + } + + /// + /// Gets the additional CSS classes to apply. + /// + public string AdditionalClass { + get { return additionalClass; } + } + + } + +} diff --git a/WebApplication/AdminUsers.aspx.designer.cs b/WebApplication/AdminUsers.aspx.designer.cs new file mode 100644 index 0000000..01a0c35 --- /dev/null +++ b/WebApplication/AdminUsers.aspx.designer.cs @@ -0,0 +1,466 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AdminUsers { + + /// + /// lblAccounts control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblAccounts; + + /// + /// pnlList control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlList; + + /// + /// btnNewUser control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnNewUser; + + /// + /// lblBulkDeleteTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblBulkDeleteTitle; + + /// + /// lblBulkDelete control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblBulkDelete; + + /// + /// btnBulkDelete control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnBulkDelete; + + /// + /// lblBulkDeleteResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblBulkDeleteResult; + + /// + /// lblFilter control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblFilter; + + /// + /// chkActive control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkActive; + + /// + /// chkInactive control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkInactive; + + /// + /// txtFilter control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtFilter; + + /// + /// btnFilter control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.ImageButton btnFilter; + + /// + /// pageSelector control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.PageSelector pageSelector; + + /// + /// rptAccounts control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Repeater rptAccounts; + + /// + /// pnlEditAccount control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlEditAccount; + + /// + /// lblEditTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblEditTitle; + + /// + /// lblProvider control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblProvider; + + /// + /// providerSelector control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.ProviderSelector providerSelector; + + /// + /// pnlAccountDetails control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlAccountDetails; + + /// + /// lblUsername control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblUsername; + + /// + /// txtUsername control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtUsername; + + /// + /// rfvUsername control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RequiredFieldValidator rfvUsername; + + /// + /// revUsername control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RegularExpressionValidator revUsername; + + /// + /// cvUsername control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CustomValidator cvUsername; + + /// + /// lblDisplayName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDisplayName; + + /// + /// txtDisplayName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtDisplayName; + + /// + /// revDisplayName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RegularExpressionValidator revDisplayName; + + /// + /// lblPassword1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPassword1; + + /// + /// txtPassword1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtPassword1; + + /// + /// rfvPassword1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RequiredFieldValidator rfvPassword1; + + /// + /// revPassword1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RegularExpressionValidator revPassword1; + + /// + /// lblPassword2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPassword2; + + /// + /// txtPassword2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtPassword2; + + /// + /// cvPassword2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CustomValidator cvPassword2; + + /// + /// lblEmail control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblEmail; + + /// + /// txtEmail control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtEmail; + + /// + /// rfvEmail control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RequiredFieldValidator rfvEmail; + + /// + /// revEmail control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RegularExpressionValidator revEmail; + + /// + /// chkSetActive control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CheckBox chkSetActive; + + /// + /// lblPasswordInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblPasswordInfo; + + /// + /// lblGlobalPermissions control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblGlobalPermissions; + + /// + /// aclActionsSelector control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.AclActionsSelector aclActionsSelector; + + /// + /// lblMembership control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblMembership; + + /// + /// lstGroups control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CheckBoxList lstGroups; + + /// + /// btnSave control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnSave; + + /// + /// btnCreate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnCreate; + + /// + /// btnDelete control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnDelete; + + /// + /// btnCancel control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnCancel; + + /// + /// lblResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblResult; + + /// + /// txtCurrentUsername control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.HiddenField txtCurrentUsername; + } +} diff --git a/WebApplication/AllPages.aspx b/WebApplication/AllPages.aspx new file mode 100644 index 0000000..bfb4dd6 --- /dev/null +++ b/WebApplication/AllPages.aspx @@ -0,0 +1,21 @@ +<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.AllPages" Title="Untitled Page" Culture="auto" meta:resourcekey="PageResource1" UICulture="auto" Codebehind="AllPages.aspx.cs" %> + +<%@ Register TagPrefix="st" TagName="PageSelector" Src="~/PageSelector.ascx" %> + + + +

    +

    +
    + + + • + + +
    + +
    + + + +
    diff --git a/WebApplication/AllPages.aspx.cs b/WebApplication/AllPages.aspx.cs new file mode 100644 index 0000000..10df4a7 --- /dev/null +++ b/WebApplication/AllPages.aspx.cs @@ -0,0 +1,349 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Collections.Generic; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using ScrewTurn.Wiki.PluginFramework; +using System.Text; +using System.Globalization; + +namespace ScrewTurn.Wiki { + + public partial class AllPages : BasePage { + + /// + /// The number of items in a page. + /// + public const int PageSize = 50; + + private int selectedPage = 0; + private int rangeBegin = 0; + private int rangeEnd = PageSize - 1; + + private IList currentPages = null; + + protected void Page_Load(object sender, EventArgs e) { + Page.Title = Properties.Messages.AllPagesTitle + " - " + Settings.WikiTitle; + + LoginTools.VerifyReadPermissionsForCurrentNamespace(); + + if(Request["Cat"] != null) { + if(Request["Cat"].Equals("-")) + lblPages.Text = Properties.Messages.UncategorizedPages; + else + lblPages.Text = Properties.Messages.PagesOfCategory + " " + Request["Cat"] + ""; + } + + if(!Page.IsPostBack) { + lnkCategories.NavigateUrl = UrlTools.BuildUrl("Category.aspx"); + lnkSearch.NavigateUrl = UrlTools.BuildUrl("Search.aspx"); + + currentPages = GetAllPages(); + pageSelector.ItemCount = currentPages.Count; + + string p = Request["Page"]; + if(!int.TryParse(p, out selectedPage)) selectedPage = 0; + pageSelector.SelectPage(selectedPage); + } + + // Important note + // This page cannot use a repeater because the page list has particular elements used for grouping pages + + PrintPages(); + } + + protected void pageSelector_SelectedPageChanged(object sender, SelectedPageChangedEventArgs e) { + rangeBegin = e.SelectedPage * PageSize; + rangeEnd = rangeBegin + e.ItemCount - 1; + selectedPage = e.SelectedPage; + + PrintPages(); + } + + /// + /// Gets the creator of a page. + /// + /// The page. + /// The creator. + private string GetCreator(PageInfo page) { + List baks = Pages.GetBackups(page); + + PageContent content = null; + if(baks.Count > 0) { + content = Pages.GetBackupContent(page, baks[0]); + } + else { + content = Content.GetPageContent(page, false); + } + + return content.User; + } + + /// + /// Gets all the pages in the namespace. + /// + /// The pages. + private IList GetAllPages() { + IList pages = null; + + // Categories Management + if(Request["Cat"] != null) { + if(Request["Cat"].Equals("-")) { + pages = Pages.GetUncategorizedPages(DetectNamespaceInfo()); + } + else { + CategoryInfo cat = Pages.FindCategory(Request["Cat"]); + if(cat != null) { + pages = new PageInfo[cat.Pages.Length]; + for(int i = 0; i < cat.Pages.Length; i++) { + pages[i] = Pages.FindPage(cat.Pages[i]); + } + } + else return new List(); + } + } + else { + pages = Pages.GetPages(DetectNamespaceInfo()); + } + + return pages; + } + + /// + /// Prints the pages. + /// + public void PrintPages() { + StringBuilder sb = new StringBuilder(65536); + + if(currentPages == null) currentPages = GetAllPages(); + + // Prepare ExtendedPageInfo array + ExtendedPageInfo[] tempPageList = new ExtendedPageInfo[rangeEnd - rangeBegin + 1]; + PageContent cnt; + for(int i = 0; i < tempPageList.Length; i++) { + cnt = Content.GetPageContent(currentPages[rangeBegin + i], true); + tempPageList[i] = new ExtendedPageInfo(currentPages[rangeBegin + i], cnt.Title, cnt.LastModified, GetCreator(currentPages[rangeBegin + i]), cnt.User); + } + + // Prepare for sorting + bool reverse = false; + SortingMethod sortBy = SortingMethod.Title; + if(Request["SortBy"] != null) { + try { + sortBy = (SortingMethod)Enum.Parse(typeof(SortingMethod), Request["SortBy"], true); + } + catch { + // Backwards compatibility + if(Request["SortBy"].ToLowerInvariant() == "date") sortBy = SortingMethod.DateTime; + } + if(Request["Reverse"] != null) reverse = true; + } + + SortedDictionary> sortedPages = PageSortingTools.Sort(tempPageList, sortBy, reverse); + + sb.Append(@""); + sb.Append(""); + sb.Append(@""); + + // Page title + sb.Append(@""); + + // Message count + sb.Append(@""); + + // Creation date/time + sb.Append(@""); + + // Mod. date/time + sb.Append(@""); + + // Creator + sb.Append(@""); + + // Last author + sb.Append(@""); + + // Categories + sb.Append(""); + + sb.Append(""); + sb.Append(""); + + foreach(SortingGroup key in sortedPages.Keys) { + List pageList = sortedPages[key]; + for(int i = 0; i < pageList.Count; i++) { + if(i == 0) { + // Add group header + sb.Append(@""); + if(sortBy == SortingMethod.Title) { + sb.AppendFormat("", key.Label); + } + else if(sortBy == SortingMethod.Creation) { + sb.AppendFormat("", key.Label); + } + else if(sortBy == SortingMethod.DateTime) { + sb.AppendFormat("", key.Label); + } + else if(sortBy == SortingMethod.Creator) { + sb.AppendFormat("", key.Label); + } + else if(sortBy == SortingMethod.User) { + sb.AppendFormat("", key.Label); + } + sb.Append(""); + } + + sb.Append(@""); + + // Page title + sb.Append(@""); + + // Message count + sb.Append(@""); + + // Creation date/time + sb.Append(@""); + + // Mod. date/time + sb.Append(@""); + + // Creator + sb.Append(@""); + + // Last author + sb.Append(@""); + + // Categories + CategoryInfo[] cats = Pages.GetCategoriesForPage(pageList[i].PageInfo); + sb.Append(@""); + + sb.Append(""); + } + } + sb.Append(""); + sb.Append("
    "); + sb.Append(Properties.Messages.PageTitle); + sb.Append((reverse && sortBy.Equals("title") ? " ↑" : "")); + sb.Append((!reverse && sortBy.Equals("title") ? " ↓" : "")); + sb.Append(""); + sb.Append(Properties.Messages.CreatedOn.Replace(" ", " ")); + sb.Append((reverse && sortBy.Equals("creation") ? " ↑" : "")); + sb.Append((!reverse && sortBy.Equals("creation") ? " ↓" : "")); + sb.Append(""); + sb.Append(Properties.Messages.ModifiedOn.Replace(" ", " ")); + sb.Append((reverse && sortBy.Equals("date") ? " ↑" : "")); + sb.Append((!reverse && sortBy.Equals("date") ? " ↓" : "")); + sb.Append(""); + sb.Append(Properties.Messages.CreatedBy.Replace(" ", " ")); + sb.Append((reverse && sortBy.Equals("creator") ? " ↑" : "")); + sb.Append((!reverse && sortBy.Equals("creator") ? " ↓" : "")); + sb.Append(""); + sb.Append(Properties.Messages.ModifiedBy.Replace(" ", " ")); + sb.Append((reverse && sortBy.Equals("user") ? " ↑" : "")); + sb.Append((!reverse && sortBy.Equals("user") ? " ↓" : "")); + sb.Append(""); + sb.Append(Properties.Messages.Categories); + sb.Append("
    {0}{0}{0}{0}{0}
    "); + sb.Append(@""); + sb.Append(pageList[i].Title); + sb.Append(""); + sb.Append(""); + int msg = pageList[i].MessageCount; + if(msg > 0) { + sb.Append(@""); + sb.Append(msg.ToString()); + sb.Append(""); + } + else sb.Append(" "); + sb.Append(""); + sb.Append(Preferences.AlignWithTimezone(pageList[i].CreationDateTime).ToString(Settings.DateTimeFormat).Replace(" ", " ") + " "); + sb.Append(""); + sb.Append(Preferences.AlignWithTimezone(pageList[i].ModificationDateTime).ToString(Settings.DateTimeFormat).Replace(" ", " ") + " "); + sb.Append(""); + sb.Append(Users.UserLink(pageList[i].Creator)); + sb.Append(""); + sb.Append(Users.UserLink(pageList[i].LastAuthor)); + sb.Append(""); + if(cats.Length == 0) { + sb.Append(@""); + sb.Append(Properties.Messages.NC); + sb.Append(""); + } + else { + for(int k = 0; k < cats.Length; k++) { + sb.Append(@""); + sb.Append(NameTools.GetLocalName(cats[k].FullName)); + sb.Append(""); + if(k != cats.Length - 1) sb.Append(", "); + } + } + sb.Append("
    "); + + Literal lbl = new Literal(); + lbl.Text = sb.ToString(); + pnlPageList.Controls.Clear(); + pnlPageList.Controls.Add(lbl); + } + + } + +} diff --git a/WebApplication/AllPages.aspx.designer.cs b/WebApplication/AllPages.aspx.designer.cs new file mode 100644 index 0000000..8cd876c --- /dev/null +++ b/WebApplication/AllPages.aspx.designer.cs @@ -0,0 +1,70 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.3074 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AllPages { + + /// + /// lblPages control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPages; + + /// + /// lblDescription control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDescription; + + /// + /// lnkCategories control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkCategories; + + /// + /// lnkSearch control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HyperLink lnkSearch; + + /// + /// pageSelector control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.PageSelector pageSelector; + + /// + /// pnlPageList control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlPageList; + } +} diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.cs-CZ.resx new file mode 100644 index 0000000..b7d6d5d --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.cs-CZ.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nemáte práva pro tuto stránku. + + + Přístup odmítnut + + + Nepojmenovaná stránka + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.da-DK.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.da-DK.resx new file mode 100644 index 0000000..684854d --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.da-DK.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Du har ikke adgang til denne side + + + Adgang nægtet + + + Ikke navngivet side + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.de-DE.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.de-DE.resx new file mode 100644 index 0000000..d797f96 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.de-DE.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Sie haben keine Berechtigung um diese Seite anzuzeigen! + You do not have the permission to enter the page! + + + Zugriff verboten + Access Denied + + + Unbenannte Seite + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.es-ES.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.es-ES.resx new file mode 100644 index 0000000..9c6c4ef --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.es-ES.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + No tiene acceso a esta página + + + Acceso denegado + + + Página sin titulo + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.fr-FR.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.fr-FR.resx new file mode 100644 index 0000000..76f03f2 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.fr-FR.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Vous ne pouvez pas accéder à cette page. + + + Accès interdit + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.hu-HU.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.hu-HU.resx new file mode 100644 index 0000000..8cd0e36 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.hu-HU.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nincs jogosultságod a laphoz. + + + Elérés megtagadva + + + Cím nélküli lap + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.it-IT.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.it-IT.resx new file mode 100644 index 0000000..fad31f0 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.it-IT.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Tu non hai accesso a questa pagina. + + + Accesso Negato + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.nb-NO.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.nb-NO.resx new file mode 100644 index 0000000..9598e98 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.nb-NO.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Du har ikke rettigheter til å se denne siden! + You do not have the permission to enter the page! + + + Nektet tilgang + Access Denied + + + Side uten tittel + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.nl-NL.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.nl-NL.resx new file mode 100644 index 0000000..d1e6c32 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.nl-NL.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + U heeft geen toegang tot deze pagina. + + + Toegang geweigerd. + + + Pagina zonder naam + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.pl-PL.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.pl-PL.resx new file mode 100644 index 0000000..24c6be1 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.pl-PL.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nie masz uprawnień umożliwiających oglądanie tej strony. + + + Dostęp zabroniony + + + Strona niezatytułowana + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.pt-BR.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.pt-BR.resx new file mode 100644 index 0000000..12b3332 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.pt-BR.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Você não tem acesso a esta página. + + + Acesso negado + + + Página sem título + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.resx new file mode 100644 index 0000000..6186727 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + You do not have access to this page. + + + Access Denied + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.ro-RO.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.ro-RO.resx new file mode 100644 index 0000000..b464466 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.ro-RO.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nu aveti acces la aceasta pagina. + + + Acces interzis + + + Pagina fara titlu + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.ru-RU.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.ru-RU.resx new file mode 100644 index 0000000..a20e3c7 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.ru-RU.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + У Вас нет прав доступа к этой странице. + + + Доступ Запрещён + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.sk-SK.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.sk-SK.resx new file mode 100644 index 0000000..8c88a01 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.sk-SK.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nemáte oprávnenie na zobrazenie tejto stránky! + You do not have the permission to enter the page! + + + Prístup zamietnutý + Access Denied + + + Nepomenovaná stránka + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..a1e4851 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.sr-Latn-CS.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nemate prava da vidite ovu stranicu! + You do not have the permission to enter the page! + + + Pristup je zabranjen + Access Denied + + + Stranica bez naslova + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.tr-TR.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.tr-TR.resx new file mode 100644 index 0000000..2920b41 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.tr-TR.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bu sayfaya erişim yetkiniz yok. + + + Erişim engellendi + + + Başlıksız Sayfa + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.uk-UA.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.uk-UA.resx new file mode 100644 index 0000000..f802057 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.uk-UA.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ви не маєте прав доступу до цієї сторінки. + + + Доступ Заборонено + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.zh-cn.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.zh-cn.resx new file mode 100644 index 0000000..65be167 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.zh-cn.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 您无权访问本页。 + + + 拒绝访问 + + + 无标题页面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AccessDenied.aspx.zh-tw.resx b/WebApplication/App_LocalResources/AccessDenied.aspx.zh-tw.resx new file mode 100644 index 0000000..d5658a4 --- /dev/null +++ b/WebApplication/App_LocalResources/AccessDenied.aspx.zh-tw.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 你無權訪問本頁! + + + 拒絕訪問 + + + 無標題頁面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.cs-CZ.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.cs-CZ.resx new file mode 100644 index 0000000..e705c51 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.cs-CZ.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + Odepřít + + + Udělit + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.da-DK.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.da-DK.resx new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.da-DK.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deny + + + Grant + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.de-DE.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.de-DE.resx new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.de-DE.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deny + + + Grant + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.es-ES.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.es-ES.resx new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.es-ES.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deny + + + Grant + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.fr-FR.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.fr-FR.resx new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.fr-FR.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deny + + + Grant + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.hu-HU.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.hu-HU.resx new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.hu-HU.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deny + + + Grant + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.it-IT.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.it-IT.resx new file mode 100644 index 0000000..0717610 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.it-IT.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Negato + + + Permesso + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.nb-NO.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.nb-NO.resx new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.nb-NO.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deny + + + Grant + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.nl-NL.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.nl-NL.resx new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.nl-NL.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deny + + + Grant + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.pl-PL.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.pl-PL.resx new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.pl-PL.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deny + + + Grant + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.pt-BR.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.pt-BR.resx new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.pt-BR.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deny + + + Grant + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.resx new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deny + + + Grant + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.ro-RO.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.ro-RO.resx new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.ro-RO.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deny + + + Grant + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.ru-RU.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.ru-RU.resx new file mode 100644 index 0000000..6f2b329 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.ru-RU.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Запрещено + + + Разрешено + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.sk-SK.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.sk-SK.resx new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.sk-SK.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deny + + + Grant + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.sr-Latn-CS.resx new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.sr-Latn-CS.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deny + + + Grant + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.tr-TR.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.tr-TR.resx new file mode 100644 index 0000000..1dbab4d --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.tr-TR.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Yasakla + + + İzin ver + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.uk-UA.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.uk-UA.resx new file mode 100644 index 0000000..e645a27 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.uk-UA.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Заборонено + + + Дозволено + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.zh-cn.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.zh-cn.resx new file mode 100644 index 0000000..401e738 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.zh-cn.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 拒绝 + + + 允许 + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AclActionsSelector.ascx.zh-tw.resx b/WebApplication/App_LocalResources/AclActionsSelector.ascx.zh-tw.resx new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/WebApplication/App_LocalResources/AclActionsSelector.ascx.zh-tw.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Deny + + + Grant + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.cs-CZ.resx b/WebApplication/App_LocalResources/Admin.master.cs-CZ.resx new file mode 100644 index 0000000..0530548 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.cs-CZ.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Administrace + + + Váš prohlížeč může mít problémy se zobrazením této stránky (Internet Explorer 7+ a Firefox 3+ je doporučen). + + + Hlavní stránka + + + Účty + + + Domov administrace + + + Kategorie + + + Konfigurace + + + Editace obsahu + + + Uživatelské skupiny + + + Systemový log + + + Jmenné prostory + + + Nav. cesty + + + Wiki stránky + + + Poskytovatelé + + + Úryvky/Šablony + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.da-DK.resx b/WebApplication/App_LocalResources/Admin.master.da-DK.resx new file mode 100644 index 0000000..ec58667 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.da-DK.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Administration + + + Your Browser might present problems with this Page (Internet Explorer 7+ and Firefox 3+ are suggested). + + + + + + Main Page + + + + + + Accounts + + + + + + Admin Home + + + + + + Categories + + + + + + Configuration + + + + + + Content Editing + + + + + + User Groups + + + + + + System Log + + + + + + Namespaces + + + + + + Nav. Paths + + + + + + Wiki Pages + + + + + + Providers + + + + + + Snippets/Templates + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.de-DE.resx b/WebApplication/App_LocalResources/Admin.master.de-DE.resx new file mode 100644 index 0000000..ec58667 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.de-DE.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Administration + + + Your Browser might present problems with this Page (Internet Explorer 7+ and Firefox 3+ are suggested). + + + + + + Main Page + + + + + + Accounts + + + + + + Admin Home + + + + + + Categories + + + + + + Configuration + + + + + + Content Editing + + + + + + User Groups + + + + + + System Log + + + + + + Namespaces + + + + + + Nav. Paths + + + + + + Wiki Pages + + + + + + Providers + + + + + + Snippets/Templates + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.es-ES.resx b/WebApplication/App_LocalResources/Admin.master.es-ES.resx new file mode 100644 index 0000000..ec58667 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.es-ES.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Administration + + + Your Browser might present problems with this Page (Internet Explorer 7+ and Firefox 3+ are suggested). + + + + + + Main Page + + + + + + Accounts + + + + + + Admin Home + + + + + + Categories + + + + + + Configuration + + + + + + Content Editing + + + + + + User Groups + + + + + + System Log + + + + + + Namespaces + + + + + + Nav. Paths + + + + + + Wiki Pages + + + + + + Providers + + + + + + Snippets/Templates + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.fr-FR.resx b/WebApplication/App_LocalResources/Admin.master.fr-FR.resx new file mode 100644 index 0000000..ec58667 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.fr-FR.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Administration + + + Your Browser might present problems with this Page (Internet Explorer 7+ and Firefox 3+ are suggested). + + + + + + Main Page + + + + + + Accounts + + + + + + Admin Home + + + + + + Categories + + + + + + Configuration + + + + + + Content Editing + + + + + + User Groups + + + + + + System Log + + + + + + Namespaces + + + + + + Nav. Paths + + + + + + Wiki Pages + + + + + + Providers + + + + + + Snippets/Templates + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.hu-HU.resx b/WebApplication/App_LocalResources/Admin.master.hu-HU.resx new file mode 100644 index 0000000..ec58667 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.hu-HU.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Administration + + + Your Browser might present problems with this Page (Internet Explorer 7+ and Firefox 3+ are suggested). + + + + + + Main Page + + + + + + Accounts + + + + + + Admin Home + + + + + + Categories + + + + + + Configuration + + + + + + Content Editing + + + + + + User Groups + + + + + + System Log + + + + + + Namespaces + + + + + + Nav. Paths + + + + + + Wiki Pages + + + + + + Providers + + + + + + Snippets/Templates + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.it-IT.resx b/WebApplication/App_LocalResources/Admin.master.it-IT.resx new file mode 100644 index 0000000..9ab6d9d --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.it-IT.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Amministrazione + + + Il tuo browser può presentare problemi con questa pagina (sono consigliati Internet Explorer 7+ e Firefox 3+). + + + + + + Pagina Principale + + + + + + Account + + + + + + Home Amministrazione + + + + + + Categorie + + + + + + Configurazione + + + + + + Modifica Contenuti + + + + + + Gruppi Utenti + + + + + + Log di Sistema + + + + + + Namespace + + + + + + Nav. Paths + + + + + + Pagine Wiki + + + + + + Provider + + + + + + Snippet/Template + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.nb-NO.resx b/WebApplication/App_LocalResources/Admin.master.nb-NO.resx new file mode 100644 index 0000000..ec58667 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.nb-NO.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Administration + + + Your Browser might present problems with this Page (Internet Explorer 7+ and Firefox 3+ are suggested). + + + + + + Main Page + + + + + + Accounts + + + + + + Admin Home + + + + + + Categories + + + + + + Configuration + + + + + + Content Editing + + + + + + User Groups + + + + + + System Log + + + + + + Namespaces + + + + + + Nav. Paths + + + + + + Wiki Pages + + + + + + Providers + + + + + + Snippets/Templates + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.nl-NL.resx b/WebApplication/App_LocalResources/Admin.master.nl-NL.resx new file mode 100644 index 0000000..ec58667 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.nl-NL.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Administration + + + Your Browser might present problems with this Page (Internet Explorer 7+ and Firefox 3+ are suggested). + + + + + + Main Page + + + + + + Accounts + + + + + + Admin Home + + + + + + Categories + + + + + + Configuration + + + + + + Content Editing + + + + + + User Groups + + + + + + System Log + + + + + + Namespaces + + + + + + Nav. Paths + + + + + + Wiki Pages + + + + + + Providers + + + + + + Snippets/Templates + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.pl-PL.resx b/WebApplication/App_LocalResources/Admin.master.pl-PL.resx new file mode 100644 index 0000000..ec58667 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.pl-PL.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Administration + + + Your Browser might present problems with this Page (Internet Explorer 7+ and Firefox 3+ are suggested). + + + + + + Main Page + + + + + + Accounts + + + + + + Admin Home + + + + + + Categories + + + + + + Configuration + + + + + + Content Editing + + + + + + User Groups + + + + + + System Log + + + + + + Namespaces + + + + + + Nav. Paths + + + + + + Wiki Pages + + + + + + Providers + + + + + + Snippets/Templates + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.pt-BR.resx b/WebApplication/App_LocalResources/Admin.master.pt-BR.resx new file mode 100644 index 0000000..ec58667 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.pt-BR.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Administration + + + Your Browser might present problems with this Page (Internet Explorer 7+ and Firefox 3+ are suggested). + + + + + + Main Page + + + + + + Accounts + + + + + + Admin Home + + + + + + Categories + + + + + + Configuration + + + + + + Content Editing + + + + + + User Groups + + + + + + System Log + + + + + + Namespaces + + + + + + Nav. Paths + + + + + + Wiki Pages + + + + + + Providers + + + + + + Snippets/Templates + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.resx b/WebApplication/App_LocalResources/Admin.master.resx new file mode 100644 index 0000000..ec58667 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Administration + + + Your Browser might present problems with this Page (Internet Explorer 7+ and Firefox 3+ are suggested). + + + + + + Main Page + + + + + + Accounts + + + + + + Admin Home + + + + + + Categories + + + + + + Configuration + + + + + + Content Editing + + + + + + User Groups + + + + + + System Log + + + + + + Namespaces + + + + + + Nav. Paths + + + + + + Wiki Pages + + + + + + Providers + + + + + + Snippets/Templates + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.ro-RO.resx b/WebApplication/App_LocalResources/Admin.master.ro-RO.resx new file mode 100644 index 0000000..ec58667 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.ro-RO.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Administration + + + Your Browser might present problems with this Page (Internet Explorer 7+ and Firefox 3+ are suggested). + + + + + + Main Page + + + + + + Accounts + + + + + + Admin Home + + + + + + Categories + + + + + + Configuration + + + + + + Content Editing + + + + + + User Groups + + + + + + System Log + + + + + + Namespaces + + + + + + Nav. Paths + + + + + + Wiki Pages + + + + + + Providers + + + + + + Snippets/Templates + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.ru-RU.resx b/WebApplication/App_LocalResources/Admin.master.ru-RU.resx new file mode 100644 index 0000000..4fd0d77 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.ru-RU.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Администрирование + + + Используя, установленный у Вас браузер, Вы столкнётесь с проблемами отображения страниц (установите Internet Explorer 7+ или Firefox 3+). + + + + + + Main Page + + + + + + Учётные записи + + + + + + Начало администрирования + + + + + + Категории + + + + + + Настройки + + + + + + Операции с контентом + + + + + + Группы пользователей + + + + + + Системный лог + + + + + + Пространства Имён + + + + + + Навиг.Пути + + + + + + Страницы Wiki + + + + + + Провайдеры + + + + + + Сниппеты/Шаблоны + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.sk-SK.resx b/WebApplication/App_LocalResources/Admin.master.sk-SK.resx new file mode 100644 index 0000000..ec58667 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.sk-SK.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Administration + + + Your Browser might present problems with this Page (Internet Explorer 7+ and Firefox 3+ are suggested). + + + + + + Main Page + + + + + + Accounts + + + + + + Admin Home + + + + + + Categories + + + + + + Configuration + + + + + + Content Editing + + + + + + User Groups + + + + + + System Log + + + + + + Namespaces + + + + + + Nav. Paths + + + + + + Wiki Pages + + + + + + Providers + + + + + + Snippets/Templates + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.sr-Latn-CS.resx b/WebApplication/App_LocalResources/Admin.master.sr-Latn-CS.resx new file mode 100644 index 0000000..ec58667 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.sr-Latn-CS.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Administration + + + Your Browser might present problems with this Page (Internet Explorer 7+ and Firefox 3+ are suggested). + + + + + + Main Page + + + + + + Accounts + + + + + + Admin Home + + + + + + Categories + + + + + + Configuration + + + + + + Content Editing + + + + + + User Groups + + + + + + System Log + + + + + + Namespaces + + + + + + Nav. Paths + + + + + + Wiki Pages + + + + + + Providers + + + + + + Snippets/Templates + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.tr-TR.resx b/WebApplication/App_LocalResources/Admin.master.tr-TR.resx new file mode 100644 index 0000000..6dc1a99 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.tr-TR.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Yönetim + + + Tarayıcınız bu sayfa ile ilgili problem çıkarabilir. IE 7 ve Firefox 3 üstü kullanmanız tavsiye edilir. + + + + + + Giriş sayfası + + + + + + Hesaplar + + + + + + Yönetim Girişi + + + + + + Kategoriler + + + + + + Yapılandırma + + + + + + İçerik Düzenleme + + + + + + Kullanıcı Gurupları + + + + + + Sistem Log'u + + + + + + Ad Alanaları + + + + + + Dolaşım Patikaları + + + + + + Wiki Sayfaları + + + + + + Sağlayıcılar + + + + + + Minik Kod ve Şablonlar + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.uk-UA.resx b/WebApplication/App_LocalResources/Admin.master.uk-UA.resx new file mode 100644 index 0000000..65a0203 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.uk-UA.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Адміністрування + + + Використовуючи свій браузер, Ви зіштовхнетеся з проблемами відображення сторінок (встановіть Internet Explorer 7+ чи Firefox 3+). + + + + + + Main Page + + + + + + Облікові записи + + + + + + Початок Адміністрування + + + + + + Категорії + + + + + + Функції + + + + + + Операції з контентом + + + + + + Групи користувачів + + + + + + Системний лог + + + + + + Простори Імен + + + + + + Нав.Шляхи + + + + + + Сторінки Wiki + + + + + + Несучі + + + + + + Сніпети/Шаблони + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.zh-cn.resx b/WebApplication/App_LocalResources/Admin.master.zh-cn.resx new file mode 100644 index 0000000..39dd6ef --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.zh-cn.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 管理 + + + 您的浏览器可能无法正常显示本页(建议使用 Internet Explorer 7+ 或 Firefox 3+)。 + + + + + + 首页 + + + + + + 帐户 + + + + + + 管理首页 + + + + + + 分类 + + + + + + 配置 + + + + + + 内容编辑 + + + + + + 用户组 + + + + + + 系统日志 + + + + + + 命名空间 + + + + + + 导航路径 + + + + + + 维基页面 + + + + + + 提供者 + + + + + + 片段/模板 + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Admin.master.zh-tw.resx b/WebApplication/App_LocalResources/Admin.master.zh-tw.resx new file mode 100644 index 0000000..ec58667 --- /dev/null +++ b/WebApplication/App_LocalResources/Admin.master.zh-tw.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Administration + + + Your Browser might present problems with this Page (Internet Explorer 7+ and Firefox 3+ are suggested). + + + + + + Main Page + + + + + + Accounts + + + + + + Admin Home + + + + + + Categories + + + + + + Configuration + + + + + + Content Editing + + + + + + User Groups + + + + + + System Log + + + + + + Namespaces + + + + + + Nav. Paths + + + + + + Wiki Pages + + + + + + Providers + + + + + + Snippets/Templates + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.cs-CZ.resx new file mode 100644 index 0000000..83effe9 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.cs-CZ.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Zpět + + + Zpět na seznam kategorií + + + Bulk Manage + + + Spravovat kategorie propojené k více stránkám najednou. + + + Uložit propojení + + + 1. Vyběr stránek + + + 2. Výběr kategorií + + + 3. Uložení propojení + + + Přidá vybrané kategorie k vybraným stránkám, zachová existující mapování kategorií + + + Pro vybrané stránky nahradí mapování existujících kategorií za vybrané kategorie (nevybraní kategorie způsobí že kategoie nebude zařazena do kategorií) + + + Zpět + + + Zpět na seznam kategorií + + + Smazat kategorii + + + Smaže kategorii + + + Spojit + + + Spojit kategorie + + + Vytvořit + + + Přejmenovat + + + Přejmenuje kategorii + + + Vybrat + + + Vybere tuto kategori + + + Vybrat + + + Vybere tuto kategori + + + Neplatný název kategorie + + + Neplatný název kategorie + + + Kategorie + + + Smazat kategorii + + + Operace s kategoriemi + + + Spojit kategorii + + + Spojit do + + + Název + + + Jmenný prostor + + + Nová Kategorie + + + Nové jméno + + + Stránky + + + Poskytovatel + + + Přejmenovat kategorii + + + Administrace + + + Jméno kategorie je vyžadováno + + + Nový název je vyžadován + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.da-DK.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.da-DK.resx new file mode 100644 index 0000000..023724d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.da-DK.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add selected categories to selected pages, preserving existing categories mapping + + + + + + For selected pages, replace existing categories mapping with selected categories (selecting no categories causes the pages to be uncategorized) + + + + + + Back + + + Back to the Category list + + + Bulk Manage + + + Manage categories binding for many pages at once + + + Save Binding + + + + + + 1. Select Pages + + + 2. Select Categories + + + 3. Save Binding + + + Back + + + Back to the Category list + + + Delete Category + + + Delete this Category + + + Merge + + + Merges the Category + + + Create + + + + + + Rename + + + Rename the Category + + + Select + + + Select this Category + + + Select + + + Select this Category + + + Invalid Category Name + + + + + + + + + Invalid Category Name + + + + + + + + + Categories + + + + + + Delete Category + + + + + + + + + Category Operations + + + Merge Category + + + Merge into + + + + + + + + + Name + + + Namespace + + + New Category + + + + + + + + + New Name + + + Pages + + + Provider + + + Rename Category + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Category Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.de-DE.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.de-DE.resx new file mode 100644 index 0000000..023724d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.de-DE.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add selected categories to selected pages, preserving existing categories mapping + + + + + + For selected pages, replace existing categories mapping with selected categories (selecting no categories causes the pages to be uncategorized) + + + + + + Back + + + Back to the Category list + + + Bulk Manage + + + Manage categories binding for many pages at once + + + Save Binding + + + + + + 1. Select Pages + + + 2. Select Categories + + + 3. Save Binding + + + Back + + + Back to the Category list + + + Delete Category + + + Delete this Category + + + Merge + + + Merges the Category + + + Create + + + + + + Rename + + + Rename the Category + + + Select + + + Select this Category + + + Select + + + Select this Category + + + Invalid Category Name + + + + + + + + + Invalid Category Name + + + + + + + + + Categories + + + + + + Delete Category + + + + + + + + + Category Operations + + + Merge Category + + + Merge into + + + + + + + + + Name + + + Namespace + + + New Category + + + + + + + + + New Name + + + Pages + + + Provider + + + Rename Category + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Category Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.es-ES.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.es-ES.resx new file mode 100644 index 0000000..023724d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.es-ES.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add selected categories to selected pages, preserving existing categories mapping + + + + + + For selected pages, replace existing categories mapping with selected categories (selecting no categories causes the pages to be uncategorized) + + + + + + Back + + + Back to the Category list + + + Bulk Manage + + + Manage categories binding for many pages at once + + + Save Binding + + + + + + 1. Select Pages + + + 2. Select Categories + + + 3. Save Binding + + + Back + + + Back to the Category list + + + Delete Category + + + Delete this Category + + + Merge + + + Merges the Category + + + Create + + + + + + Rename + + + Rename the Category + + + Select + + + Select this Category + + + Select + + + Select this Category + + + Invalid Category Name + + + + + + + + + Invalid Category Name + + + + + + + + + Categories + + + + + + Delete Category + + + + + + + + + Category Operations + + + Merge Category + + + Merge into + + + + + + + + + Name + + + Namespace + + + New Category + + + + + + + + + New Name + + + Pages + + + Provider + + + Rename Category + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Category Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.fr-FR.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.fr-FR.resx new file mode 100644 index 0000000..023724d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.fr-FR.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add selected categories to selected pages, preserving existing categories mapping + + + + + + For selected pages, replace existing categories mapping with selected categories (selecting no categories causes the pages to be uncategorized) + + + + + + Back + + + Back to the Category list + + + Bulk Manage + + + Manage categories binding for many pages at once + + + Save Binding + + + + + + 1. Select Pages + + + 2. Select Categories + + + 3. Save Binding + + + Back + + + Back to the Category list + + + Delete Category + + + Delete this Category + + + Merge + + + Merges the Category + + + Create + + + + + + Rename + + + Rename the Category + + + Select + + + Select this Category + + + Select + + + Select this Category + + + Invalid Category Name + + + + + + + + + Invalid Category Name + + + + + + + + + Categories + + + + + + Delete Category + + + + + + + + + Category Operations + + + Merge Category + + + Merge into + + + + + + + + + Name + + + Namespace + + + New Category + + + + + + + + + New Name + + + Pages + + + Provider + + + Rename Category + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Category Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.hu-HU.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.hu-HU.resx new file mode 100644 index 0000000..023724d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.hu-HU.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add selected categories to selected pages, preserving existing categories mapping + + + + + + For selected pages, replace existing categories mapping with selected categories (selecting no categories causes the pages to be uncategorized) + + + + + + Back + + + Back to the Category list + + + Bulk Manage + + + Manage categories binding for many pages at once + + + Save Binding + + + + + + 1. Select Pages + + + 2. Select Categories + + + 3. Save Binding + + + Back + + + Back to the Category list + + + Delete Category + + + Delete this Category + + + Merge + + + Merges the Category + + + Create + + + + + + Rename + + + Rename the Category + + + Select + + + Select this Category + + + Select + + + Select this Category + + + Invalid Category Name + + + + + + + + + Invalid Category Name + + + + + + + + + Categories + + + + + + Delete Category + + + + + + + + + Category Operations + + + Merge Category + + + Merge into + + + + + + + + + Name + + + Namespace + + + New Category + + + + + + + + + New Name + + + Pages + + + Provider + + + Rename Category + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Category Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.it-IT.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.it-IT.resx new file mode 100644 index 0000000..f3868eb --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.it-IT.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Aggiungi le categorie selezionate alle pagine selezionate, mantenendo gli abbinamenti correnti + + + + + + Per le pagine selezionate, sostituisci gli abbinamenti esistenti con le gategorie selezionate (selezionando zero categorie imposta le pagine come <i>non categorizzate</i>) + + + + + + Indietro + + + Indietro alla lista categorie + + + Gestione in blocco + + + Gestisci l'abbinamento pagine/categorie in blocco + + + Salva abbinamento + + + + + + 1. Seleziona pagine + + + 2. Seleziona categorie + + + 3. Salva abbinamento + + + Indietro + + + Indietro alla lista categorie + + + Elimina categoria + + + Elimina questa categoriea + + + Fondi + + + Fondi questa categoriea + + + Crea + + + + + + Rinomina + + + Rinomina questa categoria + + + Seleziona + + + Seleziona questa categoriea + + + Seleziona + + + Seleziona questa categoria + + + Nome categoria non valido + + + + + + + + + Nome categoria non valido + + + + + + + + + Categorie + + + + + + Elimina categoria + + + + + + + + + Operazioni categoria + + + Fondi categoria + + + Fondi in + + + + + + + + + Nome + + + Namespace + + + Nuova categoria + + + + + + + + + Nuovo nome + + + Pagine + + + Provider + + + Rinomina categoria + + + + + + + + + + + + + + + Amministrazione + + + + + + + + + + + + + + + Il nome è richieso + + + + + + + + + Il nuovo nome è richiesto + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.nb-NO.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.nb-NO.resx new file mode 100644 index 0000000..023724d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.nb-NO.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add selected categories to selected pages, preserving existing categories mapping + + + + + + For selected pages, replace existing categories mapping with selected categories (selecting no categories causes the pages to be uncategorized) + + + + + + Back + + + Back to the Category list + + + Bulk Manage + + + Manage categories binding for many pages at once + + + Save Binding + + + + + + 1. Select Pages + + + 2. Select Categories + + + 3. Save Binding + + + Back + + + Back to the Category list + + + Delete Category + + + Delete this Category + + + Merge + + + Merges the Category + + + Create + + + + + + Rename + + + Rename the Category + + + Select + + + Select this Category + + + Select + + + Select this Category + + + Invalid Category Name + + + + + + + + + Invalid Category Name + + + + + + + + + Categories + + + + + + Delete Category + + + + + + + + + Category Operations + + + Merge Category + + + Merge into + + + + + + + + + Name + + + Namespace + + + New Category + + + + + + + + + New Name + + + Pages + + + Provider + + + Rename Category + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Category Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.nl-NL.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.nl-NL.resx new file mode 100644 index 0000000..023724d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.nl-NL.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add selected categories to selected pages, preserving existing categories mapping + + + + + + For selected pages, replace existing categories mapping with selected categories (selecting no categories causes the pages to be uncategorized) + + + + + + Back + + + Back to the Category list + + + Bulk Manage + + + Manage categories binding for many pages at once + + + Save Binding + + + + + + 1. Select Pages + + + 2. Select Categories + + + 3. Save Binding + + + Back + + + Back to the Category list + + + Delete Category + + + Delete this Category + + + Merge + + + Merges the Category + + + Create + + + + + + Rename + + + Rename the Category + + + Select + + + Select this Category + + + Select + + + Select this Category + + + Invalid Category Name + + + + + + + + + Invalid Category Name + + + + + + + + + Categories + + + + + + Delete Category + + + + + + + + + Category Operations + + + Merge Category + + + Merge into + + + + + + + + + Name + + + Namespace + + + New Category + + + + + + + + + New Name + + + Pages + + + Provider + + + Rename Category + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Category Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.pl-PL.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.pl-PL.resx new file mode 100644 index 0000000..023724d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.pl-PL.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add selected categories to selected pages, preserving existing categories mapping + + + + + + For selected pages, replace existing categories mapping with selected categories (selecting no categories causes the pages to be uncategorized) + + + + + + Back + + + Back to the Category list + + + Bulk Manage + + + Manage categories binding for many pages at once + + + Save Binding + + + + + + 1. Select Pages + + + 2. Select Categories + + + 3. Save Binding + + + Back + + + Back to the Category list + + + Delete Category + + + Delete this Category + + + Merge + + + Merges the Category + + + Create + + + + + + Rename + + + Rename the Category + + + Select + + + Select this Category + + + Select + + + Select this Category + + + Invalid Category Name + + + + + + + + + Invalid Category Name + + + + + + + + + Categories + + + + + + Delete Category + + + + + + + + + Category Operations + + + Merge Category + + + Merge into + + + + + + + + + Name + + + Namespace + + + New Category + + + + + + + + + New Name + + + Pages + + + Provider + + + Rename Category + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Category Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.pt-BR.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.pt-BR.resx new file mode 100644 index 0000000..023724d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.pt-BR.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add selected categories to selected pages, preserving existing categories mapping + + + + + + For selected pages, replace existing categories mapping with selected categories (selecting no categories causes the pages to be uncategorized) + + + + + + Back + + + Back to the Category list + + + Bulk Manage + + + Manage categories binding for many pages at once + + + Save Binding + + + + + + 1. Select Pages + + + 2. Select Categories + + + 3. Save Binding + + + Back + + + Back to the Category list + + + Delete Category + + + Delete this Category + + + Merge + + + Merges the Category + + + Create + + + + + + Rename + + + Rename the Category + + + Select + + + Select this Category + + + Select + + + Select this Category + + + Invalid Category Name + + + + + + + + + Invalid Category Name + + + + + + + + + Categories + + + + + + Delete Category + + + + + + + + + Category Operations + + + Merge Category + + + Merge into + + + + + + + + + Name + + + Namespace + + + New Category + + + + + + + + + New Name + + + Pages + + + Provider + + + Rename Category + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Category Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.resx new file mode 100644 index 0000000..dbd74b0 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Category list + + + Delete Category + + + Delete this Category + + + Merge + + + Merges the Category + + + Create + + + + + + Rename + + + Rename the Category + + + Select + + + Select this Category + + + Select + + + Select this Category + + + Invalid Category Name + + + + + + + + + Invalid Category Name + + + + + + + + + Categories + + + + + + Delete Category + + + + + + + + + Category Operations + + + Merge Category + + + Merge into + + + + + + + + + Name + + + Namespace + + + New Category + + + + + + + + + New Name + + + Pages + + + Provider + + + Rename Category + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Category Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + + Back + + + Back to the Category list + + + Bulk Manage + + + Manage categories binding for many pages at once + + + Save Binding + + + + + + 1. Select Pages + + + 2. Select Categories + + + 3. Save Binding + + + Add selected categories to selected pages, preserving existing categories mapping + + + + + + For selected pages, replace existing categories mapping with selected categories (selecting no categories causes the pages to be uncategorized) + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.ro-RO.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.ro-RO.resx new file mode 100644 index 0000000..023724d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.ro-RO.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add selected categories to selected pages, preserving existing categories mapping + + + + + + For selected pages, replace existing categories mapping with selected categories (selecting no categories causes the pages to be uncategorized) + + + + + + Back + + + Back to the Category list + + + Bulk Manage + + + Manage categories binding for many pages at once + + + Save Binding + + + + + + 1. Select Pages + + + 2. Select Categories + + + 3. Save Binding + + + Back + + + Back to the Category list + + + Delete Category + + + Delete this Category + + + Merge + + + Merges the Category + + + Create + + + + + + Rename + + + Rename the Category + + + Select + + + Select this Category + + + Select + + + Select this Category + + + Invalid Category Name + + + + + + + + + Invalid Category Name + + + + + + + + + Categories + + + + + + Delete Category + + + + + + + + + Category Operations + + + Merge Category + + + Merge into + + + + + + + + + Name + + + Namespace + + + New Category + + + + + + + + + New Name + + + Pages + + + Provider + + + Rename Category + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Category Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.ru-RU.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.ru-RU.resx new file mode 100644 index 0000000..f7ad756 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.ru-RU.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Добавить выбранные категории отмеченным страницам, оставив и существующие категории + + + + + + Заменить на выбранных страницах существующие категории, указанными (если категории не выбраны, страницы будут не категоризированы) + + + + + + Назад + + + Назад к списку Категорий + + + Массовые Изменения + + + Управление изменениями категорий для множества страниц одним махом + + + Сохранить изменения + + + + + + 1. Выбрать Страницы + + + 2. Выбрать Категории + + + 3. Сохранить Изменения + + + Назад + + + Назад к списку категорий + + + Удалить категорию + + + Удалить эту категорию + + + Слияние + + + Объединить эту категорию + + + Создать + + + + + + Переименовать + + + Переименовать эту категорию + + + Отметить + + + Отметить эту категорию + + + Отметить + + + Отметить эту категорию + + + Неправильное название категории + + + + + + + + + Неправильное название категории + + + + + + + + + Категории + + + + + + Удалить Категорию + + + + + + + + + Действия с категориями + + + Объединить категории + + + Объединить в + + + + + + + + + Название + + + Пространство Имён + + + Новая категория + + + + + + + + + Новое название + + + Страницы + + + Провайдер + + + Переименовать категорию + + + + + + + + + + + + + + + Администрирование + + + + + + + + + + + + + + + Название категории обязательно + + + + + + + + + Новое название обязательно + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.sk-SK.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.sk-SK.resx new file mode 100644 index 0000000..023724d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.sk-SK.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add selected categories to selected pages, preserving existing categories mapping + + + + + + For selected pages, replace existing categories mapping with selected categories (selecting no categories causes the pages to be uncategorized) + + + + + + Back + + + Back to the Category list + + + Bulk Manage + + + Manage categories binding for many pages at once + + + Save Binding + + + + + + 1. Select Pages + + + 2. Select Categories + + + 3. Save Binding + + + Back + + + Back to the Category list + + + Delete Category + + + Delete this Category + + + Merge + + + Merges the Category + + + Create + + + + + + Rename + + + Rename the Category + + + Select + + + Select this Category + + + Select + + + Select this Category + + + Invalid Category Name + + + + + + + + + Invalid Category Name + + + + + + + + + Categories + + + + + + Delete Category + + + + + + + + + Category Operations + + + Merge Category + + + Merge into + + + + + + + + + Name + + + Namespace + + + New Category + + + + + + + + + New Name + + + Pages + + + Provider + + + Rename Category + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Category Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..023724d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.sr-Latn-CS.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add selected categories to selected pages, preserving existing categories mapping + + + + + + For selected pages, replace existing categories mapping with selected categories (selecting no categories causes the pages to be uncategorized) + + + + + + Back + + + Back to the Category list + + + Bulk Manage + + + Manage categories binding for many pages at once + + + Save Binding + + + + + + 1. Select Pages + + + 2. Select Categories + + + 3. Save Binding + + + Back + + + Back to the Category list + + + Delete Category + + + Delete this Category + + + Merge + + + Merges the Category + + + Create + + + + + + Rename + + + Rename the Category + + + Select + + + Select this Category + + + Select + + + Select this Category + + + Invalid Category Name + + + + + + + + + Invalid Category Name + + + + + + + + + Categories + + + + + + Delete Category + + + + + + + + + Category Operations + + + Merge Category + + + Merge into + + + + + + + + + Name + + + Namespace + + + New Category + + + + + + + + + New Name + + + Pages + + + Provider + + + Rename Category + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Category Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.tr-TR.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.tr-TR.resx new file mode 100644 index 0000000..aeba740 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.tr-TR.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Category list + + + Bulk Manage + + + Manage categories binding for many pages at once + + + Save Binding + + + + + + 1. Select Pages + + + 2. Select Categories + + + 3. Save Binding + + + Add selected categories to selected pages, preserving existing categories mapping + + + + + + For selected pages, replace existing categories mapping with selected categories (selecting no categories causes the pages to be uncategorized) + + + + + + Geri + + + Kategori listesine dön + + + Kategoriyi Sil + + + Bu Kategoriyi Sil + + + Birleştir + + + Kategorileri birleştir + + + Oluştur + + + + + + Yeniden adlandır + + + Kategoriyi yeniden adlandır + + + Seç + + + Kategoriyi seç + + + Seç + + + Bu kategoriyi seç + + + Geçersiz kategori adı + + + + + + + + + Geçersiz kategori adı + + + + + + + + + Kategoriler + + + + + + Kategori sil + + + + + + + + + Kategori işlemleri + + + Kategori birleştir + + + Şununla birleştir + + + + + + + + + Ad + + + Ad alanı + + + Yeni Kategori + + + + + + + + + Yeni ad + + + Sayfalar + + + Sağlayıcı + + + Kategoriyi yeniden adlandrı + + + + + + + + + + + + + + + Yönetim + + + + + + + + + + + + + + + Kategori adı gereklidir + + + + + + + + + Yeni ad gereklidir + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.uk-UA.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.uk-UA.resx new file mode 100644 index 0000000..1fe0bac --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.uk-UA.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Додати обрані категорії обраним сторінкам, залишивши й існуючи категорії + + + + + + Для обраних сторінок замінити існуючи категорії обраними зараз (якщо категорії не обрані, буде без категорій) + + + + + + Повернутися + + + Повернутися до переліку категорій + + + Масовані зміни + + + Керування задіяними категоріями для багатьох сторінок одразу + + + Зберегти + + + + + + 1. Оберіть Сторінки + + + 2. Оберіть Категорії + + + 3. Збережіть Змінене + + + Повернутися + + + Повернутися до переліку категорій + + + Видалити категорію + + + Видалити цю категорію + + + Об'єднати + + + Об'єднати цю категорію + + + Створити + + + + + + Змінити назву + + + Змінити назву цієї категорії + + + Обрати + + + Обрати цю категорію + + + Обрати + + + Обрати цю категорію + + + Невірна назва категорії + + + + + + + + + Невірна назва категорії + + + + + + + + + Категорії + + + + + + Видалити категорію + + + + + + + + + Дії над категоріями + + + Об'єднати категорію + + + Об'єднати з + + + + + + + + + Назва + + + Простір Імен + + + Нова Категорія + + + + + + + + + Нова Назва + + + Сторінки + + + Несуча + + + Перейменувати категорію + + + + + + + + + + + + + + + Адміністрування + + + + + + + + + + + + + + + Назва категорії обов'язкова + + + + + + + + + Нова назва обов'язкова + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.zh-cn.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.zh-cn.resx new file mode 100644 index 0000000..df59f5e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.zh-cn.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 添加选中的分类到选定的页面,保留现有分类映射 + + + + + + 对于选中页面,将现有分类映射替换为选中分类(不选分类使页面变为未分类) + + + + + + 返回 + + + 返回至分类列表 + + + 批量管理 + + + 同时为许多页面管理分类绑定 + + + 保存绑定 + + + + + + 1. 选择页面 + + + 2. 选择分类 + + + 3. 保存绑定 + + + 返回 + + + 返回至分类列表 + + + 删除分类 + + + 删除此分类 + + + 合并 + + + 合并分类 + + + 创建 + + + + + + 重命名 + + + 重命名分类 + + + 选择 + + + 选择此分类 + + + 选择 + + + 选择此分类 + + + 分类名称无效 + + + + + + + + + 分类名称无效 + + + + + + + + + 分类 + + + + + + 删除分类 + + + + + + + + + 分类操作 + + + 合并分类 + + + 合并至 + + + + + + + + + 名称 + + + 命名空间 + + + 新分类 + + + + + + + + + 新名称 + + + 页面 + + + 提供者 + + + 重命名分类 + + + + + + + + + + + + + + + 管理 + + + + + + + + + + + + + + + 需要分类名称 + + + + + + + + + 需要新名称 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminCategories.aspx.zh-tw.resx b/WebApplication/App_LocalResources/AdminCategories.aspx.zh-tw.resx new file mode 100644 index 0000000..023724d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminCategories.aspx.zh-tw.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add selected categories to selected pages, preserving existing categories mapping + + + + + + For selected pages, replace existing categories mapping with selected categories (selecting no categories causes the pages to be uncategorized) + + + + + + Back + + + Back to the Category list + + + Bulk Manage + + + Manage categories binding for many pages at once + + + Save Binding + + + + + + 1. Select Pages + + + 2. Select Categories + + + 3. Save Binding + + + Back + + + Back to the Category list + + + Delete Category + + + Delete this Category + + + Merge + + + Merges the Category + + + Create + + + + + + Rename + + + Rename the Category + + + Select + + + Select this Category + + + Select + + + Select this Category + + + Invalid Category Name + + + + + + + + + Invalid Category Name + + + + + + + + + Categories + + + + + + Delete Category + + + + + + + + + Category Operations + + + Merge Category + + + Merge into + + + + + + + + + Name + + + Namespace + + + New Category + + + + + + + + + New Name + + + Pages + + + Provider + + + Rename Category + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Category Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.cs-CZ.resx new file mode 100644 index 0000000..530b236 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.cs-CZ.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IP Filter pro povolení editace (oddělujte čárkou, použijte '*' pro zástupné znaky, příklad: 192.168.1.*) + + + Process single line breaks in content (experimental) + + + Default automaticky generovat názvy stránek v editoru + + + Koncovky souborů povolené pro upload (oddělte čárkou, '*' pro libovolný typ) + + + Regulární výrazy pro validaci uživatelského jména + + + Regulární výrazy pro validaci hesla + + + Mód poskytování RSS + + + Full Text + + + FullText + + + Souhrn + + + Souhrn + + + Zakázáno (RSS kompletně zakázáno) + + + Zakázáno + + + Počítat všechny stažení souborů + + + CountAll + + + Počítat stažení pro konkrétní rozšíření (oddělte čárkami) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + autodetekce + + + Uložit konfiguraci + + + Povolit tag SCRIPT ve WikiMarkup + + + Povolit registraci uživatelů + + + Kompletně zakázat cache + + + Zobrazit Gravatary pro uživatelské konta + + + Povolit automatickou kontrolu aktualizace (system a poskytovatelé) + + + Povolit drobečkovou navigaci + + + Povolit CAPTCHA pro veškeré veřejné funkce + + + Povolit editaci dvojklikem + + + Povolit HTTP kompresi + + + Povolit sekci informací na stránce (Změněno, Kategorizováno, atd.) + + + Povolit panel nástrojů (Editovat tuto stránku, Historie, Administrace) + + + Povolit editaci sekcí stránky + + + Povolit SSL + + + Povolit možnost 'Zobrazit zdrojový kód stránky' + + + Povolit kompresi ViewState + + + Bránit současné editaci stránky + + + Použít standardně vizuální (WYSIWYG) editor + + + Neplatný formát Datumu/Času + + + Neplatný email + + + Je požadováno Uživatelské jméno + + + Je požadováno heslo + + + Mód aktivace účtu + + + Pokročilá konfigurace + + + Cache cut size + + + Velikost Cache + + + Mód moderování změn stránky + + + Konfigurace + + + Kontaktní email + + + Konfigurace obsahu + + + Formát data/času + + + Standardní skupina administrátorů + + + Standardní skupina anonymních uživatelů + + + Defaultní jazyk + + + Defaultní časová zóna + + + Standardní skupina uživatelů + + + Emailové adresy pro upozornění v případě chyb (oddělte čárkou) + + + Obecná konfigurace + + + zálohy pro každou stránku (nechte prázdné bez limitu) + + + Zachovat nejvýše + + + Úroveň logování + + + Maximální velikost souboru pro upload + + + Maximální velikost logu + + + položky v <code>{RecentChanges}</code> tag + + + Zobrazit nejvíce + + + Jmenné prostory + + + Jmenné prostory + + + stránky + + + stránky + + + Hlavní stránka kořenového jmenného prostoru + + + Téma kořenového jmenného prostoru + + + Nastavení zabezpečení + + + viz + + + viz + + + Vyberte... + + + Email odesílatele + + + SMTP jméno a heslo + + + SMTP adresa server u a port + + + použito pro komunikaci emailem + + + Wiki titulek + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Střední Evropa + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Uživatelé musí aktivovat jejich konto přes email + + + EMAIL + + + Administrátoři musí aktivovat konto + + + ADMIN + + + Konta jsou automaticky aktivní + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + Administrace + + + Všechny zprávy + + + Zakázat log + + + Jen chyby + + + Zakázat moderování + + + Není použit systém moderování: stránky mohou být editovány pouze uživatelem s právy pro editaci + + + Vyžaduje práva pro editaci stránky + + + Stránky mohou být editovány uživatelem, který má práva editace ale nemá práva pro spravování, a změny jsou drženy v moderování + + + Vyžaduje práva pro zobrazení stránky + + + Stránky mohou být editovány uživatelem, který má práva zobrazení, ale nemá práva pro editaci, a změny jsou drženy v moderování + + + Varování a chyby + + + Neplatný email + + + Neplatná Wiki URL + + + Neplatný email + + + Neplatná adresa SMTP serveru + + + Neplatná Wiki URL + + + Je vyžadována velikost Cache Cut + + + Je vyžadována velikost Cache + + + Je vyžadována kontaktní email + + + Je vyžadován formát datumu a času + + + Je vyžadována URL Wiki + + + Je vyžadována maximální velikost souboru + + + Je vyžadována maximální velikost logu + + + Je vyžadováno číslo + + + Je vyžadován email odesílatele + + + Je vyžadová SMTP server + + + Je vyžadová titulek Wiki + + + Neplatná velikosti Cache Cut (min. 5, max. 50000) + + + Neplatná velikost (min. 10, max. 100000) + + + Číslo musí být mezi 0 a 1000 + + + Neplatná velikost souboru (min. 256, max. 102400) + + + Neplatná velikost logu (min. 16, max. 10240) + + + Číslo musí být mezi 0 a 50 + + + Neplatný port (min. 1, max. 65535) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.da-DK.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.da-DK.resx new file mode 100644 index 0000000..2791ea1 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.da-DK.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP/Host Filter for allowed editing (seperate with commas, Use '*' for wild cards) + + + Process single line breaks in content (experimental) + + + + + + Auto-Generate Page Names in Editor by default + + + File extensions allowed for upload (separate with commas, '*' for any type) + + + Regular Expression for validating username + + + Regular Expression for validating password + + + RSS Feeds Serving Mode + + + Full Text + + + FullText + + + Summary + + + Summary + + + Disabled (RSS feeds completely disabled) + + + Disabled + + + + + + Count all file downloads + + + CountAll + + + Count downloads for specified extensions (separate with commas) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + + + + + + + autodetect + + + + + + Save Configuration + + + + + + Allow SCRIPT tags in WikiMarkup + + + + + + Allow users to register + + + + + + Completely disable cache + + + + + + Display Gravatars for user accounts + + + + + + Enable automatic update checks (system and providers) + + + + + + Enable Breadcrumbs Trail + + + + + + Enable CAPTCHA control for all public functionalities + + + + + + Enable Double-Click editing + + + + + + Enable HTTP compression + + + + + + Enable Page Information section (Modified on, Categorized as, etc.) + + + + + + Enable Page Toolbar (Edit this Page, History, Admin) + + + + + + Enable editing of pages' sections + + + + + + Enable SSL + + + + + + Enable 'View Page Code' feature + + + + + + Enable ViewState compression + + + + + + Prevent concurrent page editing + + + + + + Use visual (WYSIWYG) editor as default + + + + + + Invalid Date/Time Format + + + + + + + + + Invalid email address + + + + + + + + + Username is required + + + + + + + + + Password is required + + + + + + + + + Account activation mode + + + Advanced Configuration + + + Cache cut size + + + Cache Size + + + Page change moderation mode + + + Configuration + + + Contact email + + + Content Configuration + + + Date/time format + + + + + + Default Administrators Group + + + Default Anonymous Users Group + + + Default language + + + Default time zone + + + Default Users Group + + + Email addresses to notify in case of errors (separate with commas) + + + General Configuration + + + backups for each page (leave empty for no limit) + + + Keep at most + + + Logging level + + + Max file size allowed for upload + + + Max log size + + + items in <code>{RecentChanges}</code> tags + + + Display at most + + + Namespaces + + + Namespaces + + + pages + + + pages + + + + + + + + + Root namespace main page + + + Root namespace theme + + + Security Configuration + + + see also + + + see also + + + Select... + + + Sender email + + + SMTP username and password + + + SMTP server address and port + + + + + + used for email communications + + + Wiki title + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Users must activate their account via Email + + + EMAIL + + + Administrators must activate accounts + + + ADMIN + + + Accounts are active by default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + All Messages + + + + + + Disable Log + + + + + + Errors Only + + + + + + Disable Moderation + + + No moderation system is used: pages can be edited only by users who have editing permissions + + + Require Page Editing permissions + + + Pages can be edited by users who have editing permissions but not management permissions, and the changes are held in moderation + + + Require Page Viewing permissions + + + Pages can be edited by users who have viewing permissions but not editing permissions, and the changes are help in moderation + + + Warnings and Errors + + + + + + Invalid email address + + + + + + + + + Invalid Wiki URL + + + + + + + + + Invalid email address + + + + + + + + + Invalid SMTP Server address + + + + + + + + + Invalid Wiki Title + + + + + + + + + Cache Cut Size is required + + + + + + + + + Cache Size is required + + + + + + + + + Contact Email is required + + + + + + + + + Date/Time Format is required + + + + + + + + + Wiki URL is required + + + + + + + + + Max File Size is required + + + + + + + + + Max Log Size is required + + + + + + + + + Number is required + + + + + + + + + Sender Email is required + + + + + + + + + SMTP Server is required + + + + + + + + + Wiki Title is required + + + + + + + + + Invalid Cache Cut Size (min. 5, max. 50000) + + + + + + + + + Invalid Size (min. 10, max. 100000) + + + + + + + + + Number must be between 0 and 1000 + + + + + + + + + Invalid File Size (min. 256, max. 102400) + + + + + + + + + Invalid Log Size (min. 16, max. 10240) + + + + + + + + + Number must be between 0 and 50 + + + + + + + + + Invalid Port (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.de-DE.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.de-DE.resx new file mode 100644 index 0000000..2791ea1 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.de-DE.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP/Host Filter for allowed editing (seperate with commas, Use '*' for wild cards) + + + Process single line breaks in content (experimental) + + + + + + Auto-Generate Page Names in Editor by default + + + File extensions allowed for upload (separate with commas, '*' for any type) + + + Regular Expression for validating username + + + Regular Expression for validating password + + + RSS Feeds Serving Mode + + + Full Text + + + FullText + + + Summary + + + Summary + + + Disabled (RSS feeds completely disabled) + + + Disabled + + + + + + Count all file downloads + + + CountAll + + + Count downloads for specified extensions (separate with commas) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + + + + + + + autodetect + + + + + + Save Configuration + + + + + + Allow SCRIPT tags in WikiMarkup + + + + + + Allow users to register + + + + + + Completely disable cache + + + + + + Display Gravatars for user accounts + + + + + + Enable automatic update checks (system and providers) + + + + + + Enable Breadcrumbs Trail + + + + + + Enable CAPTCHA control for all public functionalities + + + + + + Enable Double-Click editing + + + + + + Enable HTTP compression + + + + + + Enable Page Information section (Modified on, Categorized as, etc.) + + + + + + Enable Page Toolbar (Edit this Page, History, Admin) + + + + + + Enable editing of pages' sections + + + + + + Enable SSL + + + + + + Enable 'View Page Code' feature + + + + + + Enable ViewState compression + + + + + + Prevent concurrent page editing + + + + + + Use visual (WYSIWYG) editor as default + + + + + + Invalid Date/Time Format + + + + + + + + + Invalid email address + + + + + + + + + Username is required + + + + + + + + + Password is required + + + + + + + + + Account activation mode + + + Advanced Configuration + + + Cache cut size + + + Cache Size + + + Page change moderation mode + + + Configuration + + + Contact email + + + Content Configuration + + + Date/time format + + + + + + Default Administrators Group + + + Default Anonymous Users Group + + + Default language + + + Default time zone + + + Default Users Group + + + Email addresses to notify in case of errors (separate with commas) + + + General Configuration + + + backups for each page (leave empty for no limit) + + + Keep at most + + + Logging level + + + Max file size allowed for upload + + + Max log size + + + items in <code>{RecentChanges}</code> tags + + + Display at most + + + Namespaces + + + Namespaces + + + pages + + + pages + + + + + + + + + Root namespace main page + + + Root namespace theme + + + Security Configuration + + + see also + + + see also + + + Select... + + + Sender email + + + SMTP username and password + + + SMTP server address and port + + + + + + used for email communications + + + Wiki title + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Users must activate their account via Email + + + EMAIL + + + Administrators must activate accounts + + + ADMIN + + + Accounts are active by default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + All Messages + + + + + + Disable Log + + + + + + Errors Only + + + + + + Disable Moderation + + + No moderation system is used: pages can be edited only by users who have editing permissions + + + Require Page Editing permissions + + + Pages can be edited by users who have editing permissions but not management permissions, and the changes are held in moderation + + + Require Page Viewing permissions + + + Pages can be edited by users who have viewing permissions but not editing permissions, and the changes are help in moderation + + + Warnings and Errors + + + + + + Invalid email address + + + + + + + + + Invalid Wiki URL + + + + + + + + + Invalid email address + + + + + + + + + Invalid SMTP Server address + + + + + + + + + Invalid Wiki Title + + + + + + + + + Cache Cut Size is required + + + + + + + + + Cache Size is required + + + + + + + + + Contact Email is required + + + + + + + + + Date/Time Format is required + + + + + + + + + Wiki URL is required + + + + + + + + + Max File Size is required + + + + + + + + + Max Log Size is required + + + + + + + + + Number is required + + + + + + + + + Sender Email is required + + + + + + + + + SMTP Server is required + + + + + + + + + Wiki Title is required + + + + + + + + + Invalid Cache Cut Size (min. 5, max. 50000) + + + + + + + + + Invalid Size (min. 10, max. 100000) + + + + + + + + + Number must be between 0 and 1000 + + + + + + + + + Invalid File Size (min. 256, max. 102400) + + + + + + + + + Invalid Log Size (min. 16, max. 10240) + + + + + + + + + Number must be between 0 and 50 + + + + + + + + + Invalid Port (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.es-ES.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.es-ES.resx new file mode 100644 index 0000000..2791ea1 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.es-ES.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP/Host Filter for allowed editing (seperate with commas, Use '*' for wild cards) + + + Process single line breaks in content (experimental) + + + + + + Auto-Generate Page Names in Editor by default + + + File extensions allowed for upload (separate with commas, '*' for any type) + + + Regular Expression for validating username + + + Regular Expression for validating password + + + RSS Feeds Serving Mode + + + Full Text + + + FullText + + + Summary + + + Summary + + + Disabled (RSS feeds completely disabled) + + + Disabled + + + + + + Count all file downloads + + + CountAll + + + Count downloads for specified extensions (separate with commas) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + + + + + + + autodetect + + + + + + Save Configuration + + + + + + Allow SCRIPT tags in WikiMarkup + + + + + + Allow users to register + + + + + + Completely disable cache + + + + + + Display Gravatars for user accounts + + + + + + Enable automatic update checks (system and providers) + + + + + + Enable Breadcrumbs Trail + + + + + + Enable CAPTCHA control for all public functionalities + + + + + + Enable Double-Click editing + + + + + + Enable HTTP compression + + + + + + Enable Page Information section (Modified on, Categorized as, etc.) + + + + + + Enable Page Toolbar (Edit this Page, History, Admin) + + + + + + Enable editing of pages' sections + + + + + + Enable SSL + + + + + + Enable 'View Page Code' feature + + + + + + Enable ViewState compression + + + + + + Prevent concurrent page editing + + + + + + Use visual (WYSIWYG) editor as default + + + + + + Invalid Date/Time Format + + + + + + + + + Invalid email address + + + + + + + + + Username is required + + + + + + + + + Password is required + + + + + + + + + Account activation mode + + + Advanced Configuration + + + Cache cut size + + + Cache Size + + + Page change moderation mode + + + Configuration + + + Contact email + + + Content Configuration + + + Date/time format + + + + + + Default Administrators Group + + + Default Anonymous Users Group + + + Default language + + + Default time zone + + + Default Users Group + + + Email addresses to notify in case of errors (separate with commas) + + + General Configuration + + + backups for each page (leave empty for no limit) + + + Keep at most + + + Logging level + + + Max file size allowed for upload + + + Max log size + + + items in <code>{RecentChanges}</code> tags + + + Display at most + + + Namespaces + + + Namespaces + + + pages + + + pages + + + + + + + + + Root namespace main page + + + Root namespace theme + + + Security Configuration + + + see also + + + see also + + + Select... + + + Sender email + + + SMTP username and password + + + SMTP server address and port + + + + + + used for email communications + + + Wiki title + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Users must activate their account via Email + + + EMAIL + + + Administrators must activate accounts + + + ADMIN + + + Accounts are active by default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + All Messages + + + + + + Disable Log + + + + + + Errors Only + + + + + + Disable Moderation + + + No moderation system is used: pages can be edited only by users who have editing permissions + + + Require Page Editing permissions + + + Pages can be edited by users who have editing permissions but not management permissions, and the changes are held in moderation + + + Require Page Viewing permissions + + + Pages can be edited by users who have viewing permissions but not editing permissions, and the changes are help in moderation + + + Warnings and Errors + + + + + + Invalid email address + + + + + + + + + Invalid Wiki URL + + + + + + + + + Invalid email address + + + + + + + + + Invalid SMTP Server address + + + + + + + + + Invalid Wiki Title + + + + + + + + + Cache Cut Size is required + + + + + + + + + Cache Size is required + + + + + + + + + Contact Email is required + + + + + + + + + Date/Time Format is required + + + + + + + + + Wiki URL is required + + + + + + + + + Max File Size is required + + + + + + + + + Max Log Size is required + + + + + + + + + Number is required + + + + + + + + + Sender Email is required + + + + + + + + + SMTP Server is required + + + + + + + + + Wiki Title is required + + + + + + + + + Invalid Cache Cut Size (min. 5, max. 50000) + + + + + + + + + Invalid Size (min. 10, max. 100000) + + + + + + + + + Number must be between 0 and 1000 + + + + + + + + + Invalid File Size (min. 256, max. 102400) + + + + + + + + + Invalid Log Size (min. 16, max. 10240) + + + + + + + + + Number must be between 0 and 50 + + + + + + + + + Invalid Port (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.fr-FR.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.fr-FR.resx new file mode 100644 index 0000000..2791ea1 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.fr-FR.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP/Host Filter for allowed editing (seperate with commas, Use '*' for wild cards) + + + Process single line breaks in content (experimental) + + + + + + Auto-Generate Page Names in Editor by default + + + File extensions allowed for upload (separate with commas, '*' for any type) + + + Regular Expression for validating username + + + Regular Expression for validating password + + + RSS Feeds Serving Mode + + + Full Text + + + FullText + + + Summary + + + Summary + + + Disabled (RSS feeds completely disabled) + + + Disabled + + + + + + Count all file downloads + + + CountAll + + + Count downloads for specified extensions (separate with commas) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + + + + + + + autodetect + + + + + + Save Configuration + + + + + + Allow SCRIPT tags in WikiMarkup + + + + + + Allow users to register + + + + + + Completely disable cache + + + + + + Display Gravatars for user accounts + + + + + + Enable automatic update checks (system and providers) + + + + + + Enable Breadcrumbs Trail + + + + + + Enable CAPTCHA control for all public functionalities + + + + + + Enable Double-Click editing + + + + + + Enable HTTP compression + + + + + + Enable Page Information section (Modified on, Categorized as, etc.) + + + + + + Enable Page Toolbar (Edit this Page, History, Admin) + + + + + + Enable editing of pages' sections + + + + + + Enable SSL + + + + + + Enable 'View Page Code' feature + + + + + + Enable ViewState compression + + + + + + Prevent concurrent page editing + + + + + + Use visual (WYSIWYG) editor as default + + + + + + Invalid Date/Time Format + + + + + + + + + Invalid email address + + + + + + + + + Username is required + + + + + + + + + Password is required + + + + + + + + + Account activation mode + + + Advanced Configuration + + + Cache cut size + + + Cache Size + + + Page change moderation mode + + + Configuration + + + Contact email + + + Content Configuration + + + Date/time format + + + + + + Default Administrators Group + + + Default Anonymous Users Group + + + Default language + + + Default time zone + + + Default Users Group + + + Email addresses to notify in case of errors (separate with commas) + + + General Configuration + + + backups for each page (leave empty for no limit) + + + Keep at most + + + Logging level + + + Max file size allowed for upload + + + Max log size + + + items in <code>{RecentChanges}</code> tags + + + Display at most + + + Namespaces + + + Namespaces + + + pages + + + pages + + + + + + + + + Root namespace main page + + + Root namespace theme + + + Security Configuration + + + see also + + + see also + + + Select... + + + Sender email + + + SMTP username and password + + + SMTP server address and port + + + + + + used for email communications + + + Wiki title + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Users must activate their account via Email + + + EMAIL + + + Administrators must activate accounts + + + ADMIN + + + Accounts are active by default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + All Messages + + + + + + Disable Log + + + + + + Errors Only + + + + + + Disable Moderation + + + No moderation system is used: pages can be edited only by users who have editing permissions + + + Require Page Editing permissions + + + Pages can be edited by users who have editing permissions but not management permissions, and the changes are held in moderation + + + Require Page Viewing permissions + + + Pages can be edited by users who have viewing permissions but not editing permissions, and the changes are help in moderation + + + Warnings and Errors + + + + + + Invalid email address + + + + + + + + + Invalid Wiki URL + + + + + + + + + Invalid email address + + + + + + + + + Invalid SMTP Server address + + + + + + + + + Invalid Wiki Title + + + + + + + + + Cache Cut Size is required + + + + + + + + + Cache Size is required + + + + + + + + + Contact Email is required + + + + + + + + + Date/Time Format is required + + + + + + + + + Wiki URL is required + + + + + + + + + Max File Size is required + + + + + + + + + Max Log Size is required + + + + + + + + + Number is required + + + + + + + + + Sender Email is required + + + + + + + + + SMTP Server is required + + + + + + + + + Wiki Title is required + + + + + + + + + Invalid Cache Cut Size (min. 5, max. 50000) + + + + + + + + + Invalid Size (min. 10, max. 100000) + + + + + + + + + Number must be between 0 and 1000 + + + + + + + + + Invalid File Size (min. 256, max. 102400) + + + + + + + + + Invalid Log Size (min. 16, max. 10240) + + + + + + + + + Number must be between 0 and 50 + + + + + + + + + Invalid Port (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.hu-HU.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.hu-HU.resx new file mode 100644 index 0000000..2791ea1 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.hu-HU.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP/Host Filter for allowed editing (seperate with commas, Use '*' for wild cards) + + + Process single line breaks in content (experimental) + + + + + + Auto-Generate Page Names in Editor by default + + + File extensions allowed for upload (separate with commas, '*' for any type) + + + Regular Expression for validating username + + + Regular Expression for validating password + + + RSS Feeds Serving Mode + + + Full Text + + + FullText + + + Summary + + + Summary + + + Disabled (RSS feeds completely disabled) + + + Disabled + + + + + + Count all file downloads + + + CountAll + + + Count downloads for specified extensions (separate with commas) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + + + + + + + autodetect + + + + + + Save Configuration + + + + + + Allow SCRIPT tags in WikiMarkup + + + + + + Allow users to register + + + + + + Completely disable cache + + + + + + Display Gravatars for user accounts + + + + + + Enable automatic update checks (system and providers) + + + + + + Enable Breadcrumbs Trail + + + + + + Enable CAPTCHA control for all public functionalities + + + + + + Enable Double-Click editing + + + + + + Enable HTTP compression + + + + + + Enable Page Information section (Modified on, Categorized as, etc.) + + + + + + Enable Page Toolbar (Edit this Page, History, Admin) + + + + + + Enable editing of pages' sections + + + + + + Enable SSL + + + + + + Enable 'View Page Code' feature + + + + + + Enable ViewState compression + + + + + + Prevent concurrent page editing + + + + + + Use visual (WYSIWYG) editor as default + + + + + + Invalid Date/Time Format + + + + + + + + + Invalid email address + + + + + + + + + Username is required + + + + + + + + + Password is required + + + + + + + + + Account activation mode + + + Advanced Configuration + + + Cache cut size + + + Cache Size + + + Page change moderation mode + + + Configuration + + + Contact email + + + Content Configuration + + + Date/time format + + + + + + Default Administrators Group + + + Default Anonymous Users Group + + + Default language + + + Default time zone + + + Default Users Group + + + Email addresses to notify in case of errors (separate with commas) + + + General Configuration + + + backups for each page (leave empty for no limit) + + + Keep at most + + + Logging level + + + Max file size allowed for upload + + + Max log size + + + items in <code>{RecentChanges}</code> tags + + + Display at most + + + Namespaces + + + Namespaces + + + pages + + + pages + + + + + + + + + Root namespace main page + + + Root namespace theme + + + Security Configuration + + + see also + + + see also + + + Select... + + + Sender email + + + SMTP username and password + + + SMTP server address and port + + + + + + used for email communications + + + Wiki title + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Users must activate their account via Email + + + EMAIL + + + Administrators must activate accounts + + + ADMIN + + + Accounts are active by default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + All Messages + + + + + + Disable Log + + + + + + Errors Only + + + + + + Disable Moderation + + + No moderation system is used: pages can be edited only by users who have editing permissions + + + Require Page Editing permissions + + + Pages can be edited by users who have editing permissions but not management permissions, and the changes are held in moderation + + + Require Page Viewing permissions + + + Pages can be edited by users who have viewing permissions but not editing permissions, and the changes are help in moderation + + + Warnings and Errors + + + + + + Invalid email address + + + + + + + + + Invalid Wiki URL + + + + + + + + + Invalid email address + + + + + + + + + Invalid SMTP Server address + + + + + + + + + Invalid Wiki Title + + + + + + + + + Cache Cut Size is required + + + + + + + + + Cache Size is required + + + + + + + + + Contact Email is required + + + + + + + + + Date/Time Format is required + + + + + + + + + Wiki URL is required + + + + + + + + + Max File Size is required + + + + + + + + + Max Log Size is required + + + + + + + + + Number is required + + + + + + + + + Sender Email is required + + + + + + + + + SMTP Server is required + + + + + + + + + Wiki Title is required + + + + + + + + + Invalid Cache Cut Size (min. 5, max. 50000) + + + + + + + + + Invalid Size (min. 10, max. 100000) + + + + + + + + + Number must be between 0 and 1000 + + + + + + + + + Invalid File Size (min. 256, max. 102400) + + + + + + + + + Invalid Log Size (min. 16, max. 10240) + + + + + + + + + Number must be between 0 and 50 + + + + + + + + + Invalid Port (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.it-IT.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.it-IT.resx new file mode 100644 index 0000000..50b23ee --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.it-IT.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Filtro IP per modifiche (seperati da virgole; '*' per wildcard) + + + Processa ritorni a capo singoli (sperimentale) + + + + + + Auto-genera nomi pagine nell'editor + + + Estensioni file valide per upload (separate da virgole, '*' per qualsiasi tipo) + + + Regular Expression per validare username + + + Regular Expression per validare password + + + Modalità feed RSS + + + Testo completo + + + FullText + + + Sommario + + + Summary + + + Disabilitato (feed RSS completamente disabilitato) + + + Disabled + + + + + + Conta tutti i download + + + CountAll + + + Conta i download per le estensioni specificate (separate da virgole) + + + CountSpecifiedExtensions + + + Conta tutti i download tranne (estensioni separate da virgole) + + + ExcludeSpecifiedExtensions + + + + + + + + + auto-rileva + + + + + + Salva Configurazione + + + + + + Permetti tag SCRIPT in WikiMarkup + + + + + + Permetti agli utenti di registrarsi + + + + + + Disattiva completamente la cache + + + + + + Visualizza Gravatar degli utenti + + + + + + Attiva controllo aggiornamenti (sistema e providers) + + + + + + Attiva breadcrumbs trail + + + + + + Attiva controllo CAPTCHA per tutte le funzioni pubbliche + + + + + + Attiva modifica con doppio-click + + + + + + Attiva compressione HTTP + + + + + + Attiva sezione Info Pagina (Modificata il, Categorizzata come, ecc.) + + + + + + Attiva barra strumenti pagina (Modifica, Storia, Admin) + + + + + + Attiva modifica sezioni pagina + + + + + + Attiva SSL + + + + + + Attiva funzione 'Visualizza Codice' + + + + + + Attiva compressione ViewState + + + + + + Impedisci modifica pagina concorrente + + + + + + Usa l'editor visuale (WYSIWYG) come default + + + + + + Formato data/ora non valido + + + + + + + + + Indirizzo email non valido + + + + + + + + + Lo username è richiesto + + + + + + + + + La password è richiesta + + + + + + + + + Modalità attivazione account + + + Configurazione avanzata + + + Dimensione storno cache + + + Dimensione cache + + + Modalità moderazione modifiche pagine + + + Configurazione + + + Email di contatto + + + Configurazione contenuti + + + Formato data/ora + + + + + + Gruppo Amministratori predefinito + + + Gruppo Utenti Anonimi predefinito + + + Lingua predefinita + + + Fuso orario predefinito + + + Gruppo Utenti predefinito + + + Indirizzi email da notificare in caso di errori (separati da virgole) + + + Configurazione generale + + + backup per ogni pagina (vuoto = nessun limite) + + + Conserva al più + + + Livello log + + + Dimensione massima file in upload + + + Dimensione massima log + + + elementi in tag <code>{RecentChanges}</code> + + + Visualizza al più + + + Namespace + + + Namespace + + + pagine + + + pagine + + + + + + + + + Pagina principale namespace root + + + Tema namespace root + + + Configurazione di sicurezza + + + vedi anche + + + vedi anche + + + Seleziona... + + + Email mittente + + + Username e password SMTP + + + Indirizzo e porta server SMTP + + + + + + utilizzata per comunicazioni email + + + Titolo Wiki + + + URL Wiki + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Gli utenti devono attivare l'account via email + + + EMAIL + + + Gli amministratori devono attivare l'account manualmente + + + ADMIN + + + Gli account sono attivi di default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Amministrazione + + + Tutti i messaggi + + + + + + Disattiva il log + + + + + + Solo gli errori + + + + + + Disattiva la moderazione + + + Nessun sistema di moderazione: le pagine possono essere modificate solo da chi ha i permessi per farlo + + + Richiedi permessi per modifiare la pagina + + + Le pagine possono essere modificate da chi ne ha i permessi, ma le modifiche sono poste in moderazione + + + Richiedi permessi per visualizzare la pagina + + + Le pagine possono essere modificate da chi ha i permessi per visualizzarle, ma le modifiche sono poste in moderazione + + + Warning ed errori + + + + + + Indirizzo email non valido + + + + + + + + + URL non valido + + + + + + + + + Indirizzo email non valido + + + + + + + + + Indirizzo SMTP non valido + + + + + + + + + Titolo non valido + + + + + + + + + Dimensione storno cache richiesta + + + + + + + + + Dimensione cache richiesta + + + + + + + + + Email di contatto richiesta + + + + + + + + + Formato data/ora richiesto + + + + + + + + + URL richiesto + + + + + + + + + Dimensione file massima richiesta + + + + + + + + + Dimensione log massima richiesta + + + + + + + + + Numero richiesto + + + + + + + + + Email mittente richiesta + + + + + + + + + Server SMTP richiesto + + + + + + + + + Titolo richiesto + + + + + + + + + Dimensione storno cache non valida (min. 5, max. 50000) + + + + + + + + + Dimensione cache non valida (min. 10, max. 100000) + + + + + + + + + Il numero deve essere compreso tra 0 e 1000 + + + + + + + + + Dimensione file non valida (min. 256, max. 102400) + + + + + + + + + Dimensione log non valida (min. 16, max. 10240) + + + + + + + + + Il numero deve essere compreso tra 0 e 50 + + + + + + + + + Porta non valida (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.nb-NO.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.nb-NO.resx new file mode 100644 index 0000000..2791ea1 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.nb-NO.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP/Host Filter for allowed editing (seperate with commas, Use '*' for wild cards) + + + Process single line breaks in content (experimental) + + + + + + Auto-Generate Page Names in Editor by default + + + File extensions allowed for upload (separate with commas, '*' for any type) + + + Regular Expression for validating username + + + Regular Expression for validating password + + + RSS Feeds Serving Mode + + + Full Text + + + FullText + + + Summary + + + Summary + + + Disabled (RSS feeds completely disabled) + + + Disabled + + + + + + Count all file downloads + + + CountAll + + + Count downloads for specified extensions (separate with commas) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + + + + + + + autodetect + + + + + + Save Configuration + + + + + + Allow SCRIPT tags in WikiMarkup + + + + + + Allow users to register + + + + + + Completely disable cache + + + + + + Display Gravatars for user accounts + + + + + + Enable automatic update checks (system and providers) + + + + + + Enable Breadcrumbs Trail + + + + + + Enable CAPTCHA control for all public functionalities + + + + + + Enable Double-Click editing + + + + + + Enable HTTP compression + + + + + + Enable Page Information section (Modified on, Categorized as, etc.) + + + + + + Enable Page Toolbar (Edit this Page, History, Admin) + + + + + + Enable editing of pages' sections + + + + + + Enable SSL + + + + + + Enable 'View Page Code' feature + + + + + + Enable ViewState compression + + + + + + Prevent concurrent page editing + + + + + + Use visual (WYSIWYG) editor as default + + + + + + Invalid Date/Time Format + + + + + + + + + Invalid email address + + + + + + + + + Username is required + + + + + + + + + Password is required + + + + + + + + + Account activation mode + + + Advanced Configuration + + + Cache cut size + + + Cache Size + + + Page change moderation mode + + + Configuration + + + Contact email + + + Content Configuration + + + Date/time format + + + + + + Default Administrators Group + + + Default Anonymous Users Group + + + Default language + + + Default time zone + + + Default Users Group + + + Email addresses to notify in case of errors (separate with commas) + + + General Configuration + + + backups for each page (leave empty for no limit) + + + Keep at most + + + Logging level + + + Max file size allowed for upload + + + Max log size + + + items in <code>{RecentChanges}</code> tags + + + Display at most + + + Namespaces + + + Namespaces + + + pages + + + pages + + + + + + + + + Root namespace main page + + + Root namespace theme + + + Security Configuration + + + see also + + + see also + + + Select... + + + Sender email + + + SMTP username and password + + + SMTP server address and port + + + + + + used for email communications + + + Wiki title + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Users must activate their account via Email + + + EMAIL + + + Administrators must activate accounts + + + ADMIN + + + Accounts are active by default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + All Messages + + + + + + Disable Log + + + + + + Errors Only + + + + + + Disable Moderation + + + No moderation system is used: pages can be edited only by users who have editing permissions + + + Require Page Editing permissions + + + Pages can be edited by users who have editing permissions but not management permissions, and the changes are held in moderation + + + Require Page Viewing permissions + + + Pages can be edited by users who have viewing permissions but not editing permissions, and the changes are help in moderation + + + Warnings and Errors + + + + + + Invalid email address + + + + + + + + + Invalid Wiki URL + + + + + + + + + Invalid email address + + + + + + + + + Invalid SMTP Server address + + + + + + + + + Invalid Wiki Title + + + + + + + + + Cache Cut Size is required + + + + + + + + + Cache Size is required + + + + + + + + + Contact Email is required + + + + + + + + + Date/Time Format is required + + + + + + + + + Wiki URL is required + + + + + + + + + Max File Size is required + + + + + + + + + Max Log Size is required + + + + + + + + + Number is required + + + + + + + + + Sender Email is required + + + + + + + + + SMTP Server is required + + + + + + + + + Wiki Title is required + + + + + + + + + Invalid Cache Cut Size (min. 5, max. 50000) + + + + + + + + + Invalid Size (min. 10, max. 100000) + + + + + + + + + Number must be between 0 and 1000 + + + + + + + + + Invalid File Size (min. 256, max. 102400) + + + + + + + + + Invalid Log Size (min. 16, max. 10240) + + + + + + + + + Number must be between 0 and 50 + + + + + + + + + Invalid Port (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.nl-NL.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.nl-NL.resx new file mode 100644 index 0000000..2791ea1 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.nl-NL.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP/Host Filter for allowed editing (seperate with commas, Use '*' for wild cards) + + + Process single line breaks in content (experimental) + + + + + + Auto-Generate Page Names in Editor by default + + + File extensions allowed for upload (separate with commas, '*' for any type) + + + Regular Expression for validating username + + + Regular Expression for validating password + + + RSS Feeds Serving Mode + + + Full Text + + + FullText + + + Summary + + + Summary + + + Disabled (RSS feeds completely disabled) + + + Disabled + + + + + + Count all file downloads + + + CountAll + + + Count downloads for specified extensions (separate with commas) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + + + + + + + autodetect + + + + + + Save Configuration + + + + + + Allow SCRIPT tags in WikiMarkup + + + + + + Allow users to register + + + + + + Completely disable cache + + + + + + Display Gravatars for user accounts + + + + + + Enable automatic update checks (system and providers) + + + + + + Enable Breadcrumbs Trail + + + + + + Enable CAPTCHA control for all public functionalities + + + + + + Enable Double-Click editing + + + + + + Enable HTTP compression + + + + + + Enable Page Information section (Modified on, Categorized as, etc.) + + + + + + Enable Page Toolbar (Edit this Page, History, Admin) + + + + + + Enable editing of pages' sections + + + + + + Enable SSL + + + + + + Enable 'View Page Code' feature + + + + + + Enable ViewState compression + + + + + + Prevent concurrent page editing + + + + + + Use visual (WYSIWYG) editor as default + + + + + + Invalid Date/Time Format + + + + + + + + + Invalid email address + + + + + + + + + Username is required + + + + + + + + + Password is required + + + + + + + + + Account activation mode + + + Advanced Configuration + + + Cache cut size + + + Cache Size + + + Page change moderation mode + + + Configuration + + + Contact email + + + Content Configuration + + + Date/time format + + + + + + Default Administrators Group + + + Default Anonymous Users Group + + + Default language + + + Default time zone + + + Default Users Group + + + Email addresses to notify in case of errors (separate with commas) + + + General Configuration + + + backups for each page (leave empty for no limit) + + + Keep at most + + + Logging level + + + Max file size allowed for upload + + + Max log size + + + items in <code>{RecentChanges}</code> tags + + + Display at most + + + Namespaces + + + Namespaces + + + pages + + + pages + + + + + + + + + Root namespace main page + + + Root namespace theme + + + Security Configuration + + + see also + + + see also + + + Select... + + + Sender email + + + SMTP username and password + + + SMTP server address and port + + + + + + used for email communications + + + Wiki title + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Users must activate their account via Email + + + EMAIL + + + Administrators must activate accounts + + + ADMIN + + + Accounts are active by default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + All Messages + + + + + + Disable Log + + + + + + Errors Only + + + + + + Disable Moderation + + + No moderation system is used: pages can be edited only by users who have editing permissions + + + Require Page Editing permissions + + + Pages can be edited by users who have editing permissions but not management permissions, and the changes are held in moderation + + + Require Page Viewing permissions + + + Pages can be edited by users who have viewing permissions but not editing permissions, and the changes are help in moderation + + + Warnings and Errors + + + + + + Invalid email address + + + + + + + + + Invalid Wiki URL + + + + + + + + + Invalid email address + + + + + + + + + Invalid SMTP Server address + + + + + + + + + Invalid Wiki Title + + + + + + + + + Cache Cut Size is required + + + + + + + + + Cache Size is required + + + + + + + + + Contact Email is required + + + + + + + + + Date/Time Format is required + + + + + + + + + Wiki URL is required + + + + + + + + + Max File Size is required + + + + + + + + + Max Log Size is required + + + + + + + + + Number is required + + + + + + + + + Sender Email is required + + + + + + + + + SMTP Server is required + + + + + + + + + Wiki Title is required + + + + + + + + + Invalid Cache Cut Size (min. 5, max. 50000) + + + + + + + + + Invalid Size (min. 10, max. 100000) + + + + + + + + + Number must be between 0 and 1000 + + + + + + + + + Invalid File Size (min. 256, max. 102400) + + + + + + + + + Invalid Log Size (min. 16, max. 10240) + + + + + + + + + Number must be between 0 and 50 + + + + + + + + + Invalid Port (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.pl-PL.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.pl-PL.resx new file mode 100644 index 0000000..c1be1ea --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.pl-PL.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP/Host Filter for allowed editing (seperate with commas, Use '*' for wild cards) + + + Process single line breaks in content (experimental) + + + + + + Auto-Generate Page Names in Editor by default + + + File extensions allowed for upload (separate with commas, '*' for any type) + + + Regular Expression for validating username + + + Regular Expression for validating password + + + autodetect + + + + + + Save Configuration + + + + + + Allow SCRIPT tags in WikiMarkup + + + + + + Allow users to register + + + + + + Completely disable cache + + + + + + Display Gravatars for user accounts + + + + + + Enable automatic update checks (system and providers) + + + + + + Enable Breadcrumbs Trail + + + + + + Enable CAPTCHA control for all public functionalities + + + + + + Enable Double-Click editing + + + + + + Enable HTTP compression + + + + + + Enable Page Information section (Modified on, Categorized as, etc.) + + + + + + Enable Page Toolbar (Edit this Page, History, Admin) + + + + + + Enable editing of pages' sections + + + + + + Enable SSL + + + + + + Enable 'View Page Code' feature + + + + + + Enable ViewState compression + + + + + + Prevent concurrent page editing + + + + + + Use visual (WYSIWYG) editor as default + + + + + + Invalid Date/Time Format + + + + + + + + + Invalid email address + + + + + + + + + Username is required + + + + + + + + + Password is required + + + + + + + + + Account activation mode + + + Advanced Configuration + + + Cache cut size + + + Cache Size + + + Page change moderation mode + + + Configuration + + + Contact email + + + Content Configuration + + + Date/time format + + + + + + Default Administrators Group + + + Default Anonymous Users Group + + + Default language + + + Default time zone + + + Default Users Group + + + Email addresses to notify in case of errors (separate with commas) + + + General Configuration + + + backups for each page (leave empty for no limit) + + + Keep at most + + + Logging level + + + Max file size allowed for upload + + + Max log size + + + items in <code>{RecentChanges}</code> tags + + + Display at most + + + Namespaces + + + Namespaces + + + pages + + + pages + + + + + + + + + Root namespace main page + + + Root namespace theme + + + Security Configuration + + + see also + + + see also + + + Select... + + + Sender email + + + SMTP username and password + + + SMTP server address and port + + + + + + used for email communications + + + Wiki title + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Users must activate their account via Email + + + EMAIL + + + Administrators must activate accounts + + + ADMIN + + + Accounts are active by default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + All Messages + + + + + + Disable Log + + + + + + Errors Only + + + + + + Disable Moderation + + + No moderation system is used: pages can be edited only by users who have editing permissions + + + Require Page Editing permissions + + + Pages can be edited by users who have editing permissions but not management permissions, and the changes are held in moderation + + + Require Page Viewing permissions + + + Pages can be edited by users who have viewing permissions but not editing permissions, and the changes are help in moderation + + + Warnings and Errors + + + + + + Invalid email address + + + + + + + + + Invalid Wiki URL + + + + + + + + + Invalid email address + + + + + + + + + Invalid SMTP Server address + + + + + + + + + Invalid Wiki Title + + + + + + + + + Cache Cut Size is required + + + + + + + + + Cache Size is required + + + + + + + + + Contact Email is required + + + + + + + + + Date/Time Format is required + + + + + + + + + Wiki URL is required + + + + + + + + + Max File Size is required + + + + + + + + + Max Log Size is required + + + + + + + + + Number is required + + + + + + + + + Sender Email is required + + + + + + + + + SMTP Server is required + + + + + + + + + Wiki Title is required + + + + + + + + + Invalid Cache Cut Size (min. 5, max. 50000) + + + + + + + + + Invalid Size (min. 10, max. 100000) + + + + + + + + + Number must be between 0 and 1000 + + + + + + + + + Invalid File Size (min. 256, max. 102400) + + + + + + + + + Invalid Log Size (min. 16, max. 10240) + + + + + + + + + Number must be between 0 and 50 + + + + + + + + + Invalid Port (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Count all file downloads + + + CountAll + + + Count downloads for specified extensions (separate with commas) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + + + + + + + + + + RSS Feeds Serving Mode + + + Full Text + + + FullText + + + Summary + + + Summary + + + Disabled (RSS feeds completely disabled) + + + Disabled + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.pt-BR.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.pt-BR.resx new file mode 100644 index 0000000..2791ea1 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.pt-BR.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP/Host Filter for allowed editing (seperate with commas, Use '*' for wild cards) + + + Process single line breaks in content (experimental) + + + + + + Auto-Generate Page Names in Editor by default + + + File extensions allowed for upload (separate with commas, '*' for any type) + + + Regular Expression for validating username + + + Regular Expression for validating password + + + RSS Feeds Serving Mode + + + Full Text + + + FullText + + + Summary + + + Summary + + + Disabled (RSS feeds completely disabled) + + + Disabled + + + + + + Count all file downloads + + + CountAll + + + Count downloads for specified extensions (separate with commas) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + + + + + + + autodetect + + + + + + Save Configuration + + + + + + Allow SCRIPT tags in WikiMarkup + + + + + + Allow users to register + + + + + + Completely disable cache + + + + + + Display Gravatars for user accounts + + + + + + Enable automatic update checks (system and providers) + + + + + + Enable Breadcrumbs Trail + + + + + + Enable CAPTCHA control for all public functionalities + + + + + + Enable Double-Click editing + + + + + + Enable HTTP compression + + + + + + Enable Page Information section (Modified on, Categorized as, etc.) + + + + + + Enable Page Toolbar (Edit this Page, History, Admin) + + + + + + Enable editing of pages' sections + + + + + + Enable SSL + + + + + + Enable 'View Page Code' feature + + + + + + Enable ViewState compression + + + + + + Prevent concurrent page editing + + + + + + Use visual (WYSIWYG) editor as default + + + + + + Invalid Date/Time Format + + + + + + + + + Invalid email address + + + + + + + + + Username is required + + + + + + + + + Password is required + + + + + + + + + Account activation mode + + + Advanced Configuration + + + Cache cut size + + + Cache Size + + + Page change moderation mode + + + Configuration + + + Contact email + + + Content Configuration + + + Date/time format + + + + + + Default Administrators Group + + + Default Anonymous Users Group + + + Default language + + + Default time zone + + + Default Users Group + + + Email addresses to notify in case of errors (separate with commas) + + + General Configuration + + + backups for each page (leave empty for no limit) + + + Keep at most + + + Logging level + + + Max file size allowed for upload + + + Max log size + + + items in <code>{RecentChanges}</code> tags + + + Display at most + + + Namespaces + + + Namespaces + + + pages + + + pages + + + + + + + + + Root namespace main page + + + Root namespace theme + + + Security Configuration + + + see also + + + see also + + + Select... + + + Sender email + + + SMTP username and password + + + SMTP server address and port + + + + + + used for email communications + + + Wiki title + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Users must activate their account via Email + + + EMAIL + + + Administrators must activate accounts + + + ADMIN + + + Accounts are active by default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + All Messages + + + + + + Disable Log + + + + + + Errors Only + + + + + + Disable Moderation + + + No moderation system is used: pages can be edited only by users who have editing permissions + + + Require Page Editing permissions + + + Pages can be edited by users who have editing permissions but not management permissions, and the changes are held in moderation + + + Require Page Viewing permissions + + + Pages can be edited by users who have viewing permissions but not editing permissions, and the changes are help in moderation + + + Warnings and Errors + + + + + + Invalid email address + + + + + + + + + Invalid Wiki URL + + + + + + + + + Invalid email address + + + + + + + + + Invalid SMTP Server address + + + + + + + + + Invalid Wiki Title + + + + + + + + + Cache Cut Size is required + + + + + + + + + Cache Size is required + + + + + + + + + Contact Email is required + + + + + + + + + Date/Time Format is required + + + + + + + + + Wiki URL is required + + + + + + + + + Max File Size is required + + + + + + + + + Max Log Size is required + + + + + + + + + Number is required + + + + + + + + + Sender Email is required + + + + + + + + + SMTP Server is required + + + + + + + + + Wiki Title is required + + + + + + + + + Invalid Cache Cut Size (min. 5, max. 50000) + + + + + + + + + Invalid Size (min. 10, max. 100000) + + + + + + + + + Number must be between 0 and 1000 + + + + + + + + + Invalid File Size (min. 256, max. 102400) + + + + + + + + + Invalid Log Size (min. 16, max. 10240) + + + + + + + + + Number must be between 0 and 50 + + + + + + + + + Invalid Port (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.resx new file mode 100644 index 0000000..d94663e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + autodetect + + + + + + Save Configuration + + + + + + Allow SCRIPT tags in WikiMarkup + + + + + + Allow users to register + + + + + + Completely disable cache + + + + + + Display Gravatars for user accounts + + + + + + Enable automatic update checks (system and providers) + + + + + + Enable Breadcrumbs Trail + + + + + + Enable CAPTCHA control for all public functionalities + + + + + + Enable Double-Click editing + + + + + + Enable HTTP compression + + + + + + Enable Page Information section (Modified on, Categorized as, etc.) + + + + + + Enable Page Toolbar (Edit this Page, History, Admin) + + + + + + Enable editing of pages' sections + + + + + + Enable SSL + + + + + + Enable 'View Page Code' feature + + + + + + Enable ViewState compression + + + + + + Prevent concurrent page editing + + + + + + Process single line breaks in content (experimental) + + + + + + Use visual (WYSIWYG) editor as default + + + + + + Invalid Date/Time Format + + + + + + + + + Invalid email address + + + + + + + + + Username is required + + + + + + + + + Password is required + + + + + + + + + Account activation mode + + + Advanced Configuration + + + Cache cut size + + + Cache Size + + + Page change moderation mode + + + Configuration + + + Contact email + + + Content Configuration + + + Date/time format + + + + + + Default Administrators Group + + + Default Anonymous Users Group + + + Default language + + + Default time zone + + + Default Users Group + + + Email addresses to notify in case of errors (separate with commas) + + + File extensions allowed for upload (separate with commas, '*' for any type) + + + General Configuration + + + backups for each page (leave empty for no limit) + + + Keep at most + + + Logging level + + + Max file size allowed for upload + + + Max log size + + + items in <code>{RecentChanges}</code> tags + + + Display at most + + + Namespaces + + + Namespaces + + + pages + + + pages + + + + + + + + + Root namespace main page + + + Root namespace theme + + + Security Configuration + + + see also + + + see also + + + Select... + + + Sender email + + + SMTP username and password + + + SMTP server address and port + + + + + + used for email communications + + + Wiki title + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Users must activate their account via Email + + + EMAIL + + + Administrators must activate accounts + + + ADMIN + + + Accounts are active by default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + All Messages + + + + + + Disable Log + + + + + + Errors Only + + + + + + Disable Moderation + + + No moderation system is used: pages can be edited only by users who have editing permissions + + + Require Page Editing permissions + + + Pages can be edited by users who have editing permissions but not management permissions, and the changes are held in moderation + + + Require Page Viewing permissions + + + Pages can be edited by users who have viewing permissions but not editing permissions, and the changes are help in moderation + + + Warnings and Errors + + + + + + Invalid email address + + + + + + + + + Invalid Wiki URL + + + + + + + + + Invalid email address + + + + + + + + + Invalid SMTP Server address + + + + + + + + + Invalid Wiki Title + + + + + + + + + Cache Cut Size is required + + + + + + + + + Cache Size is required + + + + + + + + + Contact Email is required + + + + + + + + + Date/Time Format is required + + + + + + + + + Wiki URL is required + + + + + + + + + Max File Size is required + + + + + + + + + Max Log Size is required + + + + + + + + + Number is required + + + + + + + + + Sender Email is required + + + + + + + + + SMTP Server is required + + + + + + + + + Wiki Title is required + + + + + + + + + Invalid Cache Cut Size (min. 5, max. 50000) + + + + + + + + + Invalid Size (min. 10, max. 100000) + + + + + + + + + Number must be between 0 and 1000 + + + + + + + + + Invalid File Size (min. 256, max. 102400) + + + + + + + + + Invalid Log Size (min. 16, max. 10240) + + + + + + + + + Number must be between 0 and 50 + + + + + + + + + Invalid Port (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Count all file downloads + + + CountAll + + + Count downloads for specified extensions (separate with commas) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + + + + + + + + + + RSS Feeds Serving Mode + + + Full Text + + + FullText + + + Summary + + + Summary + + + Disabled (RSS feeds completely disabled) + + + Disabled + + + Regular Expression for validating username + + + Regular Expression for validating password + + + Auto-Generate Page Names in Editor by default + + + IP Filter for allowed editing (seperate with commas, Use '*' for wild cards, Example: 192.168.1.*) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.ro-RO.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.ro-RO.resx new file mode 100644 index 0000000..2791ea1 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.ro-RO.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP/Host Filter for allowed editing (seperate with commas, Use '*' for wild cards) + + + Process single line breaks in content (experimental) + + + + + + Auto-Generate Page Names in Editor by default + + + File extensions allowed for upload (separate with commas, '*' for any type) + + + Regular Expression for validating username + + + Regular Expression for validating password + + + RSS Feeds Serving Mode + + + Full Text + + + FullText + + + Summary + + + Summary + + + Disabled (RSS feeds completely disabled) + + + Disabled + + + + + + Count all file downloads + + + CountAll + + + Count downloads for specified extensions (separate with commas) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + + + + + + + autodetect + + + + + + Save Configuration + + + + + + Allow SCRIPT tags in WikiMarkup + + + + + + Allow users to register + + + + + + Completely disable cache + + + + + + Display Gravatars for user accounts + + + + + + Enable automatic update checks (system and providers) + + + + + + Enable Breadcrumbs Trail + + + + + + Enable CAPTCHA control for all public functionalities + + + + + + Enable Double-Click editing + + + + + + Enable HTTP compression + + + + + + Enable Page Information section (Modified on, Categorized as, etc.) + + + + + + Enable Page Toolbar (Edit this Page, History, Admin) + + + + + + Enable editing of pages' sections + + + + + + Enable SSL + + + + + + Enable 'View Page Code' feature + + + + + + Enable ViewState compression + + + + + + Prevent concurrent page editing + + + + + + Use visual (WYSIWYG) editor as default + + + + + + Invalid Date/Time Format + + + + + + + + + Invalid email address + + + + + + + + + Username is required + + + + + + + + + Password is required + + + + + + + + + Account activation mode + + + Advanced Configuration + + + Cache cut size + + + Cache Size + + + Page change moderation mode + + + Configuration + + + Contact email + + + Content Configuration + + + Date/time format + + + + + + Default Administrators Group + + + Default Anonymous Users Group + + + Default language + + + Default time zone + + + Default Users Group + + + Email addresses to notify in case of errors (separate with commas) + + + General Configuration + + + backups for each page (leave empty for no limit) + + + Keep at most + + + Logging level + + + Max file size allowed for upload + + + Max log size + + + items in <code>{RecentChanges}</code> tags + + + Display at most + + + Namespaces + + + Namespaces + + + pages + + + pages + + + + + + + + + Root namespace main page + + + Root namespace theme + + + Security Configuration + + + see also + + + see also + + + Select... + + + Sender email + + + SMTP username and password + + + SMTP server address and port + + + + + + used for email communications + + + Wiki title + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Users must activate their account via Email + + + EMAIL + + + Administrators must activate accounts + + + ADMIN + + + Accounts are active by default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + All Messages + + + + + + Disable Log + + + + + + Errors Only + + + + + + Disable Moderation + + + No moderation system is used: pages can be edited only by users who have editing permissions + + + Require Page Editing permissions + + + Pages can be edited by users who have editing permissions but not management permissions, and the changes are held in moderation + + + Require Page Viewing permissions + + + Pages can be edited by users who have viewing permissions but not editing permissions, and the changes are help in moderation + + + Warnings and Errors + + + + + + Invalid email address + + + + + + + + + Invalid Wiki URL + + + + + + + + + Invalid email address + + + + + + + + + Invalid SMTP Server address + + + + + + + + + Invalid Wiki Title + + + + + + + + + Cache Cut Size is required + + + + + + + + + Cache Size is required + + + + + + + + + Contact Email is required + + + + + + + + + Date/Time Format is required + + + + + + + + + Wiki URL is required + + + + + + + + + Max File Size is required + + + + + + + + + Max Log Size is required + + + + + + + + + Number is required + + + + + + + + + Sender Email is required + + + + + + + + + SMTP Server is required + + + + + + + + + Wiki Title is required + + + + + + + + + Invalid Cache Cut Size (min. 5, max. 50000) + + + + + + + + + Invalid Size (min. 10, max. 100000) + + + + + + + + + Number must be between 0 and 1000 + + + + + + + + + Invalid File Size (min. 256, max. 102400) + + + + + + + + + Invalid Log Size (min. 16, max. 10240) + + + + + + + + + Number must be between 0 and 50 + + + + + + + + + Invalid Port (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.ru-RU.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.ru-RU.resx new file mode 100644 index 0000000..0a74ab9 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.ru-RU.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP/Host Фильтр с правом редактирования (через запятую, исп. '*' для отключения) + + + Новая строка по единичному переводу каретки (экспериментальная) + + + + + + Автогенерация Имени Страницы при её создании + + + Расширения файлов, разрешённых к загрузке (через запятую, или '*' для любого) + + + Регулярные Выражения для валидации юзернэймов + + + Регулярные Выражения для валидации паролей + + + Управление RSS фидами + + + Полный Текст + + + FullText + + + Всего + + + Summary + + + Отключено (RSS фиды отключены) + + + Disabled + + + Счётчик всех, загруженных файлов + + + CountAll + + + Счётчик загруженных файлов с выбранными расширениями (через запятую) + + + CountSpecifiedExtensions + + + Счётчик всех загруженных файлов, за исключением файлов с расширением (через запятую) + + + ExcludeSpecifiedExtensions + + + + + + + + + + + + автоопределение + + + + + + Сохранить настройки + + + + + + Разрешить использовать тэги SCRIPT в Wiki + + + + + + Разрешить пользовательскую регистрацию + + + + + + Полностью отключить кэш + + + + + + Отображать Граватары пользователей + + + + + + Включить проверку обновлений (аппликации и провайдеров) + + + + + + Включить Хлебные Крошки + + + + + + Включить CAPTCHA для всех публичных действий + + + + + + Включить редактирование по двойному клику + + + + + + Включить HTTP-компрессию + + + + + + Включить отображение информационного блока страниц (Изменения, присвоение категорий, др.) + + + + + + Включить тулбар страниц (Редактирование, История, Администрирование) + + + + + + Включить редактирование страниц + + + + + + Включить SSL-шифрование + + + + + + Включить функцию 'Просмотр кода страницы' + + + + + + Включить ViewState-компрессию + + + + + + Запрет одновременного редактирования страниц + + + + + + Использовать по умолчанию визуальный (WYSIWYG) редактор текста + + + + + + Не поддерживаемый формат Даты/Времени + + + + + + + + + Неверный email + + + + + + + + + Имя пользователя обязательно + + + + + + + + + Пароль обязателен + + + + + + + + + Тип активации учётных записей + + + Дополнительные настройки + + + Размер сжатого кэша + + + Размер кэша + + + Изменение типа модерации страниц + + + Настройки + + + Контактный email + + + Контент-настройки + + + Формат Даты/Времени + + + + + + Административная группа по умолчанию + + + Анонимная группа по умолчанию + + + Язык по умолчанию + + + Временной пояс по умолчанию + + + Пользовательская группа по умолчанию + + + Email-адреса получателей сообщений об ошибках (через запятую) + + + Основные настройки + + + резервных копий каждой страницы (если не заполнено - без ограничений) + + + Хранить + + + Показывать в логе + + + Максимальный размер одного файла для загрузки + + + Максимальный размер лога + + + количество <code>{RecentChanges}</code> тэгов + + + Показывать везде + + + Пространства Имён + + + Пространства Имён + + + страниц + + + страниц + + + + + + + + + Корневая Главная страница Пространства Имён + + + Корневая тема Пространства Имён + + + Конфигурирование доступа + + + см. также + + + см. также + + + Выберите... + + + Email отправителя + + + SMTP пользователь и пароль + + + SMTP сервер и порт + + + + + + используется для отправки email сообщений + + + Название всей Wiki + + + Основной URL Wiki + + + (GMT-12:00) Меридиан смены дат (запад) + + + -720 + + + (GMT-03:30) Ньюфаундленд + + + -210 + + + (GMT-03:00) Гренландия + + + -180 + + + (GMT-02:00) Среднеатлантическое время + + + -120 + + + (GMT-01:00) Азорские о-ва + + + -60 + + + (GMT) Время по Гринвичу + + + 0 + + + (GMT+01:00) Центральноевропейское время + + + 60 + + + (GMT+02:00) Восточноевропейское время + + + 120 + + + (GMT+03:00) Москва, Багдад + + + 180 + + + (GMT+03:30) Иран + + + 210 + + + (GMT+04:00) Абу Даби, Дубаи + + + 240 + + + (GMT-11:00) о. Мидуэй, Самоа + + + -660 + + + (GMT+04:30) Кабул + + + 270 + + + (GMT+05:00) Исламабад, Карачи + + + 300 + + + (GMT+05:30) Индия + + + 330 + + + (GMT+05:45) Катманду + + + 345 + + + (GMT+06:00) Астана, Дхака + + + 360 + + + (GMT+06:30) Янгун (Рангун) + + + 390 + + + (GMT+07:00) Бангкок, Джакарта, Ханой + + + 420 + + + (GMT+08:00) Гонконг, Западная Австралия + + + 480 + + + (GMT+09:00) Япония, Корея + + + 540 + + + (GMT+09:30) Центральная Австралия + + + 570 + + + (GMT-10:00) Гавайи + + + -600 + + + (GMT+10:00) Восточная Австралия + + + 600 + + + (GMT+11:00) Магадан, Соломоновы о-ва + + + 660 + + + (GMT+12:00) Новая Зеландия, Фиджи + + + 720 + + + (GMT+12:45) Новая Зеландия + + + 765 + + + (GMT+13:00) Нуку-алофа + + + 780 + + + (GMT+14:00) Остров Рождества + + + 840 + + + Активация пользователей по ссылке из Email + + + EMAIL + + + Пользовательские учётные записи активируют администраторы + + + ADMIN + + + Учётные записи активируются сразу + + + AUTO + + + (GMT-09:00) Аляска + + + -540 + + + (GMT-08:00) Тихоокеанское время (США и Канада) + + + -480 + + + (GMT-07:00) Горное время (США и Канада) + + + -420 + + + (GMT-06:00) Центральная Америка + + + -360 + + + (GMT-05:00) Восточное время (США и Канада) + + + -300 + + + (GMT-04:00) Атлантическое время (Канада) + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Администрирование + + + Все сообщения + + + + + + Выключить лог + + + + + + Только ошибки + + + + + + Выключить модерирование + + + Используется система без модерирования: редактировать страницы могут пользователи, имеющие на это разрешение + + + Обязательно разрешение на редактирование + + + Страницы могут изменяться пользователями, имеющими на это разрешения, но не имеющие доступа к управлению разрешениями, все изменения будут подлежать модерированию + + + Обязательно разрешение на просмотр + + + Редактировать страницы разрешено пользователям, имеющим разрешение на просмотр, однако не имеющим разрешения на редактирование. Все изменения модерируются + + + Предупреждения и Ошибки + + + + + + Неверный email адрес + + + + + + + + + Неправильный Wiki URL + + + + + + + + + Неверный email адрес + + + + + + + + + Неправильный адрес SMTP-сервера + + + + + + + + + Неправильное название Wiki + + + + + + + + + Размер сжатого кэша обязателен + + + + + + + + + Размер кэша обязателен + + + + + + + + + Контактный email обязателен + + + + + + + + + Указание формата Даты/Времени обязательно + + + + + + + + + Основной URL Wiki обязателен + + + + + + + + + Максимальный размер файла обязателен + + + + + + + + + Максимальный размер лога обязателен + + + + + + + + + Количество обязательно + + + + + + + + + Email отправителя обязателен + + + + + + + + + SMTP-сервер обязателен + + + + + + + + + Название Wiki обязательно + + + + + + + + + Неправильный размер (мин. 5, макс. 50000) + + + + + + + + + Неправильный размер (мин. 10, макс. 100000) + + + + + + + + + Число должно быть от 0 до 1000 + + + + + + + + + Неправильный размер файла (мин. 256, макс. 102400) + + + + + + + + + Неправильный размер лога (мин. 16, макс. 10240) + + + + + + + + + Число должно быть от 0 до 50 + + + + + + + + + Неправильный порт (мин. 1, макс. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.sk-SK.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.sk-SK.resx new file mode 100644 index 0000000..2791ea1 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.sk-SK.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP/Host Filter for allowed editing (seperate with commas, Use '*' for wild cards) + + + Process single line breaks in content (experimental) + + + + + + Auto-Generate Page Names in Editor by default + + + File extensions allowed for upload (separate with commas, '*' for any type) + + + Regular Expression for validating username + + + Regular Expression for validating password + + + RSS Feeds Serving Mode + + + Full Text + + + FullText + + + Summary + + + Summary + + + Disabled (RSS feeds completely disabled) + + + Disabled + + + + + + Count all file downloads + + + CountAll + + + Count downloads for specified extensions (separate with commas) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + + + + + + + autodetect + + + + + + Save Configuration + + + + + + Allow SCRIPT tags in WikiMarkup + + + + + + Allow users to register + + + + + + Completely disable cache + + + + + + Display Gravatars for user accounts + + + + + + Enable automatic update checks (system and providers) + + + + + + Enable Breadcrumbs Trail + + + + + + Enable CAPTCHA control for all public functionalities + + + + + + Enable Double-Click editing + + + + + + Enable HTTP compression + + + + + + Enable Page Information section (Modified on, Categorized as, etc.) + + + + + + Enable Page Toolbar (Edit this Page, History, Admin) + + + + + + Enable editing of pages' sections + + + + + + Enable SSL + + + + + + Enable 'View Page Code' feature + + + + + + Enable ViewState compression + + + + + + Prevent concurrent page editing + + + + + + Use visual (WYSIWYG) editor as default + + + + + + Invalid Date/Time Format + + + + + + + + + Invalid email address + + + + + + + + + Username is required + + + + + + + + + Password is required + + + + + + + + + Account activation mode + + + Advanced Configuration + + + Cache cut size + + + Cache Size + + + Page change moderation mode + + + Configuration + + + Contact email + + + Content Configuration + + + Date/time format + + + + + + Default Administrators Group + + + Default Anonymous Users Group + + + Default language + + + Default time zone + + + Default Users Group + + + Email addresses to notify in case of errors (separate with commas) + + + General Configuration + + + backups for each page (leave empty for no limit) + + + Keep at most + + + Logging level + + + Max file size allowed for upload + + + Max log size + + + items in <code>{RecentChanges}</code> tags + + + Display at most + + + Namespaces + + + Namespaces + + + pages + + + pages + + + + + + + + + Root namespace main page + + + Root namespace theme + + + Security Configuration + + + see also + + + see also + + + Select... + + + Sender email + + + SMTP username and password + + + SMTP server address and port + + + + + + used for email communications + + + Wiki title + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Users must activate their account via Email + + + EMAIL + + + Administrators must activate accounts + + + ADMIN + + + Accounts are active by default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + All Messages + + + + + + Disable Log + + + + + + Errors Only + + + + + + Disable Moderation + + + No moderation system is used: pages can be edited only by users who have editing permissions + + + Require Page Editing permissions + + + Pages can be edited by users who have editing permissions but not management permissions, and the changes are held in moderation + + + Require Page Viewing permissions + + + Pages can be edited by users who have viewing permissions but not editing permissions, and the changes are help in moderation + + + Warnings and Errors + + + + + + Invalid email address + + + + + + + + + Invalid Wiki URL + + + + + + + + + Invalid email address + + + + + + + + + Invalid SMTP Server address + + + + + + + + + Invalid Wiki Title + + + + + + + + + Cache Cut Size is required + + + + + + + + + Cache Size is required + + + + + + + + + Contact Email is required + + + + + + + + + Date/Time Format is required + + + + + + + + + Wiki URL is required + + + + + + + + + Max File Size is required + + + + + + + + + Max Log Size is required + + + + + + + + + Number is required + + + + + + + + + Sender Email is required + + + + + + + + + SMTP Server is required + + + + + + + + + Wiki Title is required + + + + + + + + + Invalid Cache Cut Size (min. 5, max. 50000) + + + + + + + + + Invalid Size (min. 10, max. 100000) + + + + + + + + + Number must be between 0 and 1000 + + + + + + + + + Invalid File Size (min. 256, max. 102400) + + + + + + + + + Invalid Log Size (min. 16, max. 10240) + + + + + + + + + Number must be between 0 and 50 + + + + + + + + + Invalid Port (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..2791ea1 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.sr-Latn-CS.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP/Host Filter for allowed editing (seperate with commas, Use '*' for wild cards) + + + Process single line breaks in content (experimental) + + + + + + Auto-Generate Page Names in Editor by default + + + File extensions allowed for upload (separate with commas, '*' for any type) + + + Regular Expression for validating username + + + Regular Expression for validating password + + + RSS Feeds Serving Mode + + + Full Text + + + FullText + + + Summary + + + Summary + + + Disabled (RSS feeds completely disabled) + + + Disabled + + + + + + Count all file downloads + + + CountAll + + + Count downloads for specified extensions (separate with commas) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + + + + + + + autodetect + + + + + + Save Configuration + + + + + + Allow SCRIPT tags in WikiMarkup + + + + + + Allow users to register + + + + + + Completely disable cache + + + + + + Display Gravatars for user accounts + + + + + + Enable automatic update checks (system and providers) + + + + + + Enable Breadcrumbs Trail + + + + + + Enable CAPTCHA control for all public functionalities + + + + + + Enable Double-Click editing + + + + + + Enable HTTP compression + + + + + + Enable Page Information section (Modified on, Categorized as, etc.) + + + + + + Enable Page Toolbar (Edit this Page, History, Admin) + + + + + + Enable editing of pages' sections + + + + + + Enable SSL + + + + + + Enable 'View Page Code' feature + + + + + + Enable ViewState compression + + + + + + Prevent concurrent page editing + + + + + + Use visual (WYSIWYG) editor as default + + + + + + Invalid Date/Time Format + + + + + + + + + Invalid email address + + + + + + + + + Username is required + + + + + + + + + Password is required + + + + + + + + + Account activation mode + + + Advanced Configuration + + + Cache cut size + + + Cache Size + + + Page change moderation mode + + + Configuration + + + Contact email + + + Content Configuration + + + Date/time format + + + + + + Default Administrators Group + + + Default Anonymous Users Group + + + Default language + + + Default time zone + + + Default Users Group + + + Email addresses to notify in case of errors (separate with commas) + + + General Configuration + + + backups for each page (leave empty for no limit) + + + Keep at most + + + Logging level + + + Max file size allowed for upload + + + Max log size + + + items in <code>{RecentChanges}</code> tags + + + Display at most + + + Namespaces + + + Namespaces + + + pages + + + pages + + + + + + + + + Root namespace main page + + + Root namespace theme + + + Security Configuration + + + see also + + + see also + + + Select... + + + Sender email + + + SMTP username and password + + + SMTP server address and port + + + + + + used for email communications + + + Wiki title + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Users must activate their account via Email + + + EMAIL + + + Administrators must activate accounts + + + ADMIN + + + Accounts are active by default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + All Messages + + + + + + Disable Log + + + + + + Errors Only + + + + + + Disable Moderation + + + No moderation system is used: pages can be edited only by users who have editing permissions + + + Require Page Editing permissions + + + Pages can be edited by users who have editing permissions but not management permissions, and the changes are held in moderation + + + Require Page Viewing permissions + + + Pages can be edited by users who have viewing permissions but not editing permissions, and the changes are help in moderation + + + Warnings and Errors + + + + + + Invalid email address + + + + + + + + + Invalid Wiki URL + + + + + + + + + Invalid email address + + + + + + + + + Invalid SMTP Server address + + + + + + + + + Invalid Wiki Title + + + + + + + + + Cache Cut Size is required + + + + + + + + + Cache Size is required + + + + + + + + + Contact Email is required + + + + + + + + + Date/Time Format is required + + + + + + + + + Wiki URL is required + + + + + + + + + Max File Size is required + + + + + + + + + Max Log Size is required + + + + + + + + + Number is required + + + + + + + + + Sender Email is required + + + + + + + + + SMTP Server is required + + + + + + + + + Wiki Title is required + + + + + + + + + Invalid Cache Cut Size (min. 5, max. 50000) + + + + + + + + + Invalid Size (min. 10, max. 100000) + + + + + + + + + Number must be between 0 and 1000 + + + + + + + + + Invalid File Size (min. 256, max. 102400) + + + + + + + + + Invalid Log Size (min. 16, max. 10240) + + + + + + + + + Number must be between 0 and 50 + + + + + + + + + Invalid Port (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.tr-TR.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.tr-TR.resx new file mode 100644 index 0000000..34e4af9 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.tr-TR.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Process single line breaks in content (experimental) + + + + + + File extensions allowed for upload (separate with commas, '*' for any type) + + + Count all file downloads + + + CountAll + + + Count downloads for specified extensions (separate with commas) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + + + + + + + + + + RSS Feeds Serving Mode + + + Full Text + + + FullText + + + Summary + + + Summary + + + Disabled (RSS feeds completely disabled) + + + Disabled + + + Regular Expression for validating username + + + Regular Expression for validating password + + + Auto-Generate Page Names in Editor by default + + + IP Filter for allowed editing (seperate with commas, Use '*' for wild cards, Example: 192.168.1.*) + + + Otomatik algıla + + + + + + Yapılandırmayı Kaydet + + + + + + WikiMarkup içinde SCRIPT etiketlerine izin ver + + + + + + Kullanıcıların kayıt olmalarına izin ver + + + + + + Önbelleği tamamen iptal et + + + + + + Kullanıcı hesapları için "Gravatar" simgelerini göster + + + + + + Sistem ve sağlayıcılar için otomatik güncelleme denetimi yap + + + + + + Ekmek kırıntısı şeklindeki menüyü aktifleştir + + + + + + Tüm kamuya açık işlevsellik için CAPTCHA denetimini aç + + + + + + Çift tıklama ile düzenlemeyi aç + + + + + + HTTP sıkıştırmayı aktifleştir + + + + + + Sayfa bilgi kısmını aç (Değiştirme zamanı, kategori, vs.) + + + + + + Sayfa araç çubuğunu aç (Sayfayı düzenle, geçmiş, yönetim) + + + + + + Sayfa kısımlarının bağımsız olarak düzenlenebilmesini sağla + + + + + + SSL'i aç + + + + + + Sayfa kodunu görme özelliğini aç + + + + + + ViewState sıkıştırmayı aç + + + + + + Bir sayfannın aynı zamanda düzenlenebilmesini engelle + + + + + + Görsel editörü varsayılan olarak kullan + + + + + + Geçersiz tarih / saat biçimi + + + + + + + + + Geçersiz email adresi + + + + + + + + + Kullanıcı adı gereklidir + + + + + + + + + Parola gereklidir + + + + + + + + + Hesap aktifleştirme kipi + + + Gelişmiş yapılandırma + + + Önbellek kesim büyüklüğü + + + Önbellek büyüklüğü + + + Sayfa değişimi moderasyon kipi + + + Yapılandırma + + + İletişim email adresi + + + İçerik yapılandırması + + + Tarih/saat biçimi + + + + + + Varsayılan yönetici gurubu + + + Varsayılan anoim kullanıcı gurubu + + + Varsayılan dil + + + Varsayılan saat dilimi + + + Varsayılan kullanıcı gurubu + + + Hata durumunda bildirim yapılacak email adresleri (virgül ile ayırınız) + + + Genel yapılandırma + + + her bir sayfa için arşiv adedi (limitsiz için boş bırakınız) + + + En fazla korunacak arşiv adedi + + + Loglama seviyesi + + + Yüklemelerde en fazla izin verilen dosya boyutu + + + En fazla log boyutu + + + <code>{RecentChanges}</code> etiketerindeki maksimum öğe adedi + + + Display at most + + + Ad alanı + + + Ad alanı + + + sayfa + + + sayfa + + + + + + + + + Ana ad alanının giriş sayfası + + + Ana ad alanı teması + + + Güvenlik Yapılandırması + + + ayrıca bakınız + + + ayrıca bakınız + + + Seçiniz... + + + Gönderen email adresi + + + SMTP kullanıcı adı ve şifresi + + + SMTP sunucu adresi ve portu + + + + + + email iletişimi için kullanılır + + + Wiki başlığı + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Orta Avrupa + + + 60 + + + (GMT+02:00) Doğu Avrupa + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Users must activate their account via Email + + + EMAIL + + + Administrators must activate accounts + + + ADMIN + + + Accounts are active by default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Yönetim + + + Tüm mesajlar + + + + + + Log'u kapat + + + + + + Sadece hatalar + + + + + + Moderasyonu iptal et + + + No moderation system is used: pages can be edited only by users who have editing permissions + + + Sayfa düzenleme yetkisini gerektirir + + + Pages can be edited by users who have editing permissions but not management permissions, and the changes are held in moderation + + + Sayfa görme yetkisini gerektirir + + + Pages can be edited by users who have viewing permissions but not editing permissions, and the changes are help in moderation + + + İkazlar ve hatalar + + + + + + Geçersiz email adresi + + + + + + + + + Geçersiz Wiki URL bilgisi + + + + + + + + + Geçersiz email adresi + + + + + + + + + Geçersiz SMTP sunucu adresi + + + + + + + + + Geçersiz Wiki başlığı + + + + + + + + + Önbellek kesim boyutu gereklidir + + + + + + + + + Önbellek boyutu gereklidir + + + + + + + + + İletişim email adresi gereklidir + + + + + + + + + Tarih/saat biçimi gereklidir + + + + + + + + + Wiki URL bilgisi gereklidir + + + + + + + + + En fazla dosya büyüklüğü girilmelidir + + + + + + + + + En fazla log büyüklüğü gereklidir + + + + + + + + + Sayı gereklidir + + + + + + + + + Gönderen email adresi gereklidir + + + + + + + + + SMTP sunucusu gereklidir + + + + + + + + + Wiki başlığı gereklidir + + + + + + + + + Geçersiz önbellek sınır boyutu (min. 5, max. 50000) + + + + + + + + + Geçersiz boyut (min. 10, max. 100000) + + + + + + + + + Rakam 0 ile 1000 arasında olmalıdır. + + + + + + + + + Geçersiz dosya boyutu. (min. 256, max. 102400) + + + + + + + + + Invalid Log Size (min. 16, max. 10240) + + + + + + + + + Rakam 0 ile 50 arasında olmalıdır. + + + + + + + + + Geçersiz port (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.uk-UA.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.uk-UA.resx new file mode 100644 index 0000000..b73ad28 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.uk-UA.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP/Хост фільтр з дозволом на редагування (через кому, використовуйте '*' для будь-яких) + + + Функція - новий рядок за одинарним переводом каретки (експериментально) + + + + + + Авто-генерація Імені Сторінки при її створенні + + + Типи файлів, дозволених до завантаження (через кому, '*' для будь-якого типу) + + + Регулярні Вирази для валідації НікНейму + + + Регулярні Вирази для валідації паролю + + + Керування RSS фідами + + + Повний Текст + + + FullText + + + Всього + + + Summary + + + Виключено (RSS фід повністю відключений) + + + Disabled + + + Лічільник всіх завантажених файлів + + + CountAll + + + Лічільник завантажених файлів, визначених типів (через кому) + + + CountSpecifiedExtensions + + + Лічільник всіх завантажених файлів, крім файлів типів (через кому) + + + ExcludeSpecifiedExtensions + + + + + + + + + + + + автовизначення + + + + + + Зберегти змінене + + + + + + Дозволити використання тегів SCRIPT у Wiki + + + + + + Дозволити реєстрацію користувачів + + + + + + Повністю відключити кеш + + + + + + Відображати Граватари користувачів + + + + + + Задіяти перевірку оновлень (аплікації та несучих) + + + + + + Задіяти Хлібні Крихти + + + + + + Задіяти CAPTCHA для усіх публічних дій + + + + + + Задіяти редагування за подвійним кліком + + + + + + Задіяти HTTP-компресію + + + + + + Задіяти інформаційний блок сторінок (Зміни, Категорії, ін.) + + + + + + Задіяти тулбар сторінок (Редагування, Історія, Адміністрування) + + + + + + Задіяти редагування сторінок + + + + + + Задіяти SSL-шифрування + + + + + + Задіяти функцію 'Перегляд коду сторінок' + + + + + + Задіяти ViewState-компресію + + + + + + Заборона одночасного редагування сторінок + + + + + + Задіяти візуальний (WYSIWYG) редактор тексту, як основний + + + + + + Такий формат Дати/Часу не підтримується + + + + + + + + + Неможливий email + + + + + + + + + Потрібне Ім'я Користувача + + + + + + + + + Потрібен пароль + + + + + + + + + Вид активації облікових записів + + + Додаткові функції + + + Розмір зменшеного кешу + + + Розмір кешу + + + Вид модерації сторінок + + + Функції + + + Контактний email + + + Контентні функції + + + Формат Дати/Часу + + + + + + Оберіть Адміністративну групу + + + Оберіть Анонімну групу + + + Оберіть мову Wiki + + + Оберіть часовий пояс + + + Оберіть групу Користувачів + + + Через кому email-адреси, на які будуть надходити повідомлення про помилки + + + Основні Функції + + + резервних копій кожної сторінки (якщо не заповнено - без обмежень) + + + Зберігати + + + Відображати в лозі + + + Максімальний розмір одного файлу для завантаження + + + Максимальний розмір логу + + + кількість <code>{RecentChanges}</code> тегів + + + Відображати всюди + + + Простори Імен + + + Простори Імен + + + сторінок + + + сторінок + + + + + + + + + Головна сторінка Простору Імен + + + Головний шаблон Простору Імен + + + Конфігурування доступу + + + див.також + + + див.також + + + Оберіть... + + + Email відправлення + + + SMTP логін та пароль + + + SMTP сервер та порт + + + + + + використовується для відправлення email-повідомлень + + + Назва Wiki + + + Головний URL Wiki + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Активація користувачів за допомогою відсилки з Email-у + + + EMAIL + + + Облікові записи користувачів активують адміністратори + + + ADMIN + + + Облікові записи активуються одразу + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Адміністрування + + + Усі повідомлення + + + + + + Виключити лог + + + + + + Тільки помилки + + + + + + Виключити модерацію + + + Використовується система без модерації: змінювати сторінки можуть користувачі, що мають на це дозвіл + + + Потрібен дозвіл на редагування + + + Редагувати сторінки можуть лише ті користувачі, що мають на таке дозвіл, але не мають дозволу на керування доступом інших, всі зміни модеруються + + + Потрібен дозвіл на перегляд + + + Редагувати сторінки можуть лише ті користувачі, які мають дозвіл на перегляд змісту сторінок, однак не мають дозволу на їх редагування, всі зміни модеруються + + + Попередження та помилки + + + + + + Помилкова email адреса + + + + + + + + + Помилковий URL Wiki + + + + + + + + + Помилкова email адреса + + + + + + + + + Помилкова адреса SMTP-серверу + + + + + + + + + Помилкова Назва Wiki + + + + + + + + + Потрібен розмір зменшеного кешу + + + + + + + + + Потрібен розмір кешу + + + + + + + + + Потібна контактна email-адреса + + + + + + + + + Потрібно визначитися з форматом Дати/Часу + + + + + + + + + Вкажіть Головний URL Wiki + + + + + + + + + Вкажіть максимальний розмір файлу + + + + + + + + + Вкажіть максимальний розмір логу + + + + + + + + + Вкажіть кількість + + + + + + + + + Потрібен email відправлення + + + + + + + + + Потрібно вказати адресу SMTP-серверу + + + + + + + + + Вкажіть Назву Wiki + + + + + + + + + Помилковий розмір (мін. 5, макс. 50000) + + + + + + + + + Помилковий розмір (мін. 10, макс. 100000) + + + + + + + + + Число повинно бути в інтервалі від 0 до 1000 + + + + + + + + + Поимлковий розмір файлу (мін. 256, макс. 102400) + + + + + + + + + Помилковий розмір логу (мін. 16, макс. 10240) + + + + + + + + + Число повинно бути в інтервалі від 0 до 50 + + + + + + + + + Помилковий порт (мін. 1, макс. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.zh-cn.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.zh-cn.resx new file mode 100644 index 0000000..dbdc0b3 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.zh-cn.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 允许编辑 IP/主机筛选(用英文逗号分隔,使用“*”作为通配符) + + + 处理内容中的单个换行符(试验) + + + + + + 默认在编辑器中自动创建页面名称 + + + 允许上传的文件扩展名(用英文逗号分隔,“*”表示任何类型) + + + 用户名有效性验证正则表达式 + + + 密码有效性验证正则表达式 + + + RSS 源发布模式 + + + 全文 + + + FullText + + + 总结 + + + Summary + + + 已禁用(完全禁用 RSS 源) + + + Disabled + + + + + + 计算所有文件下载 + + + CountAll + + + 计算含下列扩展名的文件下载(用英文逗号分隔) + + + CountSpecifiedExtensions + + + 计算不含下列扩展名的文件下载(用英文逗号分隔) + + + ExcludeSpecifiedExtensions + + + + + + + + + 自动检测 + + + + + + 保存配置 + + + + + + 允许在 WikiMarkup 中使用 SCRIPT 标签 + + + + + + 允许用户注册 + + + + + + 完全禁用缓存 + + + + + + 为用户帐户显示 Gravatars + + + + + + 启用自动更新检查(系统和提供者) + + + + + + 启用浏览路径记录 + + + + + + 为所有公用功能启用验证码控制 + + + + + + 启用双击编辑 + + + + + + 启用 HTTP 压缩 + + + + + + 启用页面信息栏(修改于,归类为,等等) + + + + + + 启用页面工具栏(编辑,历史,管理) + + + + + + 启用页面区域编辑 + + + + + + 启用 SSL + + + + + + 启用“查看页面代码”功能 + + + + + + 启用 ViewState 压缩 + + + + + + 防止并发页面编辑 + + + + + + 默认使用所见即所得(WYSIWYG)编辑器 + + + + + + 日期/时间格式无效 + + + + + + + + + 电子邮件地址无效 + + + + + + + + + 需要用户名 + + + + + + + + + 需要密码 + + + + + + + + + 帐户激活模式 + + + 高级配置 + + + 缓存剪切大小 + + + 缓存大小 + + + 页面更改限制模式 + + + 配置 + + + 联系人电子邮件地址 + + + 内容配置 + + + 日期/时间格式 + + + + + + 默认管理员组 + + + 默认匿名用户组 + + + 默认语言 + + + 默认时区 + + + 默认用户组 + + + 用于接受错误通知的电子邮件地址(用英文逗号分隔) + + + 常规配置 + + + 份备份(留空表示无限制) + + + 为每个页面保留最多 + + + 日志记录等级 + + + 允许上传的文件的最大尺寸 + + + 最大日志大小 + + + 个项目 + + + 在 <code>{RecentChanges}</code> 标签中显示最多 + + + 命名空间 + + + 命名空间 + + + 页面 + + + 页面 + + + + + + + + + 根命名空间首页 + + + 根命名空间主题 + + + 安全配置 + + + 另见 + + + 另见 + + + 选择... + + + 发件人电子邮件地址 + + + SMTP 用户名和密码 + + + SMTP 服务器地址和端口 + + + + + + 用于电子邮件通讯 + + + 维基标题 + + + 维基网址 + + + (GMT-12:00) 国际日期变更线西 + + + -720 + + + (GMT-03:30) 纽芬兰 + + + -210 + + + (GMT-03:00) 格陵兰 + + + -180 + + + (GMT-02:00) 中大西洋 + + + -120 + + + (GMT-01:00) 亚速尔群岛 + + + -60 + + + (GMT) 格林威治标准时间 + + + 0 + + + (GMT+01:00) 中欧 + + + 60 + + + (GMT+02:00) 东欧 + + + 120 + + + (GMT+03:00) 莫斯科,巴格达 + + + 180 + + + (GMT+03:30) 伊朗 + + + 210 + + + (GMT+04:00) 阿布扎比,迪拜 + + + 240 + + + (GMT-11:00) 中途岛,萨摩亚群岛 + + + -660 + + + (GMT+04:30) 喀布尔 + + + 270 + + + (GMT+05:00) 伊斯兰堡,卡拉奇 + + + 300 + + + (GMT+05:30) 印度 + + + 330 + + + (GMT+05:45) 加德满都 + + + 345 + + + (GMT+06:00) 阿斯塔纳,达卡 + + + 360 + + + (GMT+06:30) 仰光 + + + 390 + + + (GMT+07:00) 曼谷,雅加达 + + + 420 + + + (GMT+08:00) 中国,西澳大利亚 + + + 480 + + + (GMT+09:00) 日本,韩国 + + + 540 + + + (GMT+09:30) 中澳大利亚 + + + 570 + + + (GMT-10:00) 夏威夷 + + + -600 + + + (GMT+10:00) 东澳大利亚 + + + 600 + + + (GMT+11:00) 马加丹,索罗门群岛 + + + 660 + + + (GMT+12:00) 新西兰,斐济 + + + 720 + + + (GMT+12:45) 查塔姆群岛 + + + 765 + + + (GMT+13:00) 东加,菲尼克斯群岛 + + + 780 + + + (GMT+14:00) 圣诞岛 + + + 840 + + + 用户必须通过电子邮件激活帐户 + + + EMAIL + + + 用户帐户由管理员激活 + + + ADMIN + + + 用户帐户默认已激活 + + + AUTO + + + (GMT-09:00) 阿拉斯加 + + + -540 + + + (GMT-08:00) 太平洋时间(美国和加拿大) + + + -480 + + + (GMT-07:00) 山地时间(美国和加拿大) + + + -420 + + + (GMT-06:00) 中部时间(美国和加拿大) + + + -360 + + + (GMT-05:00) 东部时间(美国和加拿大) + + + -300 + + + (GMT-04:00) 大西洋时间(加拿大) + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + 管理 + + + 所有消息 + + + + + + 禁用日志 + + + + + + 仅错误 + + + + + + 无限制 + + + 不使用限制系统:页面可被有编辑权限的用户更改 + + + 需要页面编辑权限 + + + 页面可被有编辑权限但无管理权限的用户更改,更改需要审查 + + + 需要页面查看权限 + + + 页面可被有查看权限但无编辑权限的用户更改,更改需要审查 + + + 警告和错误 + + + + + + 电子邮件地址无效 + + + + + + + + + 维基网址无效 + + + + + + + + + 电子邮件地址无效 + + + + + + + + + SMTP 服务器地址无效 + + + + + + + + + 维基标题无效 + + + + + + + + + 需要缓存剪切大小 + + + + + + + + + 需要缓存大小 + + + + + + + + + 需要联系人电子邮件地址 + + + + + + + + + 需要日期/时间格式 + + + + + + + + + 需要维基网址 + + + + + + + + + 需要最大文件尺寸 + + + + + + + + + 需要最大日志大小 + + + + + + + + + 需要数字 + + + + + + + + + 需要发件人电子邮件地址 + + + + + + + + + 需要 SMTP 服务器 + + + + + + + + + 需要维基标题 + + + + + + + + + 缓存剪切大小无效(最小 5,最大 50000) + + + + + + + + + 大小无效(最小 10,最大 100000) + + + + + + + + + 数字必须在 0 到 1000 之间 + + + + + + + + + 文件尺寸无效(最小 256,最大 102400) + + + + + + + + + 日志大小无效(最小 16,最大 10240) + + + + + + + + + 数字必须在 0 到 50 之间 + + + + + + + + + 端口无效(最小 1,最大 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminConfig.aspx.zh-tw.resx b/WebApplication/App_LocalResources/AdminConfig.aspx.zh-tw.resx new file mode 100644 index 0000000..2791ea1 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminConfig.aspx.zh-tw.resx @@ -0,0 +1,1074 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP/Host Filter for allowed editing (seperate with commas, Use '*' for wild cards) + + + Process single line breaks in content (experimental) + + + + + + Auto-Generate Page Names in Editor by default + + + File extensions allowed for upload (separate with commas, '*' for any type) + + + Regular Expression for validating username + + + Regular Expression for validating password + + + RSS Feeds Serving Mode + + + Full Text + + + FullText + + + Summary + + + Summary + + + Disabled (RSS feeds completely disabled) + + + Disabled + + + + + + Count all file downloads + + + CountAll + + + Count downloads for specified extensions (separate with commas) + + + CountSpecifiedExtensions + + + Count downloads for all extensions except (separate with commas) + + + ExcludeSpecifiedExtensions + + + + + + + + + autodetect + + + + + + Save Configuration + + + + + + Allow SCRIPT tags in WikiMarkup + + + + + + Allow users to register + + + + + + Completely disable cache + + + + + + Display Gravatars for user accounts + + + + + + Enable automatic update checks (system and providers) + + + + + + Enable Breadcrumbs Trail + + + + + + Enable CAPTCHA control for all public functionalities + + + + + + Enable Double-Click editing + + + + + + Enable HTTP compression + + + + + + Enable Page Information section (Modified on, Categorized as, etc.) + + + + + + Enable Page Toolbar (Edit this Page, History, Admin) + + + + + + Enable editing of pages' sections + + + + + + Enable SSL + + + + + + Enable 'View Page Code' feature + + + + + + Enable ViewState compression + + + + + + Prevent concurrent page editing + + + + + + Use visual (WYSIWYG) editor as default + + + + + + Invalid Date/Time Format + + + + + + + + + Invalid email address + + + + + + + + + Username is required + + + + + + + + + Password is required + + + + + + + + + Account activation mode + + + Advanced Configuration + + + Cache cut size + + + Cache Size + + + Page change moderation mode + + + Configuration + + + Contact email + + + Content Configuration + + + Date/time format + + + + + + Default Administrators Group + + + Default Anonymous Users Group + + + Default language + + + Default time zone + + + Default Users Group + + + Email addresses to notify in case of errors (separate with commas) + + + General Configuration + + + backups for each page (leave empty for no limit) + + + Keep at most + + + Logging level + + + Max file size allowed for upload + + + Max log size + + + items in <code>{RecentChanges}</code> tags + + + Display at most + + + Namespaces + + + Namespaces + + + pages + + + pages + + + + + + + + + Root namespace main page + + + Root namespace theme + + + Security Configuration + + + see also + + + see also + + + Select... + + + Sender email + + + SMTP username and password + + + SMTP server address and port + + + + + + used for email communications + + + Wiki title + + + Wiki URL + + + (GMT-12:00) International Date Line West + + + -720 + + + (GMT-03:30) Newfoundland + + + -210 + + + (GMT-03:00) Greenland + + + -180 + + + (GMT-02:00) Mid-Atlantic + + + -120 + + + (GMT-01:00) Azores + + + -60 + + + (GMT) Greenwich + + + 0 + + + (GMT+01:00) Central European + + + 60 + + + (GMT+02:00) Eastern European + + + 120 + + + (GMT+03:00) Moscow, Baghdad + + + 180 + + + (GMT+03:30) Iran + + + 210 + + + (GMT+04:00) Abu Dhabi, Dubai + + + 240 + + + (GMT-11:00) Midway Island, Samoa + + + -660 + + + (GMT+04:30) Kabul + + + 270 + + + (GMT+05:00) Islamabad, Karachi + + + 300 + + + (GMT+05:30) India + + + 330 + + + (GMT+05:45) Kathmandu + + + 345 + + + (GMT+06:00) Astana, Dhaka + + + 360 + + + (GMT+06:30) Rangoon + + + 390 + + + (GMT+07:00) Bangkok, Jakarta + + + 420 + + + (GMT+08:00) China Coast, Western Australia + + + 480 + + + (GMT+09:00) Japan, Korea + + + 540 + + + (GMT+09:30) Central Australia + + + 570 + + + (GMT-10:00) Hawaii + + + -600 + + + (GMT+10:00) Eastern Australia + + + 600 + + + (GMT+11:00) Magadan, Solomon Island + + + 660 + + + (GMT+12:00) New Zealand, Fiji + + + 720 + + + (GMT+12:45) Chatham Island NZ + + + 765 + + + (GMT+13:00) Tonga, Phoenix Islands + + + 780 + + + (GMT+14:00) Christmas Islands + + + 840 + + + Users must activate their account via Email + + + EMAIL + + + Administrators must activate accounts + + + ADMIN + + + Accounts are active by default + + + AUTO + + + (GMT-09:00) Alaska + + + -540 + + + (GMT-08:00) Pacific + + + -480 + + + (GMT-07:00) Mountain + + + -420 + + + (GMT-06:00) Central + + + -360 + + + (GMT-05:00) Eastern + + + -300 + + + (GMT-04:00) Atlantic + + + -240 + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + All Messages + + + + + + Disable Log + + + + + + Errors Only + + + + + + Disable Moderation + + + No moderation system is used: pages can be edited only by users who have editing permissions + + + Require Page Editing permissions + + + Pages can be edited by users who have editing permissions but not management permissions, and the changes are held in moderation + + + Require Page Viewing permissions + + + Pages can be edited by users who have viewing permissions but not editing permissions, and the changes are help in moderation + + + Warnings and Errors + + + + + + Invalid email address + + + + + + + + + Invalid Wiki URL + + + + + + + + + Invalid email address + + + + + + + + + Invalid SMTP Server address + + + + + + + + + Invalid Wiki Title + + + + + + + + + Cache Cut Size is required + + + + + + + + + Cache Size is required + + + + + + + + + Contact Email is required + + + + + + + + + Date/Time Format is required + + + + + + + + + Wiki URL is required + + + + + + + + + Max File Size is required + + + + + + + + + Max Log Size is required + + + + + + + + + Number is required + + + + + + + + + Sender Email is required + + + + + + + + + SMTP Server is required + + + + + + + + + Wiki Title is required + + + + + + + + + Invalid Cache Cut Size (min. 5, max. 50000) + + + + + + + + + Invalid Size (min. 10, max. 100000) + + + + + + + + + Number must be between 0 and 1000 + + + + + + + + + Invalid File Size (min. 256, max. 102400) + + + + + + + + + Invalid Log Size (min. 16, max. 10240) + + + + + + + + + Number must be between 0 and 50 + + + + + + + + + Invalid Port (min. 1, max. 65535) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/AdminContent.aspx.cs-CZ.resx new file mode 100644 index 0000000..29b2eae --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.cs-CZ.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + Jít + + + Copy the content of the same layout area from the selected Namespace + + + Zkopírovat obsah z: + + + Přístup odepřen text (Globalní) + + + Editovat + + + Zpráva při aktivaci konta (Globalní) + + + Editovat + + + Zpráva při schválení konceptu (Globalní) + + + Editovat + + + Storno + + + Storno a návrat + + + Zpráva při změně diskuze (Globalní) + + + Editovat + + + Poznámka při editaci stránky (pro jmenný prostor) + + + Editovat + + + Patička + + + Editovat globální patičku + + + Hlavička + + + Editovat globální hlavičku + + + HTML Hlavička + + + Editovat HTML Head tag + + + Login poznámka (Globalní) + + + Editovat + + + Zpráva při změně stránky (Globalní) + + + Editovat + + + Patička stránky + + + Editovat patičku stránky + + + Hlavička stránky + + + Editovat hlavičku stránky + + + Zpráva pro postup při resetu hesla (Globalní) + + + Editovat + + + Registrační poznámka (Globalní) + + + Editovat + + + Uložit + + + Uložit obsah + + + Sidebar + + + Editovat sidebar (menu) + + + Zpráva nahrazující informaci nahoře na stránce s odmítnutím přístupu (pokud není prázdná). + + + Email zaslaný nově registrovaným uživatelům pro aktivaci jejich účtu. + + + The email message sent to editor/administrators when a page draft requires approval. + + + Editace obsahu + + + Email zaslaný uživatelům, když je nové zpráva vložena do diskuze. + + + Zpráva zobrazená nahoře stránky při editaci. + + + <b>Poznámka</b>: aktuální laypout aktuálního tématu může být odlišný od zde zobrazeného. + + + Zpráva nahrazující informaci nahoře v obrazovce přihlášení (pokud není prázdná). + + + Jmenný prostor + + + Ostatní prvky + + + The email message sent to users when a page is modified. + + + Obsah stránky + + + Elementy layoutu stránky (Specifické pro jmenný prostor) + + + Email zaslaný uživatelům pokud žádají reset hesla. + + + Zpráva nahrazující informaci nahoře v obrazovce registrace (pokud není prázdná). + + + Administrace + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.da-DK.resx b/WebApplication/App_LocalResources/AdminContent.aspx.da-DK.resx new file mode 100644 index 0000000..5a5c39c --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.da-DK.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Go + + + Copy the content of the same layout area from the selected Namespace + + + Copy content from: + + + Access Denied Notice (Global) + + + Edit + + + Account Activation Message (Global) + + + Edit + + + Approve Draft Message (Global) + + + Edit + + + Cancel + + + Cancel and return + + + Discussion Change Message (Global) + + + Edit + + + Editing Page Notice (Namespace-specific) + + + Edit + + + Footer + + + Edit the global footer + + + Header + + + Edit the global header + + + HTML Head + + + Edit the HTML Head tag + + + Login Notice (Global) + + + Edit + + + Page Change Message (Global) + + + Edit + + + Page Footer + + + Edit the page footer + + + Page Header + + + Edit the page header + + + Password Reset Procedure Message (Global) + + + Edit + + + Register Notice (Global) + + + Edit + + + Save + + + Save the content + + + Sidebar + + + Edit the sidebar (menu) + + + The message replacing the information at the top of the access denied page (if not empty). + + + The email message sent to a newly registered user that have to activate her account. + + + The email message sent to editor/administrators when a page draft requires approval. + + + Content Editing + + + The email message sent to users when a new message is poste to a discussion. + + + The message displayed on top of the editing page. + + + <b>Note</b>: the actual layout of your current theme might differ from the one displayed here. + + + The message replacing the information at the top of the login screen (if not empty). + + + Namespace + + + Other Elements + + + The email message sent to users when a page is modified. + + + Page Content + + + Page Layout Elements (Namespace-Specific) + + + The email message sent to when a user want to reset her password. + + + The message replacing the information at the top of the register screen (if not empty). + + + + + + Administration + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.de-DE.resx b/WebApplication/App_LocalResources/AdminContent.aspx.de-DE.resx new file mode 100644 index 0000000..5a5c39c --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.de-DE.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Go + + + Copy the content of the same layout area from the selected Namespace + + + Copy content from: + + + Access Denied Notice (Global) + + + Edit + + + Account Activation Message (Global) + + + Edit + + + Approve Draft Message (Global) + + + Edit + + + Cancel + + + Cancel and return + + + Discussion Change Message (Global) + + + Edit + + + Editing Page Notice (Namespace-specific) + + + Edit + + + Footer + + + Edit the global footer + + + Header + + + Edit the global header + + + HTML Head + + + Edit the HTML Head tag + + + Login Notice (Global) + + + Edit + + + Page Change Message (Global) + + + Edit + + + Page Footer + + + Edit the page footer + + + Page Header + + + Edit the page header + + + Password Reset Procedure Message (Global) + + + Edit + + + Register Notice (Global) + + + Edit + + + Save + + + Save the content + + + Sidebar + + + Edit the sidebar (menu) + + + The message replacing the information at the top of the access denied page (if not empty). + + + The email message sent to a newly registered user that have to activate her account. + + + The email message sent to editor/administrators when a page draft requires approval. + + + Content Editing + + + The email message sent to users when a new message is poste to a discussion. + + + The message displayed on top of the editing page. + + + <b>Note</b>: the actual layout of your current theme might differ from the one displayed here. + + + The message replacing the information at the top of the login screen (if not empty). + + + Namespace + + + Other Elements + + + The email message sent to users when a page is modified. + + + Page Content + + + Page Layout Elements (Namespace-Specific) + + + The email message sent to when a user want to reset her password. + + + The message replacing the information at the top of the register screen (if not empty). + + + + + + Administration + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.es-ES.resx b/WebApplication/App_LocalResources/AdminContent.aspx.es-ES.resx new file mode 100644 index 0000000..5a5c39c --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.es-ES.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Go + + + Copy the content of the same layout area from the selected Namespace + + + Copy content from: + + + Access Denied Notice (Global) + + + Edit + + + Account Activation Message (Global) + + + Edit + + + Approve Draft Message (Global) + + + Edit + + + Cancel + + + Cancel and return + + + Discussion Change Message (Global) + + + Edit + + + Editing Page Notice (Namespace-specific) + + + Edit + + + Footer + + + Edit the global footer + + + Header + + + Edit the global header + + + HTML Head + + + Edit the HTML Head tag + + + Login Notice (Global) + + + Edit + + + Page Change Message (Global) + + + Edit + + + Page Footer + + + Edit the page footer + + + Page Header + + + Edit the page header + + + Password Reset Procedure Message (Global) + + + Edit + + + Register Notice (Global) + + + Edit + + + Save + + + Save the content + + + Sidebar + + + Edit the sidebar (menu) + + + The message replacing the information at the top of the access denied page (if not empty). + + + The email message sent to a newly registered user that have to activate her account. + + + The email message sent to editor/administrators when a page draft requires approval. + + + Content Editing + + + The email message sent to users when a new message is poste to a discussion. + + + The message displayed on top of the editing page. + + + <b>Note</b>: the actual layout of your current theme might differ from the one displayed here. + + + The message replacing the information at the top of the login screen (if not empty). + + + Namespace + + + Other Elements + + + The email message sent to users when a page is modified. + + + Page Content + + + Page Layout Elements (Namespace-Specific) + + + The email message sent to when a user want to reset her password. + + + The message replacing the information at the top of the register screen (if not empty). + + + + + + Administration + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.fr-FR.resx b/WebApplication/App_LocalResources/AdminContent.aspx.fr-FR.resx new file mode 100644 index 0000000..5a5c39c --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.fr-FR.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Go + + + Copy the content of the same layout area from the selected Namespace + + + Copy content from: + + + Access Denied Notice (Global) + + + Edit + + + Account Activation Message (Global) + + + Edit + + + Approve Draft Message (Global) + + + Edit + + + Cancel + + + Cancel and return + + + Discussion Change Message (Global) + + + Edit + + + Editing Page Notice (Namespace-specific) + + + Edit + + + Footer + + + Edit the global footer + + + Header + + + Edit the global header + + + HTML Head + + + Edit the HTML Head tag + + + Login Notice (Global) + + + Edit + + + Page Change Message (Global) + + + Edit + + + Page Footer + + + Edit the page footer + + + Page Header + + + Edit the page header + + + Password Reset Procedure Message (Global) + + + Edit + + + Register Notice (Global) + + + Edit + + + Save + + + Save the content + + + Sidebar + + + Edit the sidebar (menu) + + + The message replacing the information at the top of the access denied page (if not empty). + + + The email message sent to a newly registered user that have to activate her account. + + + The email message sent to editor/administrators when a page draft requires approval. + + + Content Editing + + + The email message sent to users when a new message is poste to a discussion. + + + The message displayed on top of the editing page. + + + <b>Note</b>: the actual layout of your current theme might differ from the one displayed here. + + + The message replacing the information at the top of the login screen (if not empty). + + + Namespace + + + Other Elements + + + The email message sent to users when a page is modified. + + + Page Content + + + Page Layout Elements (Namespace-Specific) + + + The email message sent to when a user want to reset her password. + + + The message replacing the information at the top of the register screen (if not empty). + + + + + + Administration + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.hu-HU.resx b/WebApplication/App_LocalResources/AdminContent.aspx.hu-HU.resx new file mode 100644 index 0000000..5a5c39c --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.hu-HU.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Go + + + Copy the content of the same layout area from the selected Namespace + + + Copy content from: + + + Access Denied Notice (Global) + + + Edit + + + Account Activation Message (Global) + + + Edit + + + Approve Draft Message (Global) + + + Edit + + + Cancel + + + Cancel and return + + + Discussion Change Message (Global) + + + Edit + + + Editing Page Notice (Namespace-specific) + + + Edit + + + Footer + + + Edit the global footer + + + Header + + + Edit the global header + + + HTML Head + + + Edit the HTML Head tag + + + Login Notice (Global) + + + Edit + + + Page Change Message (Global) + + + Edit + + + Page Footer + + + Edit the page footer + + + Page Header + + + Edit the page header + + + Password Reset Procedure Message (Global) + + + Edit + + + Register Notice (Global) + + + Edit + + + Save + + + Save the content + + + Sidebar + + + Edit the sidebar (menu) + + + The message replacing the information at the top of the access denied page (if not empty). + + + The email message sent to a newly registered user that have to activate her account. + + + The email message sent to editor/administrators when a page draft requires approval. + + + Content Editing + + + The email message sent to users when a new message is poste to a discussion. + + + The message displayed on top of the editing page. + + + <b>Note</b>: the actual layout of your current theme might differ from the one displayed here. + + + The message replacing the information at the top of the login screen (if not empty). + + + Namespace + + + Other Elements + + + The email message sent to users when a page is modified. + + + Page Content + + + Page Layout Elements (Namespace-Specific) + + + The email message sent to when a user want to reset her password. + + + The message replacing the information at the top of the register screen (if not empty). + + + + + + Administration + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.it-IT.resx b/WebApplication/App_LocalResources/AdminContent.aspx.it-IT.resx new file mode 100644 index 0000000..7d8f2da --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.it-IT.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Vai + + + Copia il contenuto dal namespace selezionato + + + Copia contenuto da: + + + Messaggio Accesso negato (Globale) + + + Modifica + + + Messaggio Creazione account (Globale) + + + Modifica + + + Messaggio Approvazione bozza (Globale) + + + Modifica + + + Annulla + + + Annulla e torna indietro + + + Messaggio Modifica discussione (Globale) + + + Modifica + + + Messaggio Modifica Pagina (Specifico per namespace) + + + Modifica + + + Piè di pagina + + + Modifica il piè di pagina globale + + + Intestazione + + + Modifica l'intestazione globale + + + Head HTML + + + Modifica il HEAD + + + Moessaggio Login (Globale) + + + Modifica + + + Messaggio Modifica Pagina (Globale) + + + Modifica + + + Piè di pagina interno + + + Modifica il piè di pagina interno + + + Intestazione interna + + + Modifica l'intestazione interna + + + Messaggio Procedura reset password (Globale) + + + Modifica + + + Messaggio Registrazione (Globale) + + + Modifica + + + Salva + + + Salva il contenuto + + + Barra Laterale + + + Modifica la Barra laterale + + + Il messaggio che sostituisce le informazioni nella pagina di Accesso negato (se presente). + + + Il messaggio email inviato ad un nuovo utente che deve attivare il proprio account. + + + Il messaggio email inviato a editori/amministratori quando una bozza deve essere approvata. + + + Modifica contenuto + + + Il messaggio email inviato quando un nuovo messaggio viene inserito in una discussione. + + + Il messaggio visualizzato nella schermata di modifica. + + + <b>Nota</b>: il layout del tema corrente può essere diverso da quello visualizzato qui. + + + Il messaggio che sostituisce le informazioni nella pagina di Login (se presente). + + + Namespace + + + Altri elementi + + + Il message email inviato quando una pagina viene modificata. + + + Contenuto pagina + + + Elementi layout (specifici per namespace) + + + Il messaggio email inviato ad un utente che necessita di resettare la propria password. + + + Il messaggio che sostituisce le informazioni nella pagina di registrazione (se presente). + + + + + + Amministrazione + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.nb-NO.resx b/WebApplication/App_LocalResources/AdminContent.aspx.nb-NO.resx new file mode 100644 index 0000000..5a5c39c --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.nb-NO.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Go + + + Copy the content of the same layout area from the selected Namespace + + + Copy content from: + + + Access Denied Notice (Global) + + + Edit + + + Account Activation Message (Global) + + + Edit + + + Approve Draft Message (Global) + + + Edit + + + Cancel + + + Cancel and return + + + Discussion Change Message (Global) + + + Edit + + + Editing Page Notice (Namespace-specific) + + + Edit + + + Footer + + + Edit the global footer + + + Header + + + Edit the global header + + + HTML Head + + + Edit the HTML Head tag + + + Login Notice (Global) + + + Edit + + + Page Change Message (Global) + + + Edit + + + Page Footer + + + Edit the page footer + + + Page Header + + + Edit the page header + + + Password Reset Procedure Message (Global) + + + Edit + + + Register Notice (Global) + + + Edit + + + Save + + + Save the content + + + Sidebar + + + Edit the sidebar (menu) + + + The message replacing the information at the top of the access denied page (if not empty). + + + The email message sent to a newly registered user that have to activate her account. + + + The email message sent to editor/administrators when a page draft requires approval. + + + Content Editing + + + The email message sent to users when a new message is poste to a discussion. + + + The message displayed on top of the editing page. + + + <b>Note</b>: the actual layout of your current theme might differ from the one displayed here. + + + The message replacing the information at the top of the login screen (if not empty). + + + Namespace + + + Other Elements + + + The email message sent to users when a page is modified. + + + Page Content + + + Page Layout Elements (Namespace-Specific) + + + The email message sent to when a user want to reset her password. + + + The message replacing the information at the top of the register screen (if not empty). + + + + + + Administration + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.nl-NL.resx b/WebApplication/App_LocalResources/AdminContent.aspx.nl-NL.resx new file mode 100644 index 0000000..5a5c39c --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.nl-NL.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Go + + + Copy the content of the same layout area from the selected Namespace + + + Copy content from: + + + Access Denied Notice (Global) + + + Edit + + + Account Activation Message (Global) + + + Edit + + + Approve Draft Message (Global) + + + Edit + + + Cancel + + + Cancel and return + + + Discussion Change Message (Global) + + + Edit + + + Editing Page Notice (Namespace-specific) + + + Edit + + + Footer + + + Edit the global footer + + + Header + + + Edit the global header + + + HTML Head + + + Edit the HTML Head tag + + + Login Notice (Global) + + + Edit + + + Page Change Message (Global) + + + Edit + + + Page Footer + + + Edit the page footer + + + Page Header + + + Edit the page header + + + Password Reset Procedure Message (Global) + + + Edit + + + Register Notice (Global) + + + Edit + + + Save + + + Save the content + + + Sidebar + + + Edit the sidebar (menu) + + + The message replacing the information at the top of the access denied page (if not empty). + + + The email message sent to a newly registered user that have to activate her account. + + + The email message sent to editor/administrators when a page draft requires approval. + + + Content Editing + + + The email message sent to users when a new message is poste to a discussion. + + + The message displayed on top of the editing page. + + + <b>Note</b>: the actual layout of your current theme might differ from the one displayed here. + + + The message replacing the information at the top of the login screen (if not empty). + + + Namespace + + + Other Elements + + + The email message sent to users when a page is modified. + + + Page Content + + + Page Layout Elements (Namespace-Specific) + + + The email message sent to when a user want to reset her password. + + + The message replacing the information at the top of the register screen (if not empty). + + + + + + Administration + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.pl-PL.resx b/WebApplication/App_LocalResources/AdminContent.aspx.pl-PL.resx new file mode 100644 index 0000000..45cadd2 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.pl-PL.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Access Denied Notice (Global) + + + Edit + + + Account Activation Message (Global) + + + Edit + + + Approve Draft Message (Global) + + + Edit + + + Cancel + + + Cancel and return + + + Discussion Change Message (Global) + + + Edit + + + Editing Page Notice (Namespace-specific) + + + Edit + + + Footer + + + Edit the global footer + + + Header + + + Edit the global header + + + HTML Head + + + Edit the HTML Head tag + + + Login Notice (Global) + + + Edit + + + Page Change Message (Global) + + + Edit + + + Page Footer + + + Edit the page footer + + + Page Header + + + Edit the page header + + + Password Reset Procedure Message (Global) + + + Edit + + + Register Notice (Global) + + + Edit + + + Save + + + Save the content + + + Sidebar + + + Edit the sidebar (menu) + + + The message replacing the information at the top of the access denied page (if not empty). + + + The email message sent to a newly registered user that have to activate her account. + + + The email message sent to editor/administrators when a page draft requires approval. + + + Content Editing + + + The email message sent to users when a new message is poste to a discussion. + + + The message displayed on top of the editing page. + + + <b>Note</b>: the actual layout of your current theme might differ from the one displayed here. + + + The message replacing the information at the top of the login screen (if not empty). + + + Namespace + + + Other Elements + + + The email message sent to users when a page is modified. + + + Page Content + + + Page Layout Elements (Namespace-Specific) + + + The email message sent to when a user want to reset her password. + + + The message replacing the information at the top of the register screen (if not empty). + + + + + + Administration + + + + + + + + + + + + + + + Go + + + Copy the content of the same layout area from the selected Namespace + + + Copy content from: + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.pt-BR.resx b/WebApplication/App_LocalResources/AdminContent.aspx.pt-BR.resx new file mode 100644 index 0000000..5a5c39c --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.pt-BR.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Go + + + Copy the content of the same layout area from the selected Namespace + + + Copy content from: + + + Access Denied Notice (Global) + + + Edit + + + Account Activation Message (Global) + + + Edit + + + Approve Draft Message (Global) + + + Edit + + + Cancel + + + Cancel and return + + + Discussion Change Message (Global) + + + Edit + + + Editing Page Notice (Namespace-specific) + + + Edit + + + Footer + + + Edit the global footer + + + Header + + + Edit the global header + + + HTML Head + + + Edit the HTML Head tag + + + Login Notice (Global) + + + Edit + + + Page Change Message (Global) + + + Edit + + + Page Footer + + + Edit the page footer + + + Page Header + + + Edit the page header + + + Password Reset Procedure Message (Global) + + + Edit + + + Register Notice (Global) + + + Edit + + + Save + + + Save the content + + + Sidebar + + + Edit the sidebar (menu) + + + The message replacing the information at the top of the access denied page (if not empty). + + + The email message sent to a newly registered user that have to activate her account. + + + The email message sent to editor/administrators when a page draft requires approval. + + + Content Editing + + + The email message sent to users when a new message is poste to a discussion. + + + The message displayed on top of the editing page. + + + <b>Note</b>: the actual layout of your current theme might differ from the one displayed here. + + + The message replacing the information at the top of the login screen (if not empty). + + + Namespace + + + Other Elements + + + The email message sent to users when a page is modified. + + + Page Content + + + Page Layout Elements (Namespace-Specific) + + + The email message sent to when a user want to reset her password. + + + The message replacing the information at the top of the register screen (if not empty). + + + + + + Administration + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.resx b/WebApplication/App_LocalResources/AdminContent.aspx.resx new file mode 100644 index 0000000..c25eace --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Access Denied Notice (Global) + + + Edit + + + Account Activation Message (Global) + + + Edit + + + Approve Draft Message (Global) + + + Edit + + + Cancel + + + Cancel and return + + + Discussion Change Message (Global) + + + Edit + + + Editing Page Notice (Namespace-specific) + + + Edit + + + Footer + + + Edit the global footer + + + Header + + + Edit the global header + + + HTML Head + + + Edit the HTML Head tag + + + Login Notice (Global) + + + Edit + + + Page Change Message (Global) + + + Edit + + + Page Footer + + + Edit the page footer + + + Page Header + + + Edit the page header + + + Password Reset Procedure Message (Global) + + + Edit + + + Register Notice (Global) + + + Edit + + + Save + + + Save the content + + + Sidebar + + + Edit the sidebar (menu) + + + The message replacing the information at the top of the access denied page (if not empty). + + + The email message sent to a newly registered user that have to activate her account. + + + The email message sent to editor/administrators when a page draft requires approval. + + + Content Editing + + + The email message sent to users when a new message is posted to a discussion. + + + The message displayed on top of the editing page. + + + <b>Note</b>: the actual layout of your current theme might differ from the one displayed here. + + + The message replacing the information at the top of the login screen (if not empty). + + + Namespace + + + Other Elements + + + The email message sent to users when a page is modified. + + + Page Content + + + Page Layout Elements (Namespace-Specific) + + + The email message sent to when a user want to reset her password. + + + The message replacing the information at the top of the register screen (if not empty). + + + + + + Administration + + + + + + + + + + + + + + + Go + + + Copy the content of the same layout area from the selected Namespace + + + Copy content from: + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.ro-RO.resx b/WebApplication/App_LocalResources/AdminContent.aspx.ro-RO.resx new file mode 100644 index 0000000..5a5c39c --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.ro-RO.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Go + + + Copy the content of the same layout area from the selected Namespace + + + Copy content from: + + + Access Denied Notice (Global) + + + Edit + + + Account Activation Message (Global) + + + Edit + + + Approve Draft Message (Global) + + + Edit + + + Cancel + + + Cancel and return + + + Discussion Change Message (Global) + + + Edit + + + Editing Page Notice (Namespace-specific) + + + Edit + + + Footer + + + Edit the global footer + + + Header + + + Edit the global header + + + HTML Head + + + Edit the HTML Head tag + + + Login Notice (Global) + + + Edit + + + Page Change Message (Global) + + + Edit + + + Page Footer + + + Edit the page footer + + + Page Header + + + Edit the page header + + + Password Reset Procedure Message (Global) + + + Edit + + + Register Notice (Global) + + + Edit + + + Save + + + Save the content + + + Sidebar + + + Edit the sidebar (menu) + + + The message replacing the information at the top of the access denied page (if not empty). + + + The email message sent to a newly registered user that have to activate her account. + + + The email message sent to editor/administrators when a page draft requires approval. + + + Content Editing + + + The email message sent to users when a new message is poste to a discussion. + + + The message displayed on top of the editing page. + + + <b>Note</b>: the actual layout of your current theme might differ from the one displayed here. + + + The message replacing the information at the top of the login screen (if not empty). + + + Namespace + + + Other Elements + + + The email message sent to users when a page is modified. + + + Page Content + + + Page Layout Elements (Namespace-Specific) + + + The email message sent to when a user want to reset her password. + + + The message replacing the information at the top of the register screen (if not empty). + + + + + + Administration + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.ru-RU.resx b/WebApplication/App_LocalResources/AdminContent.aspx.ru-RU.resx new file mode 100644 index 0000000..72727a7 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.ru-RU.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Вперёд + + + Копировать содержание в этот шаблон из указанного Пространства Имён + + + Копировать содержание: + + + Сообщение о запрете доступа (Общее) + + + Изменить + + + Сообщение об активации записи (Общее) + + + Изменить + + + Сообщение об утверждении черновика (Общее) + + + Изменить + + + Отмена + + + Отмена и возврат + + + Сообщение об обновлениях в обсуждении (Общее) + + + Изменить + + + Уведомление на странице редактирования (в конкретном Пространстве Имён) + + + Изменить + + + Подвал + + + Изменить общий подвал Wiki + + + Шапка + + + Изменить общую шапку Wiki + + + HTML-шапка + + + Изменить тэги HTML-шапки + + + Текст страницы авторизации (Общее) + + + Изменить + + + Сообщение об изменении страницы (Общее) + + + Изменить + + + Подвал страницы + + + Изменить подвал страницы (сразу после содержания) + + + Шапка страницы + + + Изменить шапку страницы (до содержания) + + + Сообщение процедуры восстановления доступа (Общее) + + + Изменить + + + Текст страницы регистрации (Общее) + + + Изменить + + + Сохранить + + + Сохранить содержание + + + Меню + + + Изменить основное меню страниц Wiki + + + Заменит текст страницы о запрете доступа (если заполнено). + + + Email-сообщение новым пользователям для активации учётной записи + + + Email-сообщение редакторам/администраторам, об ожидающем утверждения черновике + + + Изменение содержания + + + Email-сообщение о размещении нового мнения посетителя в разделе обсуждения страницы + + + Этот текст будет отображаться до формы редактирования страницы + + + <b>Примечание</b>: фактическое оформление текущей темы может отличаться от того, что Вы здесь наблюдаете. + + + Заменит текст страницы авторизации (если заполнено). + + + Пространство Имён + + + Другие элементы + + + Email-сообщение об изменении страницы + + + Содержание страницы + + + Элементы шаблона страниц (конкретного Пространства Имён) + + + Email-сообщение в ответ на запрос процедуры восстановления доступа + + + Заменит текст страницы регистрации (если заполнено). + + + + + + Администрирование + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.sk-SK.resx b/WebApplication/App_LocalResources/AdminContent.aspx.sk-SK.resx new file mode 100644 index 0000000..5a5c39c --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.sk-SK.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Go + + + Copy the content of the same layout area from the selected Namespace + + + Copy content from: + + + Access Denied Notice (Global) + + + Edit + + + Account Activation Message (Global) + + + Edit + + + Approve Draft Message (Global) + + + Edit + + + Cancel + + + Cancel and return + + + Discussion Change Message (Global) + + + Edit + + + Editing Page Notice (Namespace-specific) + + + Edit + + + Footer + + + Edit the global footer + + + Header + + + Edit the global header + + + HTML Head + + + Edit the HTML Head tag + + + Login Notice (Global) + + + Edit + + + Page Change Message (Global) + + + Edit + + + Page Footer + + + Edit the page footer + + + Page Header + + + Edit the page header + + + Password Reset Procedure Message (Global) + + + Edit + + + Register Notice (Global) + + + Edit + + + Save + + + Save the content + + + Sidebar + + + Edit the sidebar (menu) + + + The message replacing the information at the top of the access denied page (if not empty). + + + The email message sent to a newly registered user that have to activate her account. + + + The email message sent to editor/administrators when a page draft requires approval. + + + Content Editing + + + The email message sent to users when a new message is poste to a discussion. + + + The message displayed on top of the editing page. + + + <b>Note</b>: the actual layout of your current theme might differ from the one displayed here. + + + The message replacing the information at the top of the login screen (if not empty). + + + Namespace + + + Other Elements + + + The email message sent to users when a page is modified. + + + Page Content + + + Page Layout Elements (Namespace-Specific) + + + The email message sent to when a user want to reset her password. + + + The message replacing the information at the top of the register screen (if not empty). + + + + + + Administration + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AdminContent.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..5a5c39c --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.sr-Latn-CS.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Go + + + Copy the content of the same layout area from the selected Namespace + + + Copy content from: + + + Access Denied Notice (Global) + + + Edit + + + Account Activation Message (Global) + + + Edit + + + Approve Draft Message (Global) + + + Edit + + + Cancel + + + Cancel and return + + + Discussion Change Message (Global) + + + Edit + + + Editing Page Notice (Namespace-specific) + + + Edit + + + Footer + + + Edit the global footer + + + Header + + + Edit the global header + + + HTML Head + + + Edit the HTML Head tag + + + Login Notice (Global) + + + Edit + + + Page Change Message (Global) + + + Edit + + + Page Footer + + + Edit the page footer + + + Page Header + + + Edit the page header + + + Password Reset Procedure Message (Global) + + + Edit + + + Register Notice (Global) + + + Edit + + + Save + + + Save the content + + + Sidebar + + + Edit the sidebar (menu) + + + The message replacing the information at the top of the access denied page (if not empty). + + + The email message sent to a newly registered user that have to activate her account. + + + The email message sent to editor/administrators when a page draft requires approval. + + + Content Editing + + + The email message sent to users when a new message is poste to a discussion. + + + The message displayed on top of the editing page. + + + <b>Note</b>: the actual layout of your current theme might differ from the one displayed here. + + + The message replacing the information at the top of the login screen (if not empty). + + + Namespace + + + Other Elements + + + The email message sent to users when a page is modified. + + + Page Content + + + Page Layout Elements (Namespace-Specific) + + + The email message sent to when a user want to reset her password. + + + The message replacing the information at the top of the register screen (if not empty). + + + + + + Administration + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.tr-TR.resx b/WebApplication/App_LocalResources/AdminContent.aspx.tr-TR.resx new file mode 100644 index 0000000..3d4c5c4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.tr-TR.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Go + + + Copy the content of the same layout area from the selected Namespace + + + Copy content from: + + + Erişim engellendi notu (genel) + + + Düzenle + + + Hesap etkinleştirme mesajı (genel) + + + Düzenle + + + Taslak onaylama mesajı (genel) + + + Düzenle + + + İptal + + + İptal et ve geri dön + + + Tartışma değişim mesajı (genel) + + + Düzenle + + + Sayfa düzenleniyor mesajı (ad alanına özel) + + + Düzenle + + + Alt kısım + + + Genel alt kısmı düzenle + + + Üst kısım + + + Genel üst kısmı düzenle + + + HTML Head kısmı + + + HTML Head kısmını düzenle + + + Giriş notu (genel) + + + Düzenle + + + Sayfa değişim notu (genel) + + + Düzenle + + + Sayfa alt kısmı + + + Sayfa alt kısmını düzenle + + + Sayfa başlığı + + + Sayfa başlığını düzenle + + + Parola sıfırlama notu (genel) + + + Düzenle + + + Kayıt notu (genel) + + + Düzenle + + + Kaydet + + + İçeriği kaydet + + + Yan çubuk + + + Yan menüyü düzenle + + + Mesaj, "Erişim engellendi" sayfasının üst kısmındaki bilgiyi değiştirecektir. + + + Hesabını aktifleştirmesi gereken yeni kullanıcıya gönderilecek e-posta mesajı + + + Editör veya yöneticilere bir sayfa veya taslağın onay beklediğini haber veren e-posta mesajı. + + + İçerik düzenleme + + + Bir tartışmaya mesaj gönderildiğinde kullanıcıları bilgilendiren e-posta mesajı + + + Düzenlenen sayfanın üstünde yazan not + + + <b>Not</b>: gerçek yerleşim temaya göre buradakinden farklı olabilir. + + + Giriş ekranındaki notun yerine gelecek mesaj + + + Ad alanı + + + Diğer bileşenler + + + Sayfa değiştiğinde kullanıcılara gönderilen e-posta mesajı + + + Sayfa içeriği + + + Sayfa yerleşim bileşenleri (ad alanına özel) + + + Bir kullanıcı parolasını fırlamak istediğinde gönderilen e-posta mesajı + + + Bu mesaj kayıt ekranının üstündeki notu değiştirir. + + + + + + Yönetim + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.uk-UA.resx b/WebApplication/App_LocalResources/AdminContent.aspx.uk-UA.resx new file mode 100644 index 0000000..81b5747 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.uk-UA.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Вперед + + + Копіювати в цей макет зміст з обраного Простору Імен + + + Копіювати зміст: + + + Повідомлення про заборону доступу (Загальне) + + + Змінити + + + Повідомлення про активацію облікового запису (Загальне) + + + Змінити + + + Повідомлення про затвердження чернетки (Загальне) + + + Змінити + + + Відмінити + + + Відмінити та повернутися + + + Повідомлення про оновлення в обговоренні (Загальне) + + + Змінити + + + Текст на сторінці редагування тексту (визначеного Простору Імен) + + + Змінити + + + Підвал + + + Змінити загальний підвал Wiki + + + Шапка + + + Змінити загальну шапку Wiki + + + HTML-шапка + + + Змінити теги HTML-шапки + + + Текст на сторінці авторизації (Загальне) + + + Змінити + + + Повідомлення про оновлення сторінки (Загальне) + + + Змінити + + + Підвал сторінок + + + Змінити підвал сторінок + + + Шапка сторінок + + + Змінити шапку сторінок + + + Повідомлення процедури відновлення доступу (Загальне) + + + Змінити + + + Текст сторінки реєстрації (Загальне) + + + Змінити + + + Зберегти + + + Зберегти текст + + + Меню + + + Змінити меню Wiki + + + Змінить текст сторінки про заборону доступу (якщо заповнено). + + + Email-повідомлення новим користувачам щодо активації облікового запису + + + Email-повідомлення редакторам/адміністраторам, про чернетку, яка очікує затвердження в якості сторінки + + + Зміни змісту + + + Email-повідомлення про оновлення розділу обговорення сторінки + + + Цей текст буде відображено до форми редактора сторінки + + + <b>Примітка</b>: фактичне оформлення сторінок може відрізнятися від того, що Ви тут бачите. + + + Змінить текст сторінки авторизації (якщо заповнено). + + + Простір Імен + + + Інші Елементи + + + Email-повідомлення про зміни змісту сторінки + + + Зміст сторінки + + + Елементи шаблону сторінок (визначеного Простору Імен) + + + Email-повідомлення у відповідь на запит процедури відновлення доступу + + + Змінить текст сторінки реєстрації (якщо заповнено). + + + + + + Адміністрування + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.zh-cn.resx b/WebApplication/App_LocalResources/AdminContent.aspx.zh-cn.resx new file mode 100644 index 0000000..78d3ad2 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.zh-cn.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Go + + + 从选中的命名空间复制相同布局区域的内容 + + + 复制内容,从: + + + 禁止访问消息(全局) + + + 编辑 + + + 帐户激活消息(全局) + + + 编辑 + + + 认可草稿消息(全局) + + + 编辑 + + + 取消 + + + 取消并返回 + + + 讨论更改消息(全局) + + + 编辑 + + + 编辑页面消息(命名空间相关) + + + 编辑 + + + 页脚 + + + 编辑全局页脚 + + + 页眉 + + + 编辑全局页眉 + + + HTML Head + + + 编辑 HTML Head 标签 + + + 登录消息(全局) + + + 编辑 + + + 页面更改消息(全局) + + + 编辑 + + + 页面页脚 + + + 编辑页面页脚 + + + 页面页眉 + + + 编辑页面页眉 + + + 重置密码步骤消息(全局) + + + 编辑 + + + 注册消息(全局) + + + 编辑 + + + 保存 + + + 保存内容 + + + 边栏 + + + 编辑边栏(菜单) + + + 用于替换禁止访问页面顶部信息的消息(如果非空)。 + + + 发送给新注册用户的关于帐户激活的电子邮件消息。 + + + 页面需要认可时发送给编辑者/管理员的电子邮件消息。 + + + 内容编辑 + + + 新讨论消息发布后发送给用户的电子邮件消息。 + + + 在编辑页面顶部显示的消息。 + + + <b>注意</b>:您当前主题的实际布局可能与此处显示的不同。 + + + 用于替换登录页面顶部信息的消息(如果非空)。 + + + 命名空间 + + + 其它元素 + + + 页面更改后发送给用户的电子邮件消息。 + + + 页面内容 + + + 页面布局元素(命名空间相关) + + + 重置密码后发送给用户的电子邮件消息。 + + + 用于替换注册页面顶部信息的消息(如果非空)。 + + + + + + 管理 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminContent.aspx.zh-tw.resx b/WebApplication/App_LocalResources/AdminContent.aspx.zh-tw.resx new file mode 100644 index 0000000..5a5c39c --- /dev/null +++ b/WebApplication/App_LocalResources/AdminContent.aspx.zh-tw.resx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Go + + + Copy the content of the same layout area from the selected Namespace + + + Copy content from: + + + Access Denied Notice (Global) + + + Edit + + + Account Activation Message (Global) + + + Edit + + + Approve Draft Message (Global) + + + Edit + + + Cancel + + + Cancel and return + + + Discussion Change Message (Global) + + + Edit + + + Editing Page Notice (Namespace-specific) + + + Edit + + + Footer + + + Edit the global footer + + + Header + + + Edit the global header + + + HTML Head + + + Edit the HTML Head tag + + + Login Notice (Global) + + + Edit + + + Page Change Message (Global) + + + Edit + + + Page Footer + + + Edit the page footer + + + Page Header + + + Edit the page header + + + Password Reset Procedure Message (Global) + + + Edit + + + Register Notice (Global) + + + Edit + + + Save + + + Save the content + + + Sidebar + + + Edit the sidebar (menu) + + + The message replacing the information at the top of the access denied page (if not empty). + + + The email message sent to a newly registered user that have to activate her account. + + + The email message sent to editor/administrators when a page draft requires approval. + + + Content Editing + + + The email message sent to users when a new message is poste to a discussion. + + + The message displayed on top of the editing page. + + + <b>Note</b>: the actual layout of your current theme might differ from the one displayed here. + + + The message replacing the information at the top of the login screen (if not empty). + + + Namespace + + + Other Elements + + + The email message sent to users when a page is modified. + + + Page Content + + + Page Layout Elements (Namespace-Specific) + + + The email message sent to when a user want to reset her password. + + + The message replacing the information at the top of the register screen (if not empty). + + + + + + Administration + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.cs-CZ.resx new file mode 100644 index 0000000..d699797 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.cs-CZ.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Storno + + + Storno a návrat do seznamu skupin + + + Vytvořit skupinu + + + Uložit novou skupinu + + + Smazat + + + Smazat skupinu + + + Nová skupina + + + Vytvořit novou uživatelskou skupinu + + + Uložit skupinu + + + Uložit změny + + + Vybrat + + + Vybrat tuto skupinu uživatelů + + + Vybrat + + + Vybrat tuto skupinu uživatelů + + + Název je již používán + + + Popis + + + Popis + + + Detail skupiny + + + Globalní oprávnění + + + Skupiny uživatele + + + Název + + + Název + + + Poskytovatel + + + Poskytovatel + + + Uživatelé + + + Administrace + + + Neplatný název + + + Je vyžadován název + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.da-DK.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.da-DK.resx new file mode 100644 index 0000000..8f903c6 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.da-DK.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the Group list + + + Create Group + + + Save the new Group + + + Delete + + + Delete the Group + + + New Group + + + Create a new User Group + + + Save Group + + + Save modifications + + + Select + + + Select this User Group + + + Select + + + Select this User Group + + + Name already in use + + + + + + + + + Description + + + Description + + + Group Details + + + Global Permissions + + + User Groups + + + Name + + + Name + + + Provider + + + Provider + + + + + + + + + Users + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Name + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.de-DE.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.de-DE.resx new file mode 100644 index 0000000..8f903c6 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.de-DE.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the Group list + + + Create Group + + + Save the new Group + + + Delete + + + Delete the Group + + + New Group + + + Create a new User Group + + + Save Group + + + Save modifications + + + Select + + + Select this User Group + + + Select + + + Select this User Group + + + Name already in use + + + + + + + + + Description + + + Description + + + Group Details + + + Global Permissions + + + User Groups + + + Name + + + Name + + + Provider + + + Provider + + + + + + + + + Users + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Name + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.es-ES.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.es-ES.resx new file mode 100644 index 0000000..8f903c6 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.es-ES.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the Group list + + + Create Group + + + Save the new Group + + + Delete + + + Delete the Group + + + New Group + + + Create a new User Group + + + Save Group + + + Save modifications + + + Select + + + Select this User Group + + + Select + + + Select this User Group + + + Name already in use + + + + + + + + + Description + + + Description + + + Group Details + + + Global Permissions + + + User Groups + + + Name + + + Name + + + Provider + + + Provider + + + + + + + + + Users + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Name + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.fr-FR.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.fr-FR.resx new file mode 100644 index 0000000..8f903c6 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.fr-FR.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the Group list + + + Create Group + + + Save the new Group + + + Delete + + + Delete the Group + + + New Group + + + Create a new User Group + + + Save Group + + + Save modifications + + + Select + + + Select this User Group + + + Select + + + Select this User Group + + + Name already in use + + + + + + + + + Description + + + Description + + + Group Details + + + Global Permissions + + + User Groups + + + Name + + + Name + + + Provider + + + Provider + + + + + + + + + Users + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Name + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.hu-HU.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.hu-HU.resx new file mode 100644 index 0000000..8f903c6 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.hu-HU.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the Group list + + + Create Group + + + Save the new Group + + + Delete + + + Delete the Group + + + New Group + + + Create a new User Group + + + Save Group + + + Save modifications + + + Select + + + Select this User Group + + + Select + + + Select this User Group + + + Name already in use + + + + + + + + + Description + + + Description + + + Group Details + + + Global Permissions + + + User Groups + + + Name + + + Name + + + Provider + + + Provider + + + + + + + + + Users + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Name + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.it-IT.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.it-IT.resx new file mode 100644 index 0000000..dccb540 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.it-IT.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Annulla + + + Annulla e torna alla lista gruppi + + + Crea gruppo + + + Salva il nuovo gruppo + + + Elimina + + + Elimina il gruppo + + + Nuovo gruppo + + + Crea un nuovo gruppo + + + Salva gruppo + + + Salva modifiche + + + Seleziona + + + Seleziona questo gruppo + + + Seleziona + + + Seleziona questo gruppo + + + Nome già in uso + + + + + + + + + Descrizione + + + Descrizione + + + Dettagli gruppo + + + Permessi globali + + + Gruppi Utente + + + Nome + + + Nome + + + Provider + + + Provider + + + + + + + + + Utenti + + + Amministrazione + + + + + + + + + + + + + + + + + + + + + Nome non valido + + + + + + + + + Nome richiesto + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.nb-NO.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.nb-NO.resx new file mode 100644 index 0000000..8f903c6 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.nb-NO.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the Group list + + + Create Group + + + Save the new Group + + + Delete + + + Delete the Group + + + New Group + + + Create a new User Group + + + Save Group + + + Save modifications + + + Select + + + Select this User Group + + + Select + + + Select this User Group + + + Name already in use + + + + + + + + + Description + + + Description + + + Group Details + + + Global Permissions + + + User Groups + + + Name + + + Name + + + Provider + + + Provider + + + + + + + + + Users + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Name + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.nl-NL.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.nl-NL.resx new file mode 100644 index 0000000..8f903c6 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.nl-NL.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the Group list + + + Create Group + + + Save the new Group + + + Delete + + + Delete the Group + + + New Group + + + Create a new User Group + + + Save Group + + + Save modifications + + + Select + + + Select this User Group + + + Select + + + Select this User Group + + + Name already in use + + + + + + + + + Description + + + Description + + + Group Details + + + Global Permissions + + + User Groups + + + Name + + + Name + + + Provider + + + Provider + + + + + + + + + Users + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Name + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.pl-PL.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.pl-PL.resx new file mode 100644 index 0000000..8f903c6 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.pl-PL.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the Group list + + + Create Group + + + Save the new Group + + + Delete + + + Delete the Group + + + New Group + + + Create a new User Group + + + Save Group + + + Save modifications + + + Select + + + Select this User Group + + + Select + + + Select this User Group + + + Name already in use + + + + + + + + + Description + + + Description + + + Group Details + + + Global Permissions + + + User Groups + + + Name + + + Name + + + Provider + + + Provider + + + + + + + + + Users + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Name + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.pt-BR.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.pt-BR.resx new file mode 100644 index 0000000..8f903c6 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.pt-BR.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the Group list + + + Create Group + + + Save the new Group + + + Delete + + + Delete the Group + + + New Group + + + Create a new User Group + + + Save Group + + + Save modifications + + + Select + + + Select this User Group + + + Select + + + Select this User Group + + + Name already in use + + + + + + + + + Description + + + Description + + + Group Details + + + Global Permissions + + + User Groups + + + Name + + + Name + + + Provider + + + Provider + + + + + + + + + Users + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Name + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.resx new file mode 100644 index 0000000..8f903c6 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the Group list + + + Create Group + + + Save the new Group + + + Delete + + + Delete the Group + + + New Group + + + Create a new User Group + + + Save Group + + + Save modifications + + + Select + + + Select this User Group + + + Select + + + Select this User Group + + + Name already in use + + + + + + + + + Description + + + Description + + + Group Details + + + Global Permissions + + + User Groups + + + Name + + + Name + + + Provider + + + Provider + + + + + + + + + Users + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Name + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.ro-RO.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.ro-RO.resx new file mode 100644 index 0000000..8f903c6 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.ro-RO.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the Group list + + + Create Group + + + Save the new Group + + + Delete + + + Delete the Group + + + New Group + + + Create a new User Group + + + Save Group + + + Save modifications + + + Select + + + Select this User Group + + + Select + + + Select this User Group + + + Name already in use + + + + + + + + + Description + + + Description + + + Group Details + + + Global Permissions + + + User Groups + + + Name + + + Name + + + Provider + + + Provider + + + + + + + + + Users + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Name + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.ru-RU.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.ru-RU.resx new file mode 100644 index 0000000..20227af --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.ru-RU.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Отмена + + + Отмена и возврат к списку Групп + + + Создать Группу + + + Сохранить новую Группу + + + Удалить + + + Удалить эту Группу + + + Новая Группа + + + Создать новую Группу Пользователей + + + Сохранить Группу + + + Сохранить изменения + + + Выбрать + + + Выбрать эту Группу Пользователей + + + Выбрать + + + Выбрать эту Группу Пользователей + + + Такое Название Используется + + + + + + + + + Описание + + + Описание + + + Подробности Группы + + + Общие Разрешения + + + Группы Пользователей + + + Название + + + Название + + + Провайдер + + + Провайдер + + + + + + + + + Пользователи + + + Администрация + + + + + + + + + + + + + + + + + + + + + Неверное Название + + + + + + + + + Название уже используется + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.sk-SK.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.sk-SK.resx new file mode 100644 index 0000000..8f903c6 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.sk-SK.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the Group list + + + Create Group + + + Save the new Group + + + Delete + + + Delete the Group + + + New Group + + + Create a new User Group + + + Save Group + + + Save modifications + + + Select + + + Select this User Group + + + Select + + + Select this User Group + + + Name already in use + + + + + + + + + Description + + + Description + + + Group Details + + + Global Permissions + + + User Groups + + + Name + + + Name + + + Provider + + + Provider + + + + + + + + + Users + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Name + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..8f903c6 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.sr-Latn-CS.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the Group list + + + Create Group + + + Save the new Group + + + Delete + + + Delete the Group + + + New Group + + + Create a new User Group + + + Save Group + + + Save modifications + + + Select + + + Select this User Group + + + Select + + + Select this User Group + + + Name already in use + + + + + + + + + Description + + + Description + + + Group Details + + + Global Permissions + + + User Groups + + + Name + + + Name + + + Provider + + + Provider + + + + + + + + + Users + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Name + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.tr-TR.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.tr-TR.resx new file mode 100644 index 0000000..0c8fe4a --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.tr-TR.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Vazgeç + + + Vazgeç ve gurup listesine dön + + + Gurup Oluştur + + + Yeni gurubu kaydet + + + Sil + + + Gurubu sil + + + Yeni gurup + + + Yeni bir kullanıcı gurubu oluştur + + + Gurubu kaydet + + + Düzenlemeleri kaydet + + + Seç + + + Bu kullanıcı gurubunu seç + + + Seç + + + Bu kullanıcı gurubunu seç + + + Bu ad kullanımdadır + + + + + + + + + Açıklama + + + Açıklama + + + Gurup Detayları + + + Genel İzinler + + + Kullanıcı Gurupları + + + Ad + + + Ad + + + Sağlayıcı + + + Sağlayıcı + + + + + + + + + Kullanıcılar + + + Yönetim + + + + + + + + + + + + + + + + + + + + + Geçersiz ad + + + + + + + + + Ad gereklidir + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.uk-UA.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.uk-UA.resx new file mode 100644 index 0000000..2dd0393 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.uk-UA.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Відміна + + + Відміна та повернення до переліку Груп + + + Створити Групу + + + Зберегти нову Групу + + + Видалити + + + Видалити цю Групу + + + Нова Група + + + Створити нову Групу Користувачів + + + Зберегти Групу + + + Зберегти зміни + + + Обрати + + + Обрати цю Групу Користувачів + + + Обрати + + + Обрати цю Групу Користувачів + + + Така Назва використовується + + + + + + + + + Опис + + + Опис + + + Подробиці Групи + + + Загальні Дозволи + + + Групи Користувачів + + + Назва + + + Назва + + + Несуча + + + Несуча + + + + + + + + + Користувачі + + + Адміністрація + + + + + + + + + + + + + + + + + + + + + Помилкова Назва + + + + + + + + + Назва вже використовується + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.zh-cn.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.zh-cn.resx new file mode 100644 index 0000000..2c9e692 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.zh-cn.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 取消 + + + 取消并返回组列表 + + + 创建组 + + + 保存新组 + + + 删除 + + + 删除组 + + + 新建组 + + + 创建新用户组 + + + 保存组 + + + 保存修改 + + + 选择 + + + 选择此用户组 + + + 选择 + + + 选择此用户组 + + + 名称已被使用 + + + + + + + + + 描述 + + + 描述 + + + 组详情 + + + 全局权限 + + + 用户组 + + + 名称 + + + 名称 + + + 提供者 + + + 提供者 + + + + + + + + + 用户 + + + 管理 + + + + + + + + + + + + + + + + + + + + + 名称无效 + + + + + + + + + 需要名称 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminGroups.aspx.zh-tw.resx b/WebApplication/App_LocalResources/AdminGroups.aspx.zh-tw.resx new file mode 100644 index 0000000..8f903c6 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminGroups.aspx.zh-tw.resx @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the Group list + + + Create Group + + + Save the new Group + + + Delete + + + Delete the Group + + + New Group + + + Create a new User Group + + + Save Group + + + Save modifications + + + Select + + + Select this User Group + + + Select + + + Select this User Group + + + Name already in use + + + + + + + + + Description + + + Description + + + Group Details + + + Global Permissions + + + User Groups + + + Name + + + Name + + + Provider + + + Provider + + + + + + + + + Users + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Name + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/AdminHome.aspx.cs-CZ.resx new file mode 100644 index 0000000..33c9ae1 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.cs-CZ.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + Ukončit aplikaci + + + Rekonstruovat odkazy stránek + + + Rekonstruuje strukturu odkazů + + + sirotci ve wiki + + + Zdá se být + + + <b>Poznámka</b>: stránka je považována za <i>sirotka</i> pokud nejsou příchozí odkazy z ostatních stránek. + + + Orphan Pages + + + <b>Varování</b>: rekonstrukce odkazů stránek může chvilku trvat. Prosím nezavírejte tuto obrazovku dokud operace probíha. + + + viz Stránky + + + Přejít na záložku Stránky v administraci + + + Vymazat Cache + + + Vymaže Cache + + + Rekonstruovat + + + Rekonstruovat tento index + + + Rekonstruovat + + + Rekonstruovat tento index + + + Administrace home + + + Můžete vynutit ukončení a restart webové aplikace. Budete požádání o potvrzení restartu dvakrát a webová aplikace bude restartována.<br /><b>Varování</b>: všechny otevřené sessions budou ztraceny a uživatelé mohou zaznamenat chyby.<br /><b>Poznámka</b>: restart se týká jen tété webové aplikace. + + + Ukončení webové aplikace + + + Dokumenty + + + Stav indexu pro hledání + + + Linked in + + + Chybějící stránky + + + Jmenné prostory + + + Odpovídá + + + Název stránky + + + Poskytovatel + + + <b>Varování</b>: rekonstrukce vyhledávacího indexu může chvilku trvat. Prosím nezavírejte tuto stránku pokud probíhá rekonstrukce. Jakmile je index rekonstruován je doporučeno restartovat aplikaci. + + + Velikost + + + Stav + + + Slova + + + Administrace + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.da-DK.resx b/WebApplication/App_LocalResources/AdminHome.aspx.da-DK.resx new file mode 100644 index 0000000..01d3c13 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.da-DK.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shutdown Application + + + + + + Rebuild Page Links + + + Rebuild the links structure + + + orphan pages in the wiki + + + There seem to be + + + <b>Note</b>: a page is considered an <i>orphan</i> when it has no incoming links from other pages. + + + Orphan Pages + + + <b>Warning</b>: rebuilding page links might take some time. Please do not close this screen while the links are being rebuilt. + + + see Pages + + + Go to the Pages administration tab + + + Clear Cache + + + Clears the Cache + + + Rebuild + + + Rebuild this index + + + Rebuild + + + Rebuild this index + + + Administration Home + + + You can force a shutdown-and-restart cycle of the Web Application. You will be asked to confirm the restart twice, then the Web Application will restart at the first subsequent request.<br /><b>Warning</b>: all the open sessions will be lost, and users may experience errors.<br /><b>Note</b>: the restart will affect only this Web Application. + + + Web Application Shutdown + + + Docs + + + Search Index Status + + + Linked in + + + Missing Pages + + + Namespace + + + Matches + + + Page Name + + + Provider + + + <b>Warning</b>: rebuilding a search index might take some time. Please do not close this screen while the index is being rebuilt. After the index is rebuilt, it is recommended to restart the application. + + + Size + + + Status + + + + + + Words + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.de-DE.resx b/WebApplication/App_LocalResources/AdminHome.aspx.de-DE.resx new file mode 100644 index 0000000..01d3c13 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.de-DE.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shutdown Application + + + + + + Rebuild Page Links + + + Rebuild the links structure + + + orphan pages in the wiki + + + There seem to be + + + <b>Note</b>: a page is considered an <i>orphan</i> when it has no incoming links from other pages. + + + Orphan Pages + + + <b>Warning</b>: rebuilding page links might take some time. Please do not close this screen while the links are being rebuilt. + + + see Pages + + + Go to the Pages administration tab + + + Clear Cache + + + Clears the Cache + + + Rebuild + + + Rebuild this index + + + Rebuild + + + Rebuild this index + + + Administration Home + + + You can force a shutdown-and-restart cycle of the Web Application. You will be asked to confirm the restart twice, then the Web Application will restart at the first subsequent request.<br /><b>Warning</b>: all the open sessions will be lost, and users may experience errors.<br /><b>Note</b>: the restart will affect only this Web Application. + + + Web Application Shutdown + + + Docs + + + Search Index Status + + + Linked in + + + Missing Pages + + + Namespace + + + Matches + + + Page Name + + + Provider + + + <b>Warning</b>: rebuilding a search index might take some time. Please do not close this screen while the index is being rebuilt. After the index is rebuilt, it is recommended to restart the application. + + + Size + + + Status + + + + + + Words + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.es-ES.resx b/WebApplication/App_LocalResources/AdminHome.aspx.es-ES.resx new file mode 100644 index 0000000..01d3c13 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.es-ES.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shutdown Application + + + + + + Rebuild Page Links + + + Rebuild the links structure + + + orphan pages in the wiki + + + There seem to be + + + <b>Note</b>: a page is considered an <i>orphan</i> when it has no incoming links from other pages. + + + Orphan Pages + + + <b>Warning</b>: rebuilding page links might take some time. Please do not close this screen while the links are being rebuilt. + + + see Pages + + + Go to the Pages administration tab + + + Clear Cache + + + Clears the Cache + + + Rebuild + + + Rebuild this index + + + Rebuild + + + Rebuild this index + + + Administration Home + + + You can force a shutdown-and-restart cycle of the Web Application. You will be asked to confirm the restart twice, then the Web Application will restart at the first subsequent request.<br /><b>Warning</b>: all the open sessions will be lost, and users may experience errors.<br /><b>Note</b>: the restart will affect only this Web Application. + + + Web Application Shutdown + + + Docs + + + Search Index Status + + + Linked in + + + Missing Pages + + + Namespace + + + Matches + + + Page Name + + + Provider + + + <b>Warning</b>: rebuilding a search index might take some time. Please do not close this screen while the index is being rebuilt. After the index is rebuilt, it is recommended to restart the application. + + + Size + + + Status + + + + + + Words + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.fr-FR.resx b/WebApplication/App_LocalResources/AdminHome.aspx.fr-FR.resx new file mode 100644 index 0000000..01d3c13 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.fr-FR.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shutdown Application + + + + + + Rebuild Page Links + + + Rebuild the links structure + + + orphan pages in the wiki + + + There seem to be + + + <b>Note</b>: a page is considered an <i>orphan</i> when it has no incoming links from other pages. + + + Orphan Pages + + + <b>Warning</b>: rebuilding page links might take some time. Please do not close this screen while the links are being rebuilt. + + + see Pages + + + Go to the Pages administration tab + + + Clear Cache + + + Clears the Cache + + + Rebuild + + + Rebuild this index + + + Rebuild + + + Rebuild this index + + + Administration Home + + + You can force a shutdown-and-restart cycle of the Web Application. You will be asked to confirm the restart twice, then the Web Application will restart at the first subsequent request.<br /><b>Warning</b>: all the open sessions will be lost, and users may experience errors.<br /><b>Note</b>: the restart will affect only this Web Application. + + + Web Application Shutdown + + + Docs + + + Search Index Status + + + Linked in + + + Missing Pages + + + Namespace + + + Matches + + + Page Name + + + Provider + + + <b>Warning</b>: rebuilding a search index might take some time. Please do not close this screen while the index is being rebuilt. After the index is rebuilt, it is recommended to restart the application. + + + Size + + + Status + + + + + + Words + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.hu-HU.resx b/WebApplication/App_LocalResources/AdminHome.aspx.hu-HU.resx new file mode 100644 index 0000000..01d3c13 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.hu-HU.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shutdown Application + + + + + + Rebuild Page Links + + + Rebuild the links structure + + + orphan pages in the wiki + + + There seem to be + + + <b>Note</b>: a page is considered an <i>orphan</i> when it has no incoming links from other pages. + + + Orphan Pages + + + <b>Warning</b>: rebuilding page links might take some time. Please do not close this screen while the links are being rebuilt. + + + see Pages + + + Go to the Pages administration tab + + + Clear Cache + + + Clears the Cache + + + Rebuild + + + Rebuild this index + + + Rebuild + + + Rebuild this index + + + Administration Home + + + You can force a shutdown-and-restart cycle of the Web Application. You will be asked to confirm the restart twice, then the Web Application will restart at the first subsequent request.<br /><b>Warning</b>: all the open sessions will be lost, and users may experience errors.<br /><b>Note</b>: the restart will affect only this Web Application. + + + Web Application Shutdown + + + Docs + + + Search Index Status + + + Linked in + + + Missing Pages + + + Namespace + + + Matches + + + Page Name + + + Provider + + + <b>Warning</b>: rebuilding a search index might take some time. Please do not close this screen while the index is being rebuilt. After the index is rebuilt, it is recommended to restart the application. + + + Size + + + Status + + + + + + Words + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.it-IT.resx b/WebApplication/App_LocalResources/AdminHome.aspx.it-IT.resx new file mode 100644 index 0000000..ab1d382 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.it-IT.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shutdown Applicazione + + + + + + Rigenera collegamenti + + + Rigenera i collegamenti tra le pagine + + + pagine orfane nel wiki + + + Sembrano esserci + + + <b>Nota</b>: una pagina è considerata <i>orfana</i> quando non ha collegamenti da altre pagine. + + + Pagine orfane + + + <b>Attenzione</b>: rigenerare i collegamenti può richiedere parecchio tempo. Non chiudere questa finestra durante l'operazione. + + + vedi Pagine + + + Vai alla pagina di amministrazione + + + Svuota cache + + + Svuota la cache + + + Rigenera + + + Rigenera l'indice + + + Rigenera + + + Rigenera + + + Home Amministrazione + + + E' possibile forzare un ciclo shutdown-restart the applicazione web. Verrà richiesta una converma, quindi l'applicazione sarà riavviata<br /><b>Attenzione</b>: tutte le sessioni aperte andranno perse e gli utenti potranno subire degli errori.<br /><b>Nota</b>: il riavvio ha effetto solo su questa applicazione. + + + Riavvio applicazione web + + + Doc. + + + Stato indice di ricerca + + + Collegata da + + + Pagine mancanti + + + Namespace + + + Corrisp. + + + Nome pagina + + + Provider + + + <b>Attenzione</b>: rigenerare un indice di ricerca può richiedere parecchio tempo. Non chiudere questa finestra durante l'operazione. Dopo la rigenerazione, è opportuno riavviare l'applicazione. + + + Dim. + + + Stato + + + + + + Parole + + + Amministrazione + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.nb-NO.resx b/WebApplication/App_LocalResources/AdminHome.aspx.nb-NO.resx new file mode 100644 index 0000000..01d3c13 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.nb-NO.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shutdown Application + + + + + + Rebuild Page Links + + + Rebuild the links structure + + + orphan pages in the wiki + + + There seem to be + + + <b>Note</b>: a page is considered an <i>orphan</i> when it has no incoming links from other pages. + + + Orphan Pages + + + <b>Warning</b>: rebuilding page links might take some time. Please do not close this screen while the links are being rebuilt. + + + see Pages + + + Go to the Pages administration tab + + + Clear Cache + + + Clears the Cache + + + Rebuild + + + Rebuild this index + + + Rebuild + + + Rebuild this index + + + Administration Home + + + You can force a shutdown-and-restart cycle of the Web Application. You will be asked to confirm the restart twice, then the Web Application will restart at the first subsequent request.<br /><b>Warning</b>: all the open sessions will be lost, and users may experience errors.<br /><b>Note</b>: the restart will affect only this Web Application. + + + Web Application Shutdown + + + Docs + + + Search Index Status + + + Linked in + + + Missing Pages + + + Namespace + + + Matches + + + Page Name + + + Provider + + + <b>Warning</b>: rebuilding a search index might take some time. Please do not close this screen while the index is being rebuilt. After the index is rebuilt, it is recommended to restart the application. + + + Size + + + Status + + + + + + Words + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.nl-NL.resx b/WebApplication/App_LocalResources/AdminHome.aspx.nl-NL.resx new file mode 100644 index 0000000..01d3c13 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.nl-NL.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shutdown Application + + + + + + Rebuild Page Links + + + Rebuild the links structure + + + orphan pages in the wiki + + + There seem to be + + + <b>Note</b>: a page is considered an <i>orphan</i> when it has no incoming links from other pages. + + + Orphan Pages + + + <b>Warning</b>: rebuilding page links might take some time. Please do not close this screen while the links are being rebuilt. + + + see Pages + + + Go to the Pages administration tab + + + Clear Cache + + + Clears the Cache + + + Rebuild + + + Rebuild this index + + + Rebuild + + + Rebuild this index + + + Administration Home + + + You can force a shutdown-and-restart cycle of the Web Application. You will be asked to confirm the restart twice, then the Web Application will restart at the first subsequent request.<br /><b>Warning</b>: all the open sessions will be lost, and users may experience errors.<br /><b>Note</b>: the restart will affect only this Web Application. + + + Web Application Shutdown + + + Docs + + + Search Index Status + + + Linked in + + + Missing Pages + + + Namespace + + + Matches + + + Page Name + + + Provider + + + <b>Warning</b>: rebuilding a search index might take some time. Please do not close this screen while the index is being rebuilt. After the index is rebuilt, it is recommended to restart the application. + + + Size + + + Status + + + + + + Words + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.pl-PL.resx b/WebApplication/App_LocalResources/AdminHome.aspx.pl-PL.resx new file mode 100644 index 0000000..3547435 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.pl-PL.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shutdown Application + + + + + + Clear Cache + + + Clears the Cache + + + Rebuild + + + Rebuild this index + + + Rebuild + + + Rebuild this index + + + Administration Home + + + You can force a shutdown-and-restart cycle of the Web Application. You will be asked to confirm the restart twice, then the Web Application will restart at the first subsequent request.<br /><b>Warning</b>: all the open sessions will be lost, and users may experience errors.<br /><b>Note</b>: the restart will affect only this Web Application. + + + Web Application Shutdown + + + Docs + + + Search Index Status + + + Linked in + + + Missing Pages + + + Namespace + + + Matches + + + Page Name + + + Provider + + + <b>Warning</b>: rebuilding a search index might take some time. Please do not close this screen while the index is being rebuilt. After the index is rebuilt, it is recommended to restart the application. + + + Size + + + Status + + + + + + Words + + + Administration + + + Rebuild Page Links + + + Rebuild the links structure + + + orphan pages in the wiki + + + There seem to be + + + <b>Note</b>: a page is considered an <i>orphan</i> when it has no incoming links from other pages. + + + Orphan Pages + + + <b>Warning</b>: rebuilding page links might take some time. Please do not close this screen while the links are being rebuilt. + + + see Pages + + + Go to the Pages administration tab + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.pt-BR.resx b/WebApplication/App_LocalResources/AdminHome.aspx.pt-BR.resx new file mode 100644 index 0000000..01d3c13 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.pt-BR.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shutdown Application + + + + + + Rebuild Page Links + + + Rebuild the links structure + + + orphan pages in the wiki + + + There seem to be + + + <b>Note</b>: a page is considered an <i>orphan</i> when it has no incoming links from other pages. + + + Orphan Pages + + + <b>Warning</b>: rebuilding page links might take some time. Please do not close this screen while the links are being rebuilt. + + + see Pages + + + Go to the Pages administration tab + + + Clear Cache + + + Clears the Cache + + + Rebuild + + + Rebuild this index + + + Rebuild + + + Rebuild this index + + + Administration Home + + + You can force a shutdown-and-restart cycle of the Web Application. You will be asked to confirm the restart twice, then the Web Application will restart at the first subsequent request.<br /><b>Warning</b>: all the open sessions will be lost, and users may experience errors.<br /><b>Note</b>: the restart will affect only this Web Application. + + + Web Application Shutdown + + + Docs + + + Search Index Status + + + Linked in + + + Missing Pages + + + Namespace + + + Matches + + + Page Name + + + Provider + + + <b>Warning</b>: rebuilding a search index might take some time. Please do not close this screen while the index is being rebuilt. After the index is rebuilt, it is recommended to restart the application. + + + Size + + + Status + + + + + + Words + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.resx b/WebApplication/App_LocalResources/AdminHome.aspx.resx new file mode 100644 index 0000000..7a920fa --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Clear Cache + + + Clears the Cache + + + Rebuild + + + Rebuild this index + + + Rebuild + + + Rebuild this index + + + Shutdown Application + + + + + + Administration Home + + + You can force a shutdown-and-restart cycle of the Web Application. You will be asked to confirm the restart twice, then the Web Application will restart at the first subsequent request.<br /><b>Warning</b>: all the open sessions will be lost, and users may experience errors.<br /><b>Note</b>: the restart will affect only this Web Application. + + + Web Application Shutdown + + + Docs + + + Search Index Status + + + Linked in + + + Missing Pages + + + Namespace + + + Matches + + + Page Name + + + Provider + + + <b>Warning</b>: rebuilding a search index might take some time. Please do not close this screen while the index is being rebuilt. After the index is rebuilt, it is recommended to restart the application. + + + Size + + + Status + + + + + + Words + + + Administration + + + Rebuild Page Links + + + Rebuild the links structure + + + orphan pages in the wiki + + + There seem to be + + + <b>Note</b>: a page is considered an <i>orphan</i> when it has no incoming links from other pages. + + + Orphan Pages + + + <b>Warning</b>: rebuilding page links might take some time. Please do not close this screen while the links are being rebuilt. + + + see Pages + + + Go to the Pages administration tab + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.ro-RO.resx b/WebApplication/App_LocalResources/AdminHome.aspx.ro-RO.resx new file mode 100644 index 0000000..01d3c13 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.ro-RO.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shutdown Application + + + + + + Rebuild Page Links + + + Rebuild the links structure + + + orphan pages in the wiki + + + There seem to be + + + <b>Note</b>: a page is considered an <i>orphan</i> when it has no incoming links from other pages. + + + Orphan Pages + + + <b>Warning</b>: rebuilding page links might take some time. Please do not close this screen while the links are being rebuilt. + + + see Pages + + + Go to the Pages administration tab + + + Clear Cache + + + Clears the Cache + + + Rebuild + + + Rebuild this index + + + Rebuild + + + Rebuild this index + + + Administration Home + + + You can force a shutdown-and-restart cycle of the Web Application. You will be asked to confirm the restart twice, then the Web Application will restart at the first subsequent request.<br /><b>Warning</b>: all the open sessions will be lost, and users may experience errors.<br /><b>Note</b>: the restart will affect only this Web Application. + + + Web Application Shutdown + + + Docs + + + Search Index Status + + + Linked in + + + Missing Pages + + + Namespace + + + Matches + + + Page Name + + + Provider + + + <b>Warning</b>: rebuilding a search index might take some time. Please do not close this screen while the index is being rebuilt. After the index is rebuilt, it is recommended to restart the application. + + + Size + + + Status + + + + + + Words + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.ru-RU.resx b/WebApplication/App_LocalResources/AdminHome.aspx.ru-RU.resx new file mode 100644 index 0000000..c524498 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.ru-RU.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Перегрузить Аппликацию + + + + + + Перестроить Ссылки Страницы + + + Перестроить структуру ссылок + + + страницы-сироты этой wiki + + + Представлена как + + + <b>Примечание</b>: страница представленная <i>сиротской</i> если на неё нет ссылок с других страниц wiki. + + + Страницы-Сироты + + + <b>Внимание</b>: перестройка структуры ссылок страницы может занять некоторое время. Не закрывайте окно до окончания процедуры перестройки. + + + см. Страницы + + + На вкладку администрирования Страниц + + + Очистить кэш + + + Очистить текущий кэш + + + Перестроить + + + Перестроить индекс + + + Перестроить + + + Перестроить индекс + + + Администрирование + + + Вы имеете возможность начать процедуру отключения-рестарта веб-аппликации. Вы должны будете дважды подтвердить решение о рестарте, причём перезагрузка начнётся после первого же подтверждения.<br /><b>Предупреждение</b>: все открытые сессии будут прекращены, а пользователи могут столкнуться с ошибками работы сайта.<br /><b>Примечание</b>: запрошенный рестарт коснётся лишь этой аппликации. + + + Остановка работы Веб-Аппликации + + + Документов + + + Статус Поискового Индекса + + + Связаны + + + Страницы с ошибками + + + Пространство Имён + + + Записей + + + Имя страницы + + + Провайдер + + + <b>Обратите внимание</b>: перестройка индекса может занять некоторое время. Пожалуйста, не закрывайте это окно, пока процедура перестройки не завершится. После окончания перестройки индекса, Вам будет предложено перезапустить аппликацию. + + + Размер + + + Состояние + + + + + + Слов + + + Администрирование + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.sk-SK.resx b/WebApplication/App_LocalResources/AdminHome.aspx.sk-SK.resx new file mode 100644 index 0000000..01d3c13 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.sk-SK.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shutdown Application + + + + + + Rebuild Page Links + + + Rebuild the links structure + + + orphan pages in the wiki + + + There seem to be + + + <b>Note</b>: a page is considered an <i>orphan</i> when it has no incoming links from other pages. + + + Orphan Pages + + + <b>Warning</b>: rebuilding page links might take some time. Please do not close this screen while the links are being rebuilt. + + + see Pages + + + Go to the Pages administration tab + + + Clear Cache + + + Clears the Cache + + + Rebuild + + + Rebuild this index + + + Rebuild + + + Rebuild this index + + + Administration Home + + + You can force a shutdown-and-restart cycle of the Web Application. You will be asked to confirm the restart twice, then the Web Application will restart at the first subsequent request.<br /><b>Warning</b>: all the open sessions will be lost, and users may experience errors.<br /><b>Note</b>: the restart will affect only this Web Application. + + + Web Application Shutdown + + + Docs + + + Search Index Status + + + Linked in + + + Missing Pages + + + Namespace + + + Matches + + + Page Name + + + Provider + + + <b>Warning</b>: rebuilding a search index might take some time. Please do not close this screen while the index is being rebuilt. After the index is rebuilt, it is recommended to restart the application. + + + Size + + + Status + + + + + + Words + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AdminHome.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..01d3c13 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.sr-Latn-CS.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shutdown Application + + + + + + Rebuild Page Links + + + Rebuild the links structure + + + orphan pages in the wiki + + + There seem to be + + + <b>Note</b>: a page is considered an <i>orphan</i> when it has no incoming links from other pages. + + + Orphan Pages + + + <b>Warning</b>: rebuilding page links might take some time. Please do not close this screen while the links are being rebuilt. + + + see Pages + + + Go to the Pages administration tab + + + Clear Cache + + + Clears the Cache + + + Rebuild + + + Rebuild this index + + + Rebuild + + + Rebuild this index + + + Administration Home + + + You can force a shutdown-and-restart cycle of the Web Application. You will be asked to confirm the restart twice, then the Web Application will restart at the first subsequent request.<br /><b>Warning</b>: all the open sessions will be lost, and users may experience errors.<br /><b>Note</b>: the restart will affect only this Web Application. + + + Web Application Shutdown + + + Docs + + + Search Index Status + + + Linked in + + + Missing Pages + + + Namespace + + + Matches + + + Page Name + + + Provider + + + <b>Warning</b>: rebuilding a search index might take some time. Please do not close this screen while the index is being rebuilt. After the index is rebuilt, it is recommended to restart the application. + + + Size + + + Status + + + + + + Words + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.tr-TR.resx b/WebApplication/App_LocalResources/AdminHome.aspx.tr-TR.resx new file mode 100644 index 0000000..80dcbc1 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.tr-TR.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shutdown Application + + + + + + Rebuild Page Links + + + Rebuild the links structure + + + orphan pages in the wiki + + + There seem to be + + + <b>Note</b>: a page is considered an <i>orphan</i> when it has no incoming links from other pages. + + + Orphan Pages + + + <b>Warning</b>: rebuilding page links might take some time. Please do not close this screen while the links are being rebuilt. + + + see Pages + + + Go to the Pages administration tab + + + Önbelleği Temizle + + + Önbelleği temizler + + + Endeksi tekrar oluştur + + + Bu endeksi tekrar oluşturur + + + Tekrar oluştur + + + Bu endeksi tekrar oluştur + + + Yönetim - Ana sayfa + + + Web uygulamasını yeniden başlatabilirsiniz. Bu işlem için iki kere onayınız alınacaktır ve ardından izleyen ilk istek ile uygulama yenilenecektir. <br /><b>Dikkat</b>: tüm açık oturumlar kapanacaktır; bu nedenle kullanıcılar sorun yaşayabilir. <br /><b>Not</b>: yeniden başlatma işlemi sadece bu uygulamayı etkiler. + + + Web uygulamasını kapat + + + Dokumanlar + + + Arama endeks durumu + + + Şuralarda linkli + + + Olmayan sayfalar + + + Ad alanı + + + Eşleşenler + + + Sayfa Adı + + + Sağlayıcı + + + <b>Dikkat</b>: arama indeksini tekrar oluşturmak vakit alabilir. Lütfen bu pencereyi indeks oluşturulduğu sırada kapatmayınız. İndeks oluşturulduktan sonra uygulamayı yeniden başlatmanızı tavsiye ederiz. + + + Boyut + + + Durum + + + + + + Sözcük + + + Yönetim + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.uk-UA.resx b/WebApplication/App_LocalResources/AdminHome.aspx.uk-UA.resx new file mode 100644 index 0000000..6282870 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.uk-UA.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Перезапуск Аплікації + + + + + + Перебудувати Посилання Сторінки + + + Перебудова структури посилань + + + сторінки wiki, що не мають зв'язків з іншими + + + Представлена як + + + <b>Примітка</b>: сторінка вважається <i>сирітською</i> коли на неї немає посилань з інших сторінок Wiki. + + + Сирітські Сторінки + + + <b>Увага</b>: перебудова посилань сторінки займає деякий час. Будь ласка, не закривайте це вікно, поки не закінчиться перебудова. + + + див. Сторінки + + + Перейти на вкладинку адміністрування Сторінок + + + Звільнити кеш + + + Очистити поточний кеш + + + Перебудувати + + + Перебудувати індекс + + + Перебудувати + + + Перебудувати індекс + + + Адміністрування + + + Ви маєте можливість розпочати процедуру відключення-рестарту веб-аплікації. Ви будете повинні двічі прийняти рішення про рестарт, до того ж перезавантаження почнеться одразу після Вашого першого ж дозволу.<br /><b>Попередження</b>: всі відкриті сесії будуть припинені, а користувачі можуть зіткнутися з помилками роботи сайту.<br /><b>Примітки</b>: розпочате Вами перезавантаження буде стосуватися лише цієї аплікації. + + + Перезавантаження Веб-аплікації + + + Документів + + + Статус Пошукового Індексу + + + Пов'язані + + + Сторінки з помилками + + + Простір Імен + + + Записів + + + Ім'я Сторінки + + + Несуча + + + <b>Увага</b>: перебудова індексу може здійснюватися деякий час. Будь ласка, не закривайте це вікно, поки процедура не закінчиться. Після закінчення перебудови індексу, Вам буде запропоновано перезавантажити аплікацію. + + + Розмір + + + Статус + + + + + + Слів + + + Адміністрування + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.zh-cn.resx b/WebApplication/App_LocalResources/AdminHome.aspx.zh-cn.resx new file mode 100644 index 0000000..1788b44 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.zh-cn.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 关闭应用程序 + + + + + + 重建页面链接 + + + 重建链接结构 + + + 个在维基中的孤立页面 + + + 存在 + + + <b>注意</b>:若一个页面没有被其它页面链接,则称之为 <i>孤立</i> 页面。 + + + 孤立页面 + + + <b>警告</b>:重建页面链接可能需要一些时间。在页面链接正在重建时请不要关闭本页。 + + + 查看页面 + + + 转到页面管理标签 + + + 清除缓存 + + + 清除缓存 + + + 重建 + + + 重建索引 + + + 重建 + + + 重建索引 + + + 管理首页 + + + 您可以强制关闭并重新启动本 Web 应用程序。您会被要求确认两次,然后本 Web 应用程序会在收到随后第一个请求时重新启动。<br /><b>警告</b>:所有开启的会话将会丢失,并且用户可能会遇到错误。<br /><b>注意</b>:重新启动只影响本 Web 应用程序。 + + + 关闭 Web 应用程序 + + + 文档 + + + 搜索索引状态 + + + 链接自 + + + 尚未撰写的页面 + + + 命名空间 + + + 匹配数目 + + + 页面名称 + + + 提供者 + + + <b>警告</b>:重建搜索索引可能需要一些时间。在索引正在重建时请不要关闭本页。重建索引后,建议重新启动应用程序。 + + + 大小 + + + 状态 + + + + + + + + + 管理 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminHome.aspx.zh-tw.resx b/WebApplication/App_LocalResources/AdminHome.aspx.zh-tw.resx new file mode 100644 index 0000000..01d3c13 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminHome.aspx.zh-tw.resx @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shutdown Application + + + + + + Rebuild Page Links + + + Rebuild the links structure + + + orphan pages in the wiki + + + There seem to be + + + <b>Note</b>: a page is considered an <i>orphan</i> when it has no incoming links from other pages. + + + Orphan Pages + + + <b>Warning</b>: rebuilding page links might take some time. Please do not close this screen while the links are being rebuilt. + + + see Pages + + + Go to the Pages administration tab + + + Clear Cache + + + Clears the Cache + + + Rebuild + + + Rebuild this index + + + Rebuild + + + Rebuild this index + + + Administration Home + + + You can force a shutdown-and-restart cycle of the Web Application. You will be asked to confirm the restart twice, then the Web Application will restart at the first subsequent request.<br /><b>Warning</b>: all the open sessions will be lost, and users may experience errors.<br /><b>Note</b>: the restart will affect only this Web Application. + + + Web Application Shutdown + + + Docs + + + Search Index Status + + + Linked in + + + Missing Pages + + + Namespace + + + Matches + + + Page Name + + + Provider + + + <b>Warning</b>: rebuilding a search index might take some time. Please do not close this screen while the index is being rebuilt. After the index is rebuilt, it is recommended to restart the application. + + + Size + + + Status + + + + + + Words + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/AdminLog.aspx.cs-CZ.resx new file mode 100644 index 0000000..9d1a607 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.cs-CZ.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Vymazat log + + + Vymaže systémový log + + + Chyby + + + Zprávy + + + Varování + + + Datum/čas + + + Zobrazit: + + + Systémový log + + + Zprávy + + + Uživatel + + + Administrace + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.da-DK.resx b/WebApplication/App_LocalResources/AdminLog.aspx.da-DK.resx new file mode 100644 index 0000000..b4dc76e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.da-DK.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Clear Log + + + Clear the system log + + + Errors + + + + + + Messages + + + + + + Warnings + + + + + + Date/Time + + + Display: + + + System Log + + + Message + + + User + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.de-DE.resx b/WebApplication/App_LocalResources/AdminLog.aspx.de-DE.resx new file mode 100644 index 0000000..b4dc76e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.de-DE.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Clear Log + + + Clear the system log + + + Errors + + + + + + Messages + + + + + + Warnings + + + + + + Date/Time + + + Display: + + + System Log + + + Message + + + User + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.es-ES.resx b/WebApplication/App_LocalResources/AdminLog.aspx.es-ES.resx new file mode 100644 index 0000000..b4dc76e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.es-ES.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Clear Log + + + Clear the system log + + + Errors + + + + + + Messages + + + + + + Warnings + + + + + + Date/Time + + + Display: + + + System Log + + + Message + + + User + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.fr-FR.resx b/WebApplication/App_LocalResources/AdminLog.aspx.fr-FR.resx new file mode 100644 index 0000000..b4dc76e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.fr-FR.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Clear Log + + + Clear the system log + + + Errors + + + + + + Messages + + + + + + Warnings + + + + + + Date/Time + + + Display: + + + System Log + + + Message + + + User + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.hu-HU.resx b/WebApplication/App_LocalResources/AdminLog.aspx.hu-HU.resx new file mode 100644 index 0000000..b4dc76e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.hu-HU.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Clear Log + + + Clear the system log + + + Errors + + + + + + Messages + + + + + + Warnings + + + + + + Date/Time + + + Display: + + + System Log + + + Message + + + User + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.it-IT.resx b/WebApplication/App_LocalResources/AdminLog.aspx.it-IT.resx new file mode 100644 index 0000000..a16a6fd --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.it-IT.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Svuota log + + + Svuota il log di sistema + + + Errori + + + + + + Messaggi + + + + + + Warning + + + + + + Data/ora + + + Visualizza: + + + Log di sistema + + + Messaggio + + + Utente + + + Amministrazione + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.nb-NO.resx b/WebApplication/App_LocalResources/AdminLog.aspx.nb-NO.resx new file mode 100644 index 0000000..b4dc76e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.nb-NO.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Clear Log + + + Clear the system log + + + Errors + + + + + + Messages + + + + + + Warnings + + + + + + Date/Time + + + Display: + + + System Log + + + Message + + + User + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.nl-NL.resx b/WebApplication/App_LocalResources/AdminLog.aspx.nl-NL.resx new file mode 100644 index 0000000..b4dc76e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.nl-NL.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Clear Log + + + Clear the system log + + + Errors + + + + + + Messages + + + + + + Warnings + + + + + + Date/Time + + + Display: + + + System Log + + + Message + + + User + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.pl-PL.resx b/WebApplication/App_LocalResources/AdminLog.aspx.pl-PL.resx new file mode 100644 index 0000000..b4dc76e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.pl-PL.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Clear Log + + + Clear the system log + + + Errors + + + + + + Messages + + + + + + Warnings + + + + + + Date/Time + + + Display: + + + System Log + + + Message + + + User + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.pt-BR.resx b/WebApplication/App_LocalResources/AdminLog.aspx.pt-BR.resx new file mode 100644 index 0000000..b4dc76e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.pt-BR.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Clear Log + + + Clear the system log + + + Errors + + + + + + Messages + + + + + + Warnings + + + + + + Date/Time + + + Display: + + + System Log + + + Message + + + User + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.resx b/WebApplication/App_LocalResources/AdminLog.aspx.resx new file mode 100644 index 0000000..b4dc76e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Clear Log + + + Clear the system log + + + Errors + + + + + + Messages + + + + + + Warnings + + + + + + Date/Time + + + Display: + + + System Log + + + Message + + + User + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.ro-RO.resx b/WebApplication/App_LocalResources/AdminLog.aspx.ro-RO.resx new file mode 100644 index 0000000..b4dc76e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.ro-RO.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Clear Log + + + Clear the system log + + + Errors + + + + + + Messages + + + + + + Warnings + + + + + + Date/Time + + + Display: + + + System Log + + + Message + + + User + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.ru-RU.resx b/WebApplication/App_LocalResources/AdminLog.aspx.ru-RU.resx new file mode 100644 index 0000000..0bf375f --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.ru-RU.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Очистить лог + + + Очистить текущий лог + + + Ошибки + + + + + + Сообщения + + + + + + Предупреждения + + + + + + Дата/Время + + + Содержание: + + + Системный лог + + + Сообщение + + + Пользователь + + + Администрирование + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.sk-SK.resx b/WebApplication/App_LocalResources/AdminLog.aspx.sk-SK.resx new file mode 100644 index 0000000..b4dc76e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.sk-SK.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Clear Log + + + Clear the system log + + + Errors + + + + + + Messages + + + + + + Warnings + + + + + + Date/Time + + + Display: + + + System Log + + + Message + + + User + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AdminLog.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..b4dc76e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.sr-Latn-CS.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Clear Log + + + Clear the system log + + + Errors + + + + + + Messages + + + + + + Warnings + + + + + + Date/Time + + + Display: + + + System Log + + + Message + + + User + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.tr-TR.resx b/WebApplication/App_LocalResources/AdminLog.aspx.tr-TR.resx new file mode 100644 index 0000000..ea3f568 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.tr-TR.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Log'u Temizle + + + Sistem Log'unu temizle + + + Hatalar + + + + + + Mesajlar + + + + + + Uyarılar + + + + + + Saat/Tarih + + + Görünen: + + + Sistem logu + + + Mesaj + + + Kullanıcı + + + Yönetim + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.uk-UA.resx b/WebApplication/App_LocalResources/AdminLog.aspx.uk-UA.resx new file mode 100644 index 0000000..2566a7d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.uk-UA.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Звільнити місце + + + Очистити цей лог + + + Помилки + + + + + + Повідомлення + + + + + + Попередження + + + + + + Дата/Час + + + Зміст: + + + Системний лог + + + Повідомлення + + + Користувач + + + Адміністрування + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.zh-cn.resx b/WebApplication/App_LocalResources/AdminLog.aspx.zh-cn.resx new file mode 100644 index 0000000..ca67f99 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.zh-cn.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 清除日志 + + + 清除系统日志 + + + 错误 + + + + + + 消息 + + + + + + 警告 + + + + + + 日期/时间 + + + 显示: + + + 系统日志 + + + 消息 + + + 用户 + + + 管理 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminLog.aspx.zh-tw.resx b/WebApplication/App_LocalResources/AdminLog.aspx.zh-tw.resx new file mode 100644 index 0000000..b4dc76e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminLog.aspx.zh-tw.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Clear Log + + + Clear the system log + + + Errors + + + + + + Messages + + + + + + Warnings + + + + + + Date/Time + + + Display: + + + System Log + + + Message + + + User + + + Administration + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.cs-CZ.resx new file mode 100644 index 0000000..6ef9358 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.cs-CZ.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Zpět + + + Zpět na seznam jmenných prostorů + + + Storno + + + Storno a návrat na seznam jmenných prostorů + + + Potvrdit smazání jmenných prostorů + + + Vytvořit jmenný prostor + + + Uložit nový jmenný prostor + + + Smazat + + + Smazat jmenný prostor + + + Nový jmenný prostor + + + Vytvoří nový jmenný prostor + + + Normální + + + Použít tuto šablonu + + + Oprávnění + + + Spravovat oprávnění pro tento jmenný prostor + + + Oprávnění + + + Spravovat oprávnění pro tento jmenný prostor + + + Soukromé + + + Použít tuto šablonu + + + Veřejné + + + Použít tuto šablonu + + + Přejmenovat + + + Uložit jmenný prostor + + + Uložit změny + + + Vybrat + + + Vybere tento jmenný prostor pro editaci + + + Vybrat + + + Vybere tento jmenný prostor pro editaci + + + Neplatný název + + + Jmenný prostor již existuje + + + Neplatný název + + + Jmenný prostor již existuje + + + Kategorie + + + Opravdu chcete smazat vybraný jmenný prostor? Provedením budou všechna data ztracena. + + + Implicitní stránka bude automaticky vytvořena s názevem 'MainPage'. + + + Implicitní stránka + + + Implicitní stránka + + + Detaily jmenného prostoru + + + <b>Poznámka</b>: pod jmenné prostory dědí oprávnění z <b>kořenového</b> jmenného prostoru.<br />Změna oprávnění v <b>kořenovém</b> jmenném prostoru nezmění oprávnění explicitně nastavené v sub prostoru. + + + Název + + + Název + + + Opravnění jmenného prostoru + + + Jmenné prostory + + + Nový název + + + Registrovaní uživatelé mohou <b>editovat</b> a <b>vytvářet</b> stránky a <b>spravovat</b> kategorie. + + + Stránky + + + <b>Varování</b>: nastavení šablony nahradí všechny existující oprávnění pro implicitní uživatelské skupiny. + + + Šablony oprávnění + + + Jen registrovaní uživatelé mohou <b>zobrazit</b> stránky (a také <b>vytvářet</b> a <b>editovat</b> je stejně jako <b>spravovat</b> kategorie). + + + Poskytovatel + + + Poskytovatel + + + Kdokoliv, včetně anonymních uživatelů může <b>editovat</b> stránky, registrovaní uživatelé mohou taky <b>vytvářet</b> stránky a spravovat kategorie. + + + <b>Varování</b>: přejmenování jmenného prostoru může přerušit všechny existující odkazy v rámci jmenného prostoru. + + + Přejmenovat tento jmenný prostor + + + Téma + + + Téma + + + Administrace + + + Je vyžadován název + + + Nový název je vyžadován + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.da-DK.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.da-DK.resx new file mode 100644 index 0000000..7d5c830 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.da-DK.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Namespace list + + + Cancel + + + Cancel and return to the Namespace list + + + Confirm Namespace Deletion + + + + + + Create Namespace + + + Save the new Namespace + + + Delete + + + Delete the Namespace + + + New Namespace + + + Create a new Namespace + + + Normal + + + Use this Template + + + Permissions + + + Manage permissions for this Namespace + + + Permissions + + + Manage permissions for this Namespace + + + Private + + + Use this Template + + + Public + + + Use this Template + + + Rename + + + + + + Save Namespace + + + Save modifications + + + Select + + + Select this Namespace for editing + + + Select + + + Select this Namespace for editing + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Categories + + + Are you sure you want to premanently delete the selected Namespace? Doing so, all the data contained in it will be lost. + + + The default page will be created automatically with name 'MainPage'. + + + + + + Default Page + + + Default Page + + + Namespace Details + + + <b>Note</b>: sub-namespaces always inherit permissions from the <b>root</b> namespace.<br />Changing the permissions in the <b>root</b> namespace does not change the permissions explicitly set in sub-namespaces. + + + Name + + + Name + + + + + + Namespace Permissions + + + Namespaces + + + New Name + + + Registered Users can <b>edit</b> and <b>create</b> pages and <b>manage</b> categories. + + + Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Only registered users can <b>view</b> pages (and also <b>create</b> and <b>edit</b> them as well as <b>manage</b> categories). + + + Provider + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> pages, registered users can also <b>create</b> pages and <b>manage</b> categories. + + + <b>Warning</b>: renaming a namespace will break all existing inter-namespace links. + + + Rename this Namespace + + + + + + + + + + + + + + + Theme + + + Theme + + + + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.de-DE.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.de-DE.resx new file mode 100644 index 0000000..7d5c830 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.de-DE.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Namespace list + + + Cancel + + + Cancel and return to the Namespace list + + + Confirm Namespace Deletion + + + + + + Create Namespace + + + Save the new Namespace + + + Delete + + + Delete the Namespace + + + New Namespace + + + Create a new Namespace + + + Normal + + + Use this Template + + + Permissions + + + Manage permissions for this Namespace + + + Permissions + + + Manage permissions for this Namespace + + + Private + + + Use this Template + + + Public + + + Use this Template + + + Rename + + + + + + Save Namespace + + + Save modifications + + + Select + + + Select this Namespace for editing + + + Select + + + Select this Namespace for editing + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Categories + + + Are you sure you want to premanently delete the selected Namespace? Doing so, all the data contained in it will be lost. + + + The default page will be created automatically with name 'MainPage'. + + + + + + Default Page + + + Default Page + + + Namespace Details + + + <b>Note</b>: sub-namespaces always inherit permissions from the <b>root</b> namespace.<br />Changing the permissions in the <b>root</b> namespace does not change the permissions explicitly set in sub-namespaces. + + + Name + + + Name + + + + + + Namespace Permissions + + + Namespaces + + + New Name + + + Registered Users can <b>edit</b> and <b>create</b> pages and <b>manage</b> categories. + + + Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Only registered users can <b>view</b> pages (and also <b>create</b> and <b>edit</b> them as well as <b>manage</b> categories). + + + Provider + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> pages, registered users can also <b>create</b> pages and <b>manage</b> categories. + + + <b>Warning</b>: renaming a namespace will break all existing inter-namespace links. + + + Rename this Namespace + + + + + + + + + + + + + + + Theme + + + Theme + + + + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.es-ES.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.es-ES.resx new file mode 100644 index 0000000..7d5c830 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.es-ES.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Namespace list + + + Cancel + + + Cancel and return to the Namespace list + + + Confirm Namespace Deletion + + + + + + Create Namespace + + + Save the new Namespace + + + Delete + + + Delete the Namespace + + + New Namespace + + + Create a new Namespace + + + Normal + + + Use this Template + + + Permissions + + + Manage permissions for this Namespace + + + Permissions + + + Manage permissions for this Namespace + + + Private + + + Use this Template + + + Public + + + Use this Template + + + Rename + + + + + + Save Namespace + + + Save modifications + + + Select + + + Select this Namespace for editing + + + Select + + + Select this Namespace for editing + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Categories + + + Are you sure you want to premanently delete the selected Namespace? Doing so, all the data contained in it will be lost. + + + The default page will be created automatically with name 'MainPage'. + + + + + + Default Page + + + Default Page + + + Namespace Details + + + <b>Note</b>: sub-namespaces always inherit permissions from the <b>root</b> namespace.<br />Changing the permissions in the <b>root</b> namespace does not change the permissions explicitly set in sub-namespaces. + + + Name + + + Name + + + + + + Namespace Permissions + + + Namespaces + + + New Name + + + Registered Users can <b>edit</b> and <b>create</b> pages and <b>manage</b> categories. + + + Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Only registered users can <b>view</b> pages (and also <b>create</b> and <b>edit</b> them as well as <b>manage</b> categories). + + + Provider + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> pages, registered users can also <b>create</b> pages and <b>manage</b> categories. + + + <b>Warning</b>: renaming a namespace will break all existing inter-namespace links. + + + Rename this Namespace + + + + + + + + + + + + + + + Theme + + + Theme + + + + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.fr-FR.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.fr-FR.resx new file mode 100644 index 0000000..7d5c830 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.fr-FR.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Namespace list + + + Cancel + + + Cancel and return to the Namespace list + + + Confirm Namespace Deletion + + + + + + Create Namespace + + + Save the new Namespace + + + Delete + + + Delete the Namespace + + + New Namespace + + + Create a new Namespace + + + Normal + + + Use this Template + + + Permissions + + + Manage permissions for this Namespace + + + Permissions + + + Manage permissions for this Namespace + + + Private + + + Use this Template + + + Public + + + Use this Template + + + Rename + + + + + + Save Namespace + + + Save modifications + + + Select + + + Select this Namespace for editing + + + Select + + + Select this Namespace for editing + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Categories + + + Are you sure you want to premanently delete the selected Namespace? Doing so, all the data contained in it will be lost. + + + The default page will be created automatically with name 'MainPage'. + + + + + + Default Page + + + Default Page + + + Namespace Details + + + <b>Note</b>: sub-namespaces always inherit permissions from the <b>root</b> namespace.<br />Changing the permissions in the <b>root</b> namespace does not change the permissions explicitly set in sub-namespaces. + + + Name + + + Name + + + + + + Namespace Permissions + + + Namespaces + + + New Name + + + Registered Users can <b>edit</b> and <b>create</b> pages and <b>manage</b> categories. + + + Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Only registered users can <b>view</b> pages (and also <b>create</b> and <b>edit</b> them as well as <b>manage</b> categories). + + + Provider + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> pages, registered users can also <b>create</b> pages and <b>manage</b> categories. + + + <b>Warning</b>: renaming a namespace will break all existing inter-namespace links. + + + Rename this Namespace + + + + + + + + + + + + + + + Theme + + + Theme + + + + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.hu-HU.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.hu-HU.resx new file mode 100644 index 0000000..7d5c830 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.hu-HU.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Namespace list + + + Cancel + + + Cancel and return to the Namespace list + + + Confirm Namespace Deletion + + + + + + Create Namespace + + + Save the new Namespace + + + Delete + + + Delete the Namespace + + + New Namespace + + + Create a new Namespace + + + Normal + + + Use this Template + + + Permissions + + + Manage permissions for this Namespace + + + Permissions + + + Manage permissions for this Namespace + + + Private + + + Use this Template + + + Public + + + Use this Template + + + Rename + + + + + + Save Namespace + + + Save modifications + + + Select + + + Select this Namespace for editing + + + Select + + + Select this Namespace for editing + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Categories + + + Are you sure you want to premanently delete the selected Namespace? Doing so, all the data contained in it will be lost. + + + The default page will be created automatically with name 'MainPage'. + + + + + + Default Page + + + Default Page + + + Namespace Details + + + <b>Note</b>: sub-namespaces always inherit permissions from the <b>root</b> namespace.<br />Changing the permissions in the <b>root</b> namespace does not change the permissions explicitly set in sub-namespaces. + + + Name + + + Name + + + + + + Namespace Permissions + + + Namespaces + + + New Name + + + Registered Users can <b>edit</b> and <b>create</b> pages and <b>manage</b> categories. + + + Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Only registered users can <b>view</b> pages (and also <b>create</b> and <b>edit</b> them as well as <b>manage</b> categories). + + + Provider + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> pages, registered users can also <b>create</b> pages and <b>manage</b> categories. + + + <b>Warning</b>: renaming a namespace will break all existing inter-namespace links. + + + Rename this Namespace + + + + + + + + + + + + + + + Theme + + + Theme + + + + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.it-IT.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.it-IT.resx new file mode 100644 index 0000000..15f7d9d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.it-IT.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Indietro + + + Indietro alla lista namespace + + + Annulla + + + Annulla e ritorna alla lista namespace + + + Conferma eliminazione namespace + + + + + + Crea namespace + + + Salva il nuovo namespace + + + Elimina + + + Elimina il namespace + + + Nuovo Namespace + + + Crea un nuovo namespace + + + Normale + + + Usa questo template + + + Permessi + + + Gestisci permessi per questo namespace + + + Permessi + + + Gestisic permessi per questo namespace + + + Privato + + + Usa questo template + + + Pubblico + + + Usa questo template + + + Rinomina + + + + + + Salva namespace + + + Salva modifiche + + + Seleziona + + + Seleziona questo namespace + + + Seleziona + + + Seleziona questo namespace + + + Nome non valido + + + + + + + + + Il namespace esiste già + + + + + + + + + Nome non valido + + + + + + + + + Il namespace esiste già + + + + + + + + + Categorie + + + Sei sicuro di voler eliminare definitivamente il namespace selezionato? Tutti i dati in esso contenuti saranno eliminati. + + + La pagina principale verrà creata automaticamente con il nome di 'MainPage'. + + + + + + Pagina principale + + + Pagina principale + + + Dettagli namespace + + + <b>Nota</b>: i sotto-namespace ereditano i permessi dal <b>root</b><br />Cambiando i permessi nel <b>root</b>, i permessi specifici per i sotto-namespace rimarranno intatti. + + + Nome + + + Nome + + + + + + Permessi namespace + + + Namespace + + + Nuovo nome + + + Gli Utenti Registrati possono <b>modificare</b> e <b>creare</b> pagine e <b>gestire</b> categorie. + + + Pagine + + + <b>Attenzione</b>: impostando un template, tutti permessi esistenti saranno eliminati. + + + Template permessi + + + Solo gli Utenti Registrati possono <b>visualizzare</b> pagine (ed anche <b>crearne</b> e <b>modificarne</b>, come anche <b>gestire</b> categorie). + + + Provider + + + Provider + + + Tutti, Utenti Anonimi inclusi, possono <b>modificare</b> pages, Utenti Registrati Registrati possono anche <b>creare</b> pagine e <b>gestire</b> categorie. + + + <b>Attenzione</b>: rinominando un namespace tutti i link inter-namespace esistenti non funzioneranno più. + + + Rinomina questo namespace + + + + + + + + + + + + + + + Tema + + + Tema + + + + + + + + + Amministrazione + + + + + + + + + + + + + + + + + + + + + + + + + + + Nome richiesto + + + + + + + + + Nuovo nome richiesto + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.nb-NO.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.nb-NO.resx new file mode 100644 index 0000000..7d5c830 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.nb-NO.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Namespace list + + + Cancel + + + Cancel and return to the Namespace list + + + Confirm Namespace Deletion + + + + + + Create Namespace + + + Save the new Namespace + + + Delete + + + Delete the Namespace + + + New Namespace + + + Create a new Namespace + + + Normal + + + Use this Template + + + Permissions + + + Manage permissions for this Namespace + + + Permissions + + + Manage permissions for this Namespace + + + Private + + + Use this Template + + + Public + + + Use this Template + + + Rename + + + + + + Save Namespace + + + Save modifications + + + Select + + + Select this Namespace for editing + + + Select + + + Select this Namespace for editing + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Categories + + + Are you sure you want to premanently delete the selected Namespace? Doing so, all the data contained in it will be lost. + + + The default page will be created automatically with name 'MainPage'. + + + + + + Default Page + + + Default Page + + + Namespace Details + + + <b>Note</b>: sub-namespaces always inherit permissions from the <b>root</b> namespace.<br />Changing the permissions in the <b>root</b> namespace does not change the permissions explicitly set in sub-namespaces. + + + Name + + + Name + + + + + + Namespace Permissions + + + Namespaces + + + New Name + + + Registered Users can <b>edit</b> and <b>create</b> pages and <b>manage</b> categories. + + + Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Only registered users can <b>view</b> pages (and also <b>create</b> and <b>edit</b> them as well as <b>manage</b> categories). + + + Provider + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> pages, registered users can also <b>create</b> pages and <b>manage</b> categories. + + + <b>Warning</b>: renaming a namespace will break all existing inter-namespace links. + + + Rename this Namespace + + + + + + + + + + + + + + + Theme + + + Theme + + + + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.nl-NL.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.nl-NL.resx new file mode 100644 index 0000000..7d5c830 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.nl-NL.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Namespace list + + + Cancel + + + Cancel and return to the Namespace list + + + Confirm Namespace Deletion + + + + + + Create Namespace + + + Save the new Namespace + + + Delete + + + Delete the Namespace + + + New Namespace + + + Create a new Namespace + + + Normal + + + Use this Template + + + Permissions + + + Manage permissions for this Namespace + + + Permissions + + + Manage permissions for this Namespace + + + Private + + + Use this Template + + + Public + + + Use this Template + + + Rename + + + + + + Save Namespace + + + Save modifications + + + Select + + + Select this Namespace for editing + + + Select + + + Select this Namespace for editing + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Categories + + + Are you sure you want to premanently delete the selected Namespace? Doing so, all the data contained in it will be lost. + + + The default page will be created automatically with name 'MainPage'. + + + + + + Default Page + + + Default Page + + + Namespace Details + + + <b>Note</b>: sub-namespaces always inherit permissions from the <b>root</b> namespace.<br />Changing the permissions in the <b>root</b> namespace does not change the permissions explicitly set in sub-namespaces. + + + Name + + + Name + + + + + + Namespace Permissions + + + Namespaces + + + New Name + + + Registered Users can <b>edit</b> and <b>create</b> pages and <b>manage</b> categories. + + + Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Only registered users can <b>view</b> pages (and also <b>create</b> and <b>edit</b> them as well as <b>manage</b> categories). + + + Provider + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> pages, registered users can also <b>create</b> pages and <b>manage</b> categories. + + + <b>Warning</b>: renaming a namespace will break all existing inter-namespace links. + + + Rename this Namespace + + + + + + + + + + + + + + + Theme + + + Theme + + + + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.pl-PL.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.pl-PL.resx new file mode 100644 index 0000000..7d5c830 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.pl-PL.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Namespace list + + + Cancel + + + Cancel and return to the Namespace list + + + Confirm Namespace Deletion + + + + + + Create Namespace + + + Save the new Namespace + + + Delete + + + Delete the Namespace + + + New Namespace + + + Create a new Namespace + + + Normal + + + Use this Template + + + Permissions + + + Manage permissions for this Namespace + + + Permissions + + + Manage permissions for this Namespace + + + Private + + + Use this Template + + + Public + + + Use this Template + + + Rename + + + + + + Save Namespace + + + Save modifications + + + Select + + + Select this Namespace for editing + + + Select + + + Select this Namespace for editing + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Categories + + + Are you sure you want to premanently delete the selected Namespace? Doing so, all the data contained in it will be lost. + + + The default page will be created automatically with name 'MainPage'. + + + + + + Default Page + + + Default Page + + + Namespace Details + + + <b>Note</b>: sub-namespaces always inherit permissions from the <b>root</b> namespace.<br />Changing the permissions in the <b>root</b> namespace does not change the permissions explicitly set in sub-namespaces. + + + Name + + + Name + + + + + + Namespace Permissions + + + Namespaces + + + New Name + + + Registered Users can <b>edit</b> and <b>create</b> pages and <b>manage</b> categories. + + + Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Only registered users can <b>view</b> pages (and also <b>create</b> and <b>edit</b> them as well as <b>manage</b> categories). + + + Provider + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> pages, registered users can also <b>create</b> pages and <b>manage</b> categories. + + + <b>Warning</b>: renaming a namespace will break all existing inter-namespace links. + + + Rename this Namespace + + + + + + + + + + + + + + + Theme + + + Theme + + + + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.pt-BR.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.pt-BR.resx new file mode 100644 index 0000000..7d5c830 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.pt-BR.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Namespace list + + + Cancel + + + Cancel and return to the Namespace list + + + Confirm Namespace Deletion + + + + + + Create Namespace + + + Save the new Namespace + + + Delete + + + Delete the Namespace + + + New Namespace + + + Create a new Namespace + + + Normal + + + Use this Template + + + Permissions + + + Manage permissions for this Namespace + + + Permissions + + + Manage permissions for this Namespace + + + Private + + + Use this Template + + + Public + + + Use this Template + + + Rename + + + + + + Save Namespace + + + Save modifications + + + Select + + + Select this Namespace for editing + + + Select + + + Select this Namespace for editing + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Categories + + + Are you sure you want to premanently delete the selected Namespace? Doing so, all the data contained in it will be lost. + + + The default page will be created automatically with name 'MainPage'. + + + + + + Default Page + + + Default Page + + + Namespace Details + + + <b>Note</b>: sub-namespaces always inherit permissions from the <b>root</b> namespace.<br />Changing the permissions in the <b>root</b> namespace does not change the permissions explicitly set in sub-namespaces. + + + Name + + + Name + + + + + + Namespace Permissions + + + Namespaces + + + New Name + + + Registered Users can <b>edit</b> and <b>create</b> pages and <b>manage</b> categories. + + + Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Only registered users can <b>view</b> pages (and also <b>create</b> and <b>edit</b> them as well as <b>manage</b> categories). + + + Provider + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> pages, registered users can also <b>create</b> pages and <b>manage</b> categories. + + + <b>Warning</b>: renaming a namespace will break all existing inter-namespace links. + + + Rename this Namespace + + + + + + + + + + + + + + + Theme + + + Theme + + + + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.resx new file mode 100644 index 0000000..7d5c830 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Namespace list + + + Cancel + + + Cancel and return to the Namespace list + + + Confirm Namespace Deletion + + + + + + Create Namespace + + + Save the new Namespace + + + Delete + + + Delete the Namespace + + + New Namespace + + + Create a new Namespace + + + Normal + + + Use this Template + + + Permissions + + + Manage permissions for this Namespace + + + Permissions + + + Manage permissions for this Namespace + + + Private + + + Use this Template + + + Public + + + Use this Template + + + Rename + + + + + + Save Namespace + + + Save modifications + + + Select + + + Select this Namespace for editing + + + Select + + + Select this Namespace for editing + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Categories + + + Are you sure you want to premanently delete the selected Namespace? Doing so, all the data contained in it will be lost. + + + The default page will be created automatically with name 'MainPage'. + + + + + + Default Page + + + Default Page + + + Namespace Details + + + <b>Note</b>: sub-namespaces always inherit permissions from the <b>root</b> namespace.<br />Changing the permissions in the <b>root</b> namespace does not change the permissions explicitly set in sub-namespaces. + + + Name + + + Name + + + + + + Namespace Permissions + + + Namespaces + + + New Name + + + Registered Users can <b>edit</b> and <b>create</b> pages and <b>manage</b> categories. + + + Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Only registered users can <b>view</b> pages (and also <b>create</b> and <b>edit</b> them as well as <b>manage</b> categories). + + + Provider + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> pages, registered users can also <b>create</b> pages and <b>manage</b> categories. + + + <b>Warning</b>: renaming a namespace will break all existing inter-namespace links. + + + Rename this Namespace + + + + + + + + + + + + + + + Theme + + + Theme + + + + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.ro-RO.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.ro-RO.resx new file mode 100644 index 0000000..7d5c830 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.ro-RO.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Namespace list + + + Cancel + + + Cancel and return to the Namespace list + + + Confirm Namespace Deletion + + + + + + Create Namespace + + + Save the new Namespace + + + Delete + + + Delete the Namespace + + + New Namespace + + + Create a new Namespace + + + Normal + + + Use this Template + + + Permissions + + + Manage permissions for this Namespace + + + Permissions + + + Manage permissions for this Namespace + + + Private + + + Use this Template + + + Public + + + Use this Template + + + Rename + + + + + + Save Namespace + + + Save modifications + + + Select + + + Select this Namespace for editing + + + Select + + + Select this Namespace for editing + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Categories + + + Are you sure you want to premanently delete the selected Namespace? Doing so, all the data contained in it will be lost. + + + The default page will be created automatically with name 'MainPage'. + + + + + + Default Page + + + Default Page + + + Namespace Details + + + <b>Note</b>: sub-namespaces always inherit permissions from the <b>root</b> namespace.<br />Changing the permissions in the <b>root</b> namespace does not change the permissions explicitly set in sub-namespaces. + + + Name + + + Name + + + + + + Namespace Permissions + + + Namespaces + + + New Name + + + Registered Users can <b>edit</b> and <b>create</b> pages and <b>manage</b> categories. + + + Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Only registered users can <b>view</b> pages (and also <b>create</b> and <b>edit</b> them as well as <b>manage</b> categories). + + + Provider + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> pages, registered users can also <b>create</b> pages and <b>manage</b> categories. + + + <b>Warning</b>: renaming a namespace will break all existing inter-namespace links. + + + Rename this Namespace + + + + + + + + + + + + + + + Theme + + + Theme + + + + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.ru-RU.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.ru-RU.resx new file mode 100644 index 0000000..9ea1207 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.ru-RU.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Назад + + + Назад к списку Пространств Имён + + + Отмена + + + Отмена и возврат к списку Пространств Имён + + + Подтвердить удаление Пространства Имён + + + + + + Создать Пространство Имён + + + Сохранить новое Пространство Имён + + + Удалить + + + Удалить Пространство Имён + + + Новое Пространство Имён + + + Создать новое Пространство Имён + + + Нормальное + + + Использовать этот Шаблон + + + Разрешения + + + Управление разрешениями этого Пространства Имён + + + Разрешения + + + Управление разрешениями этого Пространства Имён + + + Закрытое + + + Использовать этот Шаблон + + + Публичное + + + Использовать этот Шаблон + + + Переименовать + + + + + + Сохранить Пространство Имён + + + Сохранить изменения + + + Выбрать + + + Выбрать это Пространство Имён для редактирования + + + Выбрать + + + Выбрать это Пространство Имён для редактирования + + + Ошибочное Имя + + + + + + + + + Такое Пространство Имён уже существует + + + + + + + + + Ошибочное Имя + + + + + + + + + Такое Пространство Имён уже существует + + + + + + + + + Категории + + + Вы уверены, что желаете удалить выбранные Пространства Имён? Такое удаление уничтожит все данные, находящиеся в них. + + + Начальная страница будет создана автоматически с именем 'MainPage'. + + + + + + Страница по умолчанию + + + Страница по умолчанию + + + Подробности Пространства Имён + + + <b>Примечание</b>: производные Пространства Имён всегда наследуют разрешения, свойственные родительским <b>root</b> Пространствам Имён.<br />Дальнейшие изменения разрешений в <b>root</b> Пространстве Имён не влияют на разрешения, установленные для производных (созданных). + + + Имя + + + Имя + + + + + + Разрешения Пространства Имён + + + Пространство Имён + + + Новое Имя + + + Зарегистрированные пользователи могут <b>редактировать</b> и <b>создавать</b> страницы и <b>управлять</b> категориями в Wiki. + + + Страницы + + + <b>Предупреждение</b>: выбор шаблона повлияет на все разрешения по умолчанию для групп пользователей Вашей Wiki. + + + Шаблоны Разрешений + + + Только зарегистрированные пользователи могут <b>просматривать</b> страницы (а также <b>создавать</b> и <b>редактировать</b> их, кроме всего прочего и <b>управлять</b> категориями Вашей Wiki). + + + Провайдер + + + Провайдер + + + Кто угодно, даже анонимные пользователи, могут <b>редактировать</b> страницы, зарегистрированные пользователи (кроме всего прочего) могут <b>создавать</b> страницы и <b>управлять</b> категориями Вашей Wiki. + + + <b>Предупреждение</b>: переименование Пространства Имён разорвёт все связи, установленные с другими Пространствами Имён. + + + Переименовать это Пространство Имён + + + + + + + + + + + + + + + Основа + + + Основа + + + + + + + + + Администрирование + + + + + + + + + + + + + + + + + + + + + + + + + + + Требуется Имя + + + + + + + + + Новое Имя не может быть пустым + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.sk-SK.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.sk-SK.resx new file mode 100644 index 0000000..7d5c830 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.sk-SK.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Namespace list + + + Cancel + + + Cancel and return to the Namespace list + + + Confirm Namespace Deletion + + + + + + Create Namespace + + + Save the new Namespace + + + Delete + + + Delete the Namespace + + + New Namespace + + + Create a new Namespace + + + Normal + + + Use this Template + + + Permissions + + + Manage permissions for this Namespace + + + Permissions + + + Manage permissions for this Namespace + + + Private + + + Use this Template + + + Public + + + Use this Template + + + Rename + + + + + + Save Namespace + + + Save modifications + + + Select + + + Select this Namespace for editing + + + Select + + + Select this Namespace for editing + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Categories + + + Are you sure you want to premanently delete the selected Namespace? Doing so, all the data contained in it will be lost. + + + The default page will be created automatically with name 'MainPage'. + + + + + + Default Page + + + Default Page + + + Namespace Details + + + <b>Note</b>: sub-namespaces always inherit permissions from the <b>root</b> namespace.<br />Changing the permissions in the <b>root</b> namespace does not change the permissions explicitly set in sub-namespaces. + + + Name + + + Name + + + + + + Namespace Permissions + + + Namespaces + + + New Name + + + Registered Users can <b>edit</b> and <b>create</b> pages and <b>manage</b> categories. + + + Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Only registered users can <b>view</b> pages (and also <b>create</b> and <b>edit</b> them as well as <b>manage</b> categories). + + + Provider + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> pages, registered users can also <b>create</b> pages and <b>manage</b> categories. + + + <b>Warning</b>: renaming a namespace will break all existing inter-namespace links. + + + Rename this Namespace + + + + + + + + + + + + + + + Theme + + + Theme + + + + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..7d5c830 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.sr-Latn-CS.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Namespace list + + + Cancel + + + Cancel and return to the Namespace list + + + Confirm Namespace Deletion + + + + + + Create Namespace + + + Save the new Namespace + + + Delete + + + Delete the Namespace + + + New Namespace + + + Create a new Namespace + + + Normal + + + Use this Template + + + Permissions + + + Manage permissions for this Namespace + + + Permissions + + + Manage permissions for this Namespace + + + Private + + + Use this Template + + + Public + + + Use this Template + + + Rename + + + + + + Save Namespace + + + Save modifications + + + Select + + + Select this Namespace for editing + + + Select + + + Select this Namespace for editing + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Categories + + + Are you sure you want to premanently delete the selected Namespace? Doing so, all the data contained in it will be lost. + + + The default page will be created automatically with name 'MainPage'. + + + + + + Default Page + + + Default Page + + + Namespace Details + + + <b>Note</b>: sub-namespaces always inherit permissions from the <b>root</b> namespace.<br />Changing the permissions in the <b>root</b> namespace does not change the permissions explicitly set in sub-namespaces. + + + Name + + + Name + + + + + + Namespace Permissions + + + Namespaces + + + New Name + + + Registered Users can <b>edit</b> and <b>create</b> pages and <b>manage</b> categories. + + + Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Only registered users can <b>view</b> pages (and also <b>create</b> and <b>edit</b> them as well as <b>manage</b> categories). + + + Provider + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> pages, registered users can also <b>create</b> pages and <b>manage</b> categories. + + + <b>Warning</b>: renaming a namespace will break all existing inter-namespace links. + + + Rename this Namespace + + + + + + + + + + + + + + + Theme + + + Theme + + + + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.tr-TR.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.tr-TR.resx new file mode 100644 index 0000000..9985acf --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.tr-TR.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Geri + + + Ad alanı listesine dön + + + İptal + + + İptal et ve ad alanı listesien dön + + + Ad alanı silme işlemini onayla + + + + + + Ad alanı oluştur + + + Yeni ad alanını kaydedin + + + Sil + + + Ad alanını sil + + + Yeni ad alanı + + + Yeni ad alanı oluşturun + + + Normal + + + Bunu şablon olarak kullan + + + İzinler + + + Bu ad alanı için izinleri yönetin + + + İzinler + + + Bu ad alanı için izinleri yönetin + + + Özel + + + Bunu şablon olarak kullan + + + Genel + + + Bunu şablon olarak kullan + + + Yeniden adlandır + + + + + + Ad alanını kaydet + + + Değişiklikleri kaydet + + + Seç + + + Bu ad alanını düzenleme için seç + + + Seç + + + Bu ad alanını düzenleme için seç + + + Geçersiz ad + + + + + + + + + Ad alanı zaten var + + + + + + + + + Geçersiz ad + + + + + + + + + Ad alanı zaten var + + + + + + + + + Kategoriler + + + Seçili ad alanını tamamen silmek istediğinizden emin misiniz? Bu işlem ad alanı içerisindeki tüm verinin kalıcı olarak yok olmasına sebep olacaktır. + + + Öntanımlı sayfa otomatik olarak 'MainPage' adıyla oluşturulacaktır. + + + + + + Öntanımlı sayfa + + + Öntanımlı sayfa + + + Ad alanı detayları + + + <b>Not</b>: alt ad alanları daima kök ad alanından türerler.<br />Kök ad alanındaki izinleri değiştirmek, alt ad alanlarında fiilen değiştirilmiş izinleri etkilemez. + + + + + + Ad + + + + + + Ad alanı izinleri + + + Ad alanları + + + Yeni ad + + + Kayıtlı kullanıcılar kategorileri <b>düzenleyebilir</b> ve yeni sayfalar <b>oluşturabilir</b> ve kategorileri <b>yönetebilir</b>. + + + Sayfalar + + + <b>Dikkat</b>: bir şablon seçmek, öntanımlı kullanıcıları gurupları için belirlenmiş izinleri ezecektir. + + + İzin şablonları + + + Sadece kayıtlı kullanıcılar sayfaları <b>görebilir</b> (ve <b>oluşturup</b> <b>düzenleyebilir</b>, kategoriler <b>yönetebilir</b>). + + + Sağlayıcı + + + Sağlayıcı + + + Herhangi birisi (anonimler dahil) sayfaları <b>değiştirebilir</b>, kayıtlı kullanıcılar sayfa oluşturabilir ve kategorileri <b>yönetebilir</b>. + + + <b>Dikkat</b>: Ad alanını yeniden adlandırmak, ad alanları arasında verilmiş bağlantıları koparabilir. + + + Bu ad alanını yeniden adlandırın + + + + + + + + + + + + + + + Tema + + + Tema + + + + + + + + + Yönetim + + + + + + + + + + + + + + + + + + + + + + + + + + + Ad gereklidir + + + + + + + + + Yeni bir ad gereklidir + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.uk-UA.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.uk-UA.resx new file mode 100644 index 0000000..631183c --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.uk-UA.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Повернутися + + + Повернутися до переліку Просторів Імен + + + Відміна + + + Відміна та повернення до переліку Просторів Імен + + + Підтвердити видалення Простору Імен + + + + + + Створити Простір Імен + + + Зберегти новий Простір Імен + + + Видалити + + + Видалити цей Простір Імен + + + Новий Простір Імен + + + Створити новий Простір Імен + + + Нормальна + + + Використовувати цей Шаблон + + + Дозволи + + + Керування дозволами цього Простору Імен + + + Дозволи + + + Керування дозволами цього Простору Імен + + + Закрита + + + Використовувати цей Шаблон + + + Публічна + + + Використовувати цей Шаблон + + + Перейменувати + + + + + + Зберегти Простір Імен + + + Зберегти всі зміни + + + Обрати + + + Обрати цей Простір Імен для редагування + + + Обрати + + + Обрати цей Простір Імен для редагування + + + Помилкова Назва + + + + + + + + + Такий Простір Імен вже існує + + + + + + + + + Помилкова Назва + + + + + + + + + Такий Простір Імен вже існує + + + + + + + + + Категорії + + + Ви впевнені, що бажаєте знищити обрані Простори Імен? Таке знищення видалить всі дані, які в них містяться. + + + Головна сторінка буде створена автоматично та буде мати Ім'я 'MainPage'. + + + + + + Головна сторінка + + + Головна сторінка + + + Подробиці Простору Імен + + + <b>Примітка</b>: похідні Простори Імен завжди успадковують дозволи, що властиві батьківським <b>root</b> Просторам Імен.<br />Подальші зміни дозволів у <b>root</b> Просторі Імен не впливають на дозволи, що обрані для похідних (створених). + + + Назва + + + Назва + + + + + + Дозволи Простору Імен + + + Простори Імен + + + Нова Назва + + + Зареєстровані користувачі мають змогу <b>редагувати</b> й <b>створювати</b> сторінки та <b>керувати</b> категоріями Вашої Wiki. + + + Сторінки + + + <b>Попередження</b>: вибір шаблону вплине на всі звичайні дозволи для груп користувачів Вашої Wiki. + + + Шаблони Дозволів + + + Лише зареєстровані користувачі мають змогу <b>переглядати</b> сторінки (а також <b>створювати</b> та <b>редагувати</b> такі, крім цього <b>керувати</b> категоріями Вашої Wiki). + + + Несуча + + + Несуча + + + Будь хто, навіть анонімні користувачі, будуть мати змогу <b>редагувати</b> сторінки, зарестровані користувачі (крім цього) матимуть змогу <b>створювати</b> сторінки та <b>керувати</b> категоріями Вашої Wiki. + + + <b>Попередження</b>: перейменування Простору Імен знищіть всі зв'язки, встановлені з іншими Просторами Імен. + + + Перейменувати цей Простір Імен + + + + + + + + + + + + + + + Основа + + + Основа + + + + + + + + + Адміністрування + + + + + + + + + + + + + + + + + + + + + + + + + + + Потрібна Назва + + + + + + + + + Потрібна нова Назва + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.zh-cn.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.zh-cn.resx new file mode 100644 index 0000000..8ca68f9 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.zh-cn.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 返回 + + + 返回命名空间列表 + + + 取消 + + + 取消并返回命名空间列表 + + + 确认删除命名空间 + + + + + + 创建命名空间 + + + 保存新命名空间 + + + 删除 + + + 删除命名空间 + + + 新建命名空间 + + + 创建新命名空间 + + + 默认 + + + 使用此模板 + + + 权限 + + + 管理此命名空间的权限 + + + 权限 + + + 管理此命名空间的权限 + + + 私有 + + + 使用此模板 + + + 公共 + + + 使用此模板 + + + 重命名 + + + + + + 保存命名空间 + + + 保存修改 + + + 选择 + + + 选择此命名空间进行编辑 + + + 选择 + + + 选择此命名空间进行编辑 + + + 名称无效 + + + + + + + + + 命名空间已存在 + + + + + + + + + 名称无效 + + + + + + + + + 命名空间已存在 + + + + + + + + + 分类 + + + 您确定要永久删除选中的命名空间吗?删除后其包含的所有数据将会丢失。 + + + 名为 'MainPage' 的默认页面将被自动创建。 + + + + + + 默认页面 + + + 默认页面 + + + 命名空间详情 + + + <b>注意</b>:子命名空间从<b>根</b>命名空间继承权限。<br />更改<b>根</b>命名空间的权限不会更改在子命名空间中显式设定的权限。 + + + 名称 + + + 名称 + + + + + + 命名空间权限 + + + 命名空间 + + + 新名称 + + + 注册用户可以<b>编辑</b>和<b>创建</b>页面以及<b>管理</b>分类。 + + + 页面 + + + <b>警告</b>:设置模板将会替换默认用户组所有现存权限。 + + + 权限模板 + + + 只有注册用户才能<b>查看</b>页面(以及<b>编辑</b>和<b>创建</b>页面和<b>管理</b>分类)。 + + + 提供者 + + + 提供者 + + + 任何人,包括匿名用户,可以<b>编辑</b>页面,注册用户还可以<b>创建</b>页面以及<b>管理</b>分类。 + + + <b>警告</b>:重命名命名空间会破坏所有命名空间之间的链接。 + + + 重命名此命名空间 + + + + + + + + + + + + + + + 主题 + + + 主题 + + + + + + + + + 管理 + + + + + + + + + + + + + + + + + + + + + + + + + + + 需要名称 + + + + + + + + + 需要新名称 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNamespaces.aspx.zh-tw.resx b/WebApplication/App_LocalResources/AdminNamespaces.aspx.zh-tw.resx new file mode 100644 index 0000000..7d5c830 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNamespaces.aspx.zh-tw.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Namespace list + + + Cancel + + + Cancel and return to the Namespace list + + + Confirm Namespace Deletion + + + + + + Create Namespace + + + Save the new Namespace + + + Delete + + + Delete the Namespace + + + New Namespace + + + Create a new Namespace + + + Normal + + + Use this Template + + + Permissions + + + Manage permissions for this Namespace + + + Permissions + + + Manage permissions for this Namespace + + + Private + + + Use this Template + + + Public + + + Use this Template + + + Rename + + + + + + Save Namespace + + + Save modifications + + + Select + + + Select this Namespace for editing + + + Select + + + Select this Namespace for editing + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Invalid Name + + + + + + + + + Namespace already exists + + + + + + + + + Categories + + + Are you sure you want to premanently delete the selected Namespace? Doing so, all the data contained in it will be lost. + + + The default page will be created automatically with name 'MainPage'. + + + + + + Default Page + + + Default Page + + + Namespace Details + + + <b>Note</b>: sub-namespaces always inherit permissions from the <b>root</b> namespace.<br />Changing the permissions in the <b>root</b> namespace does not change the permissions explicitly set in sub-namespaces. + + + Name + + + Name + + + + + + Namespace Permissions + + + Namespaces + + + New Name + + + Registered Users can <b>edit</b> and <b>create</b> pages and <b>manage</b> categories. + + + Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Only registered users can <b>view</b> pages (and also <b>create</b> and <b>edit</b> them as well as <b>manage</b> categories). + + + Provider + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> pages, registered users can also <b>create</b> pages and <b>manage</b> categories. + + + <b>Warning</b>: renaming a namespace will break all existing inter-namespace links. + + + Rename this Namespace + + + + + + + + + + + + + + + Theme + + + Theme + + + + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + New Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.cs-CZ.resx new file mode 100644 index 0000000..b3c3547 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.cs-CZ.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Přidat + + + Přidá vybranou stránku do seznamu + + + Storno + + + Storno a návrat do seznamu navigačních cest + + + Vytvořit Nav. cestu + + + Uložit novou navigační cestu + + + Smazat + + + Smaže navigační cestu + + + Dolů + + + Nová Nav. cesta + + + Vytvoří novou navigační cestu + + + Odstranit + + + Uložit Nav. cestu + + + Uložit úpravy + + + Hledat + + + Hledat stránku + + + Vybrat + + + Vybrat + + + Nahoru + + + Neplatný název + + + Navigační cesta již existuje + + + Detaily navigační cesty + + + Název + + + Název + + + Jmenný prostor + + + Navigační cesty + + + Stránky + + + Poskytovatel + + + Administrace + + + Je vyžadován název + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.da-DK.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.da-DK.resx new file mode 100644 index 0000000..cfb29d4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.da-DK.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Cancel + + + Cancel and return to the Navigation Path list + + + Create Nav. Path + + + Save the new Navigation Path + + + Delete + + + Delete the Navigation Path + + + Down + + + + + + New Nav. Path + + + Create a new Navigation Path + + + Remove + + + + + + Save Nav. Path + + + Save modifications + + + Search + + + Search for a Page + + + Select + + + + + + Select + + + + + + Up + + + + + + Invalid Name + + + + + + + + + Navigation Path already exists + + + + + + + + + Navigation Paths Details + + + Name + + + Name + + + Namespace + + + Navigation Paths + + + Pages + + + Provider + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.de-DE.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.de-DE.resx new file mode 100644 index 0000000..cfb29d4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.de-DE.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Cancel + + + Cancel and return to the Navigation Path list + + + Create Nav. Path + + + Save the new Navigation Path + + + Delete + + + Delete the Navigation Path + + + Down + + + + + + New Nav. Path + + + Create a new Navigation Path + + + Remove + + + + + + Save Nav. Path + + + Save modifications + + + Search + + + Search for a Page + + + Select + + + + + + Select + + + + + + Up + + + + + + Invalid Name + + + + + + + + + Navigation Path already exists + + + + + + + + + Navigation Paths Details + + + Name + + + Name + + + Namespace + + + Navigation Paths + + + Pages + + + Provider + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.es-ES.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.es-ES.resx new file mode 100644 index 0000000..cfb29d4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.es-ES.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Cancel + + + Cancel and return to the Navigation Path list + + + Create Nav. Path + + + Save the new Navigation Path + + + Delete + + + Delete the Navigation Path + + + Down + + + + + + New Nav. Path + + + Create a new Navigation Path + + + Remove + + + + + + Save Nav. Path + + + Save modifications + + + Search + + + Search for a Page + + + Select + + + + + + Select + + + + + + Up + + + + + + Invalid Name + + + + + + + + + Navigation Path already exists + + + + + + + + + Navigation Paths Details + + + Name + + + Name + + + Namespace + + + Navigation Paths + + + Pages + + + Provider + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.fr-FR.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.fr-FR.resx new file mode 100644 index 0000000..cfb29d4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.fr-FR.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Cancel + + + Cancel and return to the Navigation Path list + + + Create Nav. Path + + + Save the new Navigation Path + + + Delete + + + Delete the Navigation Path + + + Down + + + + + + New Nav. Path + + + Create a new Navigation Path + + + Remove + + + + + + Save Nav. Path + + + Save modifications + + + Search + + + Search for a Page + + + Select + + + + + + Select + + + + + + Up + + + + + + Invalid Name + + + + + + + + + Navigation Path already exists + + + + + + + + + Navigation Paths Details + + + Name + + + Name + + + Namespace + + + Navigation Paths + + + Pages + + + Provider + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.hu-HU.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.hu-HU.resx new file mode 100644 index 0000000..cfb29d4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.hu-HU.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Cancel + + + Cancel and return to the Navigation Path list + + + Create Nav. Path + + + Save the new Navigation Path + + + Delete + + + Delete the Navigation Path + + + Down + + + + + + New Nav. Path + + + Create a new Navigation Path + + + Remove + + + + + + Save Nav. Path + + + Save modifications + + + Search + + + Search for a Page + + + Select + + + + + + Select + + + + + + Up + + + + + + Invalid Name + + + + + + + + + Navigation Path already exists + + + + + + + + + Navigation Paths Details + + + Name + + + Name + + + Namespace + + + Navigation Paths + + + Pages + + + Provider + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.it-IT.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.it-IT.resx new file mode 100644 index 0000000..58e7e71 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.it-IT.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Aggiungi + + + Aggiungi la pagina selezionata alla lista + + + Annulla + + + Annulla e torna alla lista + + + Crea nav. path + + + Salva nuovo navigation path + + + Elimina + + + Elimina navigation path + + + Giù + + + + + + Nuovo nav. path + + + Crea nuovo navigation path + + + Rimuovi + + + + + + Salva nav. path + + + Salva modifiche + + + Cerca + + + Cerca una pagina + + + Seleziona + + + + + + Seleziona + + + + + + Su + + + + + + Nome non valido + + + + + + + + + Il navigation path esiste già + + + + + + + + + Dettagli navigation path + + + Nome + + + Nome + + + Namespace + + + Navigation path + + + Pagine + + + Provider + + + + + + + + + + + + + + + + + + Amministrazione + + + + + + + + + + + + + + + Nome richiesto + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.nb-NO.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.nb-NO.resx new file mode 100644 index 0000000..cfb29d4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.nb-NO.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Cancel + + + Cancel and return to the Navigation Path list + + + Create Nav. Path + + + Save the new Navigation Path + + + Delete + + + Delete the Navigation Path + + + Down + + + + + + New Nav. Path + + + Create a new Navigation Path + + + Remove + + + + + + Save Nav. Path + + + Save modifications + + + Search + + + Search for a Page + + + Select + + + + + + Select + + + + + + Up + + + + + + Invalid Name + + + + + + + + + Navigation Path already exists + + + + + + + + + Navigation Paths Details + + + Name + + + Name + + + Namespace + + + Navigation Paths + + + Pages + + + Provider + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.nl-NL.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.nl-NL.resx new file mode 100644 index 0000000..cfb29d4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.nl-NL.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Cancel + + + Cancel and return to the Navigation Path list + + + Create Nav. Path + + + Save the new Navigation Path + + + Delete + + + Delete the Navigation Path + + + Down + + + + + + New Nav. Path + + + Create a new Navigation Path + + + Remove + + + + + + Save Nav. Path + + + Save modifications + + + Search + + + Search for a Page + + + Select + + + + + + Select + + + + + + Up + + + + + + Invalid Name + + + + + + + + + Navigation Path already exists + + + + + + + + + Navigation Paths Details + + + Name + + + Name + + + Namespace + + + Navigation Paths + + + Pages + + + Provider + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.pl-PL.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.pl-PL.resx new file mode 100644 index 0000000..cfb29d4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.pl-PL.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Cancel + + + Cancel and return to the Navigation Path list + + + Create Nav. Path + + + Save the new Navigation Path + + + Delete + + + Delete the Navigation Path + + + Down + + + + + + New Nav. Path + + + Create a new Navigation Path + + + Remove + + + + + + Save Nav. Path + + + Save modifications + + + Search + + + Search for a Page + + + Select + + + + + + Select + + + + + + Up + + + + + + Invalid Name + + + + + + + + + Navigation Path already exists + + + + + + + + + Navigation Paths Details + + + Name + + + Name + + + Namespace + + + Navigation Paths + + + Pages + + + Provider + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.pt-BR.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.pt-BR.resx new file mode 100644 index 0000000..cfb29d4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.pt-BR.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Cancel + + + Cancel and return to the Navigation Path list + + + Create Nav. Path + + + Save the new Navigation Path + + + Delete + + + Delete the Navigation Path + + + Down + + + + + + New Nav. Path + + + Create a new Navigation Path + + + Remove + + + + + + Save Nav. Path + + + Save modifications + + + Search + + + Search for a Page + + + Select + + + + + + Select + + + + + + Up + + + + + + Invalid Name + + + + + + + + + Navigation Path already exists + + + + + + + + + Navigation Paths Details + + + Name + + + Name + + + Namespace + + + Navigation Paths + + + Pages + + + Provider + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.resx new file mode 100644 index 0000000..cfb29d4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Cancel + + + Cancel and return to the Navigation Path list + + + Create Nav. Path + + + Save the new Navigation Path + + + Delete + + + Delete the Navigation Path + + + Down + + + + + + New Nav. Path + + + Create a new Navigation Path + + + Remove + + + + + + Save Nav. Path + + + Save modifications + + + Search + + + Search for a Page + + + Select + + + + + + Select + + + + + + Up + + + + + + Invalid Name + + + + + + + + + Navigation Path already exists + + + + + + + + + Navigation Paths Details + + + Name + + + Name + + + Namespace + + + Navigation Paths + + + Pages + + + Provider + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.ro-RO.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.ro-RO.resx new file mode 100644 index 0000000..cfb29d4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.ro-RO.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Cancel + + + Cancel and return to the Navigation Path list + + + Create Nav. Path + + + Save the new Navigation Path + + + Delete + + + Delete the Navigation Path + + + Down + + + + + + New Nav. Path + + + Create a new Navigation Path + + + Remove + + + + + + Save Nav. Path + + + Save modifications + + + Search + + + Search for a Page + + + Select + + + + + + Select + + + + + + Up + + + + + + Invalid Name + + + + + + + + + Navigation Path already exists + + + + + + + + + Navigation Paths Details + + + Name + + + Name + + + Namespace + + + Navigation Paths + + + Pages + + + Provider + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.ru-RU.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.ru-RU.resx new file mode 100644 index 0000000..fd32e8a --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.ru-RU.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Добавить + + + Добавить отмеченную страницу в список + + + Отмена + + + Отмена и возврат к списку Навигационных Меню + + + Создать Нав.Меню + + + Сохранить новое Навигационное Меню + + + Удалить + + + Удалить это Навигационное Меню + + + Вниз + + + + + + Новое Нав.Меню + + + Создать новое Навигационное Меню + + + Исключить + + + + + + Сохранить Нав.Меню + + + Сохранить изменения + + + Найти + + + Найти на странице + + + Выбрать + + + + + + Выбрать + + + + + + Вверх + + + + + + Ошибочное Имя + + + + + + + + + Такое Навигационное Меню уже существует + + + + + + + + + Подробности Навигационного Меню + + + Имя + + + Имя + + + Пространство Имён + + + Навигационные Меню + + + Страницы + + + Провайдер + + + + + + + + + + + + + + + + + + Администрирование + + + + + + + + + + + + + + + Требуется Название + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.sk-SK.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.sk-SK.resx new file mode 100644 index 0000000..cfb29d4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.sk-SK.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Cancel + + + Cancel and return to the Navigation Path list + + + Create Nav. Path + + + Save the new Navigation Path + + + Delete + + + Delete the Navigation Path + + + Down + + + + + + New Nav. Path + + + Create a new Navigation Path + + + Remove + + + + + + Save Nav. Path + + + Save modifications + + + Search + + + Search for a Page + + + Select + + + + + + Select + + + + + + Up + + + + + + Invalid Name + + + + + + + + + Navigation Path already exists + + + + + + + + + Navigation Paths Details + + + Name + + + Name + + + Namespace + + + Navigation Paths + + + Pages + + + Provider + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..cfb29d4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.sr-Latn-CS.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Cancel + + + Cancel and return to the Navigation Path list + + + Create Nav. Path + + + Save the new Navigation Path + + + Delete + + + Delete the Navigation Path + + + Down + + + + + + New Nav. Path + + + Create a new Navigation Path + + + Remove + + + + + + Save Nav. Path + + + Save modifications + + + Search + + + Search for a Page + + + Select + + + + + + Select + + + + + + Up + + + + + + Invalid Name + + + + + + + + + Navigation Path already exists + + + + + + + + + Navigation Paths Details + + + Name + + + Name + + + Namespace + + + Navigation Paths + + + Pages + + + Provider + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.tr-TR.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.tr-TR.resx new file mode 100644 index 0000000..ca6d159 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.tr-TR.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ekle + + + Seçili sayfayı listeye ekle + + + İptal + + + İptal et ve navigasyon yolu listesine dön + + + Nav. yolu oluştur + + + Kaydet + + + Sil + + + Navigasyon yolunu sil + + + Aşağı + + + + + + Yeni Nav. Yolu + + + Yeni bir navigasyon yolu oluştur + + + Çıkar + + + + + + Nav. yolunu kaydet + + + Değişiklikleri kaydet + + + Ara + + + Sayfa ara + + + Seç + + + + + + Seç + + + + + + Yukarı + + + + + + Geçersiz ad + + + + + + + + + Navigasyon yolu zaten var + + + + + + + + + Navigasyon yol detayları + + + Ad + + + Ad + + + Ad alanı + + + Navigasyon yolları + + + Sayfalar + + + Sağlayıcı + + + + + + + + + + + + + + + + + + Yönetim + + + + + + + + + + + + + + + Ad gereklidir + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.uk-UA.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.uk-UA.resx new file mode 100644 index 0000000..94476b7 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.uk-UA.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Додати + + + Додати обрану сторінку до переліку + + + Відміна + + + Відміна та повернення до переліку Навігаційних Меню + + + Створити Нав. Меню + + + Зберегти нове Навігаційне Меню + + + Видалити + + + Видалити це Навігаційне Меню + + + Вниз + + + + + + Нове Нав.Меню + + + Створити нове Навігаційне Меню + + + Виключити + + + + + + Зберегти Нав.Меню + + + Зберегти змінене + + + Знайти + + + Знайти на сторінці + + + Обрати + + + + + + Обрати + + + + + + Вгору + + + + + + Помилкова Назва + + + + + + + + + Таке Навігаційне Меню вже існує + + + + + + + + + Подробиці Навігаційного Меню + + + Назва + + + Назва + + + Простір Імен + + + Навігаційні Меню + + + Сторінки + + + Несуча + + + + + + + + + + + + + + + + + + Адміністрування + + + + + + + + + + + + + + + Потрібна Назва + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.zh-cn.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.zh-cn.resx new file mode 100644 index 0000000..ad53d06 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.zh-cn.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 添加 + + + 添加选中的页面到列表 + + + 取消 + + + 取消并返回导航路径列表 + + + 创建导航路径 + + + 保存新导航路径 + + + 删除 + + + 删除导航路径 + + + 向下 + + + + + + 新建导航路径 + + + 创建新导航路径 + + + 移除 + + + + + + 保存导航路径 + + + 保存修改 + + + 搜索 + + + 搜索页面 + + + 选择 + + + + + + 选择 + + + + + + 向上 + + + + + + 名称无效 + + + + + + + + + 导航路径已存在 + + + + + + + + + 导航路径详情 + + + 名称 + + + 名称 + + + 命名空间 + + + 导航路径 + + + 页面 + + + 提供者 + + + + + + + + + + + + + + + + + + 管理 + + + + + + + + + + + + + + + 需要名称 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminNavPaths.aspx.zh-tw.resx b/WebApplication/App_LocalResources/AdminNavPaths.aspx.zh-tw.resx new file mode 100644 index 0000000..cfb29d4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminNavPaths.aspx.zh-tw.resx @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Cancel + + + Cancel and return to the Navigation Path list + + + Create Nav. Path + + + Save the new Navigation Path + + + Delete + + + Delete the Navigation Path + + + Down + + + + + + New Nav. Path + + + Create a new Navigation Path + + + Remove + + + + + + Save Nav. Path + + + Save modifications + + + Search + + + Search for a Page + + + Select + + + + + + Select + + + + + + Up + + + + + + Invalid Name + + + + + + + + + Navigation Path already exists + + + + + + + + + Navigation Paths Details + + + Name + + + Name + + + Namespace + + + Navigation Paths + + + Pages + + + Provider + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/AdminPages.aspx.cs-CZ.resx new file mode 100644 index 0000000..dd6f80a --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.cs-CZ.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Zpět + + + Zpět na seznam stránek + + + Migrace + + + Kopírovat kategorie stránky + + + Kopírovat kategorie stránky to cílového jmenného prostoru + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Požadován cílový jmenný prostor + + + Migrace + + + Migrovat + + + Kopírovat kategorie stránky + + + Kopírovat kategorie stránky to cílového jmenného prostoru + + + Stránka migrace + + + Cílový jmenný prostor + + + Filter Pages by name + + + Povolit + + + Povolit tuto revizi + + + Jako jmenný prostor + + + Použít šablonu + + + Zpět + + + Zpět na seznam stránek + + + Zpět + + + Zpět na seznam stránek + + + Smazat diskuzi + + + Delete all Messages in the Page's Discussion + + + Smazat + + + Delete the Page's Backups + + + Smazat stránku + + + Smazat stránku + + + Apply Filter + + + Zamknuto + + + Použít šablonu + + + Nová stránka + + + Vytvořit novou stránku + + + Oprávnění + + + Spravovat oprávnění pro tuto stránku + + + Oprávnění + + + Spravovat oprávnění pro tuto stránku + + + Věřejné + + + Použít šablonu + + + Odmítnout + + + Odmítnout revizi + + + Přejmenovat + + + Přejmenovat stránku + + + Vrátit zpět + + + Vrátit stránku zpět + + + Vybrat + + + Vybrat tuto stránky pro administraci + + + Vybrat + + + Vybrat tuto stránky pro administraci + + + Display orphan pages only + + + Keep shadow Page + + + Keep the old Page and set it to auto-redirect to the new Page + + + Invalid Page Name + + + Povolit/Odmítnout koncept + + + Od: + + + Smazat diskuzi stránky + + + Vytvořil + + + Vytvořeno + + + Současný titulek + + + Smazat zálohy + + + Smazat stránku + + + Disc. + + + Editovat + + + Editovat + + + Operace stránky + + + Změnil + + + Změněno + + + Jen administrátoři mohou <b>editovat</b> tuto stránku. + + + Název + + + Jmenný prostor + + + Nový název + + + Inherit permissions from the parent namespace. + + + Sirotek + + + Opravnění stránky + + + Wiki stránky + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Šablony oprávnění + + + Poskytovatel + + + Kdokoliv,včetně anonymních uživatelů mmůže <b>editovat</b> tuto stránku. + + + Přejmenovat stránku + + + Cílová revize + + + Rev. + + + Vrátit stránku zpět + + + Uloženo: + + + Diff + + + Ukázat změny + + + Editovat + + + Editovat koncept + + + Dostupné zálohy, novější první + + + Dostupné revize, novější první + + + Administrace + + + Delete all Backups + + + Delete Backups older than and including + + + Je vyžadován nový název + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.da-DK.resx b/WebApplication/App_LocalResources/AdminPages.aspx.da-DK.resx new file mode 100644 index 0000000..eb77081 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.da-DK.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Page list + + + Migrate + + + + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Target Namespace is required + + + Migrate + + + Migrate the Page + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + Migrate Page + + + + + + Target Namespace + + + + + + + + + + + + + + + Filter Pages by name + + + Approve + + + Approve this Revision + + + As Namespace + + + Use this Template + + + Back + + + Back to the Page list + + + Back + + + Back to the Page list + + + Clear Discussion + + + Delete all Messages in the Page's Discussion + + + Delete + + + Delete the Page's Backups + + + Delete Page + + + Delete this Page + + + + + + Apply Filter + + + Locked + + + Use this Template + + + New Page + + + Create a new Page + + + Permissions + + + Manage Permissions for this Page + + + Permissions + + + Manage Permissions for this Page + + + Public + + + Use this Template + + + Reject + + + Reject this Revision + + + Rename + + + Rename the Page + + + Rollback + + + Rollback the Page + + + Select + + + Select this Page for administration + + + Select + + + Select this Page for administration + + + Display orphan pages only + + + + + + Keep shadow Page + + + Keep the old Page and set it to auto-redirect to the new Page + + + Invalid Page Name + + + + + + + + + + + + + + + Approve/Reject Draft + + + + + + + + + By: + + + Clear Page Discussion + + + Created By + + + Created On + + + + + + Current Title + + + + + + Delete Backups + + + Delete Page + + + + + + + + + Disc. + + + + + + + + + + + + + + + Edit + + + Edit + + + Page Operations + + + Modified By + + + Modified On + + + Only Administrators can <b>edit</b> this page. + + + Name + + + Namespace + + + New Name + + + Inherit permissions from the parent namespace. + + + Orphan + + + + + + Page Permissions + + + Wiki Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> this page. + + + Rename Page + + + + + + + + + Target Revision + + + Rev. + + + Rollback Page + + + + + + + + + Saved on: + + + + + + Diff + + + Show changes + + + Edit + + + Edit this Draft + + + Available Backups, newer first + + + + + + Available revisions, newer first + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete all Backups + + + + + + Delete Backups older than and including + + + + + + New Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.de-DE.resx b/WebApplication/App_LocalResources/AdminPages.aspx.de-DE.resx new file mode 100644 index 0000000..eb77081 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.de-DE.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Page list + + + Migrate + + + + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Target Namespace is required + + + Migrate + + + Migrate the Page + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + Migrate Page + + + + + + Target Namespace + + + + + + + + + + + + + + + Filter Pages by name + + + Approve + + + Approve this Revision + + + As Namespace + + + Use this Template + + + Back + + + Back to the Page list + + + Back + + + Back to the Page list + + + Clear Discussion + + + Delete all Messages in the Page's Discussion + + + Delete + + + Delete the Page's Backups + + + Delete Page + + + Delete this Page + + + + + + Apply Filter + + + Locked + + + Use this Template + + + New Page + + + Create a new Page + + + Permissions + + + Manage Permissions for this Page + + + Permissions + + + Manage Permissions for this Page + + + Public + + + Use this Template + + + Reject + + + Reject this Revision + + + Rename + + + Rename the Page + + + Rollback + + + Rollback the Page + + + Select + + + Select this Page for administration + + + Select + + + Select this Page for administration + + + Display orphan pages only + + + + + + Keep shadow Page + + + Keep the old Page and set it to auto-redirect to the new Page + + + Invalid Page Name + + + + + + + + + + + + + + + Approve/Reject Draft + + + + + + + + + By: + + + Clear Page Discussion + + + Created By + + + Created On + + + + + + Current Title + + + + + + Delete Backups + + + Delete Page + + + + + + + + + Disc. + + + + + + + + + + + + + + + Edit + + + Edit + + + Page Operations + + + Modified By + + + Modified On + + + Only Administrators can <b>edit</b> this page. + + + Name + + + Namespace + + + New Name + + + Inherit permissions from the parent namespace. + + + Orphan + + + + + + Page Permissions + + + Wiki Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> this page. + + + Rename Page + + + + + + + + + Target Revision + + + Rev. + + + Rollback Page + + + + + + + + + Saved on: + + + + + + Diff + + + Show changes + + + Edit + + + Edit this Draft + + + Available Backups, newer first + + + + + + Available revisions, newer first + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete all Backups + + + + + + Delete Backups older than and including + + + + + + New Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.es-ES.resx b/WebApplication/App_LocalResources/AdminPages.aspx.es-ES.resx new file mode 100644 index 0000000..eb77081 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.es-ES.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Page list + + + Migrate + + + + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Target Namespace is required + + + Migrate + + + Migrate the Page + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + Migrate Page + + + + + + Target Namespace + + + + + + + + + + + + + + + Filter Pages by name + + + Approve + + + Approve this Revision + + + As Namespace + + + Use this Template + + + Back + + + Back to the Page list + + + Back + + + Back to the Page list + + + Clear Discussion + + + Delete all Messages in the Page's Discussion + + + Delete + + + Delete the Page's Backups + + + Delete Page + + + Delete this Page + + + + + + Apply Filter + + + Locked + + + Use this Template + + + New Page + + + Create a new Page + + + Permissions + + + Manage Permissions for this Page + + + Permissions + + + Manage Permissions for this Page + + + Public + + + Use this Template + + + Reject + + + Reject this Revision + + + Rename + + + Rename the Page + + + Rollback + + + Rollback the Page + + + Select + + + Select this Page for administration + + + Select + + + Select this Page for administration + + + Display orphan pages only + + + + + + Keep shadow Page + + + Keep the old Page and set it to auto-redirect to the new Page + + + Invalid Page Name + + + + + + + + + + + + + + + Approve/Reject Draft + + + + + + + + + By: + + + Clear Page Discussion + + + Created By + + + Created On + + + + + + Current Title + + + + + + Delete Backups + + + Delete Page + + + + + + + + + Disc. + + + + + + + + + + + + + + + Edit + + + Edit + + + Page Operations + + + Modified By + + + Modified On + + + Only Administrators can <b>edit</b> this page. + + + Name + + + Namespace + + + New Name + + + Inherit permissions from the parent namespace. + + + Orphan + + + + + + Page Permissions + + + Wiki Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> this page. + + + Rename Page + + + + + + + + + Target Revision + + + Rev. + + + Rollback Page + + + + + + + + + Saved on: + + + + + + Diff + + + Show changes + + + Edit + + + Edit this Draft + + + Available Backups, newer first + + + + + + Available revisions, newer first + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete all Backups + + + + + + Delete Backups older than and including + + + + + + New Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.fr-FR.resx b/WebApplication/App_LocalResources/AdminPages.aspx.fr-FR.resx new file mode 100644 index 0000000..eb77081 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.fr-FR.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Page list + + + Migrate + + + + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Target Namespace is required + + + Migrate + + + Migrate the Page + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + Migrate Page + + + + + + Target Namespace + + + + + + + + + + + + + + + Filter Pages by name + + + Approve + + + Approve this Revision + + + As Namespace + + + Use this Template + + + Back + + + Back to the Page list + + + Back + + + Back to the Page list + + + Clear Discussion + + + Delete all Messages in the Page's Discussion + + + Delete + + + Delete the Page's Backups + + + Delete Page + + + Delete this Page + + + + + + Apply Filter + + + Locked + + + Use this Template + + + New Page + + + Create a new Page + + + Permissions + + + Manage Permissions for this Page + + + Permissions + + + Manage Permissions for this Page + + + Public + + + Use this Template + + + Reject + + + Reject this Revision + + + Rename + + + Rename the Page + + + Rollback + + + Rollback the Page + + + Select + + + Select this Page for administration + + + Select + + + Select this Page for administration + + + Display orphan pages only + + + + + + Keep shadow Page + + + Keep the old Page and set it to auto-redirect to the new Page + + + Invalid Page Name + + + + + + + + + + + + + + + Approve/Reject Draft + + + + + + + + + By: + + + Clear Page Discussion + + + Created By + + + Created On + + + + + + Current Title + + + + + + Delete Backups + + + Delete Page + + + + + + + + + Disc. + + + + + + + + + + + + + + + Edit + + + Edit + + + Page Operations + + + Modified By + + + Modified On + + + Only Administrators can <b>edit</b> this page. + + + Name + + + Namespace + + + New Name + + + Inherit permissions from the parent namespace. + + + Orphan + + + + + + Page Permissions + + + Wiki Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> this page. + + + Rename Page + + + + + + + + + Target Revision + + + Rev. + + + Rollback Page + + + + + + + + + Saved on: + + + + + + Diff + + + Show changes + + + Edit + + + Edit this Draft + + + Available Backups, newer first + + + + + + Available revisions, newer first + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete all Backups + + + + + + Delete Backups older than and including + + + + + + New Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.hu-HU.resx b/WebApplication/App_LocalResources/AdminPages.aspx.hu-HU.resx new file mode 100644 index 0000000..eb77081 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.hu-HU.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Page list + + + Migrate + + + + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Target Namespace is required + + + Migrate + + + Migrate the Page + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + Migrate Page + + + + + + Target Namespace + + + + + + + + + + + + + + + Filter Pages by name + + + Approve + + + Approve this Revision + + + As Namespace + + + Use this Template + + + Back + + + Back to the Page list + + + Back + + + Back to the Page list + + + Clear Discussion + + + Delete all Messages in the Page's Discussion + + + Delete + + + Delete the Page's Backups + + + Delete Page + + + Delete this Page + + + + + + Apply Filter + + + Locked + + + Use this Template + + + New Page + + + Create a new Page + + + Permissions + + + Manage Permissions for this Page + + + Permissions + + + Manage Permissions for this Page + + + Public + + + Use this Template + + + Reject + + + Reject this Revision + + + Rename + + + Rename the Page + + + Rollback + + + Rollback the Page + + + Select + + + Select this Page for administration + + + Select + + + Select this Page for administration + + + Display orphan pages only + + + + + + Keep shadow Page + + + Keep the old Page and set it to auto-redirect to the new Page + + + Invalid Page Name + + + + + + + + + + + + + + + Approve/Reject Draft + + + + + + + + + By: + + + Clear Page Discussion + + + Created By + + + Created On + + + + + + Current Title + + + + + + Delete Backups + + + Delete Page + + + + + + + + + Disc. + + + + + + + + + + + + + + + Edit + + + Edit + + + Page Operations + + + Modified By + + + Modified On + + + Only Administrators can <b>edit</b> this page. + + + Name + + + Namespace + + + New Name + + + Inherit permissions from the parent namespace. + + + Orphan + + + + + + Page Permissions + + + Wiki Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> this page. + + + Rename Page + + + + + + + + + Target Revision + + + Rev. + + + Rollback Page + + + + + + + + + Saved on: + + + + + + Diff + + + Show changes + + + Edit + + + Edit this Draft + + + Available Backups, newer first + + + + + + Available revisions, newer first + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete all Backups + + + + + + Delete Backups older than and including + + + + + + New Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.it-IT.resx b/WebApplication/App_LocalResources/AdminPages.aspx.it-IT.resx new file mode 100644 index 0000000..2db2245 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.it-IT.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Approva + + + Approva la revisione + + + Come namespace + + + Usa questo template + + + Indietro + + + Indietro alla lista pagine + + + Indietro + + + Indietro alla lista pagine + + + Svuota discussione + + + Elimina tutti i messaggi nella discussione + + + Elimina + + + Elimina i backup di questa pagina + + + Elimina pagina + + + Elimina questa pagina + + + + + + Applica filtro + + + Bloccata + + + Usa questo template + + + Nuova pagina + + + Crea una nuova pagina + + + Permessi + + + Gestisci permessi per questa pagina + + + Permessi + + + Gestisci permessi per questa pagina + + + Pubblica + + + Usa questo template + + + Respingi + + + Respingi questa revisione + + + Rinomina + + + Rinomina la pagina + + + Rollback + + + Effettua il rollback + + + Seleziona + + + Seleziona questa pagina + + + Seleziona + + + Seleziona questa pagina + + + Visualizza solo pagine orfane + + + + + + Mantieni pagina "ombra" + + + Mantieni la vecchia pagina ed impostala per auto-indirizzare a quella nuova + + + Nome non valido + + + + + + + + + + + + + + + Approva/respingi bozza + + + + + + + + + Da: + + + Svuota discussione + + + Creata da + + + Creata il + + + + + + Titolo corrente + + + + + + Elimina backup + + + Elimina pagina + + + + + + + + + Disc. + + + + + + + + + + + + + + + Modifica + + + Modifica + + + Operazioni pagina + + + Modificata da + + + Modificata il + + + Solo gli Amministratori possono <b>modificare</b> questa pagina. + + + Nome + + + Namespace + + + Nuovo nome + + + Eredita permessi dal namespace padre. + + + Orfana + + + + + + Permessi pagina + + + Pagine Wiki + + + <b>Attenzione</b>: impostando un template tutti i permessi esistenti saranno eliminati. + + + Template permessi + + + Provider + + + Chiunque, Utenti Anonimi inclusi, può <b>modificare</b> questa pagina. + + + Rinomina pagina + + + + + + + + + Revisione target + + + Rev. + + + Rollback pagina + + + + + + + + + Salvata il: + + + + + + Diff + + + Mostra modifiche + + + Modifica + + + Modifica la bozza + + + Backup disponibili, più recente per primo + + + + + + Revisioni disponibili, più recente per prima + + + Amministrazione + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Elimina tutti i backup + + + + + + Elimina i backup più vecchi di (incluso) + + + + + + Nuovo nome richiesto + + + + + + + + + + + + + + + + + + Filtra pagine per nome + + + Migra + + + Migra questa pagina + + + Copia categorie + + + Copia categorie nel namespace di destinazione + + + Migra pagina + + + + + + Namespace di destinazione + + + + + + + + + + + + Indietro + + + Indietro alla lista pagine + + + Migra + + + + + + Copia categorie + + + Copia categorie nel namespace di destinazione + + + <b>Nota</b>: la pagina principale non può essere migrata e viene saltata. + + + <b>Nota</b>: questa lista contiene solo i namespace in cui tu puoi gestire le pagine. + + + 1. Seleziona pagine + + + 2. Seleziona namespace di destinazione + + + 3. Esegui migrazione + + + Namespace di destinazione richiesto + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.nb-NO.resx b/WebApplication/App_LocalResources/AdminPages.aspx.nb-NO.resx new file mode 100644 index 0000000..eb77081 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.nb-NO.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Page list + + + Migrate + + + + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Target Namespace is required + + + Migrate + + + Migrate the Page + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + Migrate Page + + + + + + Target Namespace + + + + + + + + + + + + + + + Filter Pages by name + + + Approve + + + Approve this Revision + + + As Namespace + + + Use this Template + + + Back + + + Back to the Page list + + + Back + + + Back to the Page list + + + Clear Discussion + + + Delete all Messages in the Page's Discussion + + + Delete + + + Delete the Page's Backups + + + Delete Page + + + Delete this Page + + + + + + Apply Filter + + + Locked + + + Use this Template + + + New Page + + + Create a new Page + + + Permissions + + + Manage Permissions for this Page + + + Permissions + + + Manage Permissions for this Page + + + Public + + + Use this Template + + + Reject + + + Reject this Revision + + + Rename + + + Rename the Page + + + Rollback + + + Rollback the Page + + + Select + + + Select this Page for administration + + + Select + + + Select this Page for administration + + + Display orphan pages only + + + + + + Keep shadow Page + + + Keep the old Page and set it to auto-redirect to the new Page + + + Invalid Page Name + + + + + + + + + + + + + + + Approve/Reject Draft + + + + + + + + + By: + + + Clear Page Discussion + + + Created By + + + Created On + + + + + + Current Title + + + + + + Delete Backups + + + Delete Page + + + + + + + + + Disc. + + + + + + + + + + + + + + + Edit + + + Edit + + + Page Operations + + + Modified By + + + Modified On + + + Only Administrators can <b>edit</b> this page. + + + Name + + + Namespace + + + New Name + + + Inherit permissions from the parent namespace. + + + Orphan + + + + + + Page Permissions + + + Wiki Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> this page. + + + Rename Page + + + + + + + + + Target Revision + + + Rev. + + + Rollback Page + + + + + + + + + Saved on: + + + + + + Diff + + + Show changes + + + Edit + + + Edit this Draft + + + Available Backups, newer first + + + + + + Available revisions, newer first + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete all Backups + + + + + + Delete Backups older than and including + + + + + + New Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.nl-NL.resx b/WebApplication/App_LocalResources/AdminPages.aspx.nl-NL.resx new file mode 100644 index 0000000..eb77081 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.nl-NL.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Page list + + + Migrate + + + + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Target Namespace is required + + + Migrate + + + Migrate the Page + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + Migrate Page + + + + + + Target Namespace + + + + + + + + + + + + + + + Filter Pages by name + + + Approve + + + Approve this Revision + + + As Namespace + + + Use this Template + + + Back + + + Back to the Page list + + + Back + + + Back to the Page list + + + Clear Discussion + + + Delete all Messages in the Page's Discussion + + + Delete + + + Delete the Page's Backups + + + Delete Page + + + Delete this Page + + + + + + Apply Filter + + + Locked + + + Use this Template + + + New Page + + + Create a new Page + + + Permissions + + + Manage Permissions for this Page + + + Permissions + + + Manage Permissions for this Page + + + Public + + + Use this Template + + + Reject + + + Reject this Revision + + + Rename + + + Rename the Page + + + Rollback + + + Rollback the Page + + + Select + + + Select this Page for administration + + + Select + + + Select this Page for administration + + + Display orphan pages only + + + + + + Keep shadow Page + + + Keep the old Page and set it to auto-redirect to the new Page + + + Invalid Page Name + + + + + + + + + + + + + + + Approve/Reject Draft + + + + + + + + + By: + + + Clear Page Discussion + + + Created By + + + Created On + + + + + + Current Title + + + + + + Delete Backups + + + Delete Page + + + + + + + + + Disc. + + + + + + + + + + + + + + + Edit + + + Edit + + + Page Operations + + + Modified By + + + Modified On + + + Only Administrators can <b>edit</b> this page. + + + Name + + + Namespace + + + New Name + + + Inherit permissions from the parent namespace. + + + Orphan + + + + + + Page Permissions + + + Wiki Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> this page. + + + Rename Page + + + + + + + + + Target Revision + + + Rev. + + + Rollback Page + + + + + + + + + Saved on: + + + + + + Diff + + + Show changes + + + Edit + + + Edit this Draft + + + Available Backups, newer first + + + + + + Available revisions, newer first + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete all Backups + + + + + + Delete Backups older than and including + + + + + + New Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.pl-PL.resx b/WebApplication/App_LocalResources/AdminPages.aspx.pl-PL.resx new file mode 100644 index 0000000..7c394d0 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.pl-PL.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Page list + + + Migrate + + + + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Target Namespace is required + + + Approve + + + Approve this Revision + + + As Namespace + + + Use this Template + + + Back + + + Back to the Page list + + + Back + + + Back to the Page list + + + Clear Discussion + + + Delete all Messages in the Page's Discussion + + + Delete + + + Delete the Page's Backups + + + Delete Page + + + Delete this Page + + + + + + Apply Filter + + + Locked + + + Use this Template + + + New Page + + + Create a new Page + + + Permissions + + + Manage Permissions for this Page + + + Permissions + + + Manage Permissions for this Page + + + Public + + + Use this Template + + + Reject + + + Reject this Revision + + + Rename + + + Rename the Page + + + Rollback + + + Rollback the Page + + + Select + + + Select this Page for administration + + + Select + + + Select this Page for administration + + + Display orphan pages only + + + + + + Keep shadow Page + + + Keep the old Page and set it to auto-redirect to the new Page + + + Invalid Page Name + + + + + + + + + + + + + + + Approve/Reject Draft + + + + + + + + + By: + + + Clear Page Discussion + + + Created By + + + Created On + + + + + + Current Title + + + + + + Delete Backups + + + Delete Page + + + + + + + + + Disc. + + + + + + + + + + + + + + + Edit + + + Edit + + + Page Operations + + + Modified By + + + Modified On + + + Only Administrators can <b>edit</b> this page. + + + Name + + + Namespace + + + New Name + + + Inherit permissions from the parent namespace. + + + Orphan + + + + + + Page Permissions + + + Wiki Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> this page. + + + Rename Page + + + + + + + + + Target Revision + + + Rev. + + + Rollback Page + + + + + + + + + Saved on: + + + + + + Diff + + + Show changes + + + Edit + + + Edit this Draft + + + Available Backups, newer first + + + + + + Available revisions, newer first + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete all Backups + + + + + + Delete Backups older than and including + + + + + + New Name is required + + + + + + + + + + + + + + + + + + Filter Pages by name + + + Migrate + + + Migrate the Page + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + Migrate Page + + + + + + Target Namespace + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.pt-BR.resx b/WebApplication/App_LocalResources/AdminPages.aspx.pt-BR.resx new file mode 100644 index 0000000..eb77081 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.pt-BR.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Page list + + + Migrate + + + + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Target Namespace is required + + + Migrate + + + Migrate the Page + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + Migrate Page + + + + + + Target Namespace + + + + + + + + + + + + + + + Filter Pages by name + + + Approve + + + Approve this Revision + + + As Namespace + + + Use this Template + + + Back + + + Back to the Page list + + + Back + + + Back to the Page list + + + Clear Discussion + + + Delete all Messages in the Page's Discussion + + + Delete + + + Delete the Page's Backups + + + Delete Page + + + Delete this Page + + + + + + Apply Filter + + + Locked + + + Use this Template + + + New Page + + + Create a new Page + + + Permissions + + + Manage Permissions for this Page + + + Permissions + + + Manage Permissions for this Page + + + Public + + + Use this Template + + + Reject + + + Reject this Revision + + + Rename + + + Rename the Page + + + Rollback + + + Rollback the Page + + + Select + + + Select this Page for administration + + + Select + + + Select this Page for administration + + + Display orphan pages only + + + + + + Keep shadow Page + + + Keep the old Page and set it to auto-redirect to the new Page + + + Invalid Page Name + + + + + + + + + + + + + + + Approve/Reject Draft + + + + + + + + + By: + + + Clear Page Discussion + + + Created By + + + Created On + + + + + + Current Title + + + + + + Delete Backups + + + Delete Page + + + + + + + + + Disc. + + + + + + + + + + + + + + + Edit + + + Edit + + + Page Operations + + + Modified By + + + Modified On + + + Only Administrators can <b>edit</b> this page. + + + Name + + + Namespace + + + New Name + + + Inherit permissions from the parent namespace. + + + Orphan + + + + + + Page Permissions + + + Wiki Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> this page. + + + Rename Page + + + + + + + + + Target Revision + + + Rev. + + + Rollback Page + + + + + + + + + Saved on: + + + + + + Diff + + + Show changes + + + Edit + + + Edit this Draft + + + Available Backups, newer first + + + + + + Available revisions, newer first + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete all Backups + + + + + + Delete Backups older than and including + + + + + + New Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.resx b/WebApplication/App_LocalResources/AdminPages.aspx.resx new file mode 100644 index 0000000..aa30c92 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Approve + + + Approve this Revision + + + As Namespace + + + Use this Template + + + Back + + + Back to the Page list + + + Back + + + Back to the Page list + + + Clear Discussion + + + Delete all Messages in the Page's Discussion + + + Delete + + + Delete the Page's Backups + + + Delete Page + + + Delete this Page + + + + + + Apply Filter + + + Locked + + + Use this Template + + + New Page + + + Create a new Page + + + Permissions + + + Manage Permissions for this Page + + + Permissions + + + Manage Permissions for this Page + + + Public + + + Use this Template + + + Reject + + + Reject this Revision + + + Rename + + + Rename the Page + + + Rollback + + + Rollback the Page + + + Select + + + Select this Page for administration + + + Select + + + Select this Page for administration + + + Display orphan pages only + + + + + + Keep shadow Page + + + Keep the old Page and set it to auto-redirect to the new Page + + + Invalid Page Name + + + + + + + + + + + + + + + Approve/Reject Draft + + + + + + + + + By: + + + Clear Page Discussion + + + Created By + + + Created On + + + + + + Current Title + + + + + + Delete Backups + + + Delete Page + + + + + + + + + Disc. + + + + + + + + + + + + + + + Edit + + + Edit + + + Page Operations + + + Modified By + + + Modified On + + + Only Administrators can <b>edit</b> this page. + + + Name + + + Namespace + + + New Name + + + Inherit permissions from the parent namespace. + + + Orphan + + + + + + Page Permissions + + + Wiki Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> this page. + + + Rename Page + + + + + + + + + Target Revision + + + Rev. + + + Rollback Page + + + + + + + + + Saved on: + + + + + + Diff + + + Show changes + + + Edit + + + Edit this Draft + + + Available Backups, newer first + + + + + + Available revisions, newer first + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete all Backups + + + + + + Delete Backups older than and including + + + + + + New Name is required + + + + + + + + + + + + + + + + + + Filter Pages by name + + + Migrate + + + Migrate the Page + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + Migrate Page + + + + + + Target Namespace + + + + + + + + + + + + Back + + + Back to the Page list + + + Migrate + + + + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Target Namespace is required + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.ro-RO.resx b/WebApplication/App_LocalResources/AdminPages.aspx.ro-RO.resx new file mode 100644 index 0000000..eb77081 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.ro-RO.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Page list + + + Migrate + + + + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Target Namespace is required + + + Migrate + + + Migrate the Page + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + Migrate Page + + + + + + Target Namespace + + + + + + + + + + + + + + + Filter Pages by name + + + Approve + + + Approve this Revision + + + As Namespace + + + Use this Template + + + Back + + + Back to the Page list + + + Back + + + Back to the Page list + + + Clear Discussion + + + Delete all Messages in the Page's Discussion + + + Delete + + + Delete the Page's Backups + + + Delete Page + + + Delete this Page + + + + + + Apply Filter + + + Locked + + + Use this Template + + + New Page + + + Create a new Page + + + Permissions + + + Manage Permissions for this Page + + + Permissions + + + Manage Permissions for this Page + + + Public + + + Use this Template + + + Reject + + + Reject this Revision + + + Rename + + + Rename the Page + + + Rollback + + + Rollback the Page + + + Select + + + Select this Page for administration + + + Select + + + Select this Page for administration + + + Display orphan pages only + + + + + + Keep shadow Page + + + Keep the old Page and set it to auto-redirect to the new Page + + + Invalid Page Name + + + + + + + + + + + + + + + Approve/Reject Draft + + + + + + + + + By: + + + Clear Page Discussion + + + Created By + + + Created On + + + + + + Current Title + + + + + + Delete Backups + + + Delete Page + + + + + + + + + Disc. + + + + + + + + + + + + + + + Edit + + + Edit + + + Page Operations + + + Modified By + + + Modified On + + + Only Administrators can <b>edit</b> this page. + + + Name + + + Namespace + + + New Name + + + Inherit permissions from the parent namespace. + + + Orphan + + + + + + Page Permissions + + + Wiki Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> this page. + + + Rename Page + + + + + + + + + Target Revision + + + Rev. + + + Rollback Page + + + + + + + + + Saved on: + + + + + + Diff + + + Show changes + + + Edit + + + Edit this Draft + + + Available Backups, newer first + + + + + + Available revisions, newer first + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete all Backups + + + + + + Delete Backups older than and including + + + + + + New Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.ru-RU.resx b/WebApplication/App_LocalResources/AdminPages.aspx.ru-RU.resx new file mode 100644 index 0000000..dacc7d8 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.ru-RU.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Назад + + + Назад к списку Страниц + + + Миграция + + + + + + Копировать Категории Страницы + + + Копировать Категории Страницы в целевое Пространство Имён + + + <b>Примечание</b>: главная страница пространства имён не может быть перенесена, такое действие невыполнимо. + + + <b>Примечание</b>: в этом списке отображаются только те пространства имён, страницами которых Вы можете управлять. + + + 1. Выберите Страницы + + + 2. Выберите Целевое Пространство Имён + + + 3. Начинайте Перенос + + + Требуется целевое Пространство Имён + + + Миграция + + + Миграция Страницы + + + Копировать Категории Страницы + + + Копировать Категории Страницы в целевое Пространство Имён + + + Перенос Страницы + + + + + + Целевое Пространство Имён + + + + + + + + + + + + + + + Фильтровать страницы по названию + + + Одобрить + + + Одобрить эту версию + + + Как Пространство Имён + + + Использовать этот Шаблон + + + Назад + + + Назад к списку страниц + + + Назад + + + Назад к списку страниц + + + Очистить Обсуждение + + + Удалить все сообщения в разделе обсуждения Страницы + + + Удалить + + + Удалить версии Страницы + + + Удалить Страницу + + + Удалить эту Страницу + + + + + + Применить Фильтр + + + Заблокированная + + + Использовать этот Шаблон + + + Новая Страница + + + Создать новую Страницу + + + Разрешения + + + Управление Разрешениями этой Страницы + + + Разрешения + + + Управление Разрешениями этой Страницы + + + Публичная + + + Использовать этот Шаблон + + + Отклонить + + + Отклонить эту Версию + + + Переименовать + + + Переименовать Страницу + + + Откат + + + Откат Страницы + + + Выбрать + + + Выбрать эту Страницу для администрирования + + + Выбрать + + + Выбрать эту Страницу для администрирования + + + Показывать только несвязанные страницы + + + + + + Сохранять теневую Страницу + + + Сохранять старое содержание и перенаправлять всех на Страницу с новым содержанием + + + Ошибочное Название Страницы + + + + + + + + + + + + + + + Принять/Отклонить Черновик + + + + + + + + + Автор: + + + Очистить Обсуждение Страницы + + + Создано + + + Создано в + + + + + + Текущий Тайтл + + + + + + Удалить Версии + + + Удалить Страницу + + + + + + + + + Обсуж. + + + + + + + + + + + + + + + Изменить + + + Изменить + + + Изменения Страницы + + + Изменено + + + Изменено в + + + Только Администраторы могут <b>редактировать</b> эту Страницу. + + + Название + + + Пространство Имён + + + Новое Название + + + Наследовать разрешения от родительских Пространств Имён + + + Без наследования + + + + + + Разрешения Страницы + + + Страницы Wiki + + + <b>Предупреждение</b>: применение шаблона изменит все существующие разрешения для пользовательских групп по умолчанию. + + + Шаблоны Разрешений + + + Провайдер + + + Кто угодно, даже анонимные пользователи, могут <b>редактировать</b> эту страницу. + + + Переименовать Страницу + + + + + + + + + Цель создания + + + Верс. + + + Откатить Страницу + + + + + + + + + Сохранено в: + + + + + + Отличия + + + Показать изменения + + + Редактировать + + + Редактировать черновик + + + Доступные бэкапы, первыми идут последние + + + + + + Доступные версии, первыми идут последние + + + Администрирование + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Удалить все старые Версии + + + + + + Удалить Бэкапы, старше по времени, и в том числе + + + + + + Требуется новое Название + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.sk-SK.resx b/WebApplication/App_LocalResources/AdminPages.aspx.sk-SK.resx new file mode 100644 index 0000000..eb77081 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.sk-SK.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Page list + + + Migrate + + + + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Target Namespace is required + + + Migrate + + + Migrate the Page + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + Migrate Page + + + + + + Target Namespace + + + + + + + + + + + + + + + Filter Pages by name + + + Approve + + + Approve this Revision + + + As Namespace + + + Use this Template + + + Back + + + Back to the Page list + + + Back + + + Back to the Page list + + + Clear Discussion + + + Delete all Messages in the Page's Discussion + + + Delete + + + Delete the Page's Backups + + + Delete Page + + + Delete this Page + + + + + + Apply Filter + + + Locked + + + Use this Template + + + New Page + + + Create a new Page + + + Permissions + + + Manage Permissions for this Page + + + Permissions + + + Manage Permissions for this Page + + + Public + + + Use this Template + + + Reject + + + Reject this Revision + + + Rename + + + Rename the Page + + + Rollback + + + Rollback the Page + + + Select + + + Select this Page for administration + + + Select + + + Select this Page for administration + + + Display orphan pages only + + + + + + Keep shadow Page + + + Keep the old Page and set it to auto-redirect to the new Page + + + Invalid Page Name + + + + + + + + + + + + + + + Approve/Reject Draft + + + + + + + + + By: + + + Clear Page Discussion + + + Created By + + + Created On + + + + + + Current Title + + + + + + Delete Backups + + + Delete Page + + + + + + + + + Disc. + + + + + + + + + + + + + + + Edit + + + Edit + + + Page Operations + + + Modified By + + + Modified On + + + Only Administrators can <b>edit</b> this page. + + + Name + + + Namespace + + + New Name + + + Inherit permissions from the parent namespace. + + + Orphan + + + + + + Page Permissions + + + Wiki Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> this page. + + + Rename Page + + + + + + + + + Target Revision + + + Rev. + + + Rollback Page + + + + + + + + + Saved on: + + + + + + Diff + + + Show changes + + + Edit + + + Edit this Draft + + + Available Backups, newer first + + + + + + Available revisions, newer first + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete all Backups + + + + + + Delete Backups older than and including + + + + + + New Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AdminPages.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..eb77081 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.sr-Latn-CS.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Page list + + + Migrate + + + + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Target Namespace is required + + + Migrate + + + Migrate the Page + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + Migrate Page + + + + + + Target Namespace + + + + + + + + + + + + + + + Filter Pages by name + + + Approve + + + Approve this Revision + + + As Namespace + + + Use this Template + + + Back + + + Back to the Page list + + + Back + + + Back to the Page list + + + Clear Discussion + + + Delete all Messages in the Page's Discussion + + + Delete + + + Delete the Page's Backups + + + Delete Page + + + Delete this Page + + + + + + Apply Filter + + + Locked + + + Use this Template + + + New Page + + + Create a new Page + + + Permissions + + + Manage Permissions for this Page + + + Permissions + + + Manage Permissions for this Page + + + Public + + + Use this Template + + + Reject + + + Reject this Revision + + + Rename + + + Rename the Page + + + Rollback + + + Rollback the Page + + + Select + + + Select this Page for administration + + + Select + + + Select this Page for administration + + + Display orphan pages only + + + + + + Keep shadow Page + + + Keep the old Page and set it to auto-redirect to the new Page + + + Invalid Page Name + + + + + + + + + + + + + + + Approve/Reject Draft + + + + + + + + + By: + + + Clear Page Discussion + + + Created By + + + Created On + + + + + + Current Title + + + + + + Delete Backups + + + Delete Page + + + + + + + + + Disc. + + + + + + + + + + + + + + + Edit + + + Edit + + + Page Operations + + + Modified By + + + Modified On + + + Only Administrators can <b>edit</b> this page. + + + Name + + + Namespace + + + New Name + + + Inherit permissions from the parent namespace. + + + Orphan + + + + + + Page Permissions + + + Wiki Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> this page. + + + Rename Page + + + + + + + + + Target Revision + + + Rev. + + + Rollback Page + + + + + + + + + Saved on: + + + + + + Diff + + + Show changes + + + Edit + + + Edit this Draft + + + Available Backups, newer first + + + + + + Available revisions, newer first + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete all Backups + + + + + + Delete Backups older than and including + + + + + + New Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.tr-TR.resx b/WebApplication/App_LocalResources/AdminPages.aspx.tr-TR.resx new file mode 100644 index 0000000..2593918 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.tr-TR.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Migrate + + + Migrate the Page + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + Migrate Page + + + + + + Target Namespace + + + + + + + + + + + + Back + + + Back to the Page list + + + Migrate + + + + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Target Namespace is required + + + + + + Sayfaları ada göre süz + + + Onayla + + + Bu revizyonu onayla + + + Ad alanı olarak + + + Şablon olarak kullan + + + Geri + + + Sayfa listesien dön + + + Geri + + + Sayfa listesine dön + + + Tartışmayı temizle + + + Tartışmadaki mesajları sil + + + Sil + + + Sayfa yedeklerini sil + + + Sayfayı Sil + + + Sayfayı sil + + + + + + Süzgeç uygula + + + Kilitli + + + Şablon olarak kullan + + + Yeni sayfa + + + Yeni sayfa + + + İzinler + + + Sayfa için izinleri yönetin + + + İzinler + + + Sayfa için izinleri yönetin + + + Genel + + + Şablon olarak kullan + + + Reddet + + + Bu revizyonu reddet + + + Yeniden adlandır + + + Sayfayı yeniden adlandır + + + Geri al + + + Sayfayı geri al + + + Seç + + + Seç + + + Seç + + + Seç + + + Linklenmemiş sayfaları listele + + + + + + Gölge sayfayı koru + + + Eski sayfayı koru ve yeni sayfaya otomatik yönlendir + + + Geçersiz dosya adı + + + + + + + + + + + + + + + Taslağı onayla / reddet + + + + + + + + + Yazan: + + + Sayfa tartışmasını temizle + + + Oluşturan + + + Oluşturulma vakti + + + + + + Mevcut başlık + + + + + + Yedekleri sil + + + Sayfayı sil + + + + + + + + + Tartışma + + + + + + + + + + + + + + + Düzenle + + + Edit + + + Sayfa operasyonları + + + Değiştiren + + + Değişme vakti + + + Sadece yöneticiler bu sayfayı değiştirebilir + + + Ad + + + Ad alanı + + + Yeni ad + + + Ana ad alanından yetkileri al + + + Linklenmemişler + + + + + + Sayfa izinleri + + + Wiki sayfaları + + + <b>Dikkat</b>: Bir şablon seçince tüm izinler değişmektedir. + + + İzin şablonları + + + Sağlayıcı + + + Herhangi bir kişi bu sayfayı düzenleyebilir + + + Sayfayı yeniden adlandır + + + + + + + + + Hedef revizyon + + + Rev. + + + Sayfayı geri al + + + + + + + + + Kaydetme vakti: + + + + + + Fark + + + Farkları göster + + + Düzenle + + + Bu taslağı düzenle + + + Var olan yedekler (yeniden eskiye sıralı) + + + + + + Var olan revizyonlar (yeniden eskiye sıralı) + + + Yönetim + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tüm yedekleri sil + + + + + + Şu tarih itibariyle geçmişe doğru yedekleri sil + + + + + + Yeni ad gereklidir + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.uk-UA.resx b/WebApplication/App_LocalResources/AdminPages.aspx.uk-UA.resx new file mode 100644 index 0000000..57e895d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.uk-UA.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Повернутися + + + Повренутися до переліку Сторінок + + + Міграція + + + + + + Копіювати Категорії Сторінки + + + Копіювати Категорії Сторінки до цільового Простору Імен + + + <b>Примітка</b>: головна сторінка простору імен не може мігрувати, такий запит буде пропущений. + + + <b>Примітка</b>: в цьому переліку відображені тільки ті простори імен, до керування сторінок яких, Ви маєте доступ. + + + 1. Вибір Сторінок + + + 2. Вибір цільового Простору Імен + + + 3. Здійснення Міграції + + + Потрібен цільовий Простір Імен + + + Міграція + + + Міграція Сторінки + + + Копіювати Категорії Сторінки + + + Копіювати Категорії Сторінки до цільового Простору Імен + + + Міграція Сторінки + + + + + + Цільовий Простір Імен + + + + + + + + + + + + + + + Фільтрувати сторінки за Іменем + + + Схвалити + + + Схвалити цю версію + + + Як Простір Імен + + + Використовувати цей Шаблон + + + Повернутися + + + Повернутися до переліку сторінок + + + Повернутися + + + Повернутися до переліку сторінок + + + Вичистити Обговорення + + + Видалити всі повідомлення в розділі Обговорення + + + Видалити + + + Знищити версії Сторінки + + + Видалити Сторінку + + + Знищити цю Сторінку + + + + + + Застосувати Фільтр + + + Заблокована + + + Використати цей Шаблон + + + Нова Сторінка + + + Створити нову Сторінку + + + Дозволи + + + Керувати Дозволами цієї Сторінки + + + Дозволи + + + Керувати Дозволами цієї Сторінки + + + Публічна + + + Використати цей Шаблон + + + Відмовити + + + Відмовити застосування цієї редакції + + + Перейменувати + + + Перейменувати цю Сторінку + + + Відкат + + + Відновити зміст + + + Обрати + + + Обрати цю Сторінку для адміністрування + + + Обрати + + + Обрати цю Сторінку для адміністрування + + + Показати лише не зв'язані сторінки + + + + + + Зберігати тіньову Сторінку + + + Зберігати попередній зміст та направляти (при цьому) всіх на Сторінку з новим змістом + + + Помилкове Ім'я Сторінки + + + + + + + + + + + + + + + Схвалити/Відхилити чернетку + + + + + + + + + Автор: + + + Вичистити розділ Обговорення + + + Створено + + + Створено в + + + + + + Поточний Тайтл + + + + + + Видалити Бекапи + + + Видалити Сторінку + + + + + + + + + Обгов. + + + + + + + + + + + + + + + Змінити + + + Змінити + + + Операції Сторінки + + + Змінено + + + Змінено в + + + Тільки Адміністратори мають змогу <b>змінювати</b> цю сторінку. + + + Ім'я + + + Простір Імен + + + Нове Ім'я + + + Спадкувати дозволи від батьківських Просторів Імен + + + Без спадкування + + + + + + Дозволи Сторінки + + + Сторінки Wiki + + + <b>Попередження</b>: застосування шаблону змінить всі існуючі дозволи для звичайних груп користувачів + + + Шаблони Дозволів + + + Несуча + + + Будь хто, ба навіть анонімні користувачі, мають змогу <b>змінювати</b> цю сторінку. + + + Перейменувати Сторінку + + + + + + + + + Мета Змін + + + Верс. + + + Відновити зміст + + + + + + + + + Збережено в: + + + + + + Відмінності + + + Відобразити зміни + + + Змінити + + + Змінити цю Чернетку + + + Існуючи бекапи, першими йдуть останні + + + + + + Иснуючі версії, першими йдуть останні + + + Адміністрування + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Видалити всі Бекапи + + + + + + Видалити найстарші Бекапи, в тому числі й + + + + + + Потрібне Нове Ім'я + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.zh-cn.resx b/WebApplication/App_LocalResources/AdminPages.aspx.zh-cn.resx new file mode 100644 index 0000000..f05497d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.zh-cn.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 返回 + + + 返回页面列表 + + + 迁移 + + + + + + 复制页面分类 + + + 复制页面分类到目标命名空间 + + + <b>注意</b>:命名空间的默认页面不能被迁移并将被无提示的跳过。 + + + <b>注意</b>:此列表只显示您有权管理其中所有页面的命名空间。 + + + 2. 选择目标命名空间 + + + 3. 执行迁移 + + + 需要目标命名空间 + + + 迁移 + + + 迁移页面 + + + 复制页面分类 + + + 复制页面分类到目标命名空间 + + + 迁移页面 + + + + + + 目标命名空间 + + + + + + + + + + + + + + + 按名称筛选页面 + + + 认可 + + + 认可此修订版 + + + 和命名空间相同 + + + 使用此模板 + + + 返回 + + + 返回页面列表 + + + 返回 + + + 返回页面列表 + + + 清除讨论 + + + 删除所有页面讨论消息 + + + 删除 + + + 删除页面备份 + + + 删除页面 + + + 删除此页面 + + + + + + 应用筛选器 + + + 已锁定 + + + 使用此模板 + + + 新建页面 + + + 创建新页面 + + + 权限 + + + 管理此页的权限 + + + 权限 + + + 管理此页的权限 + + + 公共 + + + 使用此模板 + + + 拒绝 + + + 拒绝此修订版 + + + 重命名 + + + 重命名页面 + + + 回滚 + + + 回滚页面 + + + 选择 + + + 选择此页面进行管理 + + + 选择 + + + 选择此页面进行管理 + + + 只显示孤立页面 + + + + + + 保留影子页面 + + + 保留旧页面并设置为自动转到新页面 + + + 页面名称无效 + + + + + + + + + + + + + + + 认可/拒绝草稿 + + + + + + + + + 由: + + + 清除页面讨论 + + + 创建者 + + + 创建于 + + + + + + 当前标题 + + + + + + 删除备份 + + + 删除页面 + + + + + + + + + 讨论 + + + + + + + + + + + + + + + 编辑 + + + 编辑 + + + 页面操作 + + + 修改者 + + + 修改于 + + + 只有管理员能<b>编辑</b>此页。 + + + 名称 + + + 命名空间 + + + 新名称 + + + 从上级命名空间继承权限。 + + + 孤立 + + + + + + 页面权限 + + + 维基页面 + + + <b>警告</b>:设置模板将会替换默认用户组所有现存权限。 + + + 权限模板 + + + 提供者 + + + 任何人,包括匿名用户,可以<b>编辑</b>此页面。 + + + 重命名页面 + + + + + + + + + 目标修订版 + + + 修订 + + + 回滚页面 + + + + + + + + + 保存于: + + + + + + 差异 + + + 显示更改 + + + 编辑 + + + 编辑此草稿 + + + 可用备份,最新最前 + + + + + + 可用修订版,最新最前 + + + 管理 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 删除所有备份 + + + + + + 删除不晚于以下日期的备份 + + + + + + 需要新名称 + + + + + + + + + + + + + + + 1. 选择页面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminPages.aspx.zh-tw.resx b/WebApplication/App_LocalResources/AdminPages.aspx.zh-tw.resx new file mode 100644 index 0000000..eb77081 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminPages.aspx.zh-tw.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Back + + + Back to the Page list + + + Migrate + + + + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + <b>Note</b>: the default page of the namespace cannot be migrated and is silently skipped. + + + <b>Note</b>: this list only displays namespaces in which you are allowed to manage all pages. + + + 1. Select Pages + + + 2. Select Target Namespace + + + 3. Perform Migration + + + Target Namespace is required + + + Migrate + + + Migrate the Page + + + Copy Page Categories + + + Copy Page Categories to target Namespace + + + Migrate Page + + + + + + Target Namespace + + + + + + + + + + + + + + + Filter Pages by name + + + Approve + + + Approve this Revision + + + As Namespace + + + Use this Template + + + Back + + + Back to the Page list + + + Back + + + Back to the Page list + + + Clear Discussion + + + Delete all Messages in the Page's Discussion + + + Delete + + + Delete the Page's Backups + + + Delete Page + + + Delete this Page + + + + + + Apply Filter + + + Locked + + + Use this Template + + + New Page + + + Create a new Page + + + Permissions + + + Manage Permissions for this Page + + + Permissions + + + Manage Permissions for this Page + + + Public + + + Use this Template + + + Reject + + + Reject this Revision + + + Rename + + + Rename the Page + + + Rollback + + + Rollback the Page + + + Select + + + Select this Page for administration + + + Select + + + Select this Page for administration + + + Display orphan pages only + + + + + + Keep shadow Page + + + Keep the old Page and set it to auto-redirect to the new Page + + + Invalid Page Name + + + + + + + + + + + + + + + Approve/Reject Draft + + + + + + + + + By: + + + Clear Page Discussion + + + Created By + + + Created On + + + + + + Current Title + + + + + + Delete Backups + + + Delete Page + + + + + + + + + Disc. + + + + + + + + + + + + + + + Edit + + + Edit + + + Page Operations + + + Modified By + + + Modified On + + + Only Administrators can <b>edit</b> this page. + + + Name + + + Namespace + + + New Name + + + Inherit permissions from the parent namespace. + + + Orphan + + + + + + Page Permissions + + + Wiki Pages + + + <b>Warning</b>: setting a template will replace all existing permissions for the default user groups. + + + Permissions Templates + + + Provider + + + Anyone, including anonymous users, can <b>edit</b> this page. + + + Rename Page + + + + + + + + + Target Revision + + + Rev. + + + Rollback Page + + + + + + + + + Saved on: + + + + + + Diff + + + Show changes + + + Edit + + + Edit this Draft + + + Available Backups, newer first + + + + + + Available revisions, newer first + + + Administration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Delete all Backups + + + + + + Delete Backups older than and including + + + + + + New Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.cs-CZ.resx new file mode 100644 index 0000000..f6785da --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.cs-CZ.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + Update Status + + + Storno + + + Odvybrat poskytovatele + + + Kopírovat + + + Smazat + + + Zakázat + + + Zakáže poskytovatele + + + Povolit + + + Povolí poskytovatele + + + Migrovat + + + Migrovat + + + Migrovat + + + Uložit + + + Uložit + + + Uloží konfigurační řetězec + + + Vybrat + + + Vybrat + + + Uvolnit + + + Uvolní poskytovatele + + + Upload + + + Autor + + + Nápověda + + + Konfigurační řetězec + + + <b>Note</b>: in order to be detected, the destination Provider must be uploaded using the upload tool.<br />Log and recent changes will not be copied. + + + Kopírovat nastavení a související data + + + Migrace dat + + + Poskytovatel cache + + + Poskytovatel souborů + + + Implicitní poskytovatelé + + + Poskytovatel stránek + + + Poskytovatel uživatelů + + + Zobrazení + + + Migrovat soubory a související data + + + Migrovat stránky a související data + + + <b>Note</b>: migrating user accounts will reset all their passwords (an email notice will be sent to all users). + + + Migrovat uživatele a související data + + + Název + + + Poskytovatelé + + + <b>Note</b>: removing a DLL won't disable the Providers it contains until the next wiki restart,<br />but uploading a new DLL will automatically load the Providers it contains. + + + Upload nové DLL + + + Správa DLL poskytovatelů + + + Ver. + + + Administrace + + + Poskytovatelé Cache + + + Poskytovatelé souborů + + + Poskytovatelé formátu + + + Poskytovatelé stránek + + + Poskytovatelé uživatelů + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.da-DK.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.da-DK.resx new file mode 100644 index 0000000..616e7f7 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.da-DK.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + + + + Update Status + + + Cancel + + + Deselect the Provider + + + Copy + + + + + + Delete + + + + + + Disable + + + Disable the Provider + + + Enable + + + Enable the Provider + + + Migrate + + + + + + Migrate + + + + + + Migrate + + + + + + Save + + + + + + Save + + + Save the Configuration String + + + Select + + + + + + Select + + + + + + Unload + + + Unloads the Provider + + + Upload + + + + + + Author + + + Help + + + Configuration String + + + <b>Note</b>: in order to be detected, the destination Provider must be uploaded using the upload tool.<br />Log and recent changes will not be copied. + + + Copy Settings and related data + + + + + + + + + Data Migration + + + Cache Provider + + + Files Provider + + + + + + + + + Default Providers + + + Pages Provider + + + Users Provider + + + Display + + + + + + + + + Migrate Files and related data + + + + + + + + + Migrate Pages and related data + + + + + + + + + <b>Note</b>: migrating user accounts will reset all their passwords (an email notice will be sent to all users). + + + Migrate Users and related data + + + + + + + + + Name + + + + + + + + + + + + + + + Providers + + + + + + + + + + + + + + + <b>Note</b>: removing a DLL won't disable the Providers it contains until the next wiki restart,<br />but uploading a new DLL will automatically load the Providers it contains. + + + Upload new DLL + + + Providers DLLs Management + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Cache Providers + + + + + + Files Providers + + + + + + Formatter Providers + + + + + + Pages Providers + + + + + + Users Providers + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.de-DE.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.de-DE.resx new file mode 100644 index 0000000..616e7f7 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.de-DE.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + + + + Update Status + + + Cancel + + + Deselect the Provider + + + Copy + + + + + + Delete + + + + + + Disable + + + Disable the Provider + + + Enable + + + Enable the Provider + + + Migrate + + + + + + Migrate + + + + + + Migrate + + + + + + Save + + + + + + Save + + + Save the Configuration String + + + Select + + + + + + Select + + + + + + Unload + + + Unloads the Provider + + + Upload + + + + + + Author + + + Help + + + Configuration String + + + <b>Note</b>: in order to be detected, the destination Provider must be uploaded using the upload tool.<br />Log and recent changes will not be copied. + + + Copy Settings and related data + + + + + + + + + Data Migration + + + Cache Provider + + + Files Provider + + + + + + + + + Default Providers + + + Pages Provider + + + Users Provider + + + Display + + + + + + + + + Migrate Files and related data + + + + + + + + + Migrate Pages and related data + + + + + + + + + <b>Note</b>: migrating user accounts will reset all their passwords (an email notice will be sent to all users). + + + Migrate Users and related data + + + + + + + + + Name + + + + + + + + + + + + + + + Providers + + + + + + + + + + + + + + + <b>Note</b>: removing a DLL won't disable the Providers it contains until the next wiki restart,<br />but uploading a new DLL will automatically load the Providers it contains. + + + Upload new DLL + + + Providers DLLs Management + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Cache Providers + + + + + + Files Providers + + + + + + Formatter Providers + + + + + + Pages Providers + + + + + + Users Providers + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.es-ES.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.es-ES.resx new file mode 100644 index 0000000..616e7f7 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.es-ES.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + + + + Update Status + + + Cancel + + + Deselect the Provider + + + Copy + + + + + + Delete + + + + + + Disable + + + Disable the Provider + + + Enable + + + Enable the Provider + + + Migrate + + + + + + Migrate + + + + + + Migrate + + + + + + Save + + + + + + Save + + + Save the Configuration String + + + Select + + + + + + Select + + + + + + Unload + + + Unloads the Provider + + + Upload + + + + + + Author + + + Help + + + Configuration String + + + <b>Note</b>: in order to be detected, the destination Provider must be uploaded using the upload tool.<br />Log and recent changes will not be copied. + + + Copy Settings and related data + + + + + + + + + Data Migration + + + Cache Provider + + + Files Provider + + + + + + + + + Default Providers + + + Pages Provider + + + Users Provider + + + Display + + + + + + + + + Migrate Files and related data + + + + + + + + + Migrate Pages and related data + + + + + + + + + <b>Note</b>: migrating user accounts will reset all their passwords (an email notice will be sent to all users). + + + Migrate Users and related data + + + + + + + + + Name + + + + + + + + + + + + + + + Providers + + + + + + + + + + + + + + + <b>Note</b>: removing a DLL won't disable the Providers it contains until the next wiki restart,<br />but uploading a new DLL will automatically load the Providers it contains. + + + Upload new DLL + + + Providers DLLs Management + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Cache Providers + + + + + + Files Providers + + + + + + Formatter Providers + + + + + + Pages Providers + + + + + + Users Providers + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.fr-FR.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.fr-FR.resx new file mode 100644 index 0000000..616e7f7 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.fr-FR.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + + + + Update Status + + + Cancel + + + Deselect the Provider + + + Copy + + + + + + Delete + + + + + + Disable + + + Disable the Provider + + + Enable + + + Enable the Provider + + + Migrate + + + + + + Migrate + + + + + + Migrate + + + + + + Save + + + + + + Save + + + Save the Configuration String + + + Select + + + + + + Select + + + + + + Unload + + + Unloads the Provider + + + Upload + + + + + + Author + + + Help + + + Configuration String + + + <b>Note</b>: in order to be detected, the destination Provider must be uploaded using the upload tool.<br />Log and recent changes will not be copied. + + + Copy Settings and related data + + + + + + + + + Data Migration + + + Cache Provider + + + Files Provider + + + + + + + + + Default Providers + + + Pages Provider + + + Users Provider + + + Display + + + + + + + + + Migrate Files and related data + + + + + + + + + Migrate Pages and related data + + + + + + + + + <b>Note</b>: migrating user accounts will reset all their passwords (an email notice will be sent to all users). + + + Migrate Users and related data + + + + + + + + + Name + + + + + + + + + + + + + + + Providers + + + + + + + + + + + + + + + <b>Note</b>: removing a DLL won't disable the Providers it contains until the next wiki restart,<br />but uploading a new DLL will automatically load the Providers it contains. + + + Upload new DLL + + + Providers DLLs Management + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Cache Providers + + + + + + Files Providers + + + + + + Formatter Providers + + + + + + Pages Providers + + + + + + Users Providers + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.hu-HU.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.hu-HU.resx new file mode 100644 index 0000000..616e7f7 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.hu-HU.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + + + + Update Status + + + Cancel + + + Deselect the Provider + + + Copy + + + + + + Delete + + + + + + Disable + + + Disable the Provider + + + Enable + + + Enable the Provider + + + Migrate + + + + + + Migrate + + + + + + Migrate + + + + + + Save + + + + + + Save + + + Save the Configuration String + + + Select + + + + + + Select + + + + + + Unload + + + Unloads the Provider + + + Upload + + + + + + Author + + + Help + + + Configuration String + + + <b>Note</b>: in order to be detected, the destination Provider must be uploaded using the upload tool.<br />Log and recent changes will not be copied. + + + Copy Settings and related data + + + + + + + + + Data Migration + + + Cache Provider + + + Files Provider + + + + + + + + + Default Providers + + + Pages Provider + + + Users Provider + + + Display + + + + + + + + + Migrate Files and related data + + + + + + + + + Migrate Pages and related data + + + + + + + + + <b>Note</b>: migrating user accounts will reset all their passwords (an email notice will be sent to all users). + + + Migrate Users and related data + + + + + + + + + Name + + + + + + + + + + + + + + + Providers + + + + + + + + + + + + + + + <b>Note</b>: removing a DLL won't disable the Providers it contains until the next wiki restart,<br />but uploading a new DLL will automatically load the Providers it contains. + + + Upload new DLL + + + Providers DLLs Management + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Cache Providers + + + + + + Files Providers + + + + + + Formatter Providers + + + + + + Pages Providers + + + + + + Users Providers + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.it-IT.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.it-IT.resx new file mode 100644 index 0000000..ef7746e --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.it-IT.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Nota 1</b>: esegui sempre un backup prima di migrare i dati.<br /><b>Nota 2</b>: le migrazioni richiedono parecchio tempo: durante l'attesa, non eseguire nessun'altra attività nel wiki e non chiudere questa pagina.<br /><b>Nota 3</b>: il provider di destinazione dovrebbe essere completamente vuoto, altrimenti potrebbero verificarsi degli errori. Controlla la documentazione del provider per maggiori dettagli.<br /><b>Timeout</b>: è opportuno incrementare il parametro executionTimeout nel file web.config prima di avvioare una migrazione. + + + Auto-aggiorna provider + + + Aggiorna tutti i provider automaticamente + + + Impossibile disattivare il provider in quanto è il default oppure,<br />nel caso di Pages Provider, gestisce la pagina principale del namespace root + + + + + + Stato aggiorn. + + + Annulla + + + Deseleziona provider + + + Copia + + + + + + Elimina + + + + + + Disattiva + + + Disattiva il provider + + + Attiva + + + Attiva il provider + + + Migra + + + + + + Migra + + + + + + Migra + + + + + + Salva + + + + + + Salva + + + Salva la stringa di configurazione + + + Seleziona + + + + + + Seleziona + + + + + + Unload + + + Unload Provider + + + Upload + + + + + + Autore + + + Aiuto + + + Stringa di configurazione + + + <b>Nota</b>: perché sia rilevato, il provider di destinazione deve essere caricato utilizzando il tool di upload.<br />Log e modifiche recenti non saranno copiate. + + + Copia impostazioni e dati correlati + + + + + + + + + Migrazione dati + + + Cache Provider + + + Files Provider + + + + + + + + + Provider di default + + + Pages Provider + + + Users Provider + + + Visualizza + + + + + + + + + Migra file e dati correlati + + + + + + + + + Migra pagine e dati correlati + + + + + + + + + <b>Nota</b>: migrando gli utenti verranno resettate tutte le password (una notifica verrà inviata via email). + + + Migra utenti e dati correlati + + + + + + + + + Nome + + + + + + + + + + + + + + + Provider + + + + + + + + + + + + + + + <b>Nota</b>: rimuovere una DLL non disabilita i provider che contiene fino al successivo riavvio,<br />ma installando una DLL i provider che contiene saranno caricati. + + + Carica nuova DLL + + + Gestione DLL provider + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Amministrazione + + + + + + + + + + + + + + + Cache Provider + + + + + + Files Provider + + + + + + Formatter Provider + + + + + + Pages Provider + + + + + + Users Provider + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.nb-NO.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.nb-NO.resx new file mode 100644 index 0000000..616e7f7 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.nb-NO.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + + + + Update Status + + + Cancel + + + Deselect the Provider + + + Copy + + + + + + Delete + + + + + + Disable + + + Disable the Provider + + + Enable + + + Enable the Provider + + + Migrate + + + + + + Migrate + + + + + + Migrate + + + + + + Save + + + + + + Save + + + Save the Configuration String + + + Select + + + + + + Select + + + + + + Unload + + + Unloads the Provider + + + Upload + + + + + + Author + + + Help + + + Configuration String + + + <b>Note</b>: in order to be detected, the destination Provider must be uploaded using the upload tool.<br />Log and recent changes will not be copied. + + + Copy Settings and related data + + + + + + + + + Data Migration + + + Cache Provider + + + Files Provider + + + + + + + + + Default Providers + + + Pages Provider + + + Users Provider + + + Display + + + + + + + + + Migrate Files and related data + + + + + + + + + Migrate Pages and related data + + + + + + + + + <b>Note</b>: migrating user accounts will reset all their passwords (an email notice will be sent to all users). + + + Migrate Users and related data + + + + + + + + + Name + + + + + + + + + + + + + + + Providers + + + + + + + + + + + + + + + <b>Note</b>: removing a DLL won't disable the Providers it contains until the next wiki restart,<br />but uploading a new DLL will automatically load the Providers it contains. + + + Upload new DLL + + + Providers DLLs Management + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Cache Providers + + + + + + Files Providers + + + + + + Formatter Providers + + + + + + Pages Providers + + + + + + Users Providers + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.nl-NL.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.nl-NL.resx new file mode 100644 index 0000000..616e7f7 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.nl-NL.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + + + + Update Status + + + Cancel + + + Deselect the Provider + + + Copy + + + + + + Delete + + + + + + Disable + + + Disable the Provider + + + Enable + + + Enable the Provider + + + Migrate + + + + + + Migrate + + + + + + Migrate + + + + + + Save + + + + + + Save + + + Save the Configuration String + + + Select + + + + + + Select + + + + + + Unload + + + Unloads the Provider + + + Upload + + + + + + Author + + + Help + + + Configuration String + + + <b>Note</b>: in order to be detected, the destination Provider must be uploaded using the upload tool.<br />Log and recent changes will not be copied. + + + Copy Settings and related data + + + + + + + + + Data Migration + + + Cache Provider + + + Files Provider + + + + + + + + + Default Providers + + + Pages Provider + + + Users Provider + + + Display + + + + + + + + + Migrate Files and related data + + + + + + + + + Migrate Pages and related data + + + + + + + + + <b>Note</b>: migrating user accounts will reset all their passwords (an email notice will be sent to all users). + + + Migrate Users and related data + + + + + + + + + Name + + + + + + + + + + + + + + + Providers + + + + + + + + + + + + + + + <b>Note</b>: removing a DLL won't disable the Providers it contains until the next wiki restart,<br />but uploading a new DLL will automatically load the Providers it contains. + + + Upload new DLL + + + Providers DLLs Management + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Cache Providers + + + + + + Files Providers + + + + + + Formatter Providers + + + + + + Pages Providers + + + + + + Users Providers + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.pl-PL.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.pl-PL.resx new file mode 100644 index 0000000..70694ea --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.pl-PL.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + + Cancel + + + Deselect the Provider + + + Copy + + + + + + Delete + + + + + + Disable + + + Disable the Provider + + + Enable + + + Enable the Provider + + + Migrate + + + + + + Migrate + + + + + + Migrate + + + + + + Save + + + + + + Save + + + Save the Configuration String + + + Select + + + + + + Select + + + + + + Unload + + + Unloads the Provider + + + Upload + + + + + + Author + + + Help + + + Configuration String + + + <b>Note</b>: in order to be detected, the destination Provider must be uploaded using the upload tool.<br />Log and recent changes will not be copied. + + + Copy Settings and related data + + + + + + + + + Data Migration + + + Cache Provider + + + Files Provider + + + + + + + + + Default Providers + + + Pages Provider + + + Users Provider + + + Display + + + + + + + + + Migrate Files and related data + + + + + + + + + Migrate Pages and related data + + + + + + + + + <b>Note</b>: migrating user accounts will reset all their passwords (an email notice will be sent to all users). + + + Migrate Users and related data + + + + + + + + + Name + + + + + + + + + + + + + + + Providers + + + + + + + + + + + + + + + <b>Note</b>: removing a DLL won't disable the Providers it contains until the next wiki restart,<br />but uploading a new DLL will automatically load the Providers it contains. + + + Upload new DLL + + + Providers DLLs Management + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Cache Providers + + + + + + Files Providers + + + + + + Formatter Providers + + + + + + Pages Providers + + + + + + Users Providers + + + + + + + + + + + + + + + Update Status + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.pt-BR.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.pt-BR.resx new file mode 100644 index 0000000..616e7f7 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.pt-BR.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + + + + Update Status + + + Cancel + + + Deselect the Provider + + + Copy + + + + + + Delete + + + + + + Disable + + + Disable the Provider + + + Enable + + + Enable the Provider + + + Migrate + + + + + + Migrate + + + + + + Migrate + + + + + + Save + + + + + + Save + + + Save the Configuration String + + + Select + + + + + + Select + + + + + + Unload + + + Unloads the Provider + + + Upload + + + + + + Author + + + Help + + + Configuration String + + + <b>Note</b>: in order to be detected, the destination Provider must be uploaded using the upload tool.<br />Log and recent changes will not be copied. + + + Copy Settings and related data + + + + + + + + + Data Migration + + + Cache Provider + + + Files Provider + + + + + + + + + Default Providers + + + Pages Provider + + + Users Provider + + + Display + + + + + + + + + Migrate Files and related data + + + + + + + + + Migrate Pages and related data + + + + + + + + + <b>Note</b>: migrating user accounts will reset all their passwords (an email notice will be sent to all users). + + + Migrate Users and related data + + + + + + + + + Name + + + + + + + + + + + + + + + Providers + + + + + + + + + + + + + + + <b>Note</b>: removing a DLL won't disable the Providers it contains until the next wiki restart,<br />but uploading a new DLL will automatically load the Providers it contains. + + + Upload new DLL + + + Providers DLLs Management + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Cache Providers + + + + + + Files Providers + + + + + + Formatter Providers + + + + + + Pages Providers + + + + + + Users Providers + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.resx new file mode 100644 index 0000000..f21dd21 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Deselect the Provider + + + Copy + + + + + + Delete + + + + + + Disable + + + Disable the Provider + + + Enable + + + Enable the Provider + + + Migrate + + + + + + Migrate + + + + + + Migrate + + + + + + Save + + + + + + Save + + + Save the Configuration String + + + Select + + + + + + Select + + + + + + Unload + + + Unloads the Provider + + + Upload + + + + + + Author + + + Help + + + Configuration String + + + <b>Note</b>: in order to be detected, the destination Provider must be uploaded using the upload tool.<br />Log and recent changes will not be copied. + + + Copy Settings and related data + + + + + + + + + Data Migration + + + Cache Provider + + + Files Provider + + + + + + + + + Default Providers + + + Pages Provider + + + Users Provider + + + Display + + + + + + + + + Migrate Files and related data + + + + + + + + + Migrate Pages and related data + + + + + + + + + <b>Note</b>: migrating user accounts will reset all their passwords (an email notice will be sent to all users). + + + Migrate Users and related data + + + + + + + + + Name + + + + + + + + + + + + + + + Providers + + + + + + + + + + + + + + + <b>Note</b>: removing a DLL won't disable the Providers it contains until the next wiki restart,<br />but uploading a new DLL will automatically load the Providers it contains. + + + Upload new DLL + + + Providers DLLs Management + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Cache Providers + + + + + + Files Providers + + + + + + Formatter Providers + + + + + + Pages Providers + + + + + + Users Providers + + + + + + + + + + + + + + + Update Status + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.ro-RO.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.ro-RO.resx new file mode 100644 index 0000000..616e7f7 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.ro-RO.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + + + + Update Status + + + Cancel + + + Deselect the Provider + + + Copy + + + + + + Delete + + + + + + Disable + + + Disable the Provider + + + Enable + + + Enable the Provider + + + Migrate + + + + + + Migrate + + + + + + Migrate + + + + + + Save + + + + + + Save + + + Save the Configuration String + + + Select + + + + + + Select + + + + + + Unload + + + Unloads the Provider + + + Upload + + + + + + Author + + + Help + + + Configuration String + + + <b>Note</b>: in order to be detected, the destination Provider must be uploaded using the upload tool.<br />Log and recent changes will not be copied. + + + Copy Settings and related data + + + + + + + + + Data Migration + + + Cache Provider + + + Files Provider + + + + + + + + + Default Providers + + + Pages Provider + + + Users Provider + + + Display + + + + + + + + + Migrate Files and related data + + + + + + + + + Migrate Pages and related data + + + + + + + + + <b>Note</b>: migrating user accounts will reset all their passwords (an email notice will be sent to all users). + + + Migrate Users and related data + + + + + + + + + Name + + + + + + + + + + + + + + + Providers + + + + + + + + + + + + + + + <b>Note</b>: removing a DLL won't disable the Providers it contains until the next wiki restart,<br />but uploading a new DLL will automatically load the Providers it contains. + + + Upload new DLL + + + Providers DLLs Management + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Cache Providers + + + + + + Files Providers + + + + + + Formatter Providers + + + + + + Pages Providers + + + + + + Users Providers + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.ru-RU.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.ru-RU.resx new file mode 100644 index 0000000..09f28b9 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.ru-RU.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Примечание 1</b>: всегда выполняйте полное сохранение данных до начала миграции.<br /><b>Примечание 2</b>: миграция происходит в течение некоторого времени до полной загрузки данных: в это время, не выполняйте никаких действий в Wiki и не закрывайте эту страницу.<br /><b>Примечание 3</b>: значение провайдера должно быть незаполненным: если такое заполнить любыми данными это может привести к зацикливанию. В отношении деталей настройки обращайтесь к документации конкретного провайдера.<br /><b>Таймауты</b>: настоятельно рекомендуем увеличить значение параметра Timeout в web.config до начала процесса миграции данных. + + + Авто-обновление Провайдеров + + + Автоматическое обновление всех установленных провайдеров, всех типов + + + Не удаётся отключить этот провайдер, поскольку таковой является провайдером по умолчанию или,<br />в случае с Провайдером Страниц, такой управляет главной страницей основного пространства имён + + + + + + Статус Обновления + + + Отмена + + + Отменить выбор провайдера + + + Копировать + + + + + + Удалить + + + + + + Отключить + + + Отключить этот провайдер + + + Включить + + + Включить этот провайдер + + + Перенести + + + + + + Перенести + + + + + + Перенести + + + + + + Сохранить + + + + + + Сохранить + + + Сохранить строку соединения + + + Выбрать + + + + + + Выбрать + + + + + + Выгрузить + + + Выгрузить этот провайдер + + + Загрузить + + + + + + Автор + + + Помощь + + + Строка соединения + + + <b>Примечание</b>: для обнаружения Провайдера, такой должен быть загружен в соответствующее место с помощью инструментов, использующихся на этой странице.<br />Лог и последние изменения не будут перенесены. + + + Копировать Установки и связанные данные + + + + + + + + + Перенос Данных + + + Провайдер кэша + + + Провайдер файлов + + + + + + + + + Провайдеры по умолчанию + + + Провайдер страниц + + + Провайдер пользователей + + + Отображение + + + + + + + + + Перенос файлов и связанных данных + + + + + + + + + Перенос страниц и связанных данных + + + + + + + + + <b>Примечание</b>: перенос учётных записей сбросит все пользовательские пароли (о чём, каждому из пользователей будет отправлено уведомление). + + + Перенос пользователей и связанных данных + + + + + + + + + Имя + + + + + + + + + + + + + + + Провайдеры + + + + + + + + + + + + + + + <b>Примечание</b>: удаление DLL-библиотеки не отключает соответствующий провайдер до перезагрузки всей Wiki,<br />а, загрузка новой DLL тут же иницирует запуск соответствующего провайдера. + + + Загрузить новую DLL + + + Управление DLL-ками Провайдеров + + + + + + + + + Вер. + + + + + + + + + + + + + + + + + + + + + + + + + + + Администрирование + + + + + + + + + + + + + + + Провайдеры Кэша + + + + + + Файловые Провайдеры + + + + + + Провайдеры Разметки + + + + + + Провайдеры Страниц + + + + + + Провайдеры Пользователей + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.sk-SK.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.sk-SK.resx new file mode 100644 index 0000000..616e7f7 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.sk-SK.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + + + + Update Status + + + Cancel + + + Deselect the Provider + + + Copy + + + + + + Delete + + + + + + Disable + + + Disable the Provider + + + Enable + + + Enable the Provider + + + Migrate + + + + + + Migrate + + + + + + Migrate + + + + + + Save + + + + + + Save + + + Save the Configuration String + + + Select + + + + + + Select + + + + + + Unload + + + Unloads the Provider + + + Upload + + + + + + Author + + + Help + + + Configuration String + + + <b>Note</b>: in order to be detected, the destination Provider must be uploaded using the upload tool.<br />Log and recent changes will not be copied. + + + Copy Settings and related data + + + + + + + + + Data Migration + + + Cache Provider + + + Files Provider + + + + + + + + + Default Providers + + + Pages Provider + + + Users Provider + + + Display + + + + + + + + + Migrate Files and related data + + + + + + + + + Migrate Pages and related data + + + + + + + + + <b>Note</b>: migrating user accounts will reset all their passwords (an email notice will be sent to all users). + + + Migrate Users and related data + + + + + + + + + Name + + + + + + + + + + + + + + + Providers + + + + + + + + + + + + + + + <b>Note</b>: removing a DLL won't disable the Providers it contains until the next wiki restart,<br />but uploading a new DLL will automatically load the Providers it contains. + + + Upload new DLL + + + Providers DLLs Management + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Cache Providers + + + + + + Files Providers + + + + + + Formatter Providers + + + + + + Pages Providers + + + + + + Users Providers + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..616e7f7 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.sr-Latn-CS.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + + + + Update Status + + + Cancel + + + Deselect the Provider + + + Copy + + + + + + Delete + + + + + + Disable + + + Disable the Provider + + + Enable + + + Enable the Provider + + + Migrate + + + + + + Migrate + + + + + + Migrate + + + + + + Save + + + + + + Save + + + Save the Configuration String + + + Select + + + + + + Select + + + + + + Unload + + + Unloads the Provider + + + Upload + + + + + + Author + + + Help + + + Configuration String + + + <b>Note</b>: in order to be detected, the destination Provider must be uploaded using the upload tool.<br />Log and recent changes will not be copied. + + + Copy Settings and related data + + + + + + + + + Data Migration + + + Cache Provider + + + Files Provider + + + + + + + + + Default Providers + + + Pages Provider + + + Users Provider + + + Display + + + + + + + + + Migrate Files and related data + + + + + + + + + Migrate Pages and related data + + + + + + + + + <b>Note</b>: migrating user accounts will reset all their passwords (an email notice will be sent to all users). + + + Migrate Users and related data + + + + + + + + + Name + + + + + + + + + + + + + + + Providers + + + + + + + + + + + + + + + <b>Note</b>: removing a DLL won't disable the Providers it contains until the next wiki restart,<br />but uploading a new DLL will automatically load the Providers it contains. + + + Upload new DLL + + + Providers DLLs Management + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Cache Providers + + + + + + Files Providers + + + + + + Formatter Providers + + + + + + Pages Providers + + + + + + Users Providers + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.tr-TR.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.tr-TR.resx new file mode 100644 index 0000000..2f81b63 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.tr-TR.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Update Status + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + + İptal + + + Sağlayıcı seçimini kaldır + + + Kopyala + + + + + + Sil + + + + + + Pasifleştir + + + Sağlayıcı pasifleştir + + + Aktifleştir + + + Sağlayıcıyı aktifleştir + + + Taşı + + + + + + Taşı + + + + + + Taşı + + + + + + Kaydet + + + + + + Kaydet + + + Yapılandırma parametresini kaydet + + + Seç + + + + + + Seç + + + + + + Kaldır + + + Sağlayıcıyı kaldır + + + Yükle + + + + + + Yazar + + + Yardım + + + Yapılandırma parametresi + + + <b>Not</b>: Algılamamız için sağlayıcıyı bizim yükleme aracımızla gönderiniz. <br /> + + + Ayarları ve verileri kopyala + + + + + + + + + Veri taşıma + + + Önbellek sağlayıcısı + + + Dosya sağlayıcısı + + + + + + + + + Öntanımlı sağlayıcılar + + + Sayfa sağlayıcısı + + + Kullanıcı sağlayıcısı + + + Görüntü + + + + + + + + + Dosya ve verileri taşı + + + + + + + + + Dosya ve verileri taşı + + + + + + + + + <b>Not</b>: Kullanıcı hesaplarını taşımak şifrelerinin sıfırlanmasına neden olacak. Tüm kullanıcılara e-posta ile bilgi gönderilecektir. + + + Kullanıcı ve verileri taşı + + + + + + + + + Ad + + + + + + + + + + + + + + + Sağlayıcılar + + + + + + + + + + + + + + + <b>Not</b>: bir DLL'i kaldırmak sağlayıcıyı, bir sonraki uygulama başlangıcına dek pasifleştirmeyecektir. Ancak yeni DLL yüklemek, içindeki sağlayıcıların otomatik olarak geçerli olmasını sağlayacaktır. + + + Yeni DLL yükle + + + Sağlyıcı DLL'lerinin yönetimi + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Yönetim + + + + + + + + + + + + + + + Önbellek sağlayıcıları + + + + + + Dosya sağlayıcıları + + + + + + Biçimleme sağlayıcıları + + + + + + Sayfa sağlayıcıları + + + + + + Kullanıcı sağlayıcıları + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.uk-UA.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.uk-UA.resx new file mode 100644 index 0000000..0787c56 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.uk-UA.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Примітка 1</b>: завжди виконуйте повне архівування даних до початку міграції.<br /><b>Примітка 2</b>: міграція триває якийсь час до повного завантаження: в цей час, не виконуйте ніяких дій у wiki та не закривайте цю сторінку.<br /><b>Примітка 3</b>: значення несучої повинно бути незаповненим: якщо таке буде містити якісь дані це може призвести до виникнення безкінечного циклу виконання. Щодо деталей звертайтеся до документації відповідної несучої.<br /><b>Затримки</b>: дуже радимо збільшити значення параметру Timeout у web.config до початку процесу міграції даних. + + + Авто-оновлення Несучих + + + Автоматичне оновлення усіх встановлених несучих, усіх типів + + + Немає змоги відімкнути несучу, бо вона є основною або ж,<br />у випадку із Несучою Сторінок, така керує головною сторінкою основного простору імен + + + + + + Статус Оновлення + + + Відміна + + + Відмовитися від несучої + + + Копіювати + + + + + + Видалити + + + + + + Відключити + + + Відключити цю Несучу + + + Включити + + + Відключити цю Несучу + + + Перенести + + + + + + Перенести + + + + + + Перенести + + + + + + Зберегти + + + + + + Зберегти + + + Зберегти конфігурацію з'єднання + + + Обрати + + + + + + Обрати + + + + + + Вивантажити + + + Вивантажити цю Несучу + + + Завантажити + + + + + + Автор + + + Допомога + + + Рядок з'єднання + + + <b>Примітка</b>: для виявлення Провайдеру (несучої), такий повинен бути завантажений у відповідне місце за допомогою інструментів, що використовуються на цій сторінці.<br />Лог та останні зміни не будуть збережені. + + + Копіювати конфігурацію та пов'язані дані + + + + + + + + + Перенесення Даних + + + Несуча Кешу + + + Несуча файлів + + + + + + + + + Звичайні Несучі + + + Несуча Сторінок + + + Несуча Користувачів + + + Відображення + + + + + + + + + Перенесення Файлів та пов'язаних даних + + + + + + + + + Перенесення Сторінок та пов'язаних даних + + + + + + + + + <b>Примітка</b>: перенесення облікових записів скине усі паролі користувачів (про що, кожному з них буде відправлене повідомлення). + + + Пересення Користувачів та пов'язаних даних + + + + + + + + + Ім'я + + + + + + + + + + + + + + + Несучі + + + + + + + + + + + + + + + <b>Примітка</b>: видалення DLL-бібліотеки не виключає відповідну несучу до перезавантаження всієї Wiki,<br />попри це, завантаження нової DLL негайно запускає відповідну несучу. + + + Завантажити нову DLL + + + Керування DLL-ками Несучіх + + + + + + + + + Вер. + + + + + + + + + + + + + + + + + + + + + + + + + + + Адміністрування + + + + + + + + + + + + + + + Несучі Кешу + + + + + + Несучі Файлів + + + + + + Несучі Розмітки + + + + + + Несучі Сторінок + + + + + + Несучі Користувачів + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.zh-cn.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.zh-cn.resx new file mode 100644 index 0000000..342c8d0 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.zh-cn.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>注意 1</b>:总是在执行迁移之前对您的所有数据进行完整备份。<br /><b>注意 2</b>:迁移通常需要数分钟来完成:在这段时间内,不要在维基中进行任何其他活动,并且不要关闭本页。<br /><b>注意 3</b>:目标提供这应该是完全空的:如果它包含任何数据,可能会引起一致性问题。参阅目标提供商的说明文件以获取详情。<br /><b>超时</b>:强烈建议您在迁移数据之前增加 web.config 中 executionTimeout 参数的值。 + + + 自动更新提供者 + + + 自动更新已安装的所有提供者,包括所有类型 + + + 无法禁用提供者,因为它是默认提供者,<br />或者它是页面提供者并管理根命名空间的默认页面 + + + + + + 更新状态 + + + 取消 + + + 取消选择提供者 + + + 复制 + + + + + + 删除 + + + + + + 禁用 + + + 禁用提供者 + + + 启用 + + + 启用提供者 + + + 迁移 + + + + + + 迁移 + + + + + + 迁移 + + + + + + 保存 + + + + + + 保存 + + + 保存配置字符串 + + + 选择 + + + + + + 选择 + + + + + + 卸载 + + + 卸载提供者 + + + 上传 + + + + + + 作者 + + + 帮助 + + + 配置字符串 + + + <b>注意</b>:为了使之能被检测到,必须使用上传工具上传目标提供者。<br />日志和最近更改不会被复制。 + + + 复制设置和相关数据 + + + + + + + + + 数据迁移 + + + 缓存提供者 + + + 文件提供者 + + + + + + + + + 默认提供者 + + + 页面提供者 + + + 用户提供者 + + + 显示 + + + + + + + + + 迁移文件和相关数据 + + + + + + + + + 迁移页面和相关数据 + + + + + + + + + <b>注意</b>:迁移用户帐户将重置所有密码(电子邮件通知将发送给所有用户)。 + + + 迁移用户和相关数据 + + + + + + + + + 名称 + + + + + + + + + + + + + + + 提供者 + + + + + + + + + + + + + + + <b>注意</b>:直至下次维基重新启动,移除 DLL 不会禁用其包含的提供者,<br />但上传新 DLL 将会自动加载其包含的提供者。 + + + 上传新 DLL + + + 管理提供者 DLLs + + + + + + + + + 版本 + + + + + + + + + + + + + + + + + + + + + + + + + + + 管理 + + + + + + + + + + + + + + + 缓存提供者 + + + + + + 文件提供供者 + + + + + + 格式器提供者 + + + + + + 页面提供者 + + + + + + 用户提供者 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminProviders.aspx.zh-tw.resx b/WebApplication/App_LocalResources/AdminProviders.aspx.zh-tw.resx new file mode 100644 index 0000000..616e7f7 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminProviders.aspx.zh-tw.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <b>Note 1</b>: always perform a full backup of all your data before performing a migration.<br /><b>Note 2</b>: migrations usually take several minutes to complete: during this time, do not perform any other activity in the wiki, and do not close this page.<br /><b>Note 3</b>: the destination provider should be completely empty: if it contains any data, it might cause consistency issues. Refer to the target provider's documentation for details.<br /><b>Timeouts</b>: it is strongly suggested that you increase the executionTimeout parameter in web.config before migrating data. + + + Auto-update Providers + + + Automatically update all installed providers, of all types + + + Cannot disable the provider because it is the default provider or,<br />in case of a Pages Provider, it manages the default page of the root namespace + + + + + + Update Status + + + Cancel + + + Deselect the Provider + + + Copy + + + + + + Delete + + + + + + Disable + + + Disable the Provider + + + Enable + + + Enable the Provider + + + Migrate + + + + + + Migrate + + + + + + Migrate + + + + + + Save + + + + + + Save + + + Save the Configuration String + + + Select + + + + + + Select + + + + + + Unload + + + Unloads the Provider + + + Upload + + + + + + Author + + + Help + + + Configuration String + + + <b>Note</b>: in order to be detected, the destination Provider must be uploaded using the upload tool.<br />Log and recent changes will not be copied. + + + Copy Settings and related data + + + + + + + + + Data Migration + + + Cache Provider + + + Files Provider + + + + + + + + + Default Providers + + + Pages Provider + + + Users Provider + + + Display + + + + + + + + + Migrate Files and related data + + + + + + + + + Migrate Pages and related data + + + + + + + + + <b>Note</b>: migrating user accounts will reset all their passwords (an email notice will be sent to all users). + + + Migrate Users and related data + + + + + + + + + Name + + + + + + + + + + + + + + + Providers + + + + + + + + + + + + + + + <b>Note</b>: removing a DLL won't disable the Providers it contains until the next wiki restart,<br />but uploading a new DLL will automatically load the Providers it contains. + + + Upload new DLL + + + Providers DLLs Management + + + + + + + + + Ver. + + + + + + + + + + + + + + + + + + + + + + + + + + + Administration + + + + + + + + + + + + + + + Cache Providers + + + + + + Files Providers + + + + + + Formatter Providers + + + + + + Pages Providers + + + + + + Users Providers + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.cs-CZ.resx new file mode 100644 index 0000000..527c071 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.cs-CZ.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Storno + + + Storno a návrat do seznamu položek + + + Vytvořit položku + + + Uložit novou položku + + + Smazat + + + Smazat položku + + + Nový Úryvek + + + Nová Šablona + + + Uložit položku + + + Uložit změny + + + Vybrat + + + Upravit nebo editovat položku + + + Vybrat + + + Upravit nebo editovat položku + + + Neplatný název Úryvku + + + Detail úryvku + + + Detail šablony + + + Název + + + Název + + + Parametry + + + Poskytovatel + + + Poskytovatel + + + Úryvky a šablony + + + Typ + + + Administrace + + + Vyžadován název + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.da-DK.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.da-DK.resx new file mode 100644 index 0000000..846bf93 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.da-DK.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the items list + + + Create Item + + + Save the new Item + + + Delete + + + Delete the Item + + + New Snippet + + + + + + New Template + + + + + + Save Item + + + Save modifications + + + Select + + + Edit or delete this item + + + Select + + + Edit or delete this item + + + Invalid Snippet Name + + + + + + + + + Snippet Details + + + Template Details + + + Name + + + Name + + + Params + + + Provider + + + Provider + + + + + + + + + Snippets and Templates + + + Type + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.de-DE.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.de-DE.resx new file mode 100644 index 0000000..846bf93 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.de-DE.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the items list + + + Create Item + + + Save the new Item + + + Delete + + + Delete the Item + + + New Snippet + + + + + + New Template + + + + + + Save Item + + + Save modifications + + + Select + + + Edit or delete this item + + + Select + + + Edit or delete this item + + + Invalid Snippet Name + + + + + + + + + Snippet Details + + + Template Details + + + Name + + + Name + + + Params + + + Provider + + + Provider + + + + + + + + + Snippets and Templates + + + Type + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.es-ES.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.es-ES.resx new file mode 100644 index 0000000..846bf93 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.es-ES.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the items list + + + Create Item + + + Save the new Item + + + Delete + + + Delete the Item + + + New Snippet + + + + + + New Template + + + + + + Save Item + + + Save modifications + + + Select + + + Edit or delete this item + + + Select + + + Edit or delete this item + + + Invalid Snippet Name + + + + + + + + + Snippet Details + + + Template Details + + + Name + + + Name + + + Params + + + Provider + + + Provider + + + + + + + + + Snippets and Templates + + + Type + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.fr-FR.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.fr-FR.resx new file mode 100644 index 0000000..846bf93 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.fr-FR.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the items list + + + Create Item + + + Save the new Item + + + Delete + + + Delete the Item + + + New Snippet + + + + + + New Template + + + + + + Save Item + + + Save modifications + + + Select + + + Edit or delete this item + + + Select + + + Edit or delete this item + + + Invalid Snippet Name + + + + + + + + + Snippet Details + + + Template Details + + + Name + + + Name + + + Params + + + Provider + + + Provider + + + + + + + + + Snippets and Templates + + + Type + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.hu-HU.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.hu-HU.resx new file mode 100644 index 0000000..846bf93 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.hu-HU.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the items list + + + Create Item + + + Save the new Item + + + Delete + + + Delete the Item + + + New Snippet + + + + + + New Template + + + + + + Save Item + + + Save modifications + + + Select + + + Edit or delete this item + + + Select + + + Edit or delete this item + + + Invalid Snippet Name + + + + + + + + + Snippet Details + + + Template Details + + + Name + + + Name + + + Params + + + Provider + + + Provider + + + + + + + + + Snippets and Templates + + + Type + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.it-IT.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.it-IT.resx new file mode 100644 index 0000000..e23e907 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.it-IT.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Annulla + + + Annulla e torna alla lista + + + Crea elemento + + + Salva il nuovo elemento + + + Elimina + + + Elimina elemento + + + Nuovo snippet + + + + + + Nuovo template + + + + + + Salva elemento + + + Salva modifiche + + + Seleziona + + + Modifica o elimina elemento + + + Seleziona + + + Modifica o elimina elemento + + + Nome non valido + + + + + + + + + Dettagli snippet + + + Dettagli template + + + Nome + + + Nome + + + Parametri + + + Provider + + + Provider + + + + + + + + + Snippet e Template + + + Tipo + + + Amministrazione + + + + + + + + + + + + + + + Nome richiesto + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.nb-NO.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.nb-NO.resx new file mode 100644 index 0000000..846bf93 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.nb-NO.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the items list + + + Create Item + + + Save the new Item + + + Delete + + + Delete the Item + + + New Snippet + + + + + + New Template + + + + + + Save Item + + + Save modifications + + + Select + + + Edit or delete this item + + + Select + + + Edit or delete this item + + + Invalid Snippet Name + + + + + + + + + Snippet Details + + + Template Details + + + Name + + + Name + + + Params + + + Provider + + + Provider + + + + + + + + + Snippets and Templates + + + Type + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.nl-NL.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.nl-NL.resx new file mode 100644 index 0000000..846bf93 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.nl-NL.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the items list + + + Create Item + + + Save the new Item + + + Delete + + + Delete the Item + + + New Snippet + + + + + + New Template + + + + + + Save Item + + + Save modifications + + + Select + + + Edit or delete this item + + + Select + + + Edit or delete this item + + + Invalid Snippet Name + + + + + + + + + Snippet Details + + + Template Details + + + Name + + + Name + + + Params + + + Provider + + + Provider + + + + + + + + + Snippets and Templates + + + Type + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.pl-PL.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.pl-PL.resx new file mode 100644 index 0000000..846bf93 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.pl-PL.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the items list + + + Create Item + + + Save the new Item + + + Delete + + + Delete the Item + + + New Snippet + + + + + + New Template + + + + + + Save Item + + + Save modifications + + + Select + + + Edit or delete this item + + + Select + + + Edit or delete this item + + + Invalid Snippet Name + + + + + + + + + Snippet Details + + + Template Details + + + Name + + + Name + + + Params + + + Provider + + + Provider + + + + + + + + + Snippets and Templates + + + Type + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.pt-BR.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.pt-BR.resx new file mode 100644 index 0000000..846bf93 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.pt-BR.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the items list + + + Create Item + + + Save the new Item + + + Delete + + + Delete the Item + + + New Snippet + + + + + + New Template + + + + + + Save Item + + + Save modifications + + + Select + + + Edit or delete this item + + + Select + + + Edit or delete this item + + + Invalid Snippet Name + + + + + + + + + Snippet Details + + + Template Details + + + Name + + + Name + + + Params + + + Provider + + + Provider + + + + + + + + + Snippets and Templates + + + Type + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.resx new file mode 100644 index 0000000..846bf93 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the items list + + + Create Item + + + Save the new Item + + + Delete + + + Delete the Item + + + New Snippet + + + + + + New Template + + + + + + Save Item + + + Save modifications + + + Select + + + Edit or delete this item + + + Select + + + Edit or delete this item + + + Invalid Snippet Name + + + + + + + + + Snippet Details + + + Template Details + + + Name + + + Name + + + Params + + + Provider + + + Provider + + + + + + + + + Snippets and Templates + + + Type + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.ro-RO.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.ro-RO.resx new file mode 100644 index 0000000..846bf93 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.ro-RO.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the items list + + + Create Item + + + Save the new Item + + + Delete + + + Delete the Item + + + New Snippet + + + + + + New Template + + + + + + Save Item + + + Save modifications + + + Select + + + Edit or delete this item + + + Select + + + Edit or delete this item + + + Invalid Snippet Name + + + + + + + + + Snippet Details + + + Template Details + + + Name + + + Name + + + Params + + + Provider + + + Provider + + + + + + + + + Snippets and Templates + + + Type + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.ru-RU.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.ru-RU.resx new file mode 100644 index 0000000..39d1d2f --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.ru-RU.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Отмена + + + Отмена и возврат к списку + + + Создать + + + Создать новое + + + Удалить + + + Удалить это + + + Новый Сниппет + + + + + + Новый Шаблон + + + + + + Сохранить + + + Сохранить изменения + + + Выбрать + + + Изменить или удалить это + + + Выбрать + + + Изменить или удалить это + + + Ошибочное Имя Сниппета + + + + + + + + + Сведения Сниппета + + + Сведение Шаблона + + + Имя + + + Имя + + + Параметры + + + Провайдер + + + Провайдер + + + + + + + + + Сниппеты и Шаблоны + + + Тип + + + Администрирование + + + + + + + + + + + + + + + Имя обязательно + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.sk-SK.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.sk-SK.resx new file mode 100644 index 0000000..846bf93 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.sk-SK.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the items list + + + Create Item + + + Save the new Item + + + Delete + + + Delete the Item + + + New Snippet + + + + + + New Template + + + + + + Save Item + + + Save modifications + + + Select + + + Edit or delete this item + + + Select + + + Edit or delete this item + + + Invalid Snippet Name + + + + + + + + + Snippet Details + + + Template Details + + + Name + + + Name + + + Params + + + Provider + + + Provider + + + + + + + + + Snippets and Templates + + + Type + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..846bf93 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.sr-Latn-CS.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the items list + + + Create Item + + + Save the new Item + + + Delete + + + Delete the Item + + + New Snippet + + + + + + New Template + + + + + + Save Item + + + Save modifications + + + Select + + + Edit or delete this item + + + Select + + + Edit or delete this item + + + Invalid Snippet Name + + + + + + + + + Snippet Details + + + Template Details + + + Name + + + Name + + + Params + + + Provider + + + Provider + + + + + + + + + Snippets and Templates + + + Type + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.tr-TR.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.tr-TR.resx new file mode 100644 index 0000000..76f0709 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.tr-TR.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + İptal + + + İptal et ve listeye dön + + + Minik kod oluştur + + + Minik kodu kaydet + + + Sil + + + Sil + + + Yeni minik kod + + + + + + Yeni şablon + + + + + + Kaydet + + + Değişiklikleri kaydet + + + Seç + + + Düzenle veya sil + + + Seç + + + Düzenle veya sil + + + Geçersiz minik kod adı + + + + + + + + + Minik kod detayları + + + Şablon detayları + + + Ad + + + Ad + + + Parametreler + + + Sağlayıcı + + + Sağlayıcı + + + + + + + + + Minik kodlar ve şablonlar + + + Tür + + + Yönetim + + + + + + + + + + + + + + + Ad gereklidir + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.uk-UA.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.uk-UA.resx new file mode 100644 index 0000000..361aa7a --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.uk-UA.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Відміна + + + Відміна та повернення до переліку + + + Створити + + + Створити це + + + Видалити + + + Видалити це + + + Новий Сніпет + + + + + + Новий Шаблон + + + + + + Зберегти + + + Зберегти змінене + + + Обрати + + + Змінити чи видалити це + + + Обрати + + + Змінити чи видалити це + + + Помилкове Ім'я Сніпету + + + + + + + + + Відомості Сніпету + + + Відомості Шаблону + + + Ім'я + + + Ім'я + + + Параметри + + + Несуча + + + Несуча + + + + + + + + + Сніпети та Шаблони + + + Вид + + + Адміністрування + + + + + + + + + + + + + + + Потрібне Ім'я + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.zh-cn.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.zh-cn.resx new file mode 100644 index 0000000..0b6e5a9 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.zh-cn.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 取消 + + + 取消并返回项目列表 + + + 创建项目 + + + 保存新项目 + + + 删除 + + + 删除项目 + + + 新建片段 + + + + + + 新建模板 + + + + + + 保存项目 + + + 保存修改 + + + 选择 + + + 编辑或删除此项目 + + + 选择 + + + 编辑或删除此项目 + + + 片段名称无效 + + + + + + + + + 片段详情 + + + 模板详情 + + + 名称 + + + 名称 + + + 参数 + + + 提供者 + + + 提供者 + + + + + + + + + 片段和模板 + + + 类型 + + + 管理 + + + + + + + + + + + + + + + 需要名称 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminSnippets.aspx.zh-tw.resx b/WebApplication/App_LocalResources/AdminSnippets.aspx.zh-tw.resx new file mode 100644 index 0000000..846bf93 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminSnippets.aspx.zh-tw.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the items list + + + Create Item + + + Save the new Item + + + Delete + + + Delete the Item + + + New Snippet + + + + + + New Template + + + + + + Save Item + + + Save modifications + + + Select + + + Edit or delete this item + + + Select + + + Edit or delete this item + + + Invalid Snippet Name + + + + + + + + + Snippet Details + + + Template Details + + + Name + + + Name + + + Params + + + Provider + + + Provider + + + + + + + + + Snippets and Templates + + + Type + + + Administration + + + + + + + + + + + + + + + Name is required + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.cs-CZ.resx new file mode 100644 index 0000000..0fdd7c4 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.cs-CZ.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Smazat + + + Smaže stará neaktivní konta + + + Smaže všechny <b>neaktivní</b> konta starší než <b>1 měsíc</b>.<br />Prosím zálohujte nejdříve data před provedením. + + + Smazat + + + Storno + + + Storno a návrat do seznamu kont + + + Vytvořit konto + + + Uložit nové konto + + + Smazat + + + Smaže konto + + + Aplikovat filter + + + Nový uživatel + + + Vytvořit nového uživatele + + + Uložit konto + + + Uložit změny + + + Vybrat + + + Vybere toto konto + + + Vybrat + + + Vybere toto konto + + + Aktivní konta + + + Neaktivní konta + + + Aktivní + + + Pokud zaškrtnuto, umožní uživatelům přihlášení + + + Hesla musí být stejná + + + Uživatelské jméno již existuje + + + Uživatelské konta + + + Zobrazované jméno + + + Zobrazované jméno + + + Detaily konta + + + Email + + + Email + + + Zobrazit: + + + Globální oprávnění + + + Člen + + + Globální členství + + + Heslo + + + Heslo (znovu) + + + Ponechejte pole heslo prázdné pokud chcete zachovat aktuální heslo. + + + Poskytovatel + + + Poskytovatel + + + Reg. Datum/Čas + + + Uživatelské jméno + + + Uživatelské jméno + + + Administrace + + + Neplatné zobrazované jméno + + + Neplatný Email + + + Neplatné heslo + + + Neplatné uživatelské jméno + + + Email je vyžadován + + + Vyžadováno heslo + + + Uživatelské jméno je vyžadováno + + + Filtrovat konta dle uživatelského jména + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.da-DK.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.da-DK.resx new file mode 100644 index 0000000..25de110 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.da-DK.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Delete + + + Delete old inactive accounts now + + + Delete all <b>inactive</b> accounts older than <b>1 month</b>.<br />Please backup your data before proceeding. + + + Bulk Delete + + + Cancel + + + Cancel and return to the Account list + + + Create Account + + + Save the new Account + + + Delete + + + Delete the Account + + + + + + Apply Filter + + + New User + + + Create a new User + + + Save Account + + + Save modifications + + + Select + + + Select this Account + + + Select + + + Select this Account + + + Active Accounts + + + + + + Inactive Accounts + + + + + + Active + + + If checked, allows the user to login + + + Passwords must be equal + + + + + + + + + Username already in use + + + + + + + + + User Accounts + + + Display Name + + + Display Name + + + Account Details + + + Email + + + Email + + + Display: + + + Global Permissions + + + Member of + + + Group Membership + + + Password + + + Password (repeat) + + + Leave Password fields blank to keep the current password. + + + + + + Provider + + + Provider + + + Reg. Date/Time + + + + + + + + + Username + + + Username + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + + + + + + + + + + + + + + + + Filter Accounts by Username + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.de-DE.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.de-DE.resx new file mode 100644 index 0000000..25de110 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.de-DE.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Delete + + + Delete old inactive accounts now + + + Delete all <b>inactive</b> accounts older than <b>1 month</b>.<br />Please backup your data before proceeding. + + + Bulk Delete + + + Cancel + + + Cancel and return to the Account list + + + Create Account + + + Save the new Account + + + Delete + + + Delete the Account + + + + + + Apply Filter + + + New User + + + Create a new User + + + Save Account + + + Save modifications + + + Select + + + Select this Account + + + Select + + + Select this Account + + + Active Accounts + + + + + + Inactive Accounts + + + + + + Active + + + If checked, allows the user to login + + + Passwords must be equal + + + + + + + + + Username already in use + + + + + + + + + User Accounts + + + Display Name + + + Display Name + + + Account Details + + + Email + + + Email + + + Display: + + + Global Permissions + + + Member of + + + Group Membership + + + Password + + + Password (repeat) + + + Leave Password fields blank to keep the current password. + + + + + + Provider + + + Provider + + + Reg. Date/Time + + + + + + + + + Username + + + Username + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + + + + + + + + + + + + + + + + Filter Accounts by Username + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.es-ES.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.es-ES.resx new file mode 100644 index 0000000..25de110 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.es-ES.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Delete + + + Delete old inactive accounts now + + + Delete all <b>inactive</b> accounts older than <b>1 month</b>.<br />Please backup your data before proceeding. + + + Bulk Delete + + + Cancel + + + Cancel and return to the Account list + + + Create Account + + + Save the new Account + + + Delete + + + Delete the Account + + + + + + Apply Filter + + + New User + + + Create a new User + + + Save Account + + + Save modifications + + + Select + + + Select this Account + + + Select + + + Select this Account + + + Active Accounts + + + + + + Inactive Accounts + + + + + + Active + + + If checked, allows the user to login + + + Passwords must be equal + + + + + + + + + Username already in use + + + + + + + + + User Accounts + + + Display Name + + + Display Name + + + Account Details + + + Email + + + Email + + + Display: + + + Global Permissions + + + Member of + + + Group Membership + + + Password + + + Password (repeat) + + + Leave Password fields blank to keep the current password. + + + + + + Provider + + + Provider + + + Reg. Date/Time + + + + + + + + + Username + + + Username + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + + + + + + + + + + + + + + + + Filter Accounts by Username + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.fr-FR.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.fr-FR.resx new file mode 100644 index 0000000..25de110 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.fr-FR.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Delete + + + Delete old inactive accounts now + + + Delete all <b>inactive</b> accounts older than <b>1 month</b>.<br />Please backup your data before proceeding. + + + Bulk Delete + + + Cancel + + + Cancel and return to the Account list + + + Create Account + + + Save the new Account + + + Delete + + + Delete the Account + + + + + + Apply Filter + + + New User + + + Create a new User + + + Save Account + + + Save modifications + + + Select + + + Select this Account + + + Select + + + Select this Account + + + Active Accounts + + + + + + Inactive Accounts + + + + + + Active + + + If checked, allows the user to login + + + Passwords must be equal + + + + + + + + + Username already in use + + + + + + + + + User Accounts + + + Display Name + + + Display Name + + + Account Details + + + Email + + + Email + + + Display: + + + Global Permissions + + + Member of + + + Group Membership + + + Password + + + Password (repeat) + + + Leave Password fields blank to keep the current password. + + + + + + Provider + + + Provider + + + Reg. Date/Time + + + + + + + + + Username + + + Username + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + + + + + + + + + + + + + + + + Filter Accounts by Username + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.hu-HU.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.hu-HU.resx new file mode 100644 index 0000000..25de110 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.hu-HU.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Delete + + + Delete old inactive accounts now + + + Delete all <b>inactive</b> accounts older than <b>1 month</b>.<br />Please backup your data before proceeding. + + + Bulk Delete + + + Cancel + + + Cancel and return to the Account list + + + Create Account + + + Save the new Account + + + Delete + + + Delete the Account + + + + + + Apply Filter + + + New User + + + Create a new User + + + Save Account + + + Save modifications + + + Select + + + Select this Account + + + Select + + + Select this Account + + + Active Accounts + + + + + + Inactive Accounts + + + + + + Active + + + If checked, allows the user to login + + + Passwords must be equal + + + + + + + + + Username already in use + + + + + + + + + User Accounts + + + Display Name + + + Display Name + + + Account Details + + + Email + + + Email + + + Display: + + + Global Permissions + + + Member of + + + Group Membership + + + Password + + + Password (repeat) + + + Leave Password fields blank to keep the current password. + + + + + + Provider + + + Provider + + + Reg. Date/Time + + + + + + + + + Username + + + Username + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + + + + + + + + + + + + + + + + Filter Accounts by Username + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.it-IT.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.it-IT.resx new file mode 100644 index 0000000..cf68662 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.it-IT.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Elimina + + + Elimina vecchi account inattivi + + + Elimina tutti gli account <b>inattivi</b> più vecchi di <b>1 mese</b>.<br />Esegui un backup dei dati prima di procedere. + + + Eliminazione in blocco + + + Annulla + + + Annulla e torna alla lista + + + Crea account + + + Salva il nuovo account + + + Elimina + + + Elimina account + + + + + + Applica filtro + + + Nuovo utente + + + Crea nuovo utente + + + Salva account + + + Salva modifiche + + + Seleziona + + + Seleziona account + + + Seleziona + + + Seleziona account + + + Account attivi + + + + + + Account inattivi + + + + + + Attivo + + + Se selezionato, l'utente può effettuare il login + + + Le password devono essere uguali + + + + + + + + + Username già in uso + + + + + + + + + Account Utente + + + Nome visualizzato + + + Nome visualizzato + + + Dettagli account + + + Email + + + Email + + + Visualizza: + + + Permessi globali + + + Membro di + + + Appartenenza gruppi + + + Password + + + Password (ripeti) + + + Lascia i campi password vuoti per mantenere la password corrente. + + + + + + Provider + + + Provider + + + Data/ora reg. + + + + + + + + + Username + + + Username + + + + + + Amministrazione + + + + + + + + + + + + + + + + + + + + + Nome visualizzato non valido + + + + + + + + + Email non valida + + + + + + + + + Password non valida + + + + + + + + + Username non valido + + + + + + + + + Email richiesta + + + + + + + + + Password richiesta + + + + + + + + + Username richiesto + + + + + + + + + + + + + + + + + + + + + + + + Filtra account per username + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.nb-NO.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.nb-NO.resx new file mode 100644 index 0000000..25de110 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.nb-NO.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Delete + + + Delete old inactive accounts now + + + Delete all <b>inactive</b> accounts older than <b>1 month</b>.<br />Please backup your data before proceeding. + + + Bulk Delete + + + Cancel + + + Cancel and return to the Account list + + + Create Account + + + Save the new Account + + + Delete + + + Delete the Account + + + + + + Apply Filter + + + New User + + + Create a new User + + + Save Account + + + Save modifications + + + Select + + + Select this Account + + + Select + + + Select this Account + + + Active Accounts + + + + + + Inactive Accounts + + + + + + Active + + + If checked, allows the user to login + + + Passwords must be equal + + + + + + + + + Username already in use + + + + + + + + + User Accounts + + + Display Name + + + Display Name + + + Account Details + + + Email + + + Email + + + Display: + + + Global Permissions + + + Member of + + + Group Membership + + + Password + + + Password (repeat) + + + Leave Password fields blank to keep the current password. + + + + + + Provider + + + Provider + + + Reg. Date/Time + + + + + + + + + Username + + + Username + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + + + + + + + + + + + + + + + + Filter Accounts by Username + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.nl-NL.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.nl-NL.resx new file mode 100644 index 0000000..25de110 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.nl-NL.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Delete + + + Delete old inactive accounts now + + + Delete all <b>inactive</b> accounts older than <b>1 month</b>.<br />Please backup your data before proceeding. + + + Bulk Delete + + + Cancel + + + Cancel and return to the Account list + + + Create Account + + + Save the new Account + + + Delete + + + Delete the Account + + + + + + Apply Filter + + + New User + + + Create a new User + + + Save Account + + + Save modifications + + + Select + + + Select this Account + + + Select + + + Select this Account + + + Active Accounts + + + + + + Inactive Accounts + + + + + + Active + + + If checked, allows the user to login + + + Passwords must be equal + + + + + + + + + Username already in use + + + + + + + + + User Accounts + + + Display Name + + + Display Name + + + Account Details + + + Email + + + Email + + + Display: + + + Global Permissions + + + Member of + + + Group Membership + + + Password + + + Password (repeat) + + + Leave Password fields blank to keep the current password. + + + + + + Provider + + + Provider + + + Reg. Date/Time + + + + + + + + + Username + + + Username + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + + + + + + + + + + + + + + + + Filter Accounts by Username + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.pl-PL.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.pl-PL.resx new file mode 100644 index 0000000..25de110 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.pl-PL.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Delete + + + Delete old inactive accounts now + + + Delete all <b>inactive</b> accounts older than <b>1 month</b>.<br />Please backup your data before proceeding. + + + Bulk Delete + + + Cancel + + + Cancel and return to the Account list + + + Create Account + + + Save the new Account + + + Delete + + + Delete the Account + + + + + + Apply Filter + + + New User + + + Create a new User + + + Save Account + + + Save modifications + + + Select + + + Select this Account + + + Select + + + Select this Account + + + Active Accounts + + + + + + Inactive Accounts + + + + + + Active + + + If checked, allows the user to login + + + Passwords must be equal + + + + + + + + + Username already in use + + + + + + + + + User Accounts + + + Display Name + + + Display Name + + + Account Details + + + Email + + + Email + + + Display: + + + Global Permissions + + + Member of + + + Group Membership + + + Password + + + Password (repeat) + + + Leave Password fields blank to keep the current password. + + + + + + Provider + + + Provider + + + Reg. Date/Time + + + + + + + + + Username + + + Username + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + + + + + + + + + + + + + + + + Filter Accounts by Username + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.pt-BR.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.pt-BR.resx new file mode 100644 index 0000000..25de110 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.pt-BR.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Delete + + + Delete old inactive accounts now + + + Delete all <b>inactive</b> accounts older than <b>1 month</b>.<br />Please backup your data before proceeding. + + + Bulk Delete + + + Cancel + + + Cancel and return to the Account list + + + Create Account + + + Save the new Account + + + Delete + + + Delete the Account + + + + + + Apply Filter + + + New User + + + Create a new User + + + Save Account + + + Save modifications + + + Select + + + Select this Account + + + Select + + + Select this Account + + + Active Accounts + + + + + + Inactive Accounts + + + + + + Active + + + If checked, allows the user to login + + + Passwords must be equal + + + + + + + + + Username already in use + + + + + + + + + User Accounts + + + Display Name + + + Display Name + + + Account Details + + + Email + + + Email + + + Display: + + + Global Permissions + + + Member of + + + Group Membership + + + Password + + + Password (repeat) + + + Leave Password fields blank to keep the current password. + + + + + + Provider + + + Provider + + + Reg. Date/Time + + + + + + + + + Username + + + Username + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + + + + + + + + + + + + + + + + Filter Accounts by Username + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.resx new file mode 100644 index 0000000..9a9c761 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel and return to the Account list + + + Create Account + + + Save the new Account + + + Delete + + + Delete the Account + + + + + + Apply Filter + + + New User + + + Create a new User + + + Save Account + + + Save modifications + + + Select + + + Select this Account + + + Select + + + Select this Account + + + Active Accounts + + + + + + Inactive Accounts + + + + + + Active + + + If checked, allows the user to login + + + Passwords must be equal + + + + + + + + + Username already in use + + + + + + + + + User Accounts + + + Display Name + + + Display Name + + + Account Details + + + Email + + + Email + + + Display: + + + Global Permissions + + + Member of + + + Group Membership + + + Password + + + Password (repeat) + + + Leave Password fields blank to keep the current password. + + + + + + Provider + + + Provider + + + Reg. Date/Time + + + + + + + + + Username + + + Username + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + + + + + + + + + + + + + + + + Filter Accounts by Username + + + + + + + + + + + + + + + + + + + + + Delete + + + Delete old inactive accounts now + + + Delete all <b>inactive</b> accounts older than <b>1 month</b>.<br />Please backup your data before proceeding. + + + Bulk Delete + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.ro-RO.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.ro-RO.resx new file mode 100644 index 0000000..25de110 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.ro-RO.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Delete + + + Delete old inactive accounts now + + + Delete all <b>inactive</b> accounts older than <b>1 month</b>.<br />Please backup your data before proceeding. + + + Bulk Delete + + + Cancel + + + Cancel and return to the Account list + + + Create Account + + + Save the new Account + + + Delete + + + Delete the Account + + + + + + Apply Filter + + + New User + + + Create a new User + + + Save Account + + + Save modifications + + + Select + + + Select this Account + + + Select + + + Select this Account + + + Active Accounts + + + + + + Inactive Accounts + + + + + + Active + + + If checked, allows the user to login + + + Passwords must be equal + + + + + + + + + Username already in use + + + + + + + + + User Accounts + + + Display Name + + + Display Name + + + Account Details + + + Email + + + Email + + + Display: + + + Global Permissions + + + Member of + + + Group Membership + + + Password + + + Password (repeat) + + + Leave Password fields blank to keep the current password. + + + + + + Provider + + + Provider + + + Reg. Date/Time + + + + + + + + + Username + + + Username + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + + + + + + + + + + + + + + + + Filter Accounts by Username + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.ru-RU.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.ru-RU.resx new file mode 100644 index 0000000..372a639 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.ru-RU.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Удалить + + + Удалить старые неактивные учётные записи + + + Удалить все <b>неактивные</b> учётные записи старше <b>1 месяца</b>.<br />Сделайте бэкап данных перед удалением, пожалуйста. + + + Массовое удаление + + + Отмена + + + Отмена и возврат к списку Учётных Записей + + + Создать Учётную Запись + + + Сохранить новую Учётную Запись + + + Удалить + + + Удалить Учётную Запись + + + + + + Применить Фильтр + + + Новый Пользователь + + + Создать нового Пользователя + + + Сохранить Учётную Запись + + + Сохранить изменения + + + Выбрать + + + Выбрать эту Учётную Запись + + + Выбрать + + + Выбрать эту Учётную Запись + + + Активированные Учётные Записи + + + + + + Неактивированные Учётные Записи + + + + + + Активировать + + + Выбор этого, повзоляет пользователю авторизоваться + + + Пароли должны быть одинаковыми + + + + + + + + + Такой Логин уже используется + + + + + + + + + Учётные Записи + + + Отображаемое Имя + + + Отображаемое Имя + + + Сведения Учётной Записи + + + Email + + + Email + + + Показать: + + + Общие Разрешения + + + Член + + + Члены Группы + + + Пароль + + + Пароль (ещё раз) + + + Не заполняйте поле пароля для использования текущего значения. + + + + + + Провайдер + + + Провайдер + + + Рег. Дата/Время + + + + + + + + + Логин + + + Логин + + + + + + Администрирование + + + + + + + + + + + + + + + + + + + + + Ошибочное отображаемое Имя + + + + + + + + + Ошибочный Email + + + + + + + + + Ошибочный Пароль + + + + + + + + + Ошибочный Логин + + + + + + + + + Требуется Email + + + + + + + + + Требуется Пароль + + + + + + + + + Требуется Логин + + + + + + + + + + + + + + + + + + + + + + + + Фильтровать учётные записи по Логину + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.sk-SK.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.sk-SK.resx new file mode 100644 index 0000000..25de110 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.sk-SK.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Delete + + + Delete old inactive accounts now + + + Delete all <b>inactive</b> accounts older than <b>1 month</b>.<br />Please backup your data before proceeding. + + + Bulk Delete + + + Cancel + + + Cancel and return to the Account list + + + Create Account + + + Save the new Account + + + Delete + + + Delete the Account + + + + + + Apply Filter + + + New User + + + Create a new User + + + Save Account + + + Save modifications + + + Select + + + Select this Account + + + Select + + + Select this Account + + + Active Accounts + + + + + + Inactive Accounts + + + + + + Active + + + If checked, allows the user to login + + + Passwords must be equal + + + + + + + + + Username already in use + + + + + + + + + User Accounts + + + Display Name + + + Display Name + + + Account Details + + + Email + + + Email + + + Display: + + + Global Permissions + + + Member of + + + Group Membership + + + Password + + + Password (repeat) + + + Leave Password fields blank to keep the current password. + + + + + + Provider + + + Provider + + + Reg. Date/Time + + + + + + + + + Username + + + Username + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + + + + + + + + + + + + + + + + Filter Accounts by Username + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..25de110 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.sr-Latn-CS.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Delete + + + Delete old inactive accounts now + + + Delete all <b>inactive</b> accounts older than <b>1 month</b>.<br />Please backup your data before proceeding. + + + Bulk Delete + + + Cancel + + + Cancel and return to the Account list + + + Create Account + + + Save the new Account + + + Delete + + + Delete the Account + + + + + + Apply Filter + + + New User + + + Create a new User + + + Save Account + + + Save modifications + + + Select + + + Select this Account + + + Select + + + Select this Account + + + Active Accounts + + + + + + Inactive Accounts + + + + + + Active + + + If checked, allows the user to login + + + Passwords must be equal + + + + + + + + + Username already in use + + + + + + + + + User Accounts + + + Display Name + + + Display Name + + + Account Details + + + Email + + + Email + + + Display: + + + Global Permissions + + + Member of + + + Group Membership + + + Password + + + Password (repeat) + + + Leave Password fields blank to keep the current password. + + + + + + Provider + + + Provider + + + Reg. Date/Time + + + + + + + + + Username + + + Username + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + + + + + + + + + + + + + + + + Filter Accounts by Username + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.tr-TR.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.tr-TR.resx new file mode 100644 index 0000000..b94aec2 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.tr-TR.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Delete + + + Delete old inactive accounts now + + + Delete all <b>inactive</b> accounts older than <b>1 month</b>.<br />Please backup your data before proceeding. + + + Bulk Delete + + + İptal + + + Vazgeç ve hesap listesine dön + + + Hesap oluştur + + + Yeni hesabı kaydet + + + Sil + + + Hesabı sil + + + + + + Filtre uygula + + + Yeni Kullanıcı + + + Yeni kullanıcı oluşturur + + + Hesabı kaydet + + + Düzenlemeleri kaydet + + + Seç + + + Bu hesabı seç + + + Seç + + + Bu hesabı seç + + + Aktif Hesaplar + + + + + + İnaktif Hesaplar + + + + + + Aktif + + + Eğer işaretliyse kullanıcı giriş yapabilir + + + Parolalar eşit olmalıdır + + + + + + + + + Kullanıcı adı, kullanımda + + + + + + + + + Kullanıcı Hesaplaro + + + Görünür ad + + + Görünür ad + + + Hesap Detayları + + + Email + + + Email + + + Görünüm: + + + Genel Yetkiler + + + Üyesi olduğu gurup + + + Gurup üyeliği + + + Parola + + + Parola tekrarı + + + Parolayı değiştirmek istemiyorsanız boş bırakın + + + + + + Sağlayıcı + + + Sağlayıcı + + + Kayıt zamanı + + + + + + + + + Kullanıcı adı + + + Kullanıcı adı + + + + + + Yönetim + + + + + + + + + + + + + + + + + + + + + Geçersiz görünür ad + + + + + + + + + Geçersiz email + + + + + + + + + Geçersiz parola + + + + + + + + + Geçersiz kullanıcı adı + + + + + + + + + Email adresi gereklidir + + + + + + + + + Parola gereklidir + + + + + + + + + Kullanıcı adı gereklidir + + + + + + + + + + + + + + + + + + + + + + + + Hesapları kullanıcı adına göre filtrele + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.uk-UA.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.uk-UA.resx new file mode 100644 index 0000000..d7af85d --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.uk-UA.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Видалити + + + Видалити старі та недіючі облікові записи + + + Видалити всі <b>неактівні</b> облікові записи, яким більш ніж <b>1 місяць</b>.<br />Будь ласка, заархівуйте дані до початку видалення. + + + Масове видалення + + + Відміна + + + Відміна та повернення до переліку Облікових Записів + + + Створити Запис + + + Зберегти новий Обліковий Запис + + + Видалити + + + Відалити цей Обліковий Запис + + + + + + Застосувати Фільтрацію + + + Новий Користувач + + + Створити нового Користувача + + + Зберегти Обліковий Запис + + + Зберегти зміни + + + Обрати + + + Обрати цей Обліковий Запис + + + Обрати + + + Обрати цей Обліковий Запис + + + Активовані Облікові Записи + + + + + + Неактивовані Облікові Записи + + + + + + Активувати + + + Якщо встановлено, користувач може авторизуватися + + + Паролі повинні співпадати + + + + + + + + + Такий Логін вже існує + + + + + + + + + Облікові Записи Користувачів + + + Відображуване Ім'я + + + Відображуване Ім'я + + + Відомості Облікового Запису + + + Email + + + Email + + + Показати: + + + Загальні Дозволи + + + Учасник + + + Учасники Групи + + + Пароль + + + Пароль (ще раз) + + + Якщо не заповните тут, буде використане поточне значення. + + + + + + Несуча + + + Несуча + + + Реєс. Дата/Час + + + + + + + + + Логін + + + Логін + + + + + + Адміністрування + + + + + + + + + + + + + + + + + + + + + Помилкове Відображуване Ім'я + + + + + + + + + Помилковий Email + + + + + + + + + Помилковий Пароль + + + + + + + + + Помилковий Логін + + + + + + + + + Потрібен Email + + + + + + + + + Потрібен Пароль + + + + + + + + + Потрібен Логін + + + + + + + + + + + + + + + + + + + + + + + + Застосувати фільтрацію за Логіном + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.zh-cn.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.zh-cn.resx new file mode 100644 index 0000000..a65c191 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.zh-cn.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 删除 + + + 立即删除旧的非活动账户 + + + 删除<b> 1 个月</b>以上的所有<b>非活动</b>帐户。<br />继续之前请备份您的数据。 + + + 批量删除 + + + 取消 + + + 取消并返回帐户列表 + + + 创建帐户 + + + 保存新帐户 + + + 删除 + + + 删除帐户 + + + + + + 应用筛选器 + + + 新建用户 + + + 创建新用户 + + + 保存帐户 + + + 保存修改 + + + 选择 + + + 选择此帐户 + + + 选择 + + + 选择此帐户 + + + 已激活帐户 + + + + + + 未激活帐户 + + + + + + 激活 + + + 如果选中,允许用户登录 + + + 密码必须相同 + + + + + + + + + 用户名已被使用 + + + + + + + + + 用户帐户 + + + 显示名称 + + + 显示名称 + + + 帐户详情 + + + 电子邮件地址 + + + 电子邮件地址 + + + 显示: + + + 全局权限 + + + 隶属于 + + + 隶属于 + + + 密码 + + + 密码(重复) + + + 留空密码栏以保留当前密码。 + + + + + + 提供者 + + + 提供者 + + + 注册日期/时间 + + + + + + + + + 用户名 + + + 用户名 + + + + + + 管理 + + + + + + + + + + + + + + + + + + + + + 显示名称无效 + + + + + + + + + 电子邮件地址无效 + + + + + + + + + 密码无效 + + + + + + + + + 用户名无效 + + + + + + + + + 需要电子邮件地址 + + + + + + + + + 需要密码 + + + + + + + + + 需要用户名 + + + + + + + + + + + + + + + + + + + + + + + + 按用户名筛选帐户 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AdminUsers.aspx.zh-tw.resx b/WebApplication/App_LocalResources/AdminUsers.aspx.zh-tw.resx new file mode 100644 index 0000000..25de110 --- /dev/null +++ b/WebApplication/App_LocalResources/AdminUsers.aspx.zh-tw.resx @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Delete + + + Delete old inactive accounts now + + + Delete all <b>inactive</b> accounts older than <b>1 month</b>.<br />Please backup your data before proceeding. + + + Bulk Delete + + + Cancel + + + Cancel and return to the Account list + + + Create Account + + + Save the new Account + + + Delete + + + Delete the Account + + + + + + Apply Filter + + + New User + + + Create a new User + + + Save Account + + + Save modifications + + + Select + + + Select this Account + + + Select + + + Select this Account + + + Active Accounts + + + + + + Inactive Accounts + + + + + + Active + + + If checked, allows the user to login + + + Passwords must be equal + + + + + + + + + Username already in use + + + + + + + + + User Accounts + + + Display Name + + + Display Name + + + Account Details + + + Email + + + Email + + + Display: + + + Global Permissions + + + Member of + + + Group Membership + + + Password + + + Password (repeat) + + + Leave Password fields blank to keep the current password. + + + + + + Provider + + + Provider + + + Reg. Date/Time + + + + + + + + + Username + + + Username + + + + + + Administration + + + + + + + + + + + + + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + + + + + + + + + + + + + + + + Filter Accounts by Username + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/AllPages.aspx.cs-CZ.resx new file mode 100644 index 0000000..7c8a70b --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.cs-CZ.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + Kategorie stránek + + + Zobrazit všechny kategorie stránky + + + Hledat + + + Provede hledání + + + Seznam všech stránek v tomto jmenném prostoru. + + + Index stránek + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.da-DK.resx b/WebApplication/App_LocalResources/AllPages.aspx.da-DK.resx new file mode 100644 index 0000000..176cd47 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.da-DK.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Page Categories + + + View all Page Categories + + + Search + + + Perform a Search + + + + + + + + + Den komplette liste over sider på denne WIKI + + + Sider + + + Side uden navn + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.de-DE.resx b/WebApplication/App_LocalResources/AllPages.aspx.de-DE.resx new file mode 100644 index 0000000..55bd057 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.de-DE.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Page Categories + + + View all Page Categories + + + Search + + + Perform a Search + + + + + + + + + The list of all pages contained in the current namespace. + + + Page Index + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.es-ES.resx b/WebApplication/App_LocalResources/AllPages.aspx.es-ES.resx new file mode 100644 index 0000000..55bd057 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.es-ES.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Page Categories + + + View all Page Categories + + + Search + + + Perform a Search + + + + + + + + + The list of all pages contained in the current namespace. + + + Page Index + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.fr-FR.resx b/WebApplication/App_LocalResources/AllPages.aspx.fr-FR.resx new file mode 100644 index 0000000..55bd057 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.fr-FR.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Page Categories + + + View all Page Categories + + + Search + + + Perform a Search + + + + + + + + + The list of all pages contained in the current namespace. + + + Page Index + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.hu-HU.resx b/WebApplication/App_LocalResources/AllPages.aspx.hu-HU.resx new file mode 100644 index 0000000..9950dd5 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.hu-HU.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Page Categories + + + View all Page Categories + + + Search + + + Perform a Search + + + + + + + + + A névtér összes lapjának listája. + + + Lap index + + + Cím nélküli lap + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.it-IT.resx b/WebApplication/App_LocalResources/AllPages.aspx.it-IT.resx new file mode 100644 index 0000000..6fc96f2 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.it-IT.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Categorie + + + Visualizza tutte le categorie + + + Cerca + + + Esegui una ricerca + + + + + + + + + Lista di tutte le pagine in questo namespace + + + Indice pagine + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.nb-NO.resx b/WebApplication/App_LocalResources/AllPages.aspx.nb-NO.resx new file mode 100644 index 0000000..55bd057 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.nb-NO.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Page Categories + + + View all Page Categories + + + Search + + + Perform a Search + + + + + + + + + The list of all pages contained in the current namespace. + + + Page Index + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.nl-NL.resx b/WebApplication/App_LocalResources/AllPages.aspx.nl-NL.resx new file mode 100644 index 0000000..55bd057 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.nl-NL.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Page Categories + + + View all Page Categories + + + Search + + + Perform a Search + + + + + + + + + The list of all pages contained in the current namespace. + + + Page Index + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.pl-PL.resx b/WebApplication/App_LocalResources/AllPages.aspx.pl-PL.resx new file mode 100644 index 0000000..039c8cf --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.pl-PL.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Page Categories + + + View all Page Categories + + + Search + + + Perform a Search + + + + + + + + + Spis stron serwisu. + + + Strony + + + Strona niezatytułowana + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.pt-BR.resx b/WebApplication/App_LocalResources/AllPages.aspx.pt-BR.resx new file mode 100644 index 0000000..55bd057 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.pt-BR.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Page Categories + + + View all Page Categories + + + Search + + + Perform a Search + + + + + + + + + The list of all pages contained in the current namespace. + + + Page Index + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.resx b/WebApplication/App_LocalResources/AllPages.aspx.resx new file mode 100644 index 0000000..a8e4f6b --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The list of all pages contained in the current namespace. + + + Page Index + + + Page Categories + + + View all Page Categories + + + Search + + + Perform a Search + + + Untitled Page + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.ro-RO.resx b/WebApplication/App_LocalResources/AllPages.aspx.ro-RO.resx new file mode 100644 index 0000000..55bd057 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.ro-RO.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Page Categories + + + View all Page Categories + + + Search + + + Perform a Search + + + + + + + + + The list of all pages contained in the current namespace. + + + Page Index + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.ru-RU.resx b/WebApplication/App_LocalResources/AllPages.aspx.ru-RU.resx new file mode 100644 index 0000000..8598706 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.ru-RU.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Страницы категории + + + Просмотреть все страницы Категории + + + Поиск + + + Найти + + + + + + + + + Список страниц текущего Пространства Имён + + + Индекс страниц + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.sk-SK.resx b/WebApplication/App_LocalResources/AllPages.aspx.sk-SK.resx new file mode 100644 index 0000000..55bd057 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.sk-SK.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Page Categories + + + View all Page Categories + + + Search + + + Perform a Search + + + + + + + + + The list of all pages contained in the current namespace. + + + Page Index + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AllPages.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..55bd057 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.sr-Latn-CS.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Page Categories + + + View all Page Categories + + + Search + + + Perform a Search + + + + + + + + + The list of all pages contained in the current namespace. + + + Page Index + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.tr-TR.resx b/WebApplication/App_LocalResources/AllPages.aspx.tr-TR.resx new file mode 100644 index 0000000..3987136 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.tr-TR.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Sayfa Kategorileri + + + Tüm sayfa kategorilerini görüntüle + + + Ara + + + Arama yap + + + + + + + + + Mevcut ad alanındaki tüm sayfaların listesi + + + Sayfa İndeksi + + + Başlıksız Sayfa + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.uk-UA.resx b/WebApplication/App_LocalResources/AllPages.aspx.uk-UA.resx new file mode 100644 index 0000000..3bc70f7 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.uk-UA.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Сторінки категорії + + + Переглянути всі сторінки Категорії + + + Пошук + + + Знайти + + + + + + + + + Перелік сторінок цього Простору Імен + + + Індекс Сторінок + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.zh-cn.resx b/WebApplication/App_LocalResources/AllPages.aspx.zh-cn.resx new file mode 100644 index 0000000..c304f9f --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.zh-cn.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 页面分类 + + + 查看所有页面分类 + + + 搜索 + + + 执行搜索 + + + + + + + + + 当前命名空间中所有页面的列表。 + + + 页面索引 + + + 无标题页面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AllPages.aspx.zh-tw.resx b/WebApplication/App_LocalResources/AllPages.aspx.zh-tw.resx new file mode 100644 index 0000000..55bd057 --- /dev/null +++ b/WebApplication/App_LocalResources/AllPages.aspx.zh-tw.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Page Categories + + + View all Page Categories + + + Search + + + Perform a Search + + + + + + + + + The list of all pages contained in the current namespace. + + + Page Index + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.cs-CZ.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.cs-CZ.resx new file mode 100644 index 0000000..092410e --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.cs-CZ.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <br />Uložit stránku před nahrátím + + + Ke stažení + + + Smazat + + + Smazat tuto přílohu + + + Smazat + + + Smazat tuto přílohu + + + Obnovit + + + Přejmenovat + + + Přejmenovat tuto přílohu + + + Přejmenovat + + + Přejmenovat tuto přílohu + + + Název + + + Velikost + + + Storno + + + Přejmenovat + + + Nahrát + + + Přepsat existující přílohu + + + Přejmenovat soubor/adresář + + + Můžete nahrát přílohy pro tuto stránku do $1. Povolené typy souborů: $2. + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.da-DK.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.da-DK.resx new file mode 100644 index 0000000..87284f2 --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.da-DK.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />Save the page before uploading + + + + + + Cancel + + + + + + Delete + + + Delete this Attachment + + + Delete + + + Delete this Attachment + + + Refresh + + + + + + Rename + + + + + + Rename + + + Rename this Attachment + + + Rename + + + Rename this Attachment + + + Upload + + + + + + Overwrite existing attachment + + + + + + + + + Downloads + + + + + + + + + Name + + + Rename file/directory + + + + + + + + + Size + + + + + + You can upload attachments for the current Page up to $1. Allowed file types are: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.de-DE.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.de-DE.resx new file mode 100644 index 0000000..074d86a --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.de-DE.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />Save the page before uploading + + + + + + Downloads + + + Delete + + + Delete this Attachment + + + Delete + + + Delete this Attachment + + + Refresh + + + + + + Rename + + + Rename this Attachment + + + Rename + + + Rename this Attachment + + + Name + + + Size + + + Cancel + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing attachment + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + + + + You can upload attachments for the current Page up to $1. Allowed file types are: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.es-ES.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.es-ES.resx new file mode 100644 index 0000000..074d86a --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.es-ES.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />Save the page before uploading + + + + + + Downloads + + + Delete + + + Delete this Attachment + + + Delete + + + Delete this Attachment + + + Refresh + + + + + + Rename + + + Rename this Attachment + + + Rename + + + Rename this Attachment + + + Name + + + Size + + + Cancel + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing attachment + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + + + + You can upload attachments for the current Page up to $1. Allowed file types are: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.fr-FR.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.fr-FR.resx new file mode 100644 index 0000000..074d86a --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.fr-FR.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />Save the page before uploading + + + + + + Downloads + + + Delete + + + Delete this Attachment + + + Delete + + + Delete this Attachment + + + Refresh + + + + + + Rename + + + Rename this Attachment + + + Rename + + + Rename this Attachment + + + Name + + + Size + + + Cancel + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing attachment + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + + + + You can upload attachments for the current Page up to $1. Allowed file types are: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.hu-HU.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.hu-HU.resx new file mode 100644 index 0000000..1cb1f0d --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.hu-HU.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />Save the page before uploading + + + + + + Letöltések + + + Töröl + + + Csatolmány törlése + + + Töröl + + + Csatolmány törlése + + + Frissít + + + + + + Átnevez + + + Csatolmány átnevezése + + + Átnevez + + + Csatolmány átnevezése + + + Név + + + Méret + + + Mégse + + + + + + Átnevez + + + + + + Feltölt + + + + + + Csatolmány felülírása + + + + + + + + + + + + + + + Fájl/mappa átnevezése + + + + + + + + + + + + $1 csatolmányt tölthetsz fel. Engedélyezett fájltípusok: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.it-IT.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.it-IT.resx new file mode 100644 index 0000000..7e12467 --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.it-IT.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Annulla + + + + + + Elimina + + + Elimina questo allegato + + + Elimina + + + Elimina questo allegato + + + Aggiorna + + + + + + Rinomina + + + + + + Rinomina + + + Rinomina questo allegato + + + Rinomina + + + Rinomina questo allegato + + + Upload + + + + + + Sovrascrivi allegato esistente + + + + + + + + + Download + + + + + + + + + Nome + + + <br />Salva la pagina prima di aggiungere allegati + + + + + + Rinomina allegato + + + + + + + + + Dimensione + + + + + + Puoi caricare allegati per questa pagina fino a $1. I tipi permessi sono: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.nb-NO.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.nb-NO.resx new file mode 100644 index 0000000..074d86a --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.nb-NO.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />Save the page before uploading + + + + + + Downloads + + + Delete + + + Delete this Attachment + + + Delete + + + Delete this Attachment + + + Refresh + + + + + + Rename + + + Rename this Attachment + + + Rename + + + Rename this Attachment + + + Name + + + Size + + + Cancel + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing attachment + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + + + + You can upload attachments for the current Page up to $1. Allowed file types are: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.nl-NL.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.nl-NL.resx new file mode 100644 index 0000000..074d86a --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.nl-NL.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />Save the page before uploading + + + + + + Downloads + + + Delete + + + Delete this Attachment + + + Delete + + + Delete this Attachment + + + Refresh + + + + + + Rename + + + Rename this Attachment + + + Rename + + + Rename this Attachment + + + Name + + + Size + + + Cancel + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing attachment + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + + + + You can upload attachments for the current Page up to $1. Allowed file types are: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.pl-PL.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.pl-PL.resx new file mode 100644 index 0000000..d9681c3 --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.pl-PL.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Delete + + + Delete this Attachment + + + Delete + + + Delete this Attachment + + + Refresh + + + + + + Rename + + + + + + Rename + + + Rename this Attachment + + + Rename + + + Rename this Attachment + + + Upload + + + + + + Overwrite existing attachment + + + + + + + + + Downloads + + + + + + + + + Name + + + <br />Save the page before uploading + + + + + + Rename file/directory + + + + + + + + + Size + + + + + + You can upload attachments for the current Page up to $1. Allowed file types are: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.pt-BR.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.pt-BR.resx new file mode 100644 index 0000000..074d86a --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.pt-BR.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />Save the page before uploading + + + + + + Downloads + + + Delete + + + Delete this Attachment + + + Delete + + + Delete this Attachment + + + Refresh + + + + + + Rename + + + Rename this Attachment + + + Rename + + + Rename this Attachment + + + Name + + + Size + + + Cancel + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing attachment + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + + + + You can upload attachments for the current Page up to $1. Allowed file types are: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.resx new file mode 100644 index 0000000..d9681c3 --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Delete + + + Delete this Attachment + + + Delete + + + Delete this Attachment + + + Refresh + + + + + + Rename + + + + + + Rename + + + Rename this Attachment + + + Rename + + + Rename this Attachment + + + Upload + + + + + + Overwrite existing attachment + + + + + + + + + Downloads + + + + + + + + + Name + + + <br />Save the page before uploading + + + + + + Rename file/directory + + + + + + + + + Size + + + + + + You can upload attachments for the current Page up to $1. Allowed file types are: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.ro-RO.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.ro-RO.resx new file mode 100644 index 0000000..074d86a --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.ro-RO.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />Save the page before uploading + + + + + + Downloads + + + Delete + + + Delete this Attachment + + + Delete + + + Delete this Attachment + + + Refresh + + + + + + Rename + + + Rename this Attachment + + + Rename + + + Rename this Attachment + + + Name + + + Size + + + Cancel + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing attachment + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + + + + You can upload attachments for the current Page up to $1. Allowed file types are: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.ru-RU.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.ru-RU.resx new file mode 100644 index 0000000..54808ca --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.ru-RU.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />Сохранить страницу до загрузки + + + + + + Загружено + + + Удалить + + + Удалить это вложение + + + Удалить + + + Удалить это вложение + + + Обновить + + + + + + Переименовать + + + Переименовать это вложение + + + Переименовать + + + Переименовать это вложение + + + Имя + + + Размер + + + Отмена + + + + + + Переименовать + + + + + + Загрузить + + + + + + Заменить существующий + + + + + + + + + + + + + + + Переименовать файл/папку + + + + + + + + + + + + Вы имеете возможность загрузить вложения для этой страницы, размером до $1. Используемые типы файлов: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.sk-SK.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.sk-SK.resx new file mode 100644 index 0000000..074d86a --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.sk-SK.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />Save the page before uploading + + + + + + Downloads + + + Delete + + + Delete this Attachment + + + Delete + + + Delete this Attachment + + + Refresh + + + + + + Rename + + + Rename this Attachment + + + Rename + + + Rename this Attachment + + + Name + + + Size + + + Cancel + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing attachment + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + + + + You can upload attachments for the current Page up to $1. Allowed file types are: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.sr-Latn-CS.resx new file mode 100644 index 0000000..074d86a --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.sr-Latn-CS.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />Save the page before uploading + + + + + + Downloads + + + Delete + + + Delete this Attachment + + + Delete + + + Delete this Attachment + + + Refresh + + + + + + Rename + + + Rename this Attachment + + + Rename + + + Rename this Attachment + + + Name + + + Size + + + Cancel + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing attachment + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + + + + You can upload attachments for the current Page up to $1. Allowed file types are: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.tr-TR.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.tr-TR.resx new file mode 100644 index 0000000..3fad39d --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.tr-TR.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />Save the page before uploading + + + + + + İndirilenler + + + Sil + + + Bu eklentiyi silin + + + Sil + + + Bu eklentiyi silin + + + Tazele + + + + + + Yeniden adlandır + + + Bu eklentiyi yeniden adlandır + + + Yeniden adlandır + + + Bu eklentiyi yeniden adlandır + + + Ad + + + Boyut + + + İptal + + + + + + Yeniden adlandır + + + + + + Yükle + + + + + + Var olan eklentinin üzerine yaz + + + + + + + + + + + + + + + Dosya/dizini yeniden adlandır + + + + + + + + + + + + Bu sayfaya en fazla $1 dosya ekleyebilirsiniz. İzin verilen dosya türleri: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.uk-UA.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.uk-UA.resx new file mode 100644 index 0000000..4560949 --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.uk-UA.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />Зберегти сторінку до завантаження + + + + + + Завантажено + + + Видалити + + + Видалити це вкладення + + + Видалити + + + Видалити це вкладення + + + Оновити + + + + + + Перейменувати + + + Перейменувати це вкладення + + + Перейменувати + + + Перейменувати це вкладення + + + Ім'я + + + Розмір + + + Відміна + + + + + + Перейменувати + + + + + + Завантажити + + + + + + Замінити існуючий + + + + + + + + + + + + + + + Перейменувати файл/директорію + + + + + + + + + + + + Ви маєте можливість прикріпити вкладення до цієї сторінки, розміром не більше $1. Дозволені типи файлів: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.zh-cn.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.zh-cn.resx new file mode 100644 index 0000000..a634062 --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.zh-cn.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />上传前保存页面 + + + + + + 下载 + + + 删除 + + + 删除此附件 + + + 删除 + + + 删除此附件 + + + 刷新 + + + + + + 重命名 + + + 重命名此附件 + + + 重命名 + + + 重命名此附件 + + + 名称 + + + 大小 + + + 取消 + + + + + + 重命名 + + + + + + 上传 + + + + + + 覆盖已有附件 + + + + + + + + + + + + + + + 重命名文件/目录 + + + + + + + + + + + + 您可以为当前页面上传最大 $1 的附件。允许的文件类型为:$2。 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentManager.ascx.zh-tw.resx b/WebApplication/App_LocalResources/AttachmentManager.ascx.zh-tw.resx new file mode 100644 index 0000000..074d86a --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentManager.ascx.zh-tw.resx @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <br />Save the page before uploading + + + + + + Downloads + + + Delete + + + Delete this Attachment + + + Delete + + + Delete this Attachment + + + Refresh + + + + + + Rename + + + Rename this Attachment + + + Rename + + + Rename this Attachment + + + Name + + + Size + + + Cancel + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing attachment + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + + + + You can upload attachments for the current Page up to $1. Allowed file types are: $2. + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.cs-CZ.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.cs-CZ.resx new file mode 100644 index 0000000..2c00dd4 --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.cs-CZ.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Název + + + Velikost + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.da-DK.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.da-DK.resx new file mode 100644 index 0000000..1eccfde --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.da-DK.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Size + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.de-DE.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.de-DE.resx new file mode 100644 index 0000000..1eccfde --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.de-DE.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Size + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.es-ES.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.es-ES.resx new file mode 100644 index 0000000..1eccfde --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.es-ES.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Size + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.fr-FR.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.fr-FR.resx new file mode 100644 index 0000000..1eccfde --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.fr-FR.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Size + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.hu-HU.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.hu-HU.resx new file mode 100644 index 0000000..6bd849b --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.hu-HU.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Név + + + Méret + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.it-IT.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.it-IT.resx new file mode 100644 index 0000000..08aeec5 --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.it-IT.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nome + + + Dim. + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.nb-NO.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.nb-NO.resx new file mode 100644 index 0000000..1eccfde --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.nb-NO.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Size + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.nl-NL.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.nl-NL.resx new file mode 100644 index 0000000..1eccfde --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.nl-NL.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Size + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.pl-PL.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.pl-PL.resx new file mode 100644 index 0000000..1eccfde --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.pl-PL.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Size + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.pt-BR.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.pt-BR.resx new file mode 100644 index 0000000..1eccfde --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.pt-BR.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Size + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.resx new file mode 100644 index 0000000..1eccfde --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Size + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.ro-RO.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.ro-RO.resx new file mode 100644 index 0000000..1eccfde --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.ro-RO.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Size + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.ru-RU.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.ru-RU.resx new file mode 100644 index 0000000..6f66769 --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.ru-RU.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Имя + + + Размер + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.sk-SK.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.sk-SK.resx new file mode 100644 index 0000000..1eccfde --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.sk-SK.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Size + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.sr-Latn-CS.resx new file mode 100644 index 0000000..1eccfde --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.sr-Latn-CS.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Size + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.tr-TR.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.tr-TR.resx new file mode 100644 index 0000000..c159142 --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.tr-TR.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + İsim + + + Boyut + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.uk-UA.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.uk-UA.resx new file mode 100644 index 0000000..a435350 --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.uk-UA.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ім'я + + + Розмір + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.zh-cn.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.zh-cn.resx new file mode 100644 index 0000000..69dd7aa --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.zh-cn.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 名称 + + + 大小 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/AttachmentViewer.ascx.zh-tw.resx b/WebApplication/App_LocalResources/AttachmentViewer.ascx.zh-tw.resx new file mode 100644 index 0000000..1eccfde --- /dev/null +++ b/WebApplication/App_LocalResources/AttachmentViewer.ascx.zh-tw.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Size + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.cs-CZ.resx b/WebApplication/App_LocalResources/Captcha.ascx.cs-CZ.resx new file mode 100644 index 0000000..c29ed06 --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.cs-CZ.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Resources.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + CAPTCHA + + + Resources.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + Vložte text zobrazený výše (záleží na velikosti) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.da-DK.resx b/WebApplication/App_LocalResources/Captcha.ascx.da-DK.resx new file mode 100644 index 0000000..c22393b --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.da-DK.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Kopier den ovenstående tekst hertil (husk der er forskel på store og små bogstaver) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.de-DE.resx b/WebApplication/App_LocalResources/Captcha.ascx.de-DE.resx new file mode 100644 index 0000000..2f0ce2d --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.de-DE.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Copy here the text displayed above (case sensitive) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.es-ES.resx b/WebApplication/App_LocalResources/Captcha.ascx.es-ES.resx new file mode 100644 index 0000000..2f0ce2d --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.es-ES.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Copy here the text displayed above (case sensitive) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.fr-FR.resx b/WebApplication/App_LocalResources/Captcha.ascx.fr-FR.resx new file mode 100644 index 0000000..2f0ce2d --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.fr-FR.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Copy here the text displayed above (case sensitive) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.hu-HU.resx b/WebApplication/App_LocalResources/Captcha.ascx.hu-HU.resx new file mode 100644 index 0000000..2c79eeb --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.hu-HU.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Másold ide a fenti szöveget (kis-nagybetű helyesen) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.it-IT.resx b/WebApplication/App_LocalResources/Captcha.ascx.it-IT.resx new file mode 100644 index 0000000..7bc555f --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.it-IT.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Copia qui il testo soprastante (sensibile alle maiuscole) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.nb-NO.resx b/WebApplication/App_LocalResources/Captcha.ascx.nb-NO.resx new file mode 100644 index 0000000..2f0ce2d --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.nb-NO.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Copy here the text displayed above (case sensitive) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.nl-NL.resx b/WebApplication/App_LocalResources/Captcha.ascx.nl-NL.resx new file mode 100644 index 0000000..2f0ce2d --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.nl-NL.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Copy here the text displayed above (case sensitive) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.pl-PL.resx b/WebApplication/App_LocalResources/Captcha.ascx.pl-PL.resx new file mode 100644 index 0000000..8bd76bb --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.pl-PL.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Wpisz tutaj tekst wyświetlony powyżej (wielkość znaków rozróżnialna) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.pt-BR.resx b/WebApplication/App_LocalResources/Captcha.ascx.pt-BR.resx new file mode 100644 index 0000000..2f0ce2d --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.pt-BR.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Copy here the text displayed above (case sensitive) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.resx b/WebApplication/App_LocalResources/Captcha.ascx.resx new file mode 100644 index 0000000..2f0ce2d --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Copy here the text displayed above (case sensitive) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.ro-RO.resx b/WebApplication/App_LocalResources/Captcha.ascx.ro-RO.resx new file mode 100644 index 0000000..05fe225 --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.ro-RO.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Tastati aici textul afisat deasupra (conteaza majusculele) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.ru-RU.resx b/WebApplication/App_LocalResources/Captcha.ascx.ru-RU.resx new file mode 100644 index 0000000..c9f228e --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.ru-RU.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Введите текст, отображённый выше (регистрозависимо) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.sk-SK.resx b/WebApplication/App_LocalResources/Captcha.ascx.sk-SK.resx new file mode 100644 index 0000000..2f0ce2d --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.sk-SK.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Copy here the text displayed above (case sensitive) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/Captcha.ascx.sr-Latn-CS.resx new file mode 100644 index 0000000..2f0ce2d --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.sr-Latn-CS.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Copy here the text displayed above (case sensitive) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.tr-TR.resx b/WebApplication/App_LocalResources/Captcha.ascx.tr-TR.resx new file mode 100644 index 0000000..303894b --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.tr-TR.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Resources.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Resources.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Yukarıdaki metni buraya yazınız (büyük küçük harfe dikkat ederek) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.uk-UA.resx b/WebApplication/App_LocalResources/Captcha.ascx.uk-UA.resx new file mode 100644 index 0000000..3dd056a --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.uk-UA.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Введіть символи, що відображені вище (регістрозалежне) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.zh-cn.resx b/WebApplication/App_LocalResources/Captcha.ascx.zh-cn.resx new file mode 100644 index 0000000..dd4cab2 --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.zh-cn.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + 填写上面显示的文字(大小写敏感) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Captcha.ascx.zh-tw.resx b/WebApplication/App_LocalResources/Captcha.ascx.zh-tw.resx new file mode 100644 index 0000000..2f0ce2d --- /dev/null +++ b/WebApplication/App_LocalResources/Captcha.ascx.zh-tw.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Properties.Messages.WrongControlText + + + <img src="Images/InputError.png" alt="*" /> + + + + + + CAPTCHA + + + + + + Properties.Messages.RequiredField + + + <img src="Images/InputError.png" alt="*" /> + + + + + + + + + Copy here the text displayed above (case sensitive) + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/Category.aspx.cs-CZ.resx new file mode 100644 index 0000000..068c8f8 --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.cs-CZ.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Kompletní seznam Kategorií pro tento jmenný prostor. + + + Kategorie stránek + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.da-DK.resx b/WebApplication/App_LocalResources/Category.aspx.da-DK.resx new file mode 100644 index 0000000..0785ae3 --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.da-DK.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The complete list of Page Categories of this Namespace. + + + Side kategorier + + + + + + Siden uden navn + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.de-DE.resx b/WebApplication/App_LocalResources/Category.aspx.de-DE.resx new file mode 100644 index 0000000..3271488 --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.de-DE.resx @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The complete list of Page Categories of this Namespace. + + + + + + Eintragskategorien + Page Categories + + + Unbenannte Seite + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.es-ES.resx b/WebApplication/App_LocalResources/Category.aspx.es-ES.resx new file mode 100644 index 0000000..de68c6a --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.es-ES.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The complete list of Page Categories of this Namespace. + + + + + + Page Categories + + + Página Sin Titulo + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.fr-FR.resx b/WebApplication/App_LocalResources/Category.aspx.fr-FR.resx new file mode 100644 index 0000000..86122d0 --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.fr-FR.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The complete list of Page Categories of this Namespace. + + + + + + Catégories de la Page + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.hu-HU.resx b/WebApplication/App_LocalResources/Category.aspx.hu-HU.resx new file mode 100644 index 0000000..a58a2cd --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.hu-HU.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The complete list of Page Categories of this Namespace. + + + Lap kategóriák + + + + + + Cím nélküli lap + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.it-IT.resx b/WebApplication/App_LocalResources/Category.aspx.it-IT.resx new file mode 100644 index 0000000..55446f6 --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.it-IT.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Lista completa delle categorie in questo namespace. + + + + + + Categorie Pagine + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.nb-NO.resx b/WebApplication/App_LocalResources/Category.aspx.nb-NO.resx new file mode 100644 index 0000000..22e6458 --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.nb-NO.resx @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The complete list of Page Categories of this Namespace. + + + + + + Kategorier + Page Categories + + + Sider uten tittel + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.nl-NL.resx b/WebApplication/App_LocalResources/Category.aspx.nl-NL.resx new file mode 100644 index 0000000..4da89e7 --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.nl-NL.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The complete list of Page Categories of this Namespace. + + + Lijst met alle categorieën + + + + + + Pagina zonder naam + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.pl-PL.resx b/WebApplication/App_LocalResources/Category.aspx.pl-PL.resx new file mode 100644 index 0000000..b4f1149 --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.pl-PL.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Strona kategorii + + + + + + Lista kategorii serwisu Wiki + + + Strona niezatytułowana + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.pt-BR.resx b/WebApplication/App_LocalResources/Category.aspx.pt-BR.resx new file mode 100644 index 0000000..b768154 --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.pt-BR.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The complete list of Page Categories of this Namespace. + + + + + + Categorias de página + + + Página sem título + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.resx b/WebApplication/App_LocalResources/Category.aspx.resx new file mode 100644 index 0000000..51a439c --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Page Categories + + + + + + The complete list of Page Categories of this Namespace. + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.ro-RO.resx b/WebApplication/App_LocalResources/Category.aspx.ro-RO.resx new file mode 100644 index 0000000..08bdac6 --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.ro-RO.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The complete list of Page Categories of this Namespace. + + + Categorii de pagini + + + + + + Pagina fara titlu + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.ru-RU.resx b/WebApplication/App_LocalResources/Category.aspx.ru-RU.resx new file mode 100644 index 0000000..9179d17 --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.ru-RU.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Полный список Страниц этой Категории в Пространстве Имён. + + + + + + Страницы категории + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.sk-SK.resx b/WebApplication/App_LocalResources/Category.aspx.sk-SK.resx new file mode 100644 index 0000000..5129a93 --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.sk-SK.resx @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The complete list of Page Categories of this Namespace. + + + + + + Kategórie stránok + Page Categories + + + Nepomenovaná stránka + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/Category.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..7d26bdb --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.sr-Latn-CS.resx @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The complete list of Page Categories of this Namespace. + + + + + + Kategorije stranica + Page Categories + + + Neimenovane stranice + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.tr-TR.resx b/WebApplication/App_LocalResources/Category.aspx.tr-TR.resx new file mode 100644 index 0000000..6e92e14 --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.tr-TR.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bu ad alanının tüm sayfa kategorileri listesi + + + Sayfa Kategorileri + + + + + + Başlıksız Sayfa + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.uk-UA.resx b/WebApplication/App_LocalResources/Category.aspx.uk-UA.resx new file mode 100644 index 0000000..851360f --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.uk-UA.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Повний перелік Сторінок цієї Категорії в Просторі Імен + + + + + + Сторінки категорії + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.zh-cn.resx b/WebApplication/App_LocalResources/Category.aspx.zh-cn.resx new file mode 100644 index 0000000..5970d69 --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.zh-cn.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 此命名空间中的页面分类的完整列表。 + + + 页面分类 + + + + + + 无标题页面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Category.aspx.zh-tw.resx b/WebApplication/App_LocalResources/Category.aspx.zh-tw.resx new file mode 100644 index 0000000..8045103 --- /dev/null +++ b/WebApplication/App_LocalResources/Category.aspx.zh-tw.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The complete list of Page Categories of this Namespace. + + + 頁面類別 + + + + + + 無標題頁面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/Default.aspx.cs-CZ.resx new file mode 100644 index 0000000..7404d92 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.cs-CZ.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + od + + + Kategorizováno jako + + + Změněno + + + Diskuze pro + + + Titulek + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.da-DK.resx b/WebApplication/App_LocalResources/Default.aspx.da-DK.resx new file mode 100644 index 0000000..009e265 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.da-DK.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + by + + + Categorized as + + + + + + + + + + + + + + + + + + + + + Modified on + + + + + + + + + + + + Page discussion for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.de-DE.resx b/WebApplication/App_LocalResources/Default.aspx.de-DE.resx new file mode 100644 index 0000000..009e265 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.de-DE.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + by + + + Categorized as + + + + + + + + + + + + + + + + + + + + + Modified on + + + + + + + + + + + + Page discussion for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.es-ES.resx b/WebApplication/App_LocalResources/Default.aspx.es-ES.resx new file mode 100644 index 0000000..009e265 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.es-ES.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + by + + + Categorized as + + + + + + + + + + + + + + + + + + + + + Modified on + + + + + + + + + + + + Page discussion for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.fr-FR.resx b/WebApplication/App_LocalResources/Default.aspx.fr-FR.resx new file mode 100644 index 0000000..009e265 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.fr-FR.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + by + + + Categorized as + + + + + + + + + + + + + + + + + + + + + Modified on + + + + + + + + + + + + Page discussion for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.hu-HU.resx b/WebApplication/App_LocalResources/Default.aspx.hu-HU.resx new file mode 100644 index 0000000..009e265 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.hu-HU.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + by + + + Categorized as + + + + + + + + + + + + + + + + + + + + + Modified on + + + + + + + + + + + + Page discussion for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.it-IT.resx b/WebApplication/App_LocalResources/Default.aspx.it-IT.resx new file mode 100644 index 0000000..4f54f32 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.it-IT.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + da + + + Categorizzata come + + + + + + + + + + + + + + + + + + + + + Modificata il + + + + + + + + + + + + Discussione per + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Titolo + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.nb-NO.resx b/WebApplication/App_LocalResources/Default.aspx.nb-NO.resx new file mode 100644 index 0000000..009e265 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.nb-NO.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + by + + + Categorized as + + + + + + + + + + + + + + + + + + + + + Modified on + + + + + + + + + + + + Page discussion for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.nl-NL.resx b/WebApplication/App_LocalResources/Default.aspx.nl-NL.resx new file mode 100644 index 0000000..009e265 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.nl-NL.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + by + + + Categorized as + + + + + + + + + + + + + + + + + + + + + Modified on + + + + + + + + + + + + Page discussion for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.pl-PL.resx b/WebApplication/App_LocalResources/Default.aspx.pl-PL.resx new file mode 100644 index 0000000..009e265 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.pl-PL.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + by + + + Categorized as + + + + + + + + + + + + + + + + + + + + + Modified on + + + + + + + + + + + + Page discussion for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.pt-BR.resx b/WebApplication/App_LocalResources/Default.aspx.pt-BR.resx new file mode 100644 index 0000000..009e265 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.pt-BR.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + by + + + Categorized as + + + + + + + + + + + + + + + + + + + + + Modified on + + + + + + + + + + + + Page discussion for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.resx b/WebApplication/App_LocalResources/Default.aspx.resx new file mode 100644 index 0000000..009e265 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + by + + + Categorized as + + + + + + + + + + + + + + + + + + + + + Modified on + + + + + + + + + + + + Page discussion for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.ro-RO.resx b/WebApplication/App_LocalResources/Default.aspx.ro-RO.resx new file mode 100644 index 0000000..009e265 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.ro-RO.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + by + + + Categorized as + + + + + + + + + + + + + + + + + + + + + Modified on + + + + + + + + + + + + Page discussion for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.ru-RU.resx b/WebApplication/App_LocalResources/Default.aspx.ru-RU.resx new file mode 100644 index 0000000..bec037d --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.ru-RU.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Категоризировано + + + + + + + + + + + + + + + + + + + + + Изменено + + + + + + + + + + + + Обсуждение Страницы + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Название + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.sk-SK.resx b/WebApplication/App_LocalResources/Default.aspx.sk-SK.resx new file mode 100644 index 0000000..009e265 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.sk-SK.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + by + + + Categorized as + + + + + + + + + + + + + + + + + + + + + Modified on + + + + + + + + + + + + Page discussion for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/Default.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..009e265 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.sr-Latn-CS.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + by + + + Categorized as + + + + + + + + + + + + + + + + + + + + + Modified on + + + + + + + + + + + + Page discussion for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.tr-TR.resx b/WebApplication/App_LocalResources/Default.aspx.tr-TR.resx new file mode 100644 index 0000000..5a1e447 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.tr-TR.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + Değiştiren: + + + Kategori: + + + + + + + + + + + + + + + + + + + + + Değiştirme vakti: + + + + + + + + + + + + Tartışma sayfası + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Başlık + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.uk-UA.resx b/WebApplication/App_LocalResources/Default.aspx.uk-UA.resx new file mode 100644 index 0000000..17c9b20 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.uk-UA.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Категорії + + + + + + + + + + + + + + + + + + + + + Змінено + + + + + + + + + + + + Обговорення Сторінки + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Назва + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.zh-cn.resx b/WebApplication/App_LocalResources/Default.aspx.zh-cn.resx new file mode 100644 index 0000000..5b5f6a4 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.zh-cn.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归类为 + + + + + + + + + + + + + + + + + + + + + 修改于 + + + + + + + + + + + + 页面讨论 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标题 + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Default.aspx.zh-tw.resx b/WebApplication/App_LocalResources/Default.aspx.zh-tw.resx new file mode 100644 index 0000000..009e265 --- /dev/null +++ b/WebApplication/App_LocalResources/Default.aspx.zh-tw.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + by + + + Categorized as + + + + + + + + + + + + + + + + + + + + + Modified on + + + + + + + + + + + + Page discussion for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/Edit.aspx.cs-CZ.resx new file mode 100644 index 0000000..747eed0 --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.cs-CZ.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Napište název Kategorie + + + Napište poznámku pro změnu (volitelně) + + + Napište popis stránky (volitelně) + + + Napište klíčové slova pro tuto stránku oddělené čárkou (volitelně) + + + Napište název stránky + + + Napište titulek stránky + + + Specifikujte název stránky manuálně... + + + Zavřít + + + Zavřít tuto zprávu + + + Storno + + + Storno + + + Zavřít panel nástrojů šablon + + + Vytvořit + + + Uložit a pokračovat + + + Uložit + + + Šablony obsahu + + + Použít šablonu + + + Použít šablonu (nahradit aktuální obsah) + + + Malá změna (nezálohovat) + + + Uložit jako koncept + + + Neplatné jméno + + + Kategorie již existuje + + + Neplatné jméno stránky + + + Stránka již existuje + + + Vaše změny pro tuto stránku budou uloženy jako koncept a nebudou publikovány dokud editor nebo administrátor je neschválí. + + + Správa příloh stránky + + + Šablona obsahu "##TEMPLATE##" byla vybrána automaticky. Můžete zrušit obsah nebo vybrat jinou šablonu odkazem nahoře. + + + Kategorie stránky + + + Komentář pro tuto změnu + + + <b>Varování</b>: tato stránka je editována jiným uživatelem + + + Meta Description + + + Pokud myslíte, že obsah je připraven pro zobrazení, odškrtněte 'Uložit jako koncept' poblíž tlačítka 'Uložit'. Poznámka: koncepty nejsou sledovány za pomocí verzí. + + + Aktuálně editujete dříve uložený <b>koncept</b> stránky, editován <b>##USER##</b> <b>##DATETIME##</b> (##VIEWCHANGES##). + + + Jelikož nejste přihlášen, Vaše IP adresa bude použita jako Uživatelské jméno. + + + Meta Klíčové slovsa (oddělte čárkou) + + + Meta Informace + + + Název stránek (použité pro odkazující stránky) + + + Nová Kategorie + + + Uložení této stránky může způsobit <b>ztrátu dat</b>. + + + Administrátoři nepovolují uložit tuto stránku. + + + Titulek stránky + + + Titulek + + + Je vyžadován Název + + + Je vyžadován Název + + + Je vyžadován Titulek + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.da-DK.resx b/WebApplication/App_LocalResources/Edit.aspx.da-DK.resx new file mode 100644 index 0000000..68f513f --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.da-DK.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Specify page name manually... + + + + + + + + + Type the name of the category here + + + + + + Type a comment for this change here (optional) + + + + + + Type the description for this page here (optional) + + + + + + Type the keywords for this page here, separated with commas (optional) + + + + + + Type the name of the page here + + + + + + Type the title of the page here + + + Close + + + Close this message + + + Cancel + + + + + + Cancel + + + Close the Templates toolbar + + + Create + + + + + + Save & Continue + + + + + + Save + + + + + + Content Templates... + + + + + + Use Template + + + Use this Template (replace current content) + + + Minor Change (no backup) + + + + + + Save as Draft + + + + + + Invalid Name + + + + + + + + + Category already exists + + + + + + + + + Invalid Page Name + + + + + + + + + Page already exists + + + + + + + + + Your changes to this page will be saved in a draft and they will not be published until an editor or administrator approves them. + + + Page Attachments Management + + + The Content Template "##TEMPLATE##" was selected automatically. You can discard the content as well as select another Template using the link above. + + + Page Categories + + + + + + + + + Comment for this change + + + <b>Warning</b>: this Page is being edited by another user + + + + + + Meta Description + + + If you think this content is ready for display, simply unckeck the 'Save as Draft' checkbox near the 'Save' button. Note: drafts are not versioned. + + + You are currently editing a previously saved <b>draft</b> of this page, edited by <b>##USER##</b> on <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + Since you are not logged in, your IP Address will be used as Username. + + + Meta Keywords (separate with commas) + + + Meta Information + + + Page Name (used for linking pages) + + + New Category + + + + + + + + + + + + Saving this Page might result in a <b>data-loss</b>. + + + The Administrators don't allow to save this Page. + + + + + + + + + + + + Page Title + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + Name is required + + + + + + + + + Title is required + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.de-DE.resx b/WebApplication/App_LocalResources/Edit.aspx.de-DE.resx new file mode 100644 index 0000000..68f513f --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.de-DE.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Specify page name manually... + + + + + + + + + Type the name of the category here + + + + + + Type a comment for this change here (optional) + + + + + + Type the description for this page here (optional) + + + + + + Type the keywords for this page here, separated with commas (optional) + + + + + + Type the name of the page here + + + + + + Type the title of the page here + + + Close + + + Close this message + + + Cancel + + + + + + Cancel + + + Close the Templates toolbar + + + Create + + + + + + Save & Continue + + + + + + Save + + + + + + Content Templates... + + + + + + Use Template + + + Use this Template (replace current content) + + + Minor Change (no backup) + + + + + + Save as Draft + + + + + + Invalid Name + + + + + + + + + Category already exists + + + + + + + + + Invalid Page Name + + + + + + + + + Page already exists + + + + + + + + + Your changes to this page will be saved in a draft and they will not be published until an editor or administrator approves them. + + + Page Attachments Management + + + The Content Template "##TEMPLATE##" was selected automatically. You can discard the content as well as select another Template using the link above. + + + Page Categories + + + + + + + + + Comment for this change + + + <b>Warning</b>: this Page is being edited by another user + + + + + + Meta Description + + + If you think this content is ready for display, simply unckeck the 'Save as Draft' checkbox near the 'Save' button. Note: drafts are not versioned. + + + You are currently editing a previously saved <b>draft</b> of this page, edited by <b>##USER##</b> on <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + Since you are not logged in, your IP Address will be used as Username. + + + Meta Keywords (separate with commas) + + + Meta Information + + + Page Name (used for linking pages) + + + New Category + + + + + + + + + + + + Saving this Page might result in a <b>data-loss</b>. + + + The Administrators don't allow to save this Page. + + + + + + + + + + + + Page Title + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + Name is required + + + + + + + + + Title is required + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.es-ES.resx b/WebApplication/App_LocalResources/Edit.aspx.es-ES.resx new file mode 100644 index 0000000..68f513f --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.es-ES.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Specify page name manually... + + + + + + + + + Type the name of the category here + + + + + + Type a comment for this change here (optional) + + + + + + Type the description for this page here (optional) + + + + + + Type the keywords for this page here, separated with commas (optional) + + + + + + Type the name of the page here + + + + + + Type the title of the page here + + + Close + + + Close this message + + + Cancel + + + + + + Cancel + + + Close the Templates toolbar + + + Create + + + + + + Save & Continue + + + + + + Save + + + + + + Content Templates... + + + + + + Use Template + + + Use this Template (replace current content) + + + Minor Change (no backup) + + + + + + Save as Draft + + + + + + Invalid Name + + + + + + + + + Category already exists + + + + + + + + + Invalid Page Name + + + + + + + + + Page already exists + + + + + + + + + Your changes to this page will be saved in a draft and they will not be published until an editor or administrator approves them. + + + Page Attachments Management + + + The Content Template "##TEMPLATE##" was selected automatically. You can discard the content as well as select another Template using the link above. + + + Page Categories + + + + + + + + + Comment for this change + + + <b>Warning</b>: this Page is being edited by another user + + + + + + Meta Description + + + If you think this content is ready for display, simply unckeck the 'Save as Draft' checkbox near the 'Save' button. Note: drafts are not versioned. + + + You are currently editing a previously saved <b>draft</b> of this page, edited by <b>##USER##</b> on <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + Since you are not logged in, your IP Address will be used as Username. + + + Meta Keywords (separate with commas) + + + Meta Information + + + Page Name (used for linking pages) + + + New Category + + + + + + + + + + + + Saving this Page might result in a <b>data-loss</b>. + + + The Administrators don't allow to save this Page. + + + + + + + + + + + + Page Title + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + Name is required + + + + + + + + + Title is required + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.fr-FR.resx b/WebApplication/App_LocalResources/Edit.aspx.fr-FR.resx new file mode 100644 index 0000000..68f513f --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.fr-FR.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Specify page name manually... + + + + + + + + + Type the name of the category here + + + + + + Type a comment for this change here (optional) + + + + + + Type the description for this page here (optional) + + + + + + Type the keywords for this page here, separated with commas (optional) + + + + + + Type the name of the page here + + + + + + Type the title of the page here + + + Close + + + Close this message + + + Cancel + + + + + + Cancel + + + Close the Templates toolbar + + + Create + + + + + + Save & Continue + + + + + + Save + + + + + + Content Templates... + + + + + + Use Template + + + Use this Template (replace current content) + + + Minor Change (no backup) + + + + + + Save as Draft + + + + + + Invalid Name + + + + + + + + + Category already exists + + + + + + + + + Invalid Page Name + + + + + + + + + Page already exists + + + + + + + + + Your changes to this page will be saved in a draft and they will not be published until an editor or administrator approves them. + + + Page Attachments Management + + + The Content Template "##TEMPLATE##" was selected automatically. You can discard the content as well as select another Template using the link above. + + + Page Categories + + + + + + + + + Comment for this change + + + <b>Warning</b>: this Page is being edited by another user + + + + + + Meta Description + + + If you think this content is ready for display, simply unckeck the 'Save as Draft' checkbox near the 'Save' button. Note: drafts are not versioned. + + + You are currently editing a previously saved <b>draft</b> of this page, edited by <b>##USER##</b> on <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + Since you are not logged in, your IP Address will be used as Username. + + + Meta Keywords (separate with commas) + + + Meta Information + + + Page Name (used for linking pages) + + + New Category + + + + + + + + + + + + Saving this Page might result in a <b>data-loss</b>. + + + The Administrators don't allow to save this Page. + + + + + + + + + + + + Page Title + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + Name is required + + + + + + + + + Title is required + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.hu-HU.resx b/WebApplication/App_LocalResources/Edit.aspx.hu-HU.resx new file mode 100644 index 0000000..68f513f --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.hu-HU.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Specify page name manually... + + + + + + + + + Type the name of the category here + + + + + + Type a comment for this change here (optional) + + + + + + Type the description for this page here (optional) + + + + + + Type the keywords for this page here, separated with commas (optional) + + + + + + Type the name of the page here + + + + + + Type the title of the page here + + + Close + + + Close this message + + + Cancel + + + + + + Cancel + + + Close the Templates toolbar + + + Create + + + + + + Save & Continue + + + + + + Save + + + + + + Content Templates... + + + + + + Use Template + + + Use this Template (replace current content) + + + Minor Change (no backup) + + + + + + Save as Draft + + + + + + Invalid Name + + + + + + + + + Category already exists + + + + + + + + + Invalid Page Name + + + + + + + + + Page already exists + + + + + + + + + Your changes to this page will be saved in a draft and they will not be published until an editor or administrator approves them. + + + Page Attachments Management + + + The Content Template "##TEMPLATE##" was selected automatically. You can discard the content as well as select another Template using the link above. + + + Page Categories + + + + + + + + + Comment for this change + + + <b>Warning</b>: this Page is being edited by another user + + + + + + Meta Description + + + If you think this content is ready for display, simply unckeck the 'Save as Draft' checkbox near the 'Save' button. Note: drafts are not versioned. + + + You are currently editing a previously saved <b>draft</b> of this page, edited by <b>##USER##</b> on <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + Since you are not logged in, your IP Address will be used as Username. + + + Meta Keywords (separate with commas) + + + Meta Information + + + Page Name (used for linking pages) + + + New Category + + + + + + + + + + + + Saving this Page might result in a <b>data-loss</b>. + + + The Administrators don't allow to save this Page. + + + + + + + + + + + + Page Title + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + Name is required + + + + + + + + + Title is required + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.it-IT.resx b/WebApplication/App_LocalResources/Edit.aspx.it-IT.resx new file mode 100644 index 0000000..2ad2e44 --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.it-IT.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Specifica nome manualmente... + + + + + + + + + Scrivi qui il nome della categoria + + + + + + Scrivi qui un commento alla modifica (facoltativo) + + + + + + Scrivi qui la descrizione della pagina (facoltativo) + + + + + + Scrivi qui le parole chiave per la pagina, separate da virgole (facoltativo) + + + + + + Scrivi qui il nome della pagina + + + + + + Scrivi qui il titolo della pagina + + + Chiudi + + + Chiudi questo messaggi + + + Annulla + + + + + + Annulla + + + Chiudi la toolbar template + + + Crea + + + + + + Salva & Continua + + + + + + Salva + + + + + + Template... + + + + + + Usa template + + + Usa questo template (sostituisci contenuto) + + + Modifica minore (no backup) + + + + + + Salva come bozza + + + + + + Nome non valido + + + + + + + + + Categoria già esistente + + + + + + + + + Nome non valido + + + + + + + + + Pagina già esistente + + + + + + + + + Le tue midifiche saranno salvate come bozza e non verranno pubblicate finchè un editore o amministratore non le avrà approvate. + + + Gestione allegati + + + Il template "##TEMPLATE##" è stato selezionato automaticamente. + + + Categorie + + + + + + + + + Commento alla modifica + + + <b>Attenzione</b>: questa pagina è in corso di modifica da parte di un altro utente + + + + + + Meta Description + + + Se pensi che la pagina sia pronta per la pubblicazione, deseleziona la casella 'Salva come bozza' e quindi salva. Nota: le bozze non hanno versione. + + + Stai modificando una <b>bozza</b> precedentemente salvata da <b>##USER##</b> il <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + Dato che non hai effettuato il login, il tuo indirizzo IP sarà utilizzato come username. + + + Meta Keyword (separate da virgole) + + + Meta Information + + + Nome pagina (utilizzato per i link) + + + Nuova categoria + + + + + + + + + + + + Salvare questa pagina potrebbe causare <b>perdite di dati</b>. + + + Gli amministratori non permettoni di salvare questa pagina. + + + + + + + + + + + + Titolo + + + + + + + + + + + + Titolo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Nome richiesto + + + + + + + + + Nome richiesto + + + + + + + + + Titolo richiesto + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.nb-NO.resx b/WebApplication/App_LocalResources/Edit.aspx.nb-NO.resx new file mode 100644 index 0000000..68f513f --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.nb-NO.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Specify page name manually... + + + + + + + + + Type the name of the category here + + + + + + Type a comment for this change here (optional) + + + + + + Type the description for this page here (optional) + + + + + + Type the keywords for this page here, separated with commas (optional) + + + + + + Type the name of the page here + + + + + + Type the title of the page here + + + Close + + + Close this message + + + Cancel + + + + + + Cancel + + + Close the Templates toolbar + + + Create + + + + + + Save & Continue + + + + + + Save + + + + + + Content Templates... + + + + + + Use Template + + + Use this Template (replace current content) + + + Minor Change (no backup) + + + + + + Save as Draft + + + + + + Invalid Name + + + + + + + + + Category already exists + + + + + + + + + Invalid Page Name + + + + + + + + + Page already exists + + + + + + + + + Your changes to this page will be saved in a draft and they will not be published until an editor or administrator approves them. + + + Page Attachments Management + + + The Content Template "##TEMPLATE##" was selected automatically. You can discard the content as well as select another Template using the link above. + + + Page Categories + + + + + + + + + Comment for this change + + + <b>Warning</b>: this Page is being edited by another user + + + + + + Meta Description + + + If you think this content is ready for display, simply unckeck the 'Save as Draft' checkbox near the 'Save' button. Note: drafts are not versioned. + + + You are currently editing a previously saved <b>draft</b> of this page, edited by <b>##USER##</b> on <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + Since you are not logged in, your IP Address will be used as Username. + + + Meta Keywords (separate with commas) + + + Meta Information + + + Page Name (used for linking pages) + + + New Category + + + + + + + + + + + + Saving this Page might result in a <b>data-loss</b>. + + + The Administrators don't allow to save this Page. + + + + + + + + + + + + Page Title + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + Name is required + + + + + + + + + Title is required + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.nl-NL.resx b/WebApplication/App_LocalResources/Edit.aspx.nl-NL.resx new file mode 100644 index 0000000..68f513f --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.nl-NL.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Specify page name manually... + + + + + + + + + Type the name of the category here + + + + + + Type a comment for this change here (optional) + + + + + + Type the description for this page here (optional) + + + + + + Type the keywords for this page here, separated with commas (optional) + + + + + + Type the name of the page here + + + + + + Type the title of the page here + + + Close + + + Close this message + + + Cancel + + + + + + Cancel + + + Close the Templates toolbar + + + Create + + + + + + Save & Continue + + + + + + Save + + + + + + Content Templates... + + + + + + Use Template + + + Use this Template (replace current content) + + + Minor Change (no backup) + + + + + + Save as Draft + + + + + + Invalid Name + + + + + + + + + Category already exists + + + + + + + + + Invalid Page Name + + + + + + + + + Page already exists + + + + + + + + + Your changes to this page will be saved in a draft and they will not be published until an editor or administrator approves them. + + + Page Attachments Management + + + The Content Template "##TEMPLATE##" was selected automatically. You can discard the content as well as select another Template using the link above. + + + Page Categories + + + + + + + + + Comment for this change + + + <b>Warning</b>: this Page is being edited by another user + + + + + + Meta Description + + + If you think this content is ready for display, simply unckeck the 'Save as Draft' checkbox near the 'Save' button. Note: drafts are not versioned. + + + You are currently editing a previously saved <b>draft</b> of this page, edited by <b>##USER##</b> on <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + Since you are not logged in, your IP Address will be used as Username. + + + Meta Keywords (separate with commas) + + + Meta Information + + + Page Name (used for linking pages) + + + New Category + + + + + + + + + + + + Saving this Page might result in a <b>data-loss</b>. + + + The Administrators don't allow to save this Page. + + + + + + + + + + + + Page Title + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + Name is required + + + + + + + + + Title is required + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.pl-PL.resx b/WebApplication/App_LocalResources/Edit.aspx.pl-PL.resx new file mode 100644 index 0000000..d7216ef --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.pl-PL.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Specify page name manually... + + + + + + + + + Type the name of the category here + + + + + + Type a comment for this change here (optional) + + + + + + Type the description for this page here (optional) + + + + + + Type the keywords for this page here, separated with commas (optional) + + + + + + Type the name of the page here + + + + + + Type the title of the page here + + + Close + + + Close this message + + + Cancel + + + Close the Templates toolbar + + + Create + + + + + + Content Templates... + + + + + + Use Template + + + Use this Template (replace current content) + + + Minor Change (no backup) + + + + + + Save as Draft + + + + + + Invalid Name + + + + + + + + + Category already exists + + + + + + + + + Invalid Page Name + + + + + + + + + Page already exists + + + + + + + + + Your changes to this page will be saved in a draft and they will not be published until an editor or administrator approves them. + + + Page Attachments Management + + + The Content Template "##TEMPLATE##" was selected automatically. You can discard the content as well as select another Template using the link above. + + + + + + + + + Comment for this change + + + + + + Meta Description + + + If you think this content is ready for display, simply unckeck the 'Save as Draft' checkbox near the 'Save' button. Note: drafts are not versioned. + + + You are currently editing a previously saved <b>draft</b> of this page, edited by <b>##USER##</b> on <b>##DATETIME##</b> (##VIEWCHANGES##). + + + Meta Keywords (separate with commas) + + + Meta Information + + + Page Name (used for linking pages) + + + New Category + + + + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + Name is required + + + + + + + + + Title is required + + + + + + + + + Anuluj + + + Anuluj edycję + + + Zapisz i kontynuuj + + + Zapisz zmiany i kontunuuj edycję + + + Zapisz + + + + + + Kategorie + + + <b>Uwaga</b>: ta strona jest edytowana przez innego użytkownika + + + + + + Dopóki nie zalogujesz się do serwisu Twój adres IP będzie wykorzystywany jako nazwa użytkownika. + + + + + + + + + + + + Zapisanie tej strony może doprowadzić do <b>utraty danych</b>. + + + Administrator nie zezwolił na zapisywanie tej strony. + + + + + + Tytuł strony + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.pt-BR.resx b/WebApplication/App_LocalResources/Edit.aspx.pt-BR.resx new file mode 100644 index 0000000..68f513f --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.pt-BR.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Specify page name manually... + + + + + + + + + Type the name of the category here + + + + + + Type a comment for this change here (optional) + + + + + + Type the description for this page here (optional) + + + + + + Type the keywords for this page here, separated with commas (optional) + + + + + + Type the name of the page here + + + + + + Type the title of the page here + + + Close + + + Close this message + + + Cancel + + + + + + Cancel + + + Close the Templates toolbar + + + Create + + + + + + Save & Continue + + + + + + Save + + + + + + Content Templates... + + + + + + Use Template + + + Use this Template (replace current content) + + + Minor Change (no backup) + + + + + + Save as Draft + + + + + + Invalid Name + + + + + + + + + Category already exists + + + + + + + + + Invalid Page Name + + + + + + + + + Page already exists + + + + + + + + + Your changes to this page will be saved in a draft and they will not be published until an editor or administrator approves them. + + + Page Attachments Management + + + The Content Template "##TEMPLATE##" was selected automatically. You can discard the content as well as select another Template using the link above. + + + Page Categories + + + + + + + + + Comment for this change + + + <b>Warning</b>: this Page is being edited by another user + + + + + + Meta Description + + + If you think this content is ready for display, simply unckeck the 'Save as Draft' checkbox near the 'Save' button. Note: drafts are not versioned. + + + You are currently editing a previously saved <b>draft</b> of this page, edited by <b>##USER##</b> on <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + Since you are not logged in, your IP Address will be used as Username. + + + Meta Keywords (separate with commas) + + + Meta Information + + + Page Name (used for linking pages) + + + New Category + + + + + + + + + + + + Saving this Page might result in a <b>data-loss</b>. + + + The Administrators don't allow to save this Page. + + + + + + + + + + + + Page Title + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + Name is required + + + + + + + + + Title is required + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.resx b/WebApplication/App_LocalResources/Edit.aspx.resx new file mode 100644 index 0000000..f5e9cb6 --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Close + + + Close this message + + + Cancel + + + + + + Cancel + + + Close the Templates toolbar + + + Create + + + + + + Save & Continue + + + + + + Save + + + + + + Content Templates... + + + + + + Use Template + + + Use this Template (replace current content) + + + Minor Change (no backup) + + + + + + Save as Draft + + + + + + Invalid Name + + + + + + + + + Category already exists + + + + + + + + + Invalid Page Name + + + + + + + + + Page already exists + + + + + + + + + Your changes to this page will be saved in a draft and they will not be published until an editor or administrator approves them. + + + Page Attachments Management + + + The Content Template "##TEMPLATE##" was selected automatically. You can discard the content as well as select another Template using the link above. + + + Page Categories + + + + + + + + + Comment for this change + + + <b>Warning</b>: this Page is being edited by another user + + + + + + Meta Description + + + If you think this content is ready for display, simply unckeck the 'Save as Draft' checkbox near the 'Save' button. Note: drafts are not versioned. + + + You are currently editing a previously saved <b>draft</b> of this page, edited by <b>##USER##</b> on <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + Since you are not logged in, your IP Address will be used as Username. + + + Meta Keywords (separate with commas) + + + Meta Information + + + Page Name (used for linking pages) + + + New Category + + + + + + + + + + + + Saving this Page might result in a <b>data-loss</b>. + + + The Administrators don't allow to save this Page. + + + + + + + + + + + + Page Title + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + Name is required + + + + + + + + + Title is required + + + + + + + + + + + + Type the name of the category here + + + + + + Type a comment for this change here (optional) + + + + + + Type the description for this page here (optional) + + + + + + Type the keywords for this page here, separated with commas (optional) + + + + + + Type the name of the page here + + + + + + Type the title of the page here + + + Specify page name manually... + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.ro-RO.resx b/WebApplication/App_LocalResources/Edit.aspx.ro-RO.resx new file mode 100644 index 0000000..68f513f --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.ro-RO.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Specify page name manually... + + + + + + + + + Type the name of the category here + + + + + + Type a comment for this change here (optional) + + + + + + Type the description for this page here (optional) + + + + + + Type the keywords for this page here, separated with commas (optional) + + + + + + Type the name of the page here + + + + + + Type the title of the page here + + + Close + + + Close this message + + + Cancel + + + + + + Cancel + + + Close the Templates toolbar + + + Create + + + + + + Save & Continue + + + + + + Save + + + + + + Content Templates... + + + + + + Use Template + + + Use this Template (replace current content) + + + Minor Change (no backup) + + + + + + Save as Draft + + + + + + Invalid Name + + + + + + + + + Category already exists + + + + + + + + + Invalid Page Name + + + + + + + + + Page already exists + + + + + + + + + Your changes to this page will be saved in a draft and they will not be published until an editor or administrator approves them. + + + Page Attachments Management + + + The Content Template "##TEMPLATE##" was selected automatically. You can discard the content as well as select another Template using the link above. + + + Page Categories + + + + + + + + + Comment for this change + + + <b>Warning</b>: this Page is being edited by another user + + + + + + Meta Description + + + If you think this content is ready for display, simply unckeck the 'Save as Draft' checkbox near the 'Save' button. Note: drafts are not versioned. + + + You are currently editing a previously saved <b>draft</b> of this page, edited by <b>##USER##</b> on <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + Since you are not logged in, your IP Address will be used as Username. + + + Meta Keywords (separate with commas) + + + Meta Information + + + Page Name (used for linking pages) + + + New Category + + + + + + + + + + + + Saving this Page might result in a <b>data-loss</b>. + + + The Administrators don't allow to save this Page. + + + + + + + + + + + + Page Title + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + Name is required + + + + + + + + + Title is required + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.ru-RU.resx b/WebApplication/App_LocalResources/Edit.aspx.ru-RU.resx new file mode 100644 index 0000000..966667d --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.ru-RU.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Введите имя страницы... + + + + + + + + + Введите название категории + + + + + + Прокомментируйте свои изменения (не обязательно) + + + + + + Опишите содержание этой страницы (не обязательно) + + + + + + Укажите ключевые слова этой страницы, отделяя их запятыми (не обязательно) + + + + + + Придумайте название для этой страницы + + + + + + Укажите тайтл этой страницы + + + Закрыть + + + Закрыть это сообщение + + + Отмена + + + + + + Отмена + + + Закрыть тулбар Шаблонов + + + Создать + + + + + + Сохранить и продолжить + + + + + + Сохранить + + + + + + Шаблоны оформления... + + + + + + Использовать Шаблон + + + Использовать этот Шаблон (замена текущего содержания) + + + Незначительное изменение (без бэкапа) + + + + + + Сохранить как Черновик + + + + + + Ошибочное Имя + + + + + + + + + Такая Категория уже существует + + + + + + + + + Ошибочное Имя Страницы + + + + + + + + + Такая Страница уже существует + + + + + + + + + Ваши изменения содержания обязательно будут сохранены, однако не будут опубликованы до тех пор, пока редактор или администратор их не одобрит + + + Управление Вложениями Страницы + + + Содержание Шаблона "##TEMPLATE##" было выбрано автоматически. Вы, вправе отказаться от этого содержания, а также выбрать иной шаблон, используя ссылку, находящуюся выше на странице. + + + Категории Страницы + + + + + + + + + Цель Ваших изменений (коротко) + + + <b>Предупреждение</b>: эта Страница редактируется другим пользователем + + + + + + Содержание тэга Description страницы + + + Если Вы считаете, что Ваш текст готов для обозрения, снимите отметку 'Сохранить как Черновик' возле кнопки 'Сохранить'. Примечание: черновики сохраняются без контроля версий. + + + Вы редактируете ранее сохранённый <b>черновик</b> этой страницы, изменённый <b>##USER##</b>, <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + В связи с тем, что Вы не авторизованы, вместо Вашего Логина будет отображён IP, который Вы используете для сёрфинга в Сети. + + + Содержание тэга Keywords страницы (отделяйте запятыми) + + + Содержание Мета Тэгов Страницы + + + Имя Страницы (будет использовано, как часть URL-а) + + + Новая Категория + + + + + + + + + + + + Сохранение этой Страницы возможно приведёт к <b>потере данных</b>. + + + Администраторы запретили сохранение этой Страницы. + + + + + + + + + + + + Название Страницы + + + + + + + + + + + + Название + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Имя обязательно + + + + + + + + + Имя обязательно + + + + + + + + + Название обязательно + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.sk-SK.resx b/WebApplication/App_LocalResources/Edit.aspx.sk-SK.resx new file mode 100644 index 0000000..68f513f --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.sk-SK.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Specify page name manually... + + + + + + + + + Type the name of the category here + + + + + + Type a comment for this change here (optional) + + + + + + Type the description for this page here (optional) + + + + + + Type the keywords for this page here, separated with commas (optional) + + + + + + Type the name of the page here + + + + + + Type the title of the page here + + + Close + + + Close this message + + + Cancel + + + + + + Cancel + + + Close the Templates toolbar + + + Create + + + + + + Save & Continue + + + + + + Save + + + + + + Content Templates... + + + + + + Use Template + + + Use this Template (replace current content) + + + Minor Change (no backup) + + + + + + Save as Draft + + + + + + Invalid Name + + + + + + + + + Category already exists + + + + + + + + + Invalid Page Name + + + + + + + + + Page already exists + + + + + + + + + Your changes to this page will be saved in a draft and they will not be published until an editor or administrator approves them. + + + Page Attachments Management + + + The Content Template "##TEMPLATE##" was selected automatically. You can discard the content as well as select another Template using the link above. + + + Page Categories + + + + + + + + + Comment for this change + + + <b>Warning</b>: this Page is being edited by another user + + + + + + Meta Description + + + If you think this content is ready for display, simply unckeck the 'Save as Draft' checkbox near the 'Save' button. Note: drafts are not versioned. + + + You are currently editing a previously saved <b>draft</b> of this page, edited by <b>##USER##</b> on <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + Since you are not logged in, your IP Address will be used as Username. + + + Meta Keywords (separate with commas) + + + Meta Information + + + Page Name (used for linking pages) + + + New Category + + + + + + + + + + + + Saving this Page might result in a <b>data-loss</b>. + + + The Administrators don't allow to save this Page. + + + + + + + + + + + + Page Title + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + Name is required + + + + + + + + + Title is required + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/Edit.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..68f513f --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.sr-Latn-CS.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Specify page name manually... + + + + + + + + + Type the name of the category here + + + + + + Type a comment for this change here (optional) + + + + + + Type the description for this page here (optional) + + + + + + Type the keywords for this page here, separated with commas (optional) + + + + + + Type the name of the page here + + + + + + Type the title of the page here + + + Close + + + Close this message + + + Cancel + + + + + + Cancel + + + Close the Templates toolbar + + + Create + + + + + + Save & Continue + + + + + + Save + + + + + + Content Templates... + + + + + + Use Template + + + Use this Template (replace current content) + + + Minor Change (no backup) + + + + + + Save as Draft + + + + + + Invalid Name + + + + + + + + + Category already exists + + + + + + + + + Invalid Page Name + + + + + + + + + Page already exists + + + + + + + + + Your changes to this page will be saved in a draft and they will not be published until an editor or administrator approves them. + + + Page Attachments Management + + + The Content Template "##TEMPLATE##" was selected automatically. You can discard the content as well as select another Template using the link above. + + + Page Categories + + + + + + + + + Comment for this change + + + <b>Warning</b>: this Page is being edited by another user + + + + + + Meta Description + + + If you think this content is ready for display, simply unckeck the 'Save as Draft' checkbox near the 'Save' button. Note: drafts are not versioned. + + + You are currently editing a previously saved <b>draft</b> of this page, edited by <b>##USER##</b> on <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + Since you are not logged in, your IP Address will be used as Username. + + + Meta Keywords (separate with commas) + + + Meta Information + + + Page Name (used for linking pages) + + + New Category + + + + + + + + + + + + Saving this Page might result in a <b>data-loss</b>. + + + The Administrators don't allow to save this Page. + + + + + + + + + + + + Page Title + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + Name is required + + + + + + + + + Title is required + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.tr-TR.resx b/WebApplication/App_LocalResources/Edit.aspx.tr-TR.resx new file mode 100644 index 0000000..c79a136 --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.tr-TR.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Type the name of the category here + + + + + + Type a comment for this change here (optional) + + + + + + Type the description for this page here (optional) + + + + + + Type the keywords for this page here, separated with commas (optional) + + + + + + Type the name of the page here + + + + + + Type the title of the page here + + + Specify page name manually... + + + + + + Kapat + + + Bu mesajı kapat + + + İptal + + + + + + İptal + + + Şablon araç çubuğunu kapat + + + Oluştur + + + + + + Kaydet & Devam et + + + + + + Oluştur + + + + + + İçerik şablonları... + + + + + + Şablonu Kullan + + + Bu şablonu kullan (mevcut içeriği değiştirir) + + + Ufak düzeltme (arşivlenmez) + + + + + + Taslak olarak kaydet + + + + + + Geçersiz ad + + + + + + + + + Kategori zaten var + + + + + + + + + Geçersiz sayfa adı + + + + + + + + + Sayfa zaten var + + + + + + + + + Değişiklikleriniz kaydedilecek ve bir editör veya yönetici onaylayana dek yayınlanmayacaktır. + + + Sayfa eklenti yönetimi + + + İçerik şablonu "##TEMPLATE##" olarak seçildi. Yukarıdan yeni şablon seçerek içeriği değiştirebilirsiniz. + + + Sayfa Kategorileri + + + + + + + + + Bu düzeltme için yorum + + + <b>Dikkat</b>: bu sayfa şu an başka birisi tarafından değiştiriliyor + + + + + + Meta Açıklaması + + + Eğer içeriğin hazır olduğunu düşünüyorsanız "taslak olarak kaydet" işaretini kaldırarak "Kaydet" düğmesine tıklayınız. Not: taslaklar versiyonlanmamaktadır. + + + You are currently editing a previously saved <b>draft</b> of this page, edited by <b>##USER##</b> on <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + Giriş yapmadığınız için IP adresiniz kullanıcı adı olarak kaydedilecektir. + + + Meta anahtar kelimeleri (virgül ile ayırınız) + + + Meta bilgisi + + + Sayfa adı (sayfaları bağlamak için kullanılıyor) + + + Yeni Kategori + + + + + + + + + + + + Bu sayfayı kaydetmek <b>veri kaybına</b> neden olabilir. + + + Bu sayfanın değişmesine izin verilmemektedir. + + + + + + + + + + + + Sayfa Başlığı + + + + + + + + + + + + Başlık + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ad gereklidir + + + + + + + + + Ad gereklidir + + + + + + + + + Başlık gereklidir + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.uk-UA.resx b/WebApplication/App_LocalResources/Edit.aspx.uk-UA.resx new file mode 100644 index 0000000..d69975f --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.uk-UA.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Означте ім'я сторінки, вручну... + + + + + + + + + Введіть сюди назву категорії + + + + + + Прокоментуйте мету змін (не обов'язково) + + + + + + Опишіть зміст цієї сторінки (не обов'язково) + + + + + + Введіть ключові слова для цієї сторінки, розділяючи комами (не обов'язково) + + + + + + Введіть назву цієї сторінки + + + + + + Вкажіть Тайтл цієї сторінки + + + Закрити + + + Закрити це повідомлення + + + Відміна + + + + + + Відміна + + + Закрити Тулбар Шаблонів + + + Створити + + + + + + Зберегти та продовжити + + + + + + Зберегти + + + + + + Шаблони оформлення... + + + + + + Використати Шаблон + + + Використати цей Шаблон (повна заміна поточного змісту) + + + Незначні зміни (не буде бекапу) + + + + + + Зберегти як Чернетку + + + + + + Помилкове Ім'я + + + + + + + + + Така Категорія вже існує + + + + + + + + + Помилкове Ім'я Сторінки + + + + + + + + + Така Сторінка вже існує + + + + + + + + + Ваші зміни змісту будуть збережені, однак не будуть опубліковані до тих пір, поки редактор чи адміністратор їх не схвалить + + + Керування Вкладеннями Сторінок + + + Зміст Шаблону "##TEMPLATE##" обраний автоматично. Ви, маєте право відмовитися від цього змісту, та обрати інший шаблон, використовуючи посилання, що знаходиться на цій сторінці (вище). + + + Категорії Сторінки + + + + + + + + + Мета змін + + + <b>Попередження</b>: ця Сторінка редагується іншим користувачем + + + + + + Зміст тегу Description сторінки + + + Якщо Ви вважаєте, що Ваш текст готовий для огляду, зніміть відмітку 'Зберегти як Чернетку' біля кнопки 'Зберегти'. Примітка: чернетки зберігаються без контролю версій. + + + Ви редагуєте раніше збережену <b>чернетку</b> цієї сторінки, змінену <b>##USER##</b>, <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + У зв'язку з тим, що Ви не авторизовані, замість Вашого логіна буде використаний Ваш IP, визначений системою Wiki. + + + Зміст тегу Keywords сторінки (розділяйте комами) + + + Зміст мета-тегів Сторінки + + + Ім'я Сторінки (буде використано, як частина URL-у) + + + Нова Категорія + + + + + + + + + + + + Збереження цієї Сторінки може призвести до <b>втрати даних</b>. + + + Адміністратори заборонили збереження змісту цієї Сторінки + + + + + + + + + + + + Назва Сторінки + + + + + + + + + + + + Назва + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Потрібне Ім'я + + + + + + + + + Потрібне Ім'я + + + + + + + + + Потрібна Назва + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.zh-CN.resx b/WebApplication/App_LocalResources/Edit.aspx.zh-CN.resx new file mode 100644 index 0000000..96dd53e --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.zh-CN.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 手动指定页面名称... + + + + + + + + + 在这里键入分类名称 + + + + + + 在这里键入编辑摘要(可选) + + + + + + 在这里键入页面描述(可选) + + + + + + 在这里键入页面关键词,用英文逗号分隔(可选) + + + + + + 在这里键入页面名称 + + + + + + 在这里键入页面标题 + + + 关闭 + + + 关闭此消息 + + + 取消 + + + + + + 取消 + + + 关闭模板工具栏 + + + 创建 + + + + + + 保存并继续 + + + + + + 保存 + + + + + + 内容模板... + + + + + + 使用模板 + + + 使用此模板(替换当前内容) + + + 小修改(不备份) + + + + + + 保存为草稿 + + + + + + 名称无效 + + + + + + + + + 分类已存在 + + + + + + + + + 页面名称无效 + + + + + + + + + 页面已存在 + + + + + + + + + 您对本页的更改将被保存为草稿,在编辑或管理员认可之前不会被发布。 + + + 页面附件管理 + + + 内容模板 "##TEMPLATE##" 已被自动选择。您可以舍弃内容或使用上面的链接选择其他模板。 + + + 页面分类 + + + + + + + + + 编辑摘要 + + + <b>警告</b>:本页面正被其他用户编辑 + + + + + + 描述 + + + 如果您认为此内容已经可以显示了,只需去除“保存为草稿”复选框(在“保存”按钮附近)的勾选。注意:草稿不分版本。 + + + 您正在编辑一份以前保存的本页<b>草稿</b>,编辑者 <b>##USER##</b>,编辑时间 <b>##DATETIME##</b>(##VIEWCHANGES##)。 + + + + + + 由于您没有登录,您的 IP 地址将被当作用户名。 + + + 关键词(用英文逗号分隔) + + + 元信息 + + + 页面名称(用于链接页面) + + + 新建分类 + + + + + + + + + + + + 保存此页面可能会造成<b>数据丢失</b>。 + + + 管理员不允许保存此页面。 + + + + + + + + + + + + 页面标题 + + + + + + + + + + + + 标题 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 需要名称 + + + + + + + + + 需要名称 + + + + + + + + + 需要标题 + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Edit.aspx.zh-TW.resx b/WebApplication/App_LocalResources/Edit.aspx.zh-TW.resx new file mode 100644 index 0000000..68f513f --- /dev/null +++ b/WebApplication/App_LocalResources/Edit.aspx.zh-TW.resx @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Specify page name manually... + + + + + + + + + Type the name of the category here + + + + + + Type a comment for this change here (optional) + + + + + + Type the description for this page here (optional) + + + + + + Type the keywords for this page here, separated with commas (optional) + + + + + + Type the name of the page here + + + + + + Type the title of the page here + + + Close + + + Close this message + + + Cancel + + + + + + Cancel + + + Close the Templates toolbar + + + Create + + + + + + Save & Continue + + + + + + Save + + + + + + Content Templates... + + + + + + Use Template + + + Use this Template (replace current content) + + + Minor Change (no backup) + + + + + + Save as Draft + + + + + + Invalid Name + + + + + + + + + Category already exists + + + + + + + + + Invalid Page Name + + + + + + + + + Page already exists + + + + + + + + + Your changes to this page will be saved in a draft and they will not be published until an editor or administrator approves them. + + + Page Attachments Management + + + The Content Template "##TEMPLATE##" was selected automatically. You can discard the content as well as select another Template using the link above. + + + Page Categories + + + + + + + + + Comment for this change + + + <b>Warning</b>: this Page is being edited by another user + + + + + + Meta Description + + + If you think this content is ready for display, simply unckeck the 'Save as Draft' checkbox near the 'Save' button. Note: drafts are not versioned. + + + You are currently editing a previously saved <b>draft</b> of this page, edited by <b>##USER##</b> on <b>##DATETIME##</b> (##VIEWCHANGES##). + + + + + + Since you are not logged in, your IP Address will be used as Username. + + + Meta Keywords (separate with commas) + + + Meta Information + + + Page Name (used for linking pages) + + + New Category + + + + + + + + + + + + Saving this Page might result in a <b>data-loss</b>. + + + The Administrators don't allow to save this Page. + + + + + + + + + + + + Page Title + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name is required + + + + + + + + + Name is required + + + + + + + + + Title is required + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.cs-CZ.resx b/WebApplication/App_LocalResources/Editor.ascx.cs-CZ.resx new file mode 100644 index 0000000..fd7b477 --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.cs-CZ.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Zvětšit velikost editoru + + + Zmenšit velikost editoru + + + Zvětšit velikost editoru + + + Náhled + + + Zmenšit velikost editoru + + + Vizuální + + + WikiMarkup + + + <b>Varování</b>: toto je jen náhled. Obsah nebyl uložen. + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.da-DK.resx b/WebApplication/App_LocalResources/Editor.ascx.da-DK.resx new file mode 100644 index 0000000..a0aae76 --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.da-DK.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Increase editor size + + + + + + Decrease editor size + + + + + + Increase editor size + + + Preview + + + + + + + + + Decrease editor size + + + Visual + + + + + + WikiMarkup + + + + + + + + + <b>Warning</b>: this is only a preview. The content was not saved. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.de-DE.resx b/WebApplication/App_LocalResources/Editor.ascx.de-DE.resx new file mode 100644 index 0000000..a0aae76 --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.de-DE.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Increase editor size + + + + + + Decrease editor size + + + + + + Increase editor size + + + Preview + + + + + + + + + Decrease editor size + + + Visual + + + + + + WikiMarkup + + + + + + + + + <b>Warning</b>: this is only a preview. The content was not saved. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.es-ES.resx b/WebApplication/App_LocalResources/Editor.ascx.es-ES.resx new file mode 100644 index 0000000..a0aae76 --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.es-ES.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Increase editor size + + + + + + Decrease editor size + + + + + + Increase editor size + + + Preview + + + + + + + + + Decrease editor size + + + Visual + + + + + + WikiMarkup + + + + + + + + + <b>Warning</b>: this is only a preview. The content was not saved. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.fr-FR.resx b/WebApplication/App_LocalResources/Editor.ascx.fr-FR.resx new file mode 100644 index 0000000..a0aae76 --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.fr-FR.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Increase editor size + + + + + + Decrease editor size + + + + + + Increase editor size + + + Preview + + + + + + + + + Decrease editor size + + + Visual + + + + + + WikiMarkup + + + + + + + + + <b>Warning</b>: this is only a preview. The content was not saved. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.hu-HU.resx b/WebApplication/App_LocalResources/Editor.ascx.hu-HU.resx new file mode 100644 index 0000000..33b4986 --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.hu-HU.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Increase editor size + + + + + + Decrease editor size + + + + + + Szerkesztőablak méretének növelése + + + Előnézet + + + + + + + + + Szerkesztőablak méretének csökkentése + + + Látvány + + + + + + WikiMarkup + + + + + + + + + <b>Figyelem!</b> Ez csak előnézet. A tartalom nincs elmentve. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.it-IT.resx b/WebApplication/App_LocalResources/Editor.ascx.it-IT.resx new file mode 100644 index 0000000..2f03f3a --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.it-IT.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Aumenta la dimensione dell'editor + + + + + + Riduci la dimensione dell'editor + + + + + + Aumenta la dimensione dell'editor + + + Anteprima + + + + + + + + + Riduci la dimensione dell'editor + + + Visual + + + + + + WikiMarkup + + + + + + + + + <b>Attenzione</b>: questa è solo un'anteprima. Il contenuto non è stato salvato. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.nb-NO.resx b/WebApplication/App_LocalResources/Editor.ascx.nb-NO.resx new file mode 100644 index 0000000..a0aae76 --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.nb-NO.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Increase editor size + + + + + + Decrease editor size + + + + + + Increase editor size + + + Preview + + + + + + + + + Decrease editor size + + + Visual + + + + + + WikiMarkup + + + + + + + + + <b>Warning</b>: this is only a preview. The content was not saved. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.nl-NL.resx b/WebApplication/App_LocalResources/Editor.ascx.nl-NL.resx new file mode 100644 index 0000000..a0aae76 --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.nl-NL.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Increase editor size + + + + + + Decrease editor size + + + + + + Increase editor size + + + Preview + + + + + + + + + Decrease editor size + + + Visual + + + + + + WikiMarkup + + + + + + + + + <b>Warning</b>: this is only a preview. The content was not saved. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.pl-PL.resx b/WebApplication/App_LocalResources/Editor.ascx.pl-PL.resx new file mode 100644 index 0000000..944f3bc --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.pl-PL.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Increase editor size + + + + + + Increase editor size + + + Preview + + + + + + + + + Decrease editor size + + + + + + Decrease editor size + + + Visual + + + + + + WikiMarkup + + + + + + + + + <b>Warning</b>: this is only a preview. The content was not saved. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.pt-BR.resx b/WebApplication/App_LocalResources/Editor.ascx.pt-BR.resx new file mode 100644 index 0000000..a0aae76 --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.pt-BR.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Increase editor size + + + + + + Decrease editor size + + + + + + Increase editor size + + + Preview + + + + + + + + + Decrease editor size + + + Visual + + + + + + WikiMarkup + + + + + + + + + <b>Warning</b>: this is only a preview. The content was not saved. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.resx b/WebApplication/App_LocalResources/Editor.ascx.resx new file mode 100644 index 0000000..944f3bc --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Increase editor size + + + + + + Increase editor size + + + Preview + + + + + + + + + Decrease editor size + + + + + + Decrease editor size + + + Visual + + + + + + WikiMarkup + + + + + + + + + <b>Warning</b>: this is only a preview. The content was not saved. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.ro-RO.resx b/WebApplication/App_LocalResources/Editor.ascx.ro-RO.resx new file mode 100644 index 0000000..a0aae76 --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.ro-RO.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Increase editor size + + + + + + Decrease editor size + + + + + + Increase editor size + + + Preview + + + + + + + + + Decrease editor size + + + Visual + + + + + + WikiMarkup + + + + + + + + + <b>Warning</b>: this is only a preview. The content was not saved. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.ru-RU.resx b/WebApplication/App_LocalResources/Editor.ascx.ru-RU.resx new file mode 100644 index 0000000..4fa63ee --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.ru-RU.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Увеличение размера редактора + + + + + + Уменьшение размера редактора + + + + + + Увеличение размера окна редактора + + + Предосмотр + + + + + + + + + Уменьшение размера окна редактора + + + Отображение + + + + + + Язык форматирования (WikiMarkup) + + + + + + + + + <b>Внимание</b>: это только предосмотр. Ваша редакция текста не сохранена. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.sk-SK.resx b/WebApplication/App_LocalResources/Editor.ascx.sk-SK.resx new file mode 100644 index 0000000..a0aae76 --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.sk-SK.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Increase editor size + + + + + + Decrease editor size + + + + + + Increase editor size + + + Preview + + + + + + + + + Decrease editor size + + + Visual + + + + + + WikiMarkup + + + + + + + + + <b>Warning</b>: this is only a preview. The content was not saved. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/Editor.ascx.sr-Latn-CS.resx new file mode 100644 index 0000000..a0aae76 --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.sr-Latn-CS.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Increase editor size + + + + + + Decrease editor size + + + + + + Increase editor size + + + Preview + + + + + + + + + Decrease editor size + + + Visual + + + + + + WikiMarkup + + + + + + + + + <b>Warning</b>: this is only a preview. The content was not saved. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.tr-TR.resx b/WebApplication/App_LocalResources/Editor.ascx.tr-TR.resx new file mode 100644 index 0000000..28e222d --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.tr-TR.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Increase editor size + + + + + + Decrease editor size + + + + + + Editör boyutunu büyüt + + + Önizleme + + + + + + + + + Editör boyutunu küçült + + + Görsel + + + + + + WikiMarkup + + + + + + + + + <b>İkaz</b>: bu sadece önizlemedir. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.uk-UA.resx b/WebApplication/App_LocalResources/Editor.ascx.uk-UA.resx new file mode 100644 index 0000000..005a718 --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.uk-UA.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Збільшення розміру редактора + + + + + + Зменьшення розміру вікна редактора + + + + + + Збільшення розміру вікна редактору + + + Попередній перегляд + + + + + + + + + Зменьшення розміру вікна редактору + + + Відображення + + + + + + Мова форматування (WikiMarkup) + + + + + + + + + <b>Увага</b>: це тільки попередній перегляд. Ваша редакція тексту не збережена. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.zh-cn.resx b/WebApplication/App_LocalResources/Editor.ascx.zh-cn.resx new file mode 100644 index 0000000..f790b6c --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.zh-cn.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + 增加编辑器大小 + + + + + + 减少编辑器大小 + + + + + + 增加编辑器大小 + + + 预览 + + + + + + + + + 减少编辑器大小 + + + 可视化 + + + + + + WikiMarkup + + + + + + + + + <b>警告</b>:这只是预览。内容未保存。 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Editor.ascx.zh-tw.resx b/WebApplication/App_LocalResources/Editor.ascx.zh-tw.resx new file mode 100644 index 0000000..a0aae76 --- /dev/null +++ b/WebApplication/App_LocalResources/Editor.ascx.zh-tw.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + Increase editor size + + + + + + Decrease editor size + + + + + + Increase editor size + + + Preview + + + + + + + + + Decrease editor size + + + Visual + + + + + + WikiMarkup + + + + + + + + + <b>Warning</b>: this is only a preview. The content was not saved. + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/Error.aspx.cs-CZ.resx new file mode 100644 index 0000000..e2cfafe --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.cs-CZ.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Detail vyjímky + + + Je nám líto, během zpracování požadavku došlo k chybě. Informace o chybě byly zaregistrovány a budou prozkoumány.<br />Prosím zkuste znovu z <a href="Default.aspx">Hlavní stranky</a>. + + + Systémová chyba + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.da-DK.resx b/WebApplication/App_LocalResources/Error.aspx.da-DK.resx new file mode 100644 index 0000000..0c6d8ea --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.da-DK.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exception Details + + + + + + + + + + + + Vi er kede af det, en fejl under behandling af din anmodning. Fejlen er blevet registreret og det bliver undersøgt. <br /> Genstart fra <a href="Default.aspx"> Main Page </ a>. + + + System fejl! + + + Side uden titel + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.de-DE.resx b/WebApplication/App_LocalResources/Error.aspx.de-DE.resx new file mode 100644 index 0000000..cea0fe5 --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.de-DE.resx @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exception Details + + + + + + + + + + + + Während der Bearbeitung Ihrer Anfrage ist ein Fehler aufgetreten. Die Fehlerinformatinoen wurden protokolliert und werden zur Beseitigung des Fehlers näher untersucht.<br />Bitte beginnen Sie erneut von der <a href="Default.aspx">Startseite</a>. + We're sorry, an error occurred while processing your request. The error information has been registered and it will investigated.<br />Please restart from the <a href="Default.aspx">Main Page</a>. + + + System Fehler + System Error + + + Unbenannte Seite + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.es-ES.resx b/WebApplication/App_LocalResources/Error.aspx.es-ES.resx new file mode 100644 index 0000000..9f92bf9 --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.es-ES.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exception Details + + + + + + + + + + + + Disculpenos, ha ocurrido un error procesando su petición. La información del error ha sido guardada y será analizada.<br />Por favor reinicie desde <a href="Default.aspx">La Página Principal</a>. + + + Error del Sistema + + + Página Sin Titulo + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.fr-FR.resx b/WebApplication/App_LocalResources/Error.aspx.fr-FR.resx new file mode 100644 index 0000000..0b29692 --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.fr-FR.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exception Details + + + + + + + + + + + + Nous sommes désolés, une erreur est survenue lors du traitement de votre requête. Cette erreur a été enregistrée.<br />Veuillez retourner sur la <a href="Default.aspx">Page Principale</a>. + + + Erreur Système + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.hu-HU.resx b/WebApplication/App_LocalResources/Error.aspx.hu-HU.resx new file mode 100644 index 0000000..a3bc835 --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.hu-HU.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + A hiba részletei + + + + + + + + + + + + Hiba történt a lekérdezésben. A hibát naplóztuk.<br />Kérjük kezd újra a <a href="Default.aspx">Főoldalon</a>. + + + Rendszerhiba + + + Cím nélküli oldal + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.it-IT.resx b/WebApplication/App_LocalResources/Error.aspx.it-IT.resx new file mode 100644 index 0000000..cc414d6 --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.it-IT.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Dettagli eccezione + + + + + + + + + + + + Spiacenti, si è verificato un errore. Le informazioni sull'errore sono state registrate e verranno analizzate.<br />Ricomincia dalla <a href="Default.aspx">Main Page</a>. + + + Errore di Sistema + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.nb-NO.resx b/WebApplication/App_LocalResources/Error.aspx.nb-NO.resx new file mode 100644 index 0000000..e81e589 --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.nb-NO.resx @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exception Details + + + + + + + + + + + + Dessverre skjedde det en feil. Feilinformasjonen er registrert og vil bli undersøkt.<br />Vennligst start på nytt fra <a href="Default.aspx">startsiden</a>. + We're sorry, an error occurred while processing your request. The error information has been registered and it will investigated.<br />Please restart from the <a href="Default.aspx">Main Page</a>. + + + Systemfeil + System Error + + + Side uten tittel + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.nl-NL.resx b/WebApplication/App_LocalResources/Error.aspx.nl-NL.resx new file mode 100644 index 0000000..22347c9 --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.nl-NL.resx @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exception Details + + + + + + + + + + + + Het spijt, maar er is een fout opgetreden met het verwerken van uw verzoek. Informatie over de fout is opgeslagen en zal worden onderzocht.<br />Begint u alstublieft weer op de <a href="Default.aspx">hoofdpagina</a>. + We're sorry, an error occurred while processing your request. The error information has been registered and it will investigated.<br />Please restart from the <a href="Default.aspx">Main Page</a>. + + + Systeemfout + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.pl-PL.resx b/WebApplication/App_LocalResources/Error.aspx.pl-PL.resx new file mode 100644 index 0000000..0a870de --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.pl-PL.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exception Details + + + + + + + + + + + + Wystąpił błąd podczas przetwarzania żądania. Informacje o błędzie zostały zarejestrowane i będą weryfikowane. <br /> Prosimy ponownie uruchomić <a href="Default.aspx">stronę główną serwisu</ a>. Przepraszamy za utrudnienia. + + + Błąd systemu + + + Strona niezatytułowana + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.pt-BR.resx b/WebApplication/App_LocalResources/Error.aspx.pt-BR.resx new file mode 100644 index 0000000..a99d525 --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.pt-BR.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exception Details + + + + + + + + + + + + Desculpe-nos, um erro ocorreu ao processar seu pedido. A informação do erro foi registada e será investigada.<br />Por favor, retorne à <a href="Default.aspx">Página inicial</a>. + + + Erro de sistema + + + Página sem título + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.resx b/WebApplication/App_LocalResources/Error.aspx.resx new file mode 100644 index 0000000..e99437c --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exception Details + + + We're sorry, an error occurred while processing your request. The error information has been registered and it will be investigated.<br />Please restart from the <a href="Default.aspx">Main Page</a>. + + + System Error + + + + + + Untitled Page + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.ro-RO.resx b/WebApplication/App_LocalResources/Error.aspx.ro-RO.resx new file mode 100644 index 0000000..65ad7e6 --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.ro-RO.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exception Details + + + + + + + + + + + + Ne pare rau, a aparut o eroare la procesarea cererii. Eroarea a fost inregistrata si va fi investigata.<br />Va rugam reluat de la<a href="Default.aspx">pagina principala</a>. + + + Eroare a sistemului + + + Pagina fara titlu + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.ru-RU.resx b/WebApplication/App_LocalResources/Error.aspx.ru-RU.resx new file mode 100644 index 0000000..a8b21b7 --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.ru-RU.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Подробности не отображены + + + + + + + + + + + + Приносим свои извинения! В процессе обработки Вашего запроса произошла ошибка. Отчёт об ошибке уже обрабатывается.<br />Пожалуйста, перейдите на <a href="Default.aspx">Главную страницу</a>. + + + Системная Ошибка + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.sk-SK.resx b/WebApplication/App_LocalResources/Error.aspx.sk-SK.resx new file mode 100644 index 0000000..f01aadb --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.sk-SK.resx @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exception Details + + + + + + + + + + + + Je nám to ľúto, ale počas spracovania Vašej požiadavky došlo k chybe. Chybové hlásenie bolo zaznamenané a bude preverené.<br />Prosím pokračujte z <a href="Default.aspx">hlavnej stránky</a>. + We're sorry, an error occurred while processing your request. The error information has been registered and it will investigated.<br />Please restart from the <a href="Default.aspx">Main Page</a>. + + + Systémová chyba + System Error + + + Nepomenovaná stránka + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/Error.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..e3a9bb1 --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.sr-Latn-CS.resx @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exception Details + + + + + + + + + + + + Pri obradi vašeg zahteva. Informacije o problemu su protokolisane i biće analizitane.<br />Molimo vas počnite ponovo od <a href="Default.aspx">Glavne Stranice</a>. + We're sorry, an error occurred while processing your request. The error information has been registered and it will investigated.<br />Please restart from the <a href="Default.aspx">Main Page</a>. + + + Sistemska greška + System Error + + + Neimenovana stranica + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.tr-TR.resx b/WebApplication/App_LocalResources/Error.aspx.tr-TR.resx new file mode 100644 index 0000000..bd308c1 --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.tr-TR.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Hata Detayları + + + + + + + + + + + + Malesef talebinizi işlerken bir hata vuku buldu. Hataya ait bilgiler kayıt altına alındı ve araştırılacak.<br /><a href="Default.aspx">Ana sayfa</a>'dan tekrar başlayabilirsiniz. + + + Sistem Hatası + + + Başlıksız Sayfa + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.uk-UA.resx b/WebApplication/App_LocalResources/Error.aspx.uk-UA.resx new file mode 100644 index 0000000..e406623 --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.uk-UA.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Подробиці не відображені + + + + + + + + + + + + Вибачте! В процесі обробки Вашого запиту сталася помилка. Звіт про помилку вже обробляється.<br />Будь ласка, перейдіть на <a href="Default.aspx">Головну сторінку</a>. + + + Системна Помилка (Як у Матриці :)) + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.zh-cn.resx b/WebApplication/App_LocalResources/Error.aspx.zh-cn.resx new file mode 100644 index 0000000..fcd3f86 --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.zh-cn.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 错误详情 + + + + + + + + + + + + 抱歉,处理您的请求时发生了错误。错误信息已被记录并将被研究。<br />请回到<a href="Default.aspx">首页</a>。 + + + 系统错误 + + + 无标题页面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Error.aspx.zh-tw.resx b/WebApplication/App_LocalResources/Error.aspx.zh-tw.resx new file mode 100644 index 0000000..59e75c3 --- /dev/null +++ b/WebApplication/App_LocalResources/Error.aspx.zh-tw.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exception Details + + + + + + + + + + + + 抱歉,處理您的請求時發生了錯誤。錯誤資訊已被記錄,我們將追蹤解決。<br />請回到<a href="Default.aspx">主頁面</a>。 + + + 系統錯誤 + + + 無標題頁面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.cs-CZ.resx b/WebApplication/App_LocalResources/FileManager.ascx.cs-CZ.resx new file mode 100644 index 0000000..86d0b7c --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.cs-CZ.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Název adresáře + + + Správa oprávnění + + + Vytvořit nový adresář + + + Nemůžete zobrazit obsah tohoto adresáře. + + + Ke stažení + + + Smazat + + + Smazat tuto položku + + + Smazat + + + Smazat tuto položku + + + Přejmenovat + + + Přejmenovat tuto položku + + + Přejmenovat + + + Přejmenovat tuto položku + + + Odkaz + + + Název + + + Velikost + + + Storno + + + Vytvořit + + + Přejmenovat + + + Nahrát + + + Přepsat existující soubor + + + Přejmenovat soubor/adresář + + + / + + + Můžete nahrát soubory do $1. Povolené typu souborů jsou: $2. + + + Nahrát soubory + + + [..] + + + / + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.da-DK.resx b/WebApplication/App_LocalResources/FileManager.ascx.da-DK.resx new file mode 100644 index 0000000..bd14742 --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.da-DK.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Delete + + + Delete this Item + + + Delete + + + Delete this Item + + + Create + + + + + + Rename + + + + + + Rename + + + Rename this Item + + + Rename + + + Rename this Item + + + Upload + + + + + + Overwrite existing file + + + + + + + + + Directory Name + + + Downloads + + + + + + + + + Link + + + Manage Permissions + + + Name + + + + + + + + + Create New Directory + + + You cannot list the contents of this directory. + + + + + + Rename file/directory + + + + + + + + + / + + + + + + Size + + + + + + You can upload files up to $1. Allowed file types are: $2. + + + Upload Files + + + + + + + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.de-DE.resx b/WebApplication/App_LocalResources/FileManager.ascx.de-DE.resx new file mode 100644 index 0000000..81b2bca --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.de-DE.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Directory Name + + + Manage Permissions + + + Create New Directory + + + You cannot list the contents of this directory. + + + + + + + + + + + + + + + + + + + + + + + + Downloads + + + Delete + + + Delete this Item + + + Delete + + + Delete this Item + + + Rename + + + Rename this Item + + + Rename + + + Rename this Item + + + Link + + + Name + + + Size + + + + + + + + + Cancel + + + + + + Create + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing file + + + + + + + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + / + + + + + + + + + You can upload files up to $1. Allowed file types are: $2. + + + Upload Files + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.es-ES.resx b/WebApplication/App_LocalResources/FileManager.ascx.es-ES.resx new file mode 100644 index 0000000..81b2bca --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.es-ES.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Directory Name + + + Manage Permissions + + + Create New Directory + + + You cannot list the contents of this directory. + + + + + + + + + + + + + + + + + + + + + + + + Downloads + + + Delete + + + Delete this Item + + + Delete + + + Delete this Item + + + Rename + + + Rename this Item + + + Rename + + + Rename this Item + + + Link + + + Name + + + Size + + + + + + + + + Cancel + + + + + + Create + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing file + + + + + + + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + / + + + + + + + + + You can upload files up to $1. Allowed file types are: $2. + + + Upload Files + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.fr-FR.resx b/WebApplication/App_LocalResources/FileManager.ascx.fr-FR.resx new file mode 100644 index 0000000..81b2bca --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.fr-FR.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Directory Name + + + Manage Permissions + + + Create New Directory + + + You cannot list the contents of this directory. + + + + + + + + + + + + + + + + + + + + + + + + Downloads + + + Delete + + + Delete this Item + + + Delete + + + Delete this Item + + + Rename + + + Rename this Item + + + Rename + + + Rename this Item + + + Link + + + Name + + + Size + + + + + + + + + Cancel + + + + + + Create + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing file + + + + + + + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + / + + + + + + + + + You can upload files up to $1. Allowed file types are: $2. + + + Upload Files + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.hu-HU.resx b/WebApplication/App_LocalResources/FileManager.ascx.hu-HU.resx new file mode 100644 index 0000000..f40fe19 --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.hu-HU.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Mappa neve + + + Engedélyek beállítása + + + Új mappa létrehozása + + + Nem listázható a mappa tartalma. + + + + + + + + + + + + + + + + + + + + + + + + Letöltések + + + Töröl + + + Elem törlése + + + Töröl + + + Elem törlése + + + Átnevez + + + Elem átnevezése + + + Átnevez + + + Elem átnevezése + + + Link + + + Név + + + Méret + + + + + + + + + Mégse + + + + + + Létrehoz + + + + + + Átnevez + + + + + + Feltölt + + + + + + Fájl felülírása + + + + + + + + + + + + + + + + + + + + + Fájl, mappa átnevezése + + + + + + + + + / + + + + + + + + + $1 fájt tölthetsz fel. Engedélyezett fájltípusok: $2. + + + Fájlok feltöltése + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.it-IT.resx b/WebApplication/App_LocalResources/FileManager.ascx.it-IT.resx new file mode 100644 index 0000000..0e13498 --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.it-IT.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nome cartella + + + Gestione permessi + + + Crea nuova cartella + + + Non hai i permessi per elencare il contenuto di questa cartella + + + + + + + + + + + + + + + + + + + + + + + + Download + + + Elimina + + + Elimina elemento + + + Elimina + + + Elimina elemento + + + Rinomina + + + Rinomina elemento + + + Rinomina + + + Rinomina elemento + + + Link + + + Nome + + + Dim. + + + + + + + + + Annulla + + + + + + Crea + + + + + + Rinomina + + + + + + Upload + + + + + + Sovrascrivi file esistente + + + + + + + + + + + + + + + + + + + + + Rinomina file/cartella + + + + + + + + + / + + + + + + + + + Puoi caricare file fino a $1. I tipi permessi sono: $2. + + + File Upload + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.nb-NO.resx b/WebApplication/App_LocalResources/FileManager.ascx.nb-NO.resx new file mode 100644 index 0000000..81b2bca --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.nb-NO.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Directory Name + + + Manage Permissions + + + Create New Directory + + + You cannot list the contents of this directory. + + + + + + + + + + + + + + + + + + + + + + + + Downloads + + + Delete + + + Delete this Item + + + Delete + + + Delete this Item + + + Rename + + + Rename this Item + + + Rename + + + Rename this Item + + + Link + + + Name + + + Size + + + + + + + + + Cancel + + + + + + Create + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing file + + + + + + + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + / + + + + + + + + + You can upload files up to $1. Allowed file types are: $2. + + + Upload Files + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.nl-NL.resx b/WebApplication/App_LocalResources/FileManager.ascx.nl-NL.resx new file mode 100644 index 0000000..81b2bca --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.nl-NL.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Directory Name + + + Manage Permissions + + + Create New Directory + + + You cannot list the contents of this directory. + + + + + + + + + + + + + + + + + + + + + + + + Downloads + + + Delete + + + Delete this Item + + + Delete + + + Delete this Item + + + Rename + + + Rename this Item + + + Rename + + + Rename this Item + + + Link + + + Name + + + Size + + + + + + + + + Cancel + + + + + + Create + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing file + + + + + + + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + / + + + + + + + + + You can upload files up to $1. Allowed file types are: $2. + + + Upload Files + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.pl-PL.resx b/WebApplication/App_LocalResources/FileManager.ascx.pl-PL.resx new file mode 100644 index 0000000..bd14742 --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.pl-PL.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Delete + + + Delete this Item + + + Delete + + + Delete this Item + + + Create + + + + + + Rename + + + + + + Rename + + + Rename this Item + + + Rename + + + Rename this Item + + + Upload + + + + + + Overwrite existing file + + + + + + + + + Directory Name + + + Downloads + + + + + + + + + Link + + + Manage Permissions + + + Name + + + + + + + + + Create New Directory + + + You cannot list the contents of this directory. + + + + + + Rename file/directory + + + + + + + + + / + + + + + + Size + + + + + + You can upload files up to $1. Allowed file types are: $2. + + + Upload Files + + + + + + + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.pt-BR.resx b/WebApplication/App_LocalResources/FileManager.ascx.pt-BR.resx new file mode 100644 index 0000000..81b2bca --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.pt-BR.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Directory Name + + + Manage Permissions + + + Create New Directory + + + You cannot list the contents of this directory. + + + + + + + + + + + + + + + + + + + + + + + + Downloads + + + Delete + + + Delete this Item + + + Delete + + + Delete this Item + + + Rename + + + Rename this Item + + + Rename + + + Rename this Item + + + Link + + + Name + + + Size + + + + + + + + + Cancel + + + + + + Create + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing file + + + + + + + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + / + + + + + + + + + You can upload files up to $1. Allowed file types are: $2. + + + Upload Files + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.resx b/WebApplication/App_LocalResources/FileManager.ascx.resx new file mode 100644 index 0000000..bd14742 --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Delete + + + Delete this Item + + + Delete + + + Delete this Item + + + Create + + + + + + Rename + + + + + + Rename + + + Rename this Item + + + Rename + + + Rename this Item + + + Upload + + + + + + Overwrite existing file + + + + + + + + + Directory Name + + + Downloads + + + + + + + + + Link + + + Manage Permissions + + + Name + + + + + + + + + Create New Directory + + + You cannot list the contents of this directory. + + + + + + Rename file/directory + + + + + + + + + / + + + + + + Size + + + + + + You can upload files up to $1. Allowed file types are: $2. + + + Upload Files + + + + + + + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.ro-RO.resx b/WebApplication/App_LocalResources/FileManager.ascx.ro-RO.resx new file mode 100644 index 0000000..81b2bca --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.ro-RO.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Directory Name + + + Manage Permissions + + + Create New Directory + + + You cannot list the contents of this directory. + + + + + + + + + + + + + + + + + + + + + + + + Downloads + + + Delete + + + Delete this Item + + + Delete + + + Delete this Item + + + Rename + + + Rename this Item + + + Rename + + + Rename this Item + + + Link + + + Name + + + Size + + + + + + + + + Cancel + + + + + + Create + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing file + + + + + + + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + / + + + + + + + + + You can upload files up to $1. Allowed file types are: $2. + + + Upload Files + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.ru-RU.resx b/WebApplication/App_LocalResources/FileManager.ascx.ru-RU.resx new file mode 100644 index 0000000..7c8d099 --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.ru-RU.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Имя Папки + + + Управление правами доступа + + + Создать новую папку + + + У Вас нет прав просмотра этой папки + + + + + + + + + + + + + + + + + + + + + + + + Загружено + + + Удалить + + + Удалить это + + + Удалить + + + Удалить это + + + Переименовать + + + Переименовать это + + + Переименовать + + + Переименовать это + + + Ссылка + + + Название + + + Размер + + + + + + + + + Отмена + + + + + + Создать + + + + + + Переименовать + + + + + + Загрузить + + + + + + Записать поверх существующего + + + + + + + + + + + + + + + + + + + + + Переименовать файл/папку + + + + + + + + + / + + + + + + + + + Вы имеете возможность загрузить файл размером до $1. Разрешённые к загрузке типы файлов: $2. + + + Загрузить файлы + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.sk-SK.resx b/WebApplication/App_LocalResources/FileManager.ascx.sk-SK.resx new file mode 100644 index 0000000..81b2bca --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.sk-SK.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Directory Name + + + Manage Permissions + + + Create New Directory + + + You cannot list the contents of this directory. + + + + + + + + + + + + + + + + + + + + + + + + Downloads + + + Delete + + + Delete this Item + + + Delete + + + Delete this Item + + + Rename + + + Rename this Item + + + Rename + + + Rename this Item + + + Link + + + Name + + + Size + + + + + + + + + Cancel + + + + + + Create + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing file + + + + + + + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + / + + + + + + + + + You can upload files up to $1. Allowed file types are: $2. + + + Upload Files + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/FileManager.ascx.sr-Latn-CS.resx new file mode 100644 index 0000000..81b2bca --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.sr-Latn-CS.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Directory Name + + + Manage Permissions + + + Create New Directory + + + You cannot list the contents of this directory. + + + + + + + + + + + + + + + + + + + + + + + + Downloads + + + Delete + + + Delete this Item + + + Delete + + + Delete this Item + + + Rename + + + Rename this Item + + + Rename + + + Rename this Item + + + Link + + + Name + + + Size + + + + + + + + + Cancel + + + + + + Create + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing file + + + + + + + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + / + + + + + + + + + You can upload files up to $1. Allowed file types are: $2. + + + Upload Files + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.tr-TR.resx b/WebApplication/App_LocalResources/FileManager.ascx.tr-TR.resx new file mode 100644 index 0000000..0780d06 --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.tr-TR.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Dizin Adı + + + İzinleri Yönet + + + Yeni Dizin Oluştur + + + Bu dizin içeriğini listeleyemezsiniz + + + + + + + + + + + + + + + + + + + + + + + + İndirilmiş dosyalar + + + Sil + + + Bu öğeyi sil + + + Sil + + + Bu öğeyi sil + + + Yeniden adlandır + + + Bu öğeyi yeniden adlandır + + + Yeniden adlandır + + + Bu öğeyi yeniden adlandır + + + Link + + + Ad + + + Boyut + + + + + + + + + İptal + + + + + + Oluştur + + + + + + Yeniden adlandır + + + + + + Yükle + + + + + + Var olan dosyayı ez + + + + + + + + + + + + + + + + + + + + + Dosya/dizini yeniden adlandır + + + + + + + + + / + + + + + + + + + $1 büyüklüğüne kadar dosya yükleyebilirsiniz. İzin verilen dosya tipleri ise şunlar: $2. + + + Dosya yükle + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.uk-UA.resx b/WebApplication/App_LocalResources/FileManager.ascx.uk-UA.resx new file mode 100644 index 0000000..fd0db47 --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.uk-UA.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ім'я Папки + + + Управління правами доступу + + + Свторити нову папку + + + У Вас немає прав перегляду цієї папки + + + + + + + + + + + + + + + + + + + + + + + + Завантажено + + + Видалити + + + Видалити це + + + Видалити + + + Видалити це + + + Перейменувати + + + Перейменувати це + + + Перейменувати + + + Перейменувати це + + + Посилання + + + Назва + + + Розмір + + + + + + + + + Відміна + + + + + + Створити + + + + + + Перейменувати + + + + + + Завантажити + + + + + + Записати на існуючий + + + + + + + + + + + + + + + + + + + + + Перейменувати файл/папку + + + + + + + + + / + + + + + + + + + Ви маєте можливість завантажити файл розміром не більше $1. Завантажувати дозволено лише такі типи файлів: $2. + + + Завантажити файли + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.zh-cn.resx b/WebApplication/App_LocalResources/FileManager.ascx.zh-cn.resx new file mode 100644 index 0000000..efc7791 --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.zh-cn.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 目录名 + + + 管理权限 + + + 创建新目录 + + + 您不能列出此目录的内容。 + + + + + + + + + + + + + + + + + + + + + + + + 下载 + + + 删除 + + + 删除此项目 + + + 删除 + + + 删除此项目 + + + 重命名 + + + 重命名此项目 + + + 重命名 + + + 重命名此项目 + + + 链接 + + + 名称 + + + 大小 + + + + + + + + + 取消 + + + + + + 创建 + + + + + + 重命名 + + + + + + 上传 + + + + + + 覆盖已有文件 + + + + + + + + + + + + + + + + + + + + + 重命名文件/目录 + + + + + + + + + / + + + + + + + + + 您可以上传最大 $1 的文件。允许的文件类型为:$2。 + + + 上传文件 + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/FileManager.ascx.zh-tw.resx b/WebApplication/App_LocalResources/FileManager.ascx.zh-tw.resx new file mode 100644 index 0000000..81b2bca --- /dev/null +++ b/WebApplication/App_LocalResources/FileManager.ascx.zh-tw.resx @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Directory Name + + + Manage Permissions + + + Create New Directory + + + You cannot list the contents of this directory. + + + + + + + + + + + + + + + + + + + + + + + + Downloads + + + Delete + + + Delete this Item + + + Delete + + + Delete this Item + + + Rename + + + Rename this Item + + + Rename + + + Rename this Item + + + Link + + + Name + + + Size + + + + + + + + + Cancel + + + + + + Create + + + + + + Rename + + + + + + Upload + + + + + + Overwrite existing file + + + + + + + + + + + + + + + + + + + + + Rename file/directory + + + + + + + + + / + + + + + + + + + You can upload files up to $1. Allowed file types are: $2. + + + Upload Files + + + + + + + + + [..] + + + / + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/History.aspx.cs-CZ.resx new file mode 100644 index 0000000..5faec87 --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.cs-CZ.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + Porovnat + + + Vrátit zpět + + + Vrátit zpět + + + Poznámka + + + Porovnat revize stránky + + + Uložil + + + Uloženo + + + -- Titulek -- + + + Titulek + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.da-DK.resx b/WebApplication/App_LocalResources/History.aspx.da-DK.resx new file mode 100644 index 0000000..f0a4506 --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.da-DK.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Rollback + + + + + + Rollback + + + + + + Comment + + + Saved by + + + Saved on + + + Title + + + Sammenlign + + + + + + Sammenlign side versioner + + + + + + -- Titel -- + + + + + + + + + Unavngiven side + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.de-DE.resx b/WebApplication/App_LocalResources/History.aspx.de-DE.resx new file mode 100644 index 0000000..3a2f0dd --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.de-DE.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Compare + + + + + + Rollback + + + + + + Rollback + + + + + + Comment + + + Compare Page Revisions + + + + + + Saved by + + + Saved on + + + -- Title -- + + + Title + + + + + + + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.es-ES.resx b/WebApplication/App_LocalResources/History.aspx.es-ES.resx new file mode 100644 index 0000000..3a2f0dd --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.es-ES.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Compare + + + + + + Rollback + + + + + + Rollback + + + + + + Comment + + + Compare Page Revisions + + + + + + Saved by + + + Saved on + + + -- Title -- + + + Title + + + + + + + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.fr-FR.resx b/WebApplication/App_LocalResources/History.aspx.fr-FR.resx new file mode 100644 index 0000000..3a2f0dd --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.fr-FR.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Compare + + + + + + Rollback + + + + + + Rollback + + + + + + Comment + + + Compare Page Revisions + + + + + + Saved by + + + Saved on + + + -- Title -- + + + Title + + + + + + + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.hu-HU.resx b/WebApplication/App_LocalResources/History.aspx.hu-HU.resx new file mode 100644 index 0000000..18f74bd --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.hu-HU.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Összehasonlítás + + + + + + Visszaállítás + + + + + + Visszaállítás + + + + + + Megjegyzés + + + Változatok összehasonlítása + + + + + + Mentette + + + Mentve + + + -- Cím -- + + + Cím + + + + + + + + + Cím nélküli oldal + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.it-IT.resx b/WebApplication/App_LocalResources/History.aspx.it-IT.resx new file mode 100644 index 0000000..d1eb04c --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.it-IT.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Confronta + + + + + + Rollback + + + + + + Rollback + + + + + + Commento + + + Confronta revisioni + + + + + + Salvata da + + + Salvata il + + + -- Title -- + + + Titolo + + + + + + + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.nb-NO.resx b/WebApplication/App_LocalResources/History.aspx.nb-NO.resx new file mode 100644 index 0000000..3a2f0dd --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.nb-NO.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Compare + + + + + + Rollback + + + + + + Rollback + + + + + + Comment + + + Compare Page Revisions + + + + + + Saved by + + + Saved on + + + -- Title -- + + + Title + + + + + + + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.nl-NL.resx b/WebApplication/App_LocalResources/History.aspx.nl-NL.resx new file mode 100644 index 0000000..3a2f0dd --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.nl-NL.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Compare + + + + + + Rollback + + + + + + Rollback + + + + + + Comment + + + Compare Page Revisions + + + + + + Saved by + + + Saved on + + + -- Title -- + + + Title + + + + + + + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.pl-PL.resx b/WebApplication/App_LocalResources/History.aspx.pl-PL.resx new file mode 100644 index 0000000..7c5d847 --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.pl-PL.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Rollback + + + + + + Rollback + + + + + + Comment + + + Saved by + + + Saved on + + + Title + + + Porównaj + + + + + + Porównaj wersje strony + + + + + + -- Tytuł -- + + + + + + + + + Strona niezatytułowana + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.pt-BR.resx b/WebApplication/App_LocalResources/History.aspx.pt-BR.resx new file mode 100644 index 0000000..3a2f0dd --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.pt-BR.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Compare + + + + + + Rollback + + + + + + Rollback + + + + + + Comment + + + Compare Page Revisions + + + + + + Saved by + + + Saved on + + + -- Title -- + + + Title + + + + + + + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.resx b/WebApplication/App_LocalResources/History.aspx.resx new file mode 100644 index 0000000..3a2f0dd --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Compare + + + + + + Rollback + + + + + + Rollback + + + + + + Comment + + + Compare Page Revisions + + + + + + Saved by + + + Saved on + + + -- Title -- + + + Title + + + + + + + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.ro-RO.resx b/WebApplication/App_LocalResources/History.aspx.ro-RO.resx new file mode 100644 index 0000000..3a2f0dd --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.ro-RO.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Compare + + + + + + Rollback + + + + + + Rollback + + + + + + Comment + + + Compare Page Revisions + + + + + + Saved by + + + Saved on + + + -- Title -- + + + Title + + + + + + + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.ru-RU.resx b/WebApplication/App_LocalResources/History.aspx.ru-RU.resx new file mode 100644 index 0000000..ed28c05 --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.ru-RU.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Сравнить + + + + + + Откат + + + + + + Откат + + + + + + Пояснение + + + Сравнить версии страницы + + + + + + Сохранено + + + Сохранено + + + -- Title -- + + + Заголовок + + + + + + + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.sk-SK.resx b/WebApplication/App_LocalResources/History.aspx.sk-SK.resx new file mode 100644 index 0000000..3a2f0dd --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.sk-SK.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Compare + + + + + + Rollback + + + + + + Rollback + + + + + + Comment + + + Compare Page Revisions + + + + + + Saved by + + + Saved on + + + -- Title -- + + + Title + + + + + + + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/History.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..3a2f0dd --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.sr-Latn-CS.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Compare + + + + + + Rollback + + + + + + Rollback + + + + + + Comment + + + Compare Page Revisions + + + + + + Saved by + + + Saved on + + + -- Title -- + + + Title + + + + + + + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.tr-TR.resx b/WebApplication/App_LocalResources/History.aspx.tr-TR.resx new file mode 100644 index 0000000..e980af5 --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.tr-TR.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Kıyasla + + + + + + Geri al + + + + + + Geri al + + + + + + Yorum + + + Sayfa revizyonlarıyla kıyasla + + + + + + Kaydeden + + + Kaydetme vakti + + + -- Başlık -- + + + Başlık + + + + + + + + + Başlıksız Sayfa + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.uk-UA.resx b/WebApplication/App_LocalResources/History.aspx.uk-UA.resx new file mode 100644 index 0000000..d136a97 --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.uk-UA.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Порівняти + + + + + + Відкат + + + + + + Відкат + + + + + + Пояснення + + + Порівняти версії сторінки + + + + + + Збережено + + + Збережено + + + -- Title -- + + + Заголовок + + + + + + + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.zh-cn.resx b/WebApplication/App_LocalResources/History.aspx.zh-cn.resx new file mode 100644 index 0000000..1cf0508 --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.zh-cn.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 比较 + + + + + + 回滚 + + + + + + 回滚 + + + + + + 注释 + + + 比较页面修订版 + + + + + + 保存者 + + + 保存于 + + + -- 标题 -- + + + 标题 + + + + + + + + + 无标题页面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/History.aspx.zh-tw.resx b/WebApplication/App_LocalResources/History.aspx.zh-tw.resx new file mode 100644 index 0000000..3a2f0dd --- /dev/null +++ b/WebApplication/App_LocalResources/History.aspx.zh-tw.resx @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Compare + + + + + + Rollback + + + + + + Rollback + + + + + + Comment + + + Compare Page Revisions + + + + + + Saved by + + + Saved on + + + -- Title -- + + + Title + + + + + + + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.cs-CZ.resx new file mode 100644 index 0000000..d33cdef --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.cs-CZ.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Uložit jako nový soubor + + + Pokud není vybráno, originální soubor je přepsán + + + Zavřít + + + JPEG 100% + + + JPEG format, plná kvalita + + + JPEG 60% + + + JPEG format, střední kvalita + + + Resize by scale + + + Změna velikosti určená měřítkem + + + Změna velikosti absolutní velikostí + + + Změna velikosti specifikována velikostí v pixelech + + + PNG format (alpha kanál je zachován) + + + 100 + + + Resize scale (percentage) + + + Zavřít tento panel nástrojů + + + PNG + + + Rotace + + + Aktualizovat náhled + + + Uložit + + + Uložit upravený obraz + + + Upravit poměr obrazu + + + Náhled obrazu + + + Aktuální velikost: + + + pixely + + + Možnosti pro změnu velikosti + + + %) + + + (Měřítko: + + + pixely + + + Editor obrazu + + + Výška + + + Šířka + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.da-DK.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.da-DK.resx new file mode 100644 index 0000000..dac88aa --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.da-DK.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Close + + + Close this tolbox + + + Rotation + + + Save as new file + + + If not selected, the original file is overwritten + + + JPEG 100% + + + JPEG format, full quality + + + JPEG 60% + + + JPEG format, medium quality + + + Resize by scale + + + Resize the image specifying a scale + + + Resize by absolute size + + + Resize the image specifying the size in pixels + + + PNG format (alpha channel is preserved) + + + 100 + + + Resize scale (percentage) + + + PNG + + + Update Preview + + + + + + Save + + + Save the modified Image + + + Maintain Aspect Ratio + + + + + + Image Preview + + + + + + Current size: + + + pixels + + + + + + + + + Image Resize Options + + + + + + + + + + + + %) + + + + + + (Scale: + + + + + + + + + + + + pixels + + + Image Editor + + + + + + Height + + + + + + + + + + + + Width + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.de-DE.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.de-DE.resx new file mode 100644 index 0000000..dac88aa --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.de-DE.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Close + + + Close this tolbox + + + Rotation + + + Save as new file + + + If not selected, the original file is overwritten + + + JPEG 100% + + + JPEG format, full quality + + + JPEG 60% + + + JPEG format, medium quality + + + Resize by scale + + + Resize the image specifying a scale + + + Resize by absolute size + + + Resize the image specifying the size in pixels + + + PNG format (alpha channel is preserved) + + + 100 + + + Resize scale (percentage) + + + PNG + + + Update Preview + + + + + + Save + + + Save the modified Image + + + Maintain Aspect Ratio + + + + + + Image Preview + + + + + + Current size: + + + pixels + + + + + + + + + Image Resize Options + + + + + + + + + + + + %) + + + + + + (Scale: + + + + + + + + + + + + pixels + + + Image Editor + + + + + + Height + + + + + + + + + + + + Width + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.es-ES.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.es-ES.resx new file mode 100644 index 0000000..dac88aa --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.es-ES.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Close + + + Close this tolbox + + + Rotation + + + Save as new file + + + If not selected, the original file is overwritten + + + JPEG 100% + + + JPEG format, full quality + + + JPEG 60% + + + JPEG format, medium quality + + + Resize by scale + + + Resize the image specifying a scale + + + Resize by absolute size + + + Resize the image specifying the size in pixels + + + PNG format (alpha channel is preserved) + + + 100 + + + Resize scale (percentage) + + + PNG + + + Update Preview + + + + + + Save + + + Save the modified Image + + + Maintain Aspect Ratio + + + + + + Image Preview + + + + + + Current size: + + + pixels + + + + + + + + + Image Resize Options + + + + + + + + + + + + %) + + + + + + (Scale: + + + + + + + + + + + + pixels + + + Image Editor + + + + + + Height + + + + + + + + + + + + Width + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.fr-FR.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.fr-FR.resx new file mode 100644 index 0000000..dac88aa --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.fr-FR.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Close + + + Close this tolbox + + + Rotation + + + Save as new file + + + If not selected, the original file is overwritten + + + JPEG 100% + + + JPEG format, full quality + + + JPEG 60% + + + JPEG format, medium quality + + + Resize by scale + + + Resize the image specifying a scale + + + Resize by absolute size + + + Resize the image specifying the size in pixels + + + PNG format (alpha channel is preserved) + + + 100 + + + Resize scale (percentage) + + + PNG + + + Update Preview + + + + + + Save + + + Save the modified Image + + + Maintain Aspect Ratio + + + + + + Image Preview + + + + + + Current size: + + + pixels + + + + + + + + + Image Resize Options + + + + + + + + + + + + %) + + + + + + (Scale: + + + + + + + + + + + + pixels + + + Image Editor + + + + + + Height + + + + + + + + + + + + Width + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.hu-HU.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.hu-HU.resx new file mode 100644 index 0000000..0c485aa --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.hu-HU.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Close + + + Close this tolbox + + + Rotation + + + Save as new file + + + If not selected, the original file is overwritten + + + JPEG 100% + + + JPEG format, full quality + + + JPEG 60% + + + JPEG format, medium quality + + + Resize by scale + + + Resize the image specifying a scale + + + Resize by absolute size + + + Resize the image specifying the size in pixels + + + PNG format (alpha channel is preserved) + + + 100 + + + Resize scale (percentage) + + + PNG + + + Előnézet frissítése + + + + + + Ment + + + Módosított kép mentése + + + Oldalarány megtartása + + + + + + Kép előnézet + + + + + + Jelenlegi méret: + + + pixel + + + + + + + + + Kép méretező beállítások + + + + + + + + + + + + %) + + + + + + (Méretarány: + + + + + + + + + + + + pixel + + + Képszerkesztő + + + + + + Magasság + + + + + + + + + + + + Szélesség + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.it-IT.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.it-IT.resx new file mode 100644 index 0000000..cedba5f --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.it-IT.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Chiudi + + + Chiudi toolbox + + + Rotazione + + + Salva in un nuovo file + + + Se non selezionato, il file originale viene sovrascritto + + + JPEG 100% + + + Formato JPEG, qualità massima + + + JPEG 60% + + + Formato JPEG, qualità media + + + Ridimensiona per scala + + + Ridimensiona l'immagine specificando una scala + + + Ridimensiona per dimensione + + + Ridinimensiona l'immagine specificando le dimensioni + + + Formato PNG (mantiene canale alpha) + + + 100 + + + Scala (percentuale) + + + PNG + + + Aggiorna anteprima + + + + + + Salva + + + Salva l'immagine modificata + + + Mantieni rapport larghezza/altezza + + + + + + Anteprima immagina + + + + + + Dimensione corrente: + + + pixel + + + + + + + + + Opzioni ridimensionamento + + + + + + + + + + + + %) + + + + + + (Scala: + + + + + + + + + + + + pixel + + + Image Editor + + + + + + Altezza + + + + + + + + + + + + Larghezza + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.nb-NO.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.nb-NO.resx new file mode 100644 index 0000000..dac88aa --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.nb-NO.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Close + + + Close this tolbox + + + Rotation + + + Save as new file + + + If not selected, the original file is overwritten + + + JPEG 100% + + + JPEG format, full quality + + + JPEG 60% + + + JPEG format, medium quality + + + Resize by scale + + + Resize the image specifying a scale + + + Resize by absolute size + + + Resize the image specifying the size in pixels + + + PNG format (alpha channel is preserved) + + + 100 + + + Resize scale (percentage) + + + PNG + + + Update Preview + + + + + + Save + + + Save the modified Image + + + Maintain Aspect Ratio + + + + + + Image Preview + + + + + + Current size: + + + pixels + + + + + + + + + Image Resize Options + + + + + + + + + + + + %) + + + + + + (Scale: + + + + + + + + + + + + pixels + + + Image Editor + + + + + + Height + + + + + + + + + + + + Width + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.nl-NL.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.nl-NL.resx new file mode 100644 index 0000000..dac88aa --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.nl-NL.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Close + + + Close this tolbox + + + Rotation + + + Save as new file + + + If not selected, the original file is overwritten + + + JPEG 100% + + + JPEG format, full quality + + + JPEG 60% + + + JPEG format, medium quality + + + Resize by scale + + + Resize the image specifying a scale + + + Resize by absolute size + + + Resize the image specifying the size in pixels + + + PNG format (alpha channel is preserved) + + + 100 + + + Resize scale (percentage) + + + PNG + + + Update Preview + + + + + + Save + + + Save the modified Image + + + Maintain Aspect Ratio + + + + + + Image Preview + + + + + + Current size: + + + pixels + + + + + + + + + Image Resize Options + + + + + + + + + + + + %) + + + + + + (Scale: + + + + + + + + + + + + pixels + + + Image Editor + + + + + + Height + + + + + + + + + + + + Width + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.pl-PL.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.pl-PL.resx new file mode 100644 index 0000000..dac88aa --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.pl-PL.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Close + + + Close this tolbox + + + Rotation + + + Save as new file + + + If not selected, the original file is overwritten + + + JPEG 100% + + + JPEG format, full quality + + + JPEG 60% + + + JPEG format, medium quality + + + Resize by scale + + + Resize the image specifying a scale + + + Resize by absolute size + + + Resize the image specifying the size in pixels + + + PNG format (alpha channel is preserved) + + + 100 + + + Resize scale (percentage) + + + PNG + + + Update Preview + + + + + + Save + + + Save the modified Image + + + Maintain Aspect Ratio + + + + + + Image Preview + + + + + + Current size: + + + pixels + + + + + + + + + Image Resize Options + + + + + + + + + + + + %) + + + + + + (Scale: + + + + + + + + + + + + pixels + + + Image Editor + + + + + + Height + + + + + + + + + + + + Width + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.pt-BR.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.pt-BR.resx new file mode 100644 index 0000000..dac88aa --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.pt-BR.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Close + + + Close this tolbox + + + Rotation + + + Save as new file + + + If not selected, the original file is overwritten + + + JPEG 100% + + + JPEG format, full quality + + + JPEG 60% + + + JPEG format, medium quality + + + Resize by scale + + + Resize the image specifying a scale + + + Resize by absolute size + + + Resize the image specifying the size in pixels + + + PNG format (alpha channel is preserved) + + + 100 + + + Resize scale (percentage) + + + PNG + + + Update Preview + + + + + + Save + + + Save the modified Image + + + Maintain Aspect Ratio + + + + + + Image Preview + + + + + + Current size: + + + pixels + + + + + + + + + Image Resize Options + + + + + + + + + + + + %) + + + + + + (Scale: + + + + + + + + + + + + pixels + + + Image Editor + + + + + + Height + + + + + + + + + + + + Width + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.resx new file mode 100644 index 0000000..b41ba32 --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Update Preview + + + + + + Save + + + Save the modified Image + + + Maintain Aspect Ratio + + + + + + Save as new file + + + If not selected, the original file is overwritten + + + Image Preview + + + + + + Close + + + Current size: + + + pixels + + + + + + + + + Image Resize Options + + + + + + + + + + + + %) + + + + + + (Scale: + + + + + + + + + + + + pixels + + + Image Editor + + + JPEG 100% + + + JPEG format, full quality + + + JPEG 60% + + + JPEG format, medium quality + + + Resize by scale + + + Resize the image specifying a scale + + + Resize by absolute size + + + Resize the image specifying the size in pixels + + + PNG format (alpha channel is preserved) + + + + + + Height + + + + + + + + + 100 + + + Resize scale (percentage) + + + + + + Width + + + Close this tolbox + + + PNG + + + Rotation + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.ro-RO.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.ro-RO.resx new file mode 100644 index 0000000..dac88aa --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.ro-RO.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Close + + + Close this tolbox + + + Rotation + + + Save as new file + + + If not selected, the original file is overwritten + + + JPEG 100% + + + JPEG format, full quality + + + JPEG 60% + + + JPEG format, medium quality + + + Resize by scale + + + Resize the image specifying a scale + + + Resize by absolute size + + + Resize the image specifying the size in pixels + + + PNG format (alpha channel is preserved) + + + 100 + + + Resize scale (percentage) + + + PNG + + + Update Preview + + + + + + Save + + + Save the modified Image + + + Maintain Aspect Ratio + + + + + + Image Preview + + + + + + Current size: + + + pixels + + + + + + + + + Image Resize Options + + + + + + + + + + + + %) + + + + + + (Scale: + + + + + + + + + + + + pixels + + + Image Editor + + + + + + Height + + + + + + + + + + + + Width + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.ru-RU.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.ru-RU.resx new file mode 100644 index 0000000..e9babdc --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.ru-RU.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Закрыть + + + Закрыть этот тулбар + + + Ротация + + + Сохранить как новый файл + + + Если не отмечать, будет перезаписан существующий файл + + + JPEG 100% + + + JPEG формат, лучшее качество + + + JPEG 60% + + + JPEG формат, среднее качество + + + Изменить масштаб + + + Изменить масштаб отображения изображения + + + Отображать абсолютный размер + + + Изменить отображение изображения в пикселях + + + PNG формат (поддерживаются альфа-каналы) + + + 100 + + + Изменение масштаба (в процентах) + + + PNG + + + Обновить Предосмотр + + + + + + Сохранить + + + Сохранить изменённую картинку + + + Сохранение пропорций + + + + + + Превью картинки + + + + + + Текущий размер: + + + точек + + + + + + + + + Инструменты изменения размера + + + + + + + + + + + + %) + + + + + + (Масштаб: + + + + + + + + + + + + точек + + + Графический редактор + + + + + + Высота + + + + + + + + + + + + Ширина + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.sk-SK.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.sk-SK.resx new file mode 100644 index 0000000..dac88aa --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.sk-SK.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Close + + + Close this tolbox + + + Rotation + + + Save as new file + + + If not selected, the original file is overwritten + + + JPEG 100% + + + JPEG format, full quality + + + JPEG 60% + + + JPEG format, medium quality + + + Resize by scale + + + Resize the image specifying a scale + + + Resize by absolute size + + + Resize the image specifying the size in pixels + + + PNG format (alpha channel is preserved) + + + 100 + + + Resize scale (percentage) + + + PNG + + + Update Preview + + + + + + Save + + + Save the modified Image + + + Maintain Aspect Ratio + + + + + + Image Preview + + + + + + Current size: + + + pixels + + + + + + + + + Image Resize Options + + + + + + + + + + + + %) + + + + + + (Scale: + + + + + + + + + + + + pixels + + + Image Editor + + + + + + Height + + + + + + + + + + + + Width + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..dac88aa --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.sr-Latn-CS.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Close + + + Close this tolbox + + + Rotation + + + Save as new file + + + If not selected, the original file is overwritten + + + JPEG 100% + + + JPEG format, full quality + + + JPEG 60% + + + JPEG format, medium quality + + + Resize by scale + + + Resize the image specifying a scale + + + Resize by absolute size + + + Resize the image specifying the size in pixels + + + PNG format (alpha channel is preserved) + + + 100 + + + Resize scale (percentage) + + + PNG + + + Update Preview + + + + + + Save + + + Save the modified Image + + + Maintain Aspect Ratio + + + + + + Image Preview + + + + + + Current size: + + + pixels + + + + + + + + + Image Resize Options + + + + + + + + + + + + %) + + + + + + (Scale: + + + + + + + + + + + + pixels + + + Image Editor + + + + + + Height + + + + + + + + + + + + Width + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.tr-TR.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.tr-TR.resx new file mode 100644 index 0000000..7765a76 --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.tr-TR.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Save as new file + + + If not selected, the original file is overwritten + + + Close + + + JPEG 100% + + + JPEG format, full quality + + + JPEG 60% + + + JPEG format, medium quality + + + Resize by scale + + + Resize the image specifying a scale + + + Resize by absolute size + + + Resize the image specifying the size in pixels + + + PNG format (alpha channel is preserved) + + + 100 + + + Resize scale (percentage) + + + Close this tolbox + + + PNG + + + Rotation + + + Önizlemeyi Güncelle + + + + + + Kaydet + + + Değiştirilmiş imajı kaydet + + + En boy oranını koru + + + + + + İmaj Önizleme + + + + + + Şimdiki boyut: + + + piksel + + + + + + + + + İmaj Yeniden Boyutlama Seçenekleri + + + + + + + + + + + + %) + + + + + + (Ölçek: + + + + + + + + + + + + piksel + + + İmaj Editörü + + + + + + Yükseklik + + + + + + + + + + + + Genişlik + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.uk-UA.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.uk-UA.resx new file mode 100644 index 0000000..12a78ed --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.uk-UA.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Закрити + + + Закрити цю панель інструментів + + + Ротація + + + Зберегти як новий файл + + + Якщо не обрано, буде перезаписаний існуючий файл + + + JPEG 100% + + + JPEG формат, вищої якості + + + JPEG 60% + + + JPEG формат, середньої якості + + + Змінити масштаб відображення + + + Змінити масштаб відображення зображення + + + Відображати в реальному масштабі + + + Змінти розмір відображення зображення у пікселях + + + PNG формат (альфа канали підтримуються) + + + 100 + + + Зміна масштабу (відсотки) + + + PNG + + + Оновити Попередній перегляд + + + + + + Зберегти + + + Зберегти змінену картинку + + + Збереження пропорцій + + + + + + Прев'ю картинки + + + + + + Поточний розмір: + + + пікселів + + + + + + + + + Інструменти зміни розміру + + + + + + + + + + + + %) + + + + + + (Масштаб: + + + + + + + + + + + + пікселів + + + Графічний редактор + + + + + + Висота + + + + + + + + + + + + Ширина + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.zh-cn.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.zh-cn.resx new file mode 100644 index 0000000..fb81458 --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.zh-cn.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 关闭 + + + 关闭此工具箱 + + + 旋转 + + + 保存为新文件 + + + 如果未选中,原始文件将被覆盖 + + + JPEG 100% + + + JPEG 格式,最高品质 + + + JPEG 60% + + + JPEG 格式,中等品质 + + + 按比例重新调整大小 + + + 按指定比例重新调整图像大小 + + + 按绝对大小重新调整大小 + + + 按指定大小(以像素为单位)重新调整图像大小 + + + PNG 格式(保留 alpha 通道) + + + 100 + + + 重新调整大小比例(百分比) + + + PNG + + + 更新预览 + + + + + + 保存 + + + 保存修改后的图像 + + + 保持比例 + + + + + + 图像预览 + + + + + + 当前大小: + + + 像素 + + + + + + + + + 图像重新调整大小选项 + + + + + + + + + + + + %) + + + + + + (比例: + + + + + + + + + + + + 像素 + + + 图像编辑器 + + + + + + 高度 + + + + + + + + + + + + 宽度 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/ImageEditor.aspx.zh-tw.resx b/WebApplication/App_LocalResources/ImageEditor.aspx.zh-tw.resx new file mode 100644 index 0000000..dac88aa --- /dev/null +++ b/WebApplication/App_LocalResources/ImageEditor.aspx.zh-tw.resx @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Close + + + Close this tolbox + + + Rotation + + + Save as new file + + + If not selected, the original file is overwritten + + + JPEG 100% + + + JPEG format, full quality + + + JPEG 60% + + + JPEG format, medium quality + + + Resize by scale + + + Resize the image specifying a scale + + + Resize by absolute size + + + Resize the image specifying the size in pixels + + + PNG format (alpha channel is preserved) + + + 100 + + + Resize scale (percentage) + + + PNG + + + Update Preview + + + + + + Save + + + Save the modified Image + + + Maintain Aspect Ratio + + + + + + Image Preview + + + + + + Current size: + + + pixels + + + + + + + + + Image Resize Options + + + + + + + + + + + + %) + + + + + + (Scale: + + + + + + + + + + + + pixels + + + Image Editor + + + + + + Height + + + + + + + + + + + + Width + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/Login.aspx.cs-CZ.resx new file mode 100644 index 0000000..99a7007 --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.cs-CZ.resx @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Uložit nové heslo + + + Hesla nejsou stejná + + + Heslo + + + Heslo (znovu) + + + nebo + + + Prosím vložte své nové heslo dvakrát. + + + Resetovat Heslo + + + Pokud jste zapomněl své heslo, prosím vložte své Uživatelské jméno <b>nebo</b> email a klikněte na tlačítko Resetovat heslo.<br />Obdržíte email s odkazem pro resetování hesla. + + + Vyžadováno heslo + + + Vyžadováno heslo + + + Neplatné heslo + + + Neplatné heslo + + + Přihlášení + + + Klikněte pro přihlášení + + + Odhlásit + + + Klikněte pro odhlášení + + + Resetovat heslo + + + Zapamatovat si mne + + + Zaškrtněte, pokud chcete aby si Vás system pomatoval + + + Zde je možné se přihlásit do wiki. Bude Vám umožněno upravovat a vytvářet stránky. Pokud jste Administrator budete moci provádět administrátorské operace.<br />Nemáte ještě účet? Můžete se <a href="Register.aspx" title="Vytvořit nový účet">zaregistrovat</a>. + + + Email + + + nyní jste přihlášen. K odhlášení, prosím klikněte na tlačítko Odhlásit. + + + Heslo + + + Resetovat heslo + + + Přihlásit + + + Uživatelské jméno + + + Uživatelské jméno + + + Untitled Page + + + Napište zde své heslo + + + Napište zde své uživatelské jméno + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.da-DK.resx b/WebApplication/App_LocalResources/Login.aspx.da-DK.resx new file mode 100644 index 0000000..e1748c2 --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.da-DK.resx @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Save New Password + + + + + + Passwords are not equal + + + + + + + + + + + + Password + + + Password (repeat) + + + Please enter your new password twice. + + + Reset Password + + + If you forgot your Password, please insert your Username <b>or</b> Email address, then click on the Reset Password button.<br />You will receive an email with a link to reset your password. + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Password + + + + + + + + + Invalid Password + + + + + + + + + + + + + + + + + + + + + <b>--- or ---</b> + + + Login + + + Klik for at logge ind + + + Logud + + + Klik for at logge ud + + + Nulstill password + + + + + + Husk mig! + + + Marker her hvis du vil have systemet til at huske dig, på denne computer + + + Her kan du logge ind på wikien. Du vil være i stand til at redigere og oprette sider. Hvis du er administrator, kan du også være i stand til at udføre administration opgaver. <br /> Har du ikke har en konto? Du kan <a href="Register.aspx" title="Oprette en ny konto her">create one</a>. + + + E-Mail + + + De er i øjeblikket logget ind for at logge af, skal du klikke på Log af knappen. + + + Password + + + Nulstil konto + + + + + + Login + + + Bruger navn + + + Bruger navn + + + Unavngiven side + + + + + + + + + + + + Skriv dit password her + + + + + + + + + + + + Skriv dit brugernavn her + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.de-DE.resx b/WebApplication/App_LocalResources/Login.aspx.de-DE.resx new file mode 100644 index 0000000..851d466 --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.de-DE.resx @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Save New Password + + + + + + Passwords are not equal + + + + + + + + + + + + Password + + + Password (repeat) + + + or + + + Please enter your new password twice. + + + Reset Password + + + If you forgot your Password, please insert your Username <b>or</b> Email address, then click on the Reset Password button.<br />You will receive an email with a link to reset your password. + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Password + + + + + + + + + Invalid Password + + + + + + + + + + + + + + + + + + + + + Anmelden + Login + + + Klicken Sie hier um sich anzumelden + Click to Login + + + Abmelden + Logout + + + Klicken Sie hier um sich abzumelden + Click to Logout + + + Passwort zurücksetzen + Reset Password + + + Das Passwort für diesen Benutzernamen zurücksetzen + Reset the Password for the Username + + + Anmeldeinformationen speichern + Remember me + + + Wählen Sie diese Option um Ihre Anmeldeinformationen zu speichern + Check this if you want the system to remember you next time + + + Hier können Sie sich an das Wiki anmelden. Sie sind dann in der Lage Seiten anzupassen und zu erstellen. Wenn Sie Administrator sind, haben Sie auch die Möglichkeit, das Wiki zu administrieren.<br />Noch kein Zugang? Sie können hier einen <a href="Register.aspx" title="Neuen Zugang erstellen">Zugang erstellen</a>. + Here you can login to the wiki. You'll be able to edit and create pages. If you are an Administrator, you'll also be able to perform administration tasks.<br />Don't you have an account? You can <a href="Register.aspx" title="Create a new Account">create one</a>. + + + Email + + + Sie sind angemeldet. Um sich abzumelden klicken Sie auf die Schaltfläche <i>Abmelden</i>. + you are currently logged in. To logout, please click on the Logout button. + + + Passwort + Password + + + Passwort zurücksetzen + Reset Password + + + + + + Anmelden + Login + + + Benutzername + Username + + + Benutzername + Username + + + Unbenannte Seite + Untitled Page + + + + + + + + + + + + Geben Sie hier Ihr Passwort ein + Type here your Password + + + + + + + + + + + + Geben Sie hier Ihren Benutzernamen ein + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.es-ES.resx b/WebApplication/App_LocalResources/Login.aspx.es-ES.resx new file mode 100644 index 0000000..86371f7 --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.es-ES.resx @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Save New Password + + + + + + Passwords are not equal + + + + + + + + + + + + Password + + + Password (repeat) + + + or + + + Please enter your new password twice. + + + Reset Password + + + If you forgot your Password, please insert your Username <b>or</b> Email address, then click on the Reset Password button.<br />You will receive an email with a link to reset your password. + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Password + + + + + + + + + Invalid Password + + + + + + + + + + + + + + + + + + + + + + + + Login + + + Click to Login + + + Logout + + + Click to Logout + + + Reset Password + + + + + + Recordar sus Datos + + + Marque esto si quiere que el sistema le recuerde en su próxima visita + + + Aquí puede hacer login para entrar al wiki. Podrá editar y crear páginas. Si es un Administrador, podrá realizar tareas de administración.<br />No tiene una cuenta? Puede <a href="Register.aspx" title="Crear una nueva cuenta">crear una</a>. + + + Email + + + Usted está registrado en el sistema. Para salir, click en el botón “Logout” + + + Password + + + Reset Password + + + + + + Login + + + Nombre de usuario + + + Nombre de usuario + + + Página sin titulo + + + + + + + + + + + + Escriba aquí su Password + + + + + + + + + Escriba aquí su nombre de usuario + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.fr-FR.resx b/WebApplication/App_LocalResources/Login.aspx.fr-FR.resx new file mode 100644 index 0000000..895e829 --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.fr-FR.resx @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Save New Password + + + + + + Passwords are not equal + + + + + + + + + + + + Password + + + Password (repeat) + + + or + + + Please enter your new password twice. + + + Reset Password + + + If you forgot your Password, please insert your Username <b>or</b> Email address, then click on the Reset Password button.<br />You will receive an email with a link to reset your password. + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Password + + + + + + + + + Invalid Password + + + + + + + + + + + + + + + + + + + + + Connexion + + + Cliquer pour vous connecter + + + Déconnexion + + + Cliquer pour vous déconnecter + + + Remise à zéro du Mot de Passe + + + + + + Se souvenir de moi + + + Cliquez ici pour que le système vous reconnaisse lors de vos prochaines visites + + + Vous pouvez vous connecter au Wiki ici. Vous pouvez modifier ou créer des pages. Si vous êtes un Administrateur, vous pouvez effectuer les tâches prévues.<br />Vous n’avez pas de compte ? Vous pouvez <a href="Register.aspx" title="Créer un nouveau compte">en créer un</a>. + + + E-mail + + + vous êtes maintenant connecté. Pour vous déconnecter, cliquez le bouton Déconnexion. + + + Mot de Passe + + + Remise à zéro du Mot de Passe + + + + + + Connexion + + + Nom Utilisateur + + + Nom Utilisateur + + + Page sans titre + + + + + + + + + + + + Tapez ici votre mot de passe + + + + + + + + + + + + Tapez ici votre Nom Utilisateur + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.hu-HU.resx b/WebApplication/App_LocalResources/Login.aspx.hu-HU.resx new file mode 100644 index 0000000..f6e654c --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.hu-HU.resx @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Új jelszó mentése + + + + + + A jelszavak nem egyeznek + + + + + + + + + + + + Jelszó + + + Jelszó ismét + + + vagy + + + Írd be az új jelszavad kétszer! + + + Jelszó visszaállítása + + + Ha elfelejtetted a jelszavad, írd be a felhasználói neved <b>vagy</b> Email címed, majd kattints a Jelszó visszaállítása gombra.<br />Kapsz egy emailt egy linkkel a jelszavad visszaállítására. + + + + + + A jelszó kötelező + + + + + + + + + A jelszó kötelező + + + + + + + + + Érvénytelen jelszó + + + + + + + + + Érvénytelen jelszó + + + + + + + + + + + + + + + + + + + + + Bejelentkezés + + + Kattints ide a bejelentkezéshez + + + Kilépés + + + Kattints ide a kilépéshez + + + Jelszó visszaállítása + + + + + + Emlékezz rám + + + Jelöld be, hogy a rendszer emlékezzen rád legközelebb! + + + Itt jelentkezhetsz be a Wikibe. Szerkeszthetsz és létrehozhatsz oladlakat. Ha adminisztrátor vagy, admin feladatokat is végrehajthatsz.<br />Nincs még profilod? <a href="Register.aspx" title="Create a new Account">Létrehozhatsz egy új profilt.</a> + + + Email + + + be vagy jelentkezve. Kilépéshez kattints a kilépés gombra. + + + Jelszó + + + Jelszó visszaállítása + + + + + + Bejelentkezés + + + Felhasználónév + + + Felhasználónév + + + Cím nélküli oldal + + + + + + + + + + + + Írd ide a jelszavad + + + + + + + + + + + + Írd ide a felhasználóneved + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.it-IT.resx b/WebApplication/App_LocalResources/Login.aspx.it-IT.resx new file mode 100644 index 0000000..fcc7cbb --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.it-IT.resx @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Salva nuova password + + + + + + Le password non sono uguali + + + + + + + + + + + + Password + + + Password (ripeti) + + + oppure + + + Inserisci due volte la nuova password. + + + Reimposta password + + + Se hai dimenticato la password, inserisci il tuo username <b>o</b> il tuo indirizzo email, quindi fai click sul pulsante Reimposta Password.<br />Riceverai una email con un link per procedere al reset. + + + + + + Password richiesta + + + + + + + + + Password richiesta + + + + + + + + + Password non valida + + + + + + + + + Password non valida + + + + + + + + + + + + + + + + + + + + + Login + + + Fai click per effettuare il Login + + + Logout + + + Fai click per effettuare il Logout + + + Reimposta Password + + + + + + Ricordati di me + + + Spunta questo se desideri che il sistema si ricordi di te la prossima volta + + + In questa pagina puoi effettuare il login al Wiki. Sarai in grado di modificare le Pagine. Se sei un Amministratore, sarai anche in grado di eseguire operazioni di amministrazione.<br />Non hai un Account? Puoi <a href="Register.aspx" title="Create a new Account">crearne uno</a>. + + + Email + + + hai effettuato il Login. Per effettuare il Logout, clicca sul pulsante Logout. + + + Password + + + Reimposta Password + + + + + + Login + + + Username + + + Username + + + Untitled Page + + + + + + + + + + + + Digita qui la tua Password + + + + + + + + + + + + Digita qui il tuo Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.nb-NO.resx b/WebApplication/App_LocalResources/Login.aspx.nb-NO.resx new file mode 100644 index 0000000..591e869 --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.nb-NO.resx @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Save New Password + + + + + + Passwords are not equal + + + + + + + + + + + + Password + + + Password (repeat) + + + or + + + Please enter your new password twice. + + + Reset Password + + + If you forgot your Password, please insert your Username <b>or</b> Email address, then click on the Reset Password button.<br />You will receive an email with a link to reset your password. + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Password + + + + + + + + + Invalid Password + + + + + + + + + + + + + + + + + + + + + Logg inn + Login + + + Klikk for å logge inn + Click to Login + + + Logg ut + Logout + + + Klikk for å logge ut + Click to Logout + + + Tilbakestill passord + Reset Password + + + Tilbakestill passordet til dette brukernavnet + Reset the Password for the Username + + + Husk meg + Remember me + + + Kryss av her hvis du ønsker at systemet skal huske deg til neste gang + Check this if you want the system to remember you next time + + + Her kan du logg inn til wiki'en. Du vil bli i stand til å redigere og opprette sider. Hvis du er en administrator, kan du også utføre administrasjonsoppgaver.<br />Har du ingen konto? Du kan <a href="Register.aspx" title="Opprett ny konto">opprette en</a>. + Here you can login to the wiki. You'll be able to edit and create pages. If you are an Administrator, you'll also be able to perform administration tasks.<br />Don't you have an account? You can <a href="Register.aspx" title="Create a new Account">create one</a>. + + + Epost + + + For tiden er du innlogget. For å logge ut, vennligst klikk på Logg ut knappen + you are currently logged in. To logout, please click on the Logout button. + + + Passord + Password + + + Tilbakestill passord + Reset Password + + + + + + Logg inn + Login + + + Brukernavn + Username + + + Brukernavn + Username + + + Side uten tittel + Untitled Page + + + + + + + + + + + + Skriv inn passordet + Type here your Password + + + + + + + + + + + + Skriv inn brukernavnet + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.nl-NL.resx b/WebApplication/App_LocalResources/Login.aspx.nl-NL.resx new file mode 100644 index 0000000..a9fe872 --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.nl-NL.resx @@ -0,0 +1,286 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Save New Password + + + + + + Passwords are not equal + + + + + + + + + + + + Password + + + Password (repeat) + + + or + + + Please enter your new password twice. + + + Reset Password + + + If you forgot your Password, please insert your Username <b>or</b> Email address, then click on the Reset Password button.<br />You will receive an email with a link to reset your password. + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Password + + + + + + + + + Invalid Password + + + + + + + + + + + + + + + + + + + + + Inloggen + + + Klik hier om u aan te melden + + + Afmelden + + + Klik hier om uit te loggen + + + Wachtwoord herzetten + + + + + + Aanmeldgegevens onthouden + + + Vink dit veld als u wilt dat uw aanmeldgegevens op deze computer onthouden worden. + + + Hier kunt u zich aanmelden. Na het aanmelden heeft u de mogelijkheid om pagina's aan te passen en aan te maken. Als u een beheerder bent, kunt u beheerdersfuncties uitvoeren. <br />Nog geen gebruiker? <a href="Register.aspx" title="gebruikersprofiel aanmaken">Maak een nieuw gebruikersprofiel aan!</a>. + + + E-mail + + + u bent nu aangemeld. Klik de Afmelden knop om uzelf af te melden. + you are currently logged in. To logout, please click on the Logout button. + + + Wachtwoord + + + Wachtwoord herstellen. + + + + + + Aanmelden + + + Gebruikersnaam + + + Gebruikersnaam + + + Untitled Page + + + + + + + + + + + + Typ hier uw wachtwoord + + + + + + + + + + + + Typ hier uw gebruikernaam + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.pl-PL.resx b/WebApplication/App_LocalResources/Login.aspx.pl-PL.resx new file mode 100644 index 0000000..18b94a0 --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.pl-PL.resx @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Save New Password + + + + + + Passwords are not equal + + + + + + + + + + + + Password + + + Password (repeat) + + + Please enter your new password twice. + + + Reset Password + + + If you forgot your Password, please insert your Username <b>or</b> Email address, then click on the Reset Password button.<br />You will receive an email with a link to reset your password. + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Password + + + + + + + + + Invalid Password + + + + + + + + + + + + + + + + + + + + + Zaloguj się + + + Kliknij tutaj aby się zalogować + + + Wyloguj się + + + Kliknij tutaj aby się wylogować + + + Zresetuj hasło + + + + + + Zapamiętaj mnie na tym komputerze + + + Zaznacz to pole jeśli chcesz, aby Twoje poświadczenia były zapamiętane do celów przyszłych odwiedzin + + + Tutaj można zalogować się do serwisu Wiki, dzięki czemu będziesz mógł edytować i tworzyć strony. Jeśli jesteś administratorem będzie również w stanie wykonywać zadania związane z administrowaniem serwisem. <br />Jeśli nie masz konta możesz je teraz <a href="Register.aspx" title="Utwórz nowe konto">utworzyć</a>. + + + E-mail + + + aktualnie jesteś zalogowany. Aby zakończyś sesję kliknij przycisk wyloguj. + + + <b>--- lub ---</b> + + + Hasło + + + Resetuj hasło + + + + + + Zaloguj + + + Nazwa użytkownika + + + Nazwa użytkownika + + + Strona niezatytułowana + + + + + + + + + + + + Wpisz tutaj swoje hasło + + + + + + + + + + + + Wpisz tutaj swoją nazwę użytkownika + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.pt-BR.resx b/WebApplication/App_LocalResources/Login.aspx.pt-BR.resx new file mode 100644 index 0000000..3365cda --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.pt-BR.resx @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Save New Password + + + + + + Passwords are not equal + + + + + + + + + + + + Password + + + Password (repeat) + + + or + + + Please enter your new password twice. + + + Reset Password + + + If you forgot your Password, please insert your Username <b>or</b> Email address, then click on the Reset Password button.<br />You will receive an email with a link to reset your password. + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Password + + + + + + + + + Invalid Password + + + + + + + + + + + + + + + + + + + + + Login + + + Clique para fazer login + + + Logout + + + Clique para fazer logout + + + Restaurar a senha + + + + + + Lembrar + + + Selecione se você quiser que o sistema se lembre de você da próxima vez + + + Aqui você pode fazer o login no Wiki. Você poderá editar e criar páginas. Se você for um administrador, você também poderá executar atividades administrativas.<br />Você não tem uma conta? Você pode <a href="Register.aspx" title="Criar uma nova conta">criar uma</a>. + + + e-mail + + + Você está logado. Para fazer o logout, clique na tecla logout. + + + Senha + + + Restaurar a senha + + + + + + Login + + + Nome de usuário + + + Nome de usuário + + + Página sem título + + + + + + + + + + + + Digite aqui a sua senha + + + + + + + + + + + + Digite aqui o seu nome de usuário + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.resx b/WebApplication/App_LocalResources/Login.aspx.resx new file mode 100644 index 0000000..95346b7 --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.resx @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Login + + + Click to Login + + + Logout + + + Click to Logout + + + Reset Password + + + + + + Save New Password + + + + + + Remember me + + + Check this if you want the system to remember you next time + + + Passwords are not equal + + + + + + + + + Here you can login to the wiki. Don't you have an account? You can <a href="Register.aspx" title="Create a new Account">create one</a>. + + + Email + + + you are currently logged in. To logout, please click on the Logout button. + + + + + + Password + + + Password (repeat) + + + or + + + Password + + + Please enter your new password twice. + + + Reset Password + + + If you forgot your Password, please insert your Username <b>or</b> Email address, then click on the Reset Password button.<br />You will receive an email with a link to reset your password. + + + Reset Password + + + + + + + + + Login + + + Username + + + Username + + + Untitled Page + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Password + + + + + + + + + Invalid Password + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Type here your Password + + + + + + + + + + + + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.ro-RO.resx b/WebApplication/App_LocalResources/Login.aspx.ro-RO.resx new file mode 100644 index 0000000..11c5981 --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.ro-RO.resx @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Save New Password + + + + + + Passwords are not equal + + + + + + + + + + + + Password + + + Password (repeat) + + + or + + + Please enter your new password twice. + + + Reset Password + + + If you forgot your Password, please insert your Username <b>or</b> Email address, then click on the Reset Password button.<br />You will receive an email with a link to reset your password. + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Password + + + + + + + + + Invalid Password + + + + + + + + + + + + + + + + + + + + + Login + + + Click pentru Login + + + Logout + + + Click pentru Logout + + + Reset parola + + + + + + Retine-ma + + + Bifati aici pentru ca sistemul sa isi aminteasca de dvs. data viitoare + + + Aici puteti sa va logati pe Wiki. Veti putea edita si crea pagini. Daca sinteti administrator, veti putea de asemenea sa realizati sarcini de administrare.<br />Nu aveti cont? Puteti <a href="Register.aspx" title="Creare cont nou">crea unul</a>. + + + E-mail + + + sinteti logat. Pentru logout faceti click pe butonul Logout. + + + Parola + + + Reset parola + + + + + + Login + + + Nume utilizator + + + Nume utilizator + + + Pagina fara titlu + + + + + + + + + + + + Introduceti aici parola + + + + + + + + + + + + Introduceti aici numele de utilizator + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.ru-RU.resx b/WebApplication/App_LocalResources/Login.aspx.ru-RU.resx new file mode 100644 index 0000000..a884ba5 --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.ru-RU.resx @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Сохранить новый пароль + + + + + + Пароли не совпадают + + + + + + + + + + + + Пароль + + + Повторите пароль + + + или + + + Введите новый пароль дважды + + + Сброс пароля + + + Вы забыли свой пароль? Введите свой Логин <b>или</b> Email и нажмите кнопку Сброс пароля.<br />Вам будет оправлено сообщение со ссылкой изменения пароля. + + + + + + Пароль необходим + + + + + + + + + Пароль необходим + + + + + + + + + Неправильный пароль + + + + + + + + + Неправильный пароль + + + + + + + + + + + + + + + + + + + + + Вход + + + Нажмите для входа + + + Выход + + + Нажмите для Выхода + + + Восстановить Доступ + + + + + + Запомнить меня + + + Отметьте это для того, чтобы система вспомнила Вас в следующий раз + + + Авторизуйтесь (войдите в свою Учётную Запись). Это позволит Вам создавать новые и редактировать существующие страницы. Если Вы Администратор, то сможете дополнительно использовать и административные функции.<br />Ещё нет учётной записи? Вам <a href="Register.aspx" title="Создать Новую Учётную Запись">сюда</a>. + + + Email + + + Вы уже авторизованы. Для отмены авторизации, нажмите кнопку Выход. + + + Пароль + + + Восстановить Доступ + + + + + + Авторизация + + + Имя Пользователя + + + Имя Пользователя + + + Untitled Page + + + + + + + + + + + + Введите свой Пароль + + + + + + + + + + + + Введите своё Имя Пользователя + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.sk-SK.resx b/WebApplication/App_LocalResources/Login.aspx.sk-SK.resx new file mode 100644 index 0000000..e4de591 --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.sk-SK.resx @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Save New Password + + + + + + Passwords are not equal + + + + + + + + + + + + Password + + + Password (repeat) + + + or + + + Please enter your new password twice. + + + Reset Password + + + If you forgot your Password, please insert your Username <b>or</b> Email address, then click on the Reset Password button.<br />You will receive an email with a link to reset your password. + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Password + + + + + + + + + Invalid Password + + + + + + + + + + + + + + + + + + + + + Prihlásiť sa + Login + + + Klikni na prihlásenie + Click to Login + + + Odhlásiť sa + Logout + + + Klikni na odhlásenie + Click to Logout + + + Reset hesla + Reset Password + + + Resetovať heslo pre používateľa + Reset the Password for the Username + + + Pamätaj si ma + Remember me + + + Zakliknite túto položku, ak chcete aby si Vás systém pamätal nabudúce + Check this if you want the system to remember you next time + + + Tu sa môžete prihlásiť do wiki. Po prihlásení budete môcť meniť a vytvárať stránky. Ak ste administrátor, budete mať prístupné administrátorské funkcie. <br />Ak ešte nemáte konto, môžete si ho vytvoriť <a href="Register.aspx" title="Vytvoriť nové konto">tu</a>. + Here you can login to the wiki. You'll be able to edit and create pages. If you are an Administrator, you'll also be able to perform administration tasks.<br />Don't you have an account? You can <a href="Register.aspx" title="Create a new Account">create one</a>. + + + Email + + + Ste prihlásený. Na odhlásenie kliknite na tlačítko Odhlásiť. + you are currently logged in. To logout, please click on the Logout button. + + + Heslo + Password + + + Reset hesla + Reset Password + + + + + + Prihlásenie + Login + + + Prihlasovacie meno + Username + + + Prihlasovacie meno + Username + + + Nepomenovaná stránka + Untitled Page + + + + + + + + + + + + Tu napíšte Vaše heslo + Type here your Password + + + + + + + + + + + + Tu napíšte Vaše prihlasovacie meno + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/Login.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..8864b7a --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.sr-Latn-CS.resx @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Save New Password + + + + + + Passwords are not equal + + + + + + + + + + + + Password + + + Password (repeat) + + + or + + + Please enter your new password twice. + + + Reset Password + + + If you forgot your Password, please insert your Username <b>or</b> Email address, then click on the Reset Password button.<br />You will receive an email with a link to reset your password. + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Password + + + + + + + + + Invalid Password + + + + + + + + + + + + + + + + + + + + + Prijava + Login + + + Kliknite ovde da bi ste se prijavili + Click to Login + + + Odjavi me + Logout + + + Kliknite ovde da bi ste se odjavili + Click to Logout + + + Resetuj šifru + Reset Password + + + Resetuj šiftu za korisnika + Reset the Password for the Username + + + Upamti me + Remember me + + + Izaberite ovu opciju ako želite da vas sistem automatski prepozna sledeći put + Check this if you want the system to remember you next time + + + Ovde se mozete prijaviti. Tada imate mogućnost kreiranja i promene stranica. Ako ste administrator, imate mogućnost da administrirate Wiki.<br />Još niste član? Ovde se možete <a href="Register.aspx" title="Kreiraj novi konto">učlaniti</a>. + Here you can login to the wiki. You'll be able to edit and create pages. If you are an Administrator, you'll also be able to perform administration tasks.<br />Don't you have an account? You can <a href="Register.aspx" title="Create a new Account">create one</a>. + + + Email + + + Vi ste prijavljeni. Ako želite da se odjavite kliknite na dugme <i>Odjavi me</i>. + you are currently logged in. To logout, please click on the Logout button. + + + Šifra + Password + + + Resetuj šifru + Reset Password + + + + + + Prijava + Login + + + Korisničko ime + Username + + + Korisničko ime + Username + + + Neimenovana stranica + Untitled Page + + + + + + + + + + + + Unesite ovde vašu šifru + Type here your Password + + + + + + + + + + + + Unesite ovde vaše korisničko ime + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.tr-TR.resx b/WebApplication/App_LocalResources/Login.aspx.tr-TR.resx new file mode 100644 index 0000000..f0d1085 --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.tr-TR.resx @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Yeni parolayı kaydet + + + + + + Parolalar aynı değil + + + + + + + + + + + + Parola + + + Parola (tekrar) + + + ya da + + + Parolanızı iki kez yazınız + + + Parola sıfırla + + + Parolanızı hatırlayamıyorsanız, lütfen kullanıcı adınızı veya email adresinizi yazıp Parola Sıfırla düğmesine basınız. Parola sıfırlama linkini içeren bir email alacaksınız. + + + + + + Parola gereklidir + + + + + + + + + Parola gereklidir + + + + + + + + + Geçersiz parola + + + + + + + + + Geçersiz parola + + + + + + + + + + + + + + + + + + + + + Bağlan + + + Bağlanmak için tıklayın + + + Çıkış + + + Çıkmak için tıklayın + + + Şifreyi yeniden belirle + + + + + + Bilgileri hatırla + + + Siteme bir dahaki girişinizde otomatik olarak bağlanmak için işaretleyin. + + + Wiki'ye buradan bağlanabilirsiniz. Bağlanarak sayfalar oluşturabilir ve düzenleyebilirsiniz. Yönetici iseniz yönetsel görevler de gerçekleştirebilirsiniz.<br />Henüz hesabınız yok ise <a href="Register.aspx" title="Yeni hesap oluştur">bir tane açabilirsiniz</a>. + + + E-Posta + + + Şu an bağlısınız. Çıkmak için "Çıkış" tuşuna basınız. + + + Şifre + + + Şifreyi Yeniden Belirle + + + + + + Bağlan + + + Kullanıcı adı + + + Kullanıcı Adı + + + Başlıksız Sayfa + + + + + + + + + + + + Şifrenizi yazın + + + + + + + + + + + + Kullanıcı adınızı yazın + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.uk-UA.resx b/WebApplication/App_LocalResources/Login.aspx.uk-UA.resx new file mode 100644 index 0000000..61e8dd0 --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.uk-UA.resx @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Зберегти новий пароль + + + + + + Паролі не співпадають + + + + + + + + + + + + Пароль + + + Ще раз пароль + + + чи + + + Введіть новий пароль двічі + + + Зміна паролю + + + Забули пароль? Введіть свій Логін <b>чи</b> Email та натисніть Зміна паролю.<br />На Вашу адресу буде відправлено повідомлення із відсилкою для зміни паролю. + + + + + + Потрібен Пароль + + + + + + + + + Потрібен Пароль + + + + + + + + + Помилковий пароль + + + + + + + + + Помилковий пароль + + + + + + + + + + + + + + + + + + + + + Вхід + + + Натисніть для входу + + + Вихід + + + Натисніть для виходу + + + Відновити Доступ + + + + + + Запам'ятати мене + + + Ця відмітку потрібна для того, щоб система згадала Вас наступного разу + + + Авторизуйтеся (увійдіть у свій профіль). Це дозволить Вам створювати нові та редагувати існуючі документи. Якщо Ви Адміністратор, то зможете додатково використовувати й адміністративні функції.<br />Ще немає Запису Користувача? Вам <a href="Register.aspx" title="Створіть Новий Запис Користувача">сюди</a>. + + + Email + + + Ви вже авторизовані. Для того що відмінити авторизацію, натисніть Вихід. + + + Пароль + + + Відновити доступ + + + + + + Авторизація + + + Им'я Користувача + + + Им'я Користувача + + + Untitled Page + + + + + + + + + + + + Введіть свій Пароль + + + + + + + + + + + + Введіть своє Ім'я Користувача + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.zh-cn.resx b/WebApplication/App_LocalResources/Login.aspx.zh-cn.resx new file mode 100644 index 0000000..6eba76a --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.zh-cn.resx @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 保存新密码 + + + + + + 密码不匹配 + + + + + + + + + + + + 密码 + + + 密码(重复) + + + + + + 请输入您的新密码两次。 + + + 重置密码 + + + 如果您忘记了您的密码,请填写您的用户名<b>或</b>电子邮件地址,然后点击重置密码按钮。<br />您会收到一封包含重置密码链接的电子邮件。 + + + + + + 需要密码 + + + + + + + + + 需要密码 + + + + + + + + + 密码无效 + + + + + + + + + 密码无效 + + + + + + + + + + + + + + + + + + + + + 登录 + + + 点击以登录 + + + 注销 + + + 点击以注销 + + + 重置密码 + + + + + + 记住我 + + + 如果您想让系统下次记住您的话选中这里 + + + 在这里您可以登录到维基。您没有帐户?您可以<a href="Register.aspx" title="创建新帐户">创建一个</a>。 + + + 电子邮件地址 + + + 您当前已经登录。要注销,请点击“注销”按钮。 + + + 密码 + + + 重置密码 + + + + + + 登录 + + + 用户名 + + + 用户名 + + + 无标题页面 + + + + + + + + + + + + 在这里键入您的密码 + + + + + + + + + + + + 在这里键入您的用户名 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Login.aspx.zh-tw.resx b/WebApplication/App_LocalResources/Login.aspx.zh-tw.resx new file mode 100644 index 0000000..00d7124 --- /dev/null +++ b/WebApplication/App_LocalResources/Login.aspx.zh-tw.resx @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Save New Password + + + + + + Passwords are not equal + + + + + + + + + + + + Password + + + Password (repeat) + + + or + + + Please enter your new password twice. + + + Reset Password + + + If you forgot your Password, please insert your Username <b>or</b> Email address, then click on the Reset Password button.<br />You will receive an email with a link to reset your password. + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Password + + + + + + + + + Invalid Password + + + + + + + + + + + + + + + + + + + + + 登錄 + + + 點擊登錄 + + + 登出 + + + 點擊註銷 + + + 重置密碼 + + + + + + 記住我 + + + 如果你想讓系統下次記住你的話點擊這裏 + + + 這裏你可以登錄到Wiki。你將能夠編輯和創建頁面。如果你是管理員的話還能夠進行管理性操作。<br />還沒有帳戶嗎?你可以<a href="Register.aspx" title="創建新帳戶">創建一個</a>。 + + + Email + + + 當前你已經登錄,可以點擊“登出”按鈕登出。 + + + 密碼 + + + 重置密碼 + + + + + + 登錄 + + + 用戶名 + + + 用戶名 + + + 無標題頁面 + + + + + + + + + + + + 輸入你的密碼 + + + + + + + + + + + + 輸入你的用戶名 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.cs-CZ.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.cs-CZ.resx new file mode 100644 index 0000000..566a554 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.cs-CZ.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Hlavní stránka + + + Přejít na hlavní stránku aktuálního jmenného prostoru + + + « Zpět na předchozí stránku + + + Přejde na předchozí stránku + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.da-DK.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.da-DK.resx new file mode 100644 index 0000000..846ac34 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.da-DK.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Main Page + + + Go to the main page of the current namespace + + + « Back to previous page + + + Go to the previous page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.de-DE.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.de-DE.resx new file mode 100644 index 0000000..846ac34 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.de-DE.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Main Page + + + Go to the main page of the current namespace + + + « Back to previous page + + + Go to the previous page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.es-ES.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.es-ES.resx new file mode 100644 index 0000000..846ac34 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.es-ES.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Main Page + + + Go to the main page of the current namespace + + + « Back to previous page + + + Go to the previous page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.fr-FR.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.fr-FR.resx new file mode 100644 index 0000000..846ac34 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.fr-FR.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Main Page + + + Go to the main page of the current namespace + + + « Back to previous page + + + Go to the previous page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.hu-HU.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.hu-HU.resx new file mode 100644 index 0000000..846ac34 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.hu-HU.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Main Page + + + Go to the main page of the current namespace + + + « Back to previous page + + + Go to the previous page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.it-IT.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.it-IT.resx new file mode 100644 index 0000000..a95f1d0 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.it-IT.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Pagina principale + + + Vai alla pagina principale del namespace + + + « Pagina precedente + + + Vai alla pagina precedente + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.nb-NO.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.nb-NO.resx new file mode 100644 index 0000000..846ac34 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.nb-NO.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Main Page + + + Go to the main page of the current namespace + + + « Back to previous page + + + Go to the previous page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.nl-NL.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.nl-NL.resx new file mode 100644 index 0000000..846ac34 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.nl-NL.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Main Page + + + Go to the main page of the current namespace + + + « Back to previous page + + + Go to the previous page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.pl-PL.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.pl-PL.resx new file mode 100644 index 0000000..846ac34 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.pl-PL.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Main Page + + + Go to the main page of the current namespace + + + « Back to previous page + + + Go to the previous page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.pt-BR.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.pt-BR.resx new file mode 100644 index 0000000..846ac34 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.pt-BR.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Main Page + + + Go to the main page of the current namespace + + + « Back to previous page + + + Go to the previous page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.resx new file mode 100644 index 0000000..846ac34 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Main Page + + + Go to the main page of the current namespace + + + « Back to previous page + + + Go to the previous page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.ro-RO.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.ro-RO.resx new file mode 100644 index 0000000..846ac34 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.ro-RO.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Main Page + + + Go to the main page of the current namespace + + + « Back to previous page + + + Go to the previous page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.ru-RU.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.ru-RU.resx new file mode 100644 index 0000000..656b339 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.ru-RU.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Главная + + + Переход на Главную этого Пространства Имён + + + « Назад к предыдущей странице + + + Вернуться к предыдущей странице + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.sk-SK.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.sk-SK.resx new file mode 100644 index 0000000..846ac34 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.sk-SK.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Main Page + + + Go to the main page of the current namespace + + + « Back to previous page + + + Go to the previous page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.sr-Latn-CS.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.sr-Latn-CS.resx new file mode 100644 index 0000000..846ac34 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.sr-Latn-CS.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Main Page + + + Go to the main page of the current namespace + + + « Back to previous page + + + Go to the previous page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.tr-TR.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.tr-TR.resx new file mode 100644 index 0000000..e203458 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.tr-TR.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Ana Sayfa + + + Şu anki ad alanının ana sayfasına git + + + « Önceki sayfaya dön + + + Önceki sayfaya dön + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.uk-UA.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.uk-UA.resx new file mode 100644 index 0000000..b7a0a8d --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.uk-UA.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Головна + + + Перейти на Головну цього Простору Імен + + + « Повернутися на попереднью сторінку + + + На попередню сторінку + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.zh-cn.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.zh-cn.resx new file mode 100644 index 0000000..3919241 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.zh-cn.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + 首页 + + + 转到当前命名空间的首页 + + + « 返回上页 + + + 返回上页 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/MasterPageSA.Master.zh-tw.resx b/WebApplication/App_LocalResources/MasterPageSA.Master.zh-tw.resx new file mode 100644 index 0000000..846ac34 --- /dev/null +++ b/WebApplication/App_LocalResources/MasterPageSA.Master.zh-tw.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + Main Page + + + Go to the main page of the current namespace + + + « Back to previous page + + + Go to the previous page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/NavPath.aspx.cs-CZ.resx new file mode 100644 index 0000000..a843fd7 --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.cs-CZ.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Navigační cesty Vás provedou skrze obsah Wiki. + + + Zde můžete začít procházet cestu. + + + Navigační cesty + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.da-DK.resx b/WebApplication/App_LocalResources/NavPath.aspx.da-DK.resx new file mode 100644 index 0000000..78e15cf --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.da-DK.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Navigationsstier guide dig gennem indholdet af Wiki. + + + Her kan du starte med at kikke på en sti + + + Navigations stier + + + Side uden titel + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.de-DE.resx b/WebApplication/App_LocalResources/NavPath.aspx.de-DE.resx new file mode 100644 index 0000000..3746e58 --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.de-DE.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Navigationspfade weisen Ihnen den Weg zu den Inhalten dieses Wiki. + Navigation Paths guide you through the contents of the Wiki. + + + Hier können sie mit einem Pfad beginnen. + Here you can start to browse a Path. + + + Navigationspfade + Navigation Paths + + + Untitled Page + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.es-ES.resx b/WebApplication/App_LocalResources/NavPath.aspx.es-ES.resx new file mode 100644 index 0000000..7704c40 --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.es-ES.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Navigation Paths lo guia a través de los contenidos del Wiki. + + + Aquí puede empezar a navegar una path. + + + Navigation Paths + + + Página Sin Titulo + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.fr-FR.resx b/WebApplication/App_LocalResources/NavPath.aspx.fr-FR.resx new file mode 100644 index 0000000..b46dd77 --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.fr-FR.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Les chemins de navigation vous guide à travers le contenu du Wiki. + + + Vous pouvez parcourir un chemin ici + + + Chemins de Navigation + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.hu-HU.resx b/WebApplication/App_LocalResources/NavPath.aspx.hu-HU.resx new file mode 100644 index 0000000..d6944fa --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.hu-HU.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + A navigációs útvonalak vezetnek végig a Wikin. + + + Kattints ide egy útvonal böngészéséhez! + + + Navigációs útvonalak + + + Cím nélküli oldal + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.it-IT.resx b/WebApplication/App_LocalResources/NavPath.aspx.it-IT.resx new file mode 100644 index 0000000..544783c --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.it-IT.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + I Navigation Path ti guidano attraverso i contenuti del Wiki. + + + Da qui puoi iniziare a navigare un Path. + + + Navigation Path + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.nb-NO.resx b/WebApplication/App_LocalResources/NavPath.aspx.nb-NO.resx new file mode 100644 index 0000000..eb84eab --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.nb-NO.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Navigasjonsstier leder deg gjennom innholdet i wiki'en. + Navigation Paths guide you through the contents of the Wiki. + + + Her kan du starte å bla gjennom en sti. + Here you can start to browse a Path. + + + Navigasjonssti + Navigation Paths + + + Side uten tittel + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.nl-NL.resx b/WebApplication/App_LocalResources/NavPath.aspx.nl-NL.resx new file mode 100644 index 0000000..226970f --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.nl-NL.resx @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Navigatiepaden leiden u door de inhoud van de Wiki + Navigation Paths guide you through the contents of the Wiki. + + + Hier kunt u beginnen met door een pad te bladeren + + + Navigatiepaden + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.pl-PL.resx b/WebApplication/App_LocalResources/NavPath.aspx.pl-PL.resx new file mode 100644 index 0000000..7223009 --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.pl-PL.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ścieżki nawigacji mają za zadanie ułatwić Ci poruszanie się po zawartości serwisu Wiki. + + + Tutaj możesz rozpocząć przeglądanie ścieżek nawigacji. + + + Ścieżki nawigacji + + + Strona niezatytułowana + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.pt-BR.resx b/WebApplication/App_LocalResources/NavPath.aspx.pt-BR.resx new file mode 100644 index 0000000..12157b3 --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.pt-BR.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Caminhos de navegação guiam-no através dos conteúdos do Wiki. + + + Aqui você pode começar a percorrer um caminho. + + + Caminhos de navegação + + + Página sem título + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.resx b/WebApplication/App_LocalResources/NavPath.aspx.resx new file mode 100644 index 0000000..0c0270b --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Navigation Paths guide you through the contents of the Wiki. + + + Here you can start to browse a Path. + + + Navigation Paths + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.ro-RO.resx b/WebApplication/App_LocalResources/NavPath.aspx.ro-RO.resx new file mode 100644 index 0000000..4461281 --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.ro-RO.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Caile de navigare va ghideaza prin continutul acestui Wiki. + + + Aici puteti incepe sa rasfoiti o cale. + + + Cai de navigare + + + Pagina fara titlu + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.ru-RU.resx b/WebApplication/App_LocalResources/NavPath.aspx.ru-RU.resx new file mode 100644 index 0000000..5922b4e --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.ru-RU.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Навигационные Пути ведут через всё содержание Wiki. + + + Здесь Вы можете начать просмотр Путей. + + + Навигационные пути + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.sk-SK.resx b/WebApplication/App_LocalResources/NavPath.aspx.sk-SK.resx new file mode 100644 index 0000000..f223600 --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.sk-SK.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Navigačné smery Vás sprevádzajú obsahom wiki + Navigation Paths guide you through the contents of the Wiki. + + + Tu môžete začať s prezeraním navigačného smeru + Here you can start to browse a Path. + + + Navigačné smery + Navigation Paths + + + Nepomenovaná stránka + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/NavPath.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..2ce479a --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.sr-Latn-CS.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Navigation Paths će vam pokazati put ka sadržajima Wiki-ja. + Navigation Paths guide you through the contents of the Wiki. + + + Ovde možete početi da navigirate. + Here you can start to browse a Path. + + + Navigation Paths + Navigation Paths + + + Neimenovana stranica + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.tr-TR.resx b/WebApplication/App_LocalResources/NavPath.aspx.tr-TR.resx new file mode 100644 index 0000000..1ce0b26 --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.tr-TR.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Dolaşım patikaları, Wiki içeriği üzerinde size kılavuzluk eder. + + + Buradan bir patikaya göz atmaya başlayabilirsiniz. + + + Dolaşım Patikaları + + + Başlıksız Sayfa + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.uk-UA.resx b/WebApplication/App_LocalResources/NavPath.aspx.uk-UA.resx new file mode 100644 index 0000000..74ee36d --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.uk-UA.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Навігаційні Меню ведуть через увесь зміст Wiki. + + + Тут Ви маєте змогу почати перегляд Навігаційних Меню. + + + Навігаційні Меню + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.zh-cn.resx b/WebApplication/App_LocalResources/NavPath.aspx.zh-cn.resx new file mode 100644 index 0000000..b0f1efa --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.zh-cn.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 导航路径引导您查看维基的内容。 + + + 在这里您可以开始浏览一个路径。 + + + 导航路径 + + + 无标题页面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/NavPath.aspx.zh-tw.resx b/WebApplication/App_LocalResources/NavPath.aspx.zh-tw.resx new file mode 100644 index 0000000..c500ff6 --- /dev/null +++ b/WebApplication/App_LocalResources/NavPath.aspx.zh-tw.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 流覽路徑引導你查看Wiki的內容。 + + + 這裏你可以開始瀏覽一個路徑 + + + 所有流覽路徑 + + + 無標題頁面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/Operation.aspx.cs-CZ.resx new file mode 100644 index 0000000..dcab6ec --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.cs-CZ.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Zrušit + + + + + + Smazat + + + + + + Smazat odpovědi na zprávu + + + + + + + + + Pro smazání vybraných zpráv klikněte na tlačítko Smazat. + + + Smazat zprávu + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.da-DK.resx b/WebApplication/App_LocalResources/Operation.aspx.da-DK.resx new file mode 100644 index 0000000..7b8ec37 --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.da-DK.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Afbryd + + + + + + Slet + + + + + + Slet meddelses svar + + + + + + + + + For at slette meddelsen, tryk på slet. + + + Slet meddelse + + + Unavngiven side + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.de-DE.resx b/WebApplication/App_LocalResources/Operation.aspx.de-DE.resx new file mode 100644 index 0000000..b378f09 --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.de-DE.resx @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Abbrechen + Cancel + + + + + + Löschen + Delete + + + + + + Löscht die Antworten auf die Nachricht + Delete Message's replies + + + + + + + + + Um die ausgewählte Nachricht zu löschen, betätigen Sie den <i>Löschen</i> Knopf. + To delete the selected message, click on the Delete button. + + + Lösche Nachricht + Delete Message + + + Untitled Page + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.es-ES.resx b/WebApplication/App_LocalResources/Operation.aspx.es-ES.resx new file mode 100644 index 0000000..d527961 --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.es-ES.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancelar + + + + + + Eliminar + + + + + + Eliminar respuestas al mensaje + + + + + + + + + Para eliminar el mensaje seleccionado click en el botón eliminar. + + + Eliminar Mensaje + + + Página Sin Titulo + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.fr-FR.resx b/WebApplication/App_LocalResources/Operation.aspx.fr-FR.resx new file mode 100644 index 0000000..435c775 --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.fr-FR.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Annuler + + + + + + Supprimer + + + + + + Supprimer les Réponses au Message + + + + + + + + + Pour supprimer le message sélectionné, cliquez sur le bouton Supprimer. + + + Supprimer le Message + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.hu-HU.resx b/WebApplication/App_LocalResources/Operation.aspx.hu-HU.resx new file mode 100644 index 0000000..c204854 --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.hu-HU.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Mégse + + + + + + Törlés + + + + + + Üzenet válaszainak törlése + + + + + + + + + Kattints a törlésre a kijelölt üzenet törléséhez + + + Üzenet törlése + + + Cím nélküli oldal + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.it-IT.resx b/WebApplication/App_LocalResources/Operation.aspx.it-IT.resx new file mode 100644 index 0000000..7c442ae --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.it-IT.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Annulla + + + + + + Elimina + + + + + + Elimina le risposte al Messaggio + + + + + + + + + Per eliminare il Messaggio selezionato, fai click sul pulsante Elimina. + + + Elimina Messaggio + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.nb-NO.resx b/WebApplication/App_LocalResources/Operation.aspx.nb-NO.resx new file mode 100644 index 0000000..231c8c1 --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.nb-NO.resx @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Avbryt + Cancel + + + + + + Slett + Delete + + + + + + Slett meldingens svar + Delete Message's replies + + + + + + + + + Klikk på Slett knappen for å slette den valgte meldingen. + To delete the selected message, click on the Delete button. + + + Slett melding + Delete Message + + + Side uten tittel + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.nl-NL.resx b/WebApplication/App_LocalResources/Operation.aspx.nl-NL.resx new file mode 100644 index 0000000..fafd964 --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.nl-NL.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Annuleren + + + + + + Verwijder + + + + + + Verwijder de antwoorden op het bericht + + + + + + + + + Klik op de Verwijder knop om het geselecteerde bericht te verwijderen. + + + Verwijder bericht + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.pl-PL.resx b/WebApplication/App_LocalResources/Operation.aspx.pl-PL.resx new file mode 100644 index 0000000..70a789a --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.pl-PL.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Anuluj + + + + + + Usuń + + + + + + Usuń odpowiedzi wiadomości + + + + + + + + + Kliknij przycisk Usuń aby skasować zaznaczone wiadomości. + + + Usuń wiadomość + + + Strona niezatytułowana + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.pt-BR.resx b/WebApplication/App_LocalResources/Operation.aspx.pt-BR.resx new file mode 100644 index 0000000..3f1ac7c --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.pt-BR.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancelar + + + + + + Apagar + + + + + + Apagar respostas da mensagem + + + + + + + + + Para apagar a mensagem selecionada, clicar sobre o botão apagar. + + + Apagar mensagem + + + Página sem título + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.resx b/WebApplication/App_LocalResources/Operation.aspx.resx new file mode 100644 index 0000000..170201b --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Delete + + + + + + Delete Message's replies + + + + + + + + + To delete the selected message, click on the Delete button. + + + Delete Message + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.ro-RO.resx b/WebApplication/App_LocalResources/Operation.aspx.ro-RO.resx new file mode 100644 index 0000000..d943800 --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.ro-RO.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Renunta + + + + + + Sterge + + + + + + Sterge raspunsurile la mesaj + + + + + + + + + Pentru a sterge mesajele selectate, faceti click butonul Sterge. + + + Sterge mesaj + + + Pagina fara titlu + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.ru-RU.resx b/WebApplication/App_LocalResources/Operation.aspx.ru-RU.resx new file mode 100644 index 0000000..0de8290 --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.ru-RU.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Отмена + + + + + + Удалить + + + + + + Удалить ответы на Сообщения + + + + + + + + + Для удаления отмеченных Сообщений, нажмите кнопку Удалить. + + + Удалить Сообщение + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.sk-SK.resx b/WebApplication/App_LocalResources/Operation.aspx.sk-SK.resx new file mode 100644 index 0000000..a767967 --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.sk-SK.resx @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Zrušiť + Cancel + + + + + + Zmazať + Delete + + + + + + Zmazať odpovede na správu + Delete Message's replies + + + + + + + + + Na zmazanie označenej správy kliknite na tlačitko Zmazať + To delete the selected message, click on the Delete button. + + + Zmazať správu + Delete Message + + + Nepomenovaná stránka + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/Operation.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..80eef5b --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.sr-Latn-CS.resx @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Otkaži + Cancel + + + + + + Briši + Delete + + + + + + Briše ogovore na poruku + Delete Message's replies + + + + + + + + + Da bi ste obrisali izabrane poruge kliknite na dugme <i>Briši</i>. + To delete the selected message, click on the Delete button. + + + Briši poruke + Delete Message + + + Neimenovana stranica + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.tr-TR.resx b/WebApplication/App_LocalResources/Operation.aspx.tr-TR.resx new file mode 100644 index 0000000..13484b5 --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.tr-TR.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + İptal + + + + + + Sil + + + + + + Mesaja cevapları sil + + + + + + + + + Seçili olan mesajı silmek için Sil tuşuna basınız + + + Mesajı Sil + + + Başlıksız Sayfa + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.uk-UA.resx b/WebApplication/App_LocalResources/Operation.aspx.uk-UA.resx new file mode 100644 index 0000000..dd95948 --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.uk-UA.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Відміна + + + + + + Видалити + + + + + + Знищити відповіді на Повідомлення + + + + + + + + + Для видалення обраних Повідомлень, натисніть Видалити. + + + Видалити Повідомлення + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.zh-cn.resx b/WebApplication/App_LocalResources/Operation.aspx.zh-cn.resx new file mode 100644 index 0000000..6620250 --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.zh-cn.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 取消 + + + + + + 删除 + + + + + + 删除消息的回复 + + + + + + + + + 要删除选中的消息,点击“删除”按钮。 + + + 删除消息 + + + 无标题页面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Operation.aspx.zh-tw.resx b/WebApplication/App_LocalResources/Operation.aspx.zh-tw.resx new file mode 100644 index 0000000..294ce1f --- /dev/null +++ b/WebApplication/App_LocalResources/Operation.aspx.zh-tw.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 取消 + + + + + + 刪除 + + + + + + 刪除消息接受者 + + + + + + + + + 點擊“刪除”按鈕刪除選中的消息 + + + 刪除消息 + + + 未命名頁面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.cs-CZ.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.cs-CZ.resx new file mode 100644 index 0000000..601215e --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.cs-CZ.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + Přidat + + + Přidat vybranou stránku do seznamu + + + Odstranit + + + Odstraní vybranou stránku ze seznamu + + + Hledat + + + Hledat stránku + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.da-DK.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.da-DK.resx new file mode 100644 index 0000000..d0dfeb2 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.da-DK.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Remove + + + Remove the selected page from the list + + + Search + + + Search for a Page + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.de-DE.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.de-DE.resx new file mode 100644 index 0000000..d0dfeb2 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.de-DE.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Remove + + + Remove the selected page from the list + + + Search + + + Search for a Page + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.es-ES.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.es-ES.resx new file mode 100644 index 0000000..d0dfeb2 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.es-ES.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Remove + + + Remove the selected page from the list + + + Search + + + Search for a Page + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.fr-FR.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.fr-FR.resx new file mode 100644 index 0000000..d0dfeb2 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.fr-FR.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Remove + + + Remove the selected page from the list + + + Search + + + Search for a Page + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.hu-HU.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.hu-HU.resx new file mode 100644 index 0000000..d0dfeb2 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.hu-HU.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Remove + + + Remove the selected page from the list + + + Search + + + Search for a Page + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.it-IT.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.it-IT.resx new file mode 100644 index 0000000..b95c441 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.it-IT.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Aggiungi + + + Aggiungi la pagina selezionata alla lista + + + Rimuovi + + + Rimuovi la pagina seleziona + + + Cerca + + + Cerca una pagina + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.nb-NO.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.nb-NO.resx new file mode 100644 index 0000000..d0dfeb2 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.nb-NO.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Remove + + + Remove the selected page from the list + + + Search + + + Search for a Page + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.nl-NL.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.nl-NL.resx new file mode 100644 index 0000000..d0dfeb2 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.nl-NL.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Remove + + + Remove the selected page from the list + + + Search + + + Search for a Page + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.pl-PL.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.pl-PL.resx new file mode 100644 index 0000000..d0dfeb2 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.pl-PL.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Remove + + + Remove the selected page from the list + + + Search + + + Search for a Page + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.pt-BR.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.pt-BR.resx new file mode 100644 index 0000000..d0dfeb2 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.pt-BR.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Remove + + + Remove the selected page from the list + + + Search + + + Search for a Page + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.resx new file mode 100644 index 0000000..d0dfeb2 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Remove + + + Remove the selected page from the list + + + Search + + + Search for a Page + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.ro-RO.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.ro-RO.resx new file mode 100644 index 0000000..d0dfeb2 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.ro-RO.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Remove + + + Remove the selected page from the list + + + Search + + + Search for a Page + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.ru-RU.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.ru-RU.resx new file mode 100644 index 0000000..1ee8938 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.ru-RU.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Добавить + + + Добавить отмеченную Страницу в список + + + Удалить + + + Удалить отмеченную Страницу из списка + + + Поиск + + + Поиск на Странице + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.sk-SK.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.sk-SK.resx new file mode 100644 index 0000000..d0dfeb2 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.sk-SK.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Remove + + + Remove the selected page from the list + + + Search + + + Search for a Page + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.sr-Latn-CS.resx new file mode 100644 index 0000000..d0dfeb2 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.sr-Latn-CS.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Remove + + + Remove the selected page from the list + + + Search + + + Search for a Page + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.tr-TR.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.tr-TR.resx new file mode 100644 index 0000000..d0dfeb2 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.tr-TR.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Remove + + + Remove the selected page from the list + + + Search + + + Search for a Page + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.uk-UA.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.uk-UA.resx new file mode 100644 index 0000000..92cdc54 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.uk-UA.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Додати + + + Додати означену сторінку до списку + + + Видалити + + + Видалити означену сторінку із списку + + + Пошук + + + Пошук на Сторінці + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.zh-cn.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.zh-cn.resx new file mode 100644 index 0000000..c8fefbd --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.zh-cn.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 添加 + + + 将选中的页面添加到列表 + + + 移除 + + + 从列表中移除选中的页面 + + + 搜索 + + + 搜索页面 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageListBuilder.ascx.zh-tw.resx b/WebApplication/App_LocalResources/PageListBuilder.ascx.zh-tw.resx new file mode 100644 index 0000000..d0dfeb2 --- /dev/null +++ b/WebApplication/App_LocalResources/PageListBuilder.ascx.zh-tw.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected Page to the list + + + Remove + + + Remove the selected page from the list + + + Search + + + Search for a Page + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.cs-CZ.resx new file mode 100644 index 0000000..0466dc3 --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.cs-CZ.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Stránka "##PAGENAME##" nebyla nalezena. + + + Stránka nenalezena + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.da-DK.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.da-DK.resx new file mode 100644 index 0000000..e0a733a --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.da-DK.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Siden "##PAGENAME##" blev ikke fundet + + + Siden ikke fundet + + + Side uden titel + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.de-DE.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.de-DE.resx new file mode 100644 index 0000000..e422650 --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.de-DE.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Der angeforderte Eintrag wurde nicht gefunden. + The Page you requested was not found. + + + Eintrag nicht gefunden + Page Not Found + + + Untitled Page + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.es-ES.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.es-ES.resx new file mode 100644 index 0000000..b238ce8 --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.es-ES.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + La página que usted pidió no ha sido encontrada. + + + Página no encontrada + + + Página Sin Titulo + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.fr-FR.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.fr-FR.resx new file mode 100644 index 0000000..46f3780 --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.fr-FR.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + La page demandée est introuvable. + + + Page Introuvable + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.hu-HU.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.hu-HU.resx new file mode 100644 index 0000000..25de290 --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.hu-HU.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + "##PAGENAME##" lap nem található. + + + A lap nem található + + + Cím nélküli oldal + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.it-IT.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.it-IT.resx new file mode 100644 index 0000000..61aaf17 --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.it-IT.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + La Pagina che hai richiesto non è stata trovata. + + + Pagina non Trovata + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.nb-NO.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.nb-NO.resx new file mode 100644 index 0000000..cfc4c35 --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.nb-NO.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Fant ikke siden du spurte om. + The Page you requested was not found. + + + Fant ikke siden + Page Not Found + + + Side uten tittel + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.nl-NL.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.nl-NL.resx new file mode 100644 index 0000000..ed27bd0 --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.nl-NL.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + De pagina "##PAGENAME##" is niet gevonden. + + + Pagina niet gevonden + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.pl-PL.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.pl-PL.resx new file mode 100644 index 0000000..9769b3a --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.pl-PL.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Strona "##PAGENAME##" nie została odnaleziona. + + + Strona nie została odnaleziona. + + + Strona niezatytułowana + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.pt-BR.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.pt-BR.resx new file mode 100644 index 0000000..f4df765 --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.pt-BR.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + A página “##PAGENAME##” não foi encontrada. + + + Página não encontrada + + + Página sem título + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.resx new file mode 100644 index 0000000..f949ea0 --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The Page "##PAGENAME##" was not found. + + + Page Not Found + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.ro-RO.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.ro-RO.resx new file mode 100644 index 0000000..b7cfc1d --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.ro-RO.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Pagina "##PAGENAME##" nu exista. + + + Pagina nu exista + + + Pagina fara titlu + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.ru-RU.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.ru-RU.resx new file mode 100644 index 0000000..43316ec --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.ru-RU.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Страница с именем "##PAGENAME##" не найдена. + + + Страница не найдена + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.sk-SK.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.sk-SK.resx new file mode 100644 index 0000000..f787207 --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.sk-SK.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Stránka, ktorú hľadáte, nebola nájdená. + The Page you requested was not found. + + + Stránka nebola nájdená + Page Not Found + + + Nepomenovaná stránka + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..5dc5cd7 --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.sr-Latn-CS.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Stranica koju ste tražili nije pronađena. + The Page you requested was not found. + + + Stranica nije nađena + Page Not Found + + + Neimenovana stranica + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.tr-TR.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.tr-TR.resx new file mode 100644 index 0000000..69bef65 --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.tr-TR.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + "##PAGENAME##" adlı sayfa bulunamadı + + + Sayfa Bulunamadı + + + Başlıksız Sayfa + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.uk-UA.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.uk-UA.resx new file mode 100644 index 0000000..4a79044 --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.uk-UA.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Сторінка з ім'ям "##PAGENAME##" не знайдена. + + + Сторінка не знайдена + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.zh-cn.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.zh-cn.resx new file mode 100644 index 0000000..6e843b0 --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.zh-cn.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 找不到页面“##PAGENAME##”。 + + + 找不到页面 + + + 无标题页面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PageNotFound.aspx.zh-tw.resx b/WebApplication/App_LocalResources/PageNotFound.aspx.zh-tw.resx new file mode 100644 index 0000000..a01915b --- /dev/null +++ b/WebApplication/App_LocalResources/PageNotFound.aspx.zh-tw.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 頁面“##PAGENAME##”找不到。 + + + 找不到頁面 + + + 無標題頁面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.cs-CZ.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.cs-CZ.resx new file mode 100644 index 0000000..2a36552 --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.cs-CZ.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Přidat + + + Přidat vybraný objekt + + + Odstranit + + + Odstranit záznamy pro vybraný objekt + + + Uložit oprávnění + + + Uložit opravnění pro tento objekt + + + Hledat + + + Hledat uživatele nebo skupinu pro přidání + + + Opravnění pro: + + + Uživatelské konta a uživatelské skupiny + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.da-DK.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.da-DK.resx new file mode 100644 index 0000000..af497ce --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.da-DK.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected subject + + + Remove + + + Remove entried for the selected subject + + + Save Permissions + + + Save this Subject's permissions + + + Search + + + Search for a User or Group to add + + + + + + + + + Permissions for: + + + + + + + + + + + + User Accounts and User Groups + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.de-DE.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.de-DE.resx new file mode 100644 index 0000000..af497ce --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.de-DE.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected subject + + + Remove + + + Remove entried for the selected subject + + + Save Permissions + + + Save this Subject's permissions + + + Search + + + Search for a User or Group to add + + + + + + + + + Permissions for: + + + + + + + + + + + + User Accounts and User Groups + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.es-ES.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.es-ES.resx new file mode 100644 index 0000000..af497ce --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.es-ES.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected subject + + + Remove + + + Remove entried for the selected subject + + + Save Permissions + + + Save this Subject's permissions + + + Search + + + Search for a User or Group to add + + + + + + + + + Permissions for: + + + + + + + + + + + + User Accounts and User Groups + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.fr-FR.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.fr-FR.resx new file mode 100644 index 0000000..af497ce --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.fr-FR.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected subject + + + Remove + + + Remove entried for the selected subject + + + Save Permissions + + + Save this Subject's permissions + + + Search + + + Search for a User or Group to add + + + + + + + + + Permissions for: + + + + + + + + + + + + User Accounts and User Groups + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.hu-HU.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.hu-HU.resx new file mode 100644 index 0000000..af497ce --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.hu-HU.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected subject + + + Remove + + + Remove entried for the selected subject + + + Save Permissions + + + Save this Subject's permissions + + + Search + + + Search for a User or Group to add + + + + + + + + + Permissions for: + + + + + + + + + + + + User Accounts and User Groups + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.it-IT.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.it-IT.resx new file mode 100644 index 0000000..ec48323 --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.it-IT.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Aggiungi + + + Aggiungi il soggetto selezionato + + + Rimuovi + + + Rimuovi elementi per il soggetto selezionato + + + Salva permessi + + + Salva i permessi del soggetto + + + Cerca + + + Cerca utente o gruppo + + + + + + + + + Permessi per: + + + + + + + + + + + + Account e gruppi + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.nb-NO.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.nb-NO.resx new file mode 100644 index 0000000..af497ce --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.nb-NO.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected subject + + + Remove + + + Remove entried for the selected subject + + + Save Permissions + + + Save this Subject's permissions + + + Search + + + Search for a User or Group to add + + + + + + + + + Permissions for: + + + + + + + + + + + + User Accounts and User Groups + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.nl-NL.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.nl-NL.resx new file mode 100644 index 0000000..af497ce --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.nl-NL.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected subject + + + Remove + + + Remove entried for the selected subject + + + Save Permissions + + + Save this Subject's permissions + + + Search + + + Search for a User or Group to add + + + + + + + + + Permissions for: + + + + + + + + + + + + User Accounts and User Groups + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.pl-PL.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.pl-PL.resx new file mode 100644 index 0000000..af497ce --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.pl-PL.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected subject + + + Remove + + + Remove entried for the selected subject + + + Save Permissions + + + Save this Subject's permissions + + + Search + + + Search for a User or Group to add + + + + + + + + + Permissions for: + + + + + + + + + + + + User Accounts and User Groups + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.pt-BR.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.pt-BR.resx new file mode 100644 index 0000000..af497ce --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.pt-BR.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected subject + + + Remove + + + Remove entried for the selected subject + + + Save Permissions + + + Save this Subject's permissions + + + Search + + + Search for a User or Group to add + + + + + + + + + Permissions for: + + + + + + + + + + + + User Accounts and User Groups + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.resx new file mode 100644 index 0000000..c14da5c --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected subject + + + Remove + + + Remove entries for the selected subject + + + Save Permissions + + + Save this Subject's permissions + + + Search + + + Search for a User or Group to add + + + + + + + + + Permissions for: + + + + + + + + + + + + User Accounts and User Groups + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.ro-RO.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.ro-RO.resx new file mode 100644 index 0000000..af497ce --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.ro-RO.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected subject + + + Remove + + + Remove entried for the selected subject + + + Save Permissions + + + Save this Subject's permissions + + + Search + + + Search for a User or Group to add + + + + + + + + + Permissions for: + + + + + + + + + + + + User Accounts and User Groups + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.ru-RU.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.ru-RU.resx new file mode 100644 index 0000000..f8f3056 --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.ru-RU.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Добавить + + + Добавить выбранное + + + Убрать + + + Убрать запись в выбранном + + + Сохранить Разрешения + + + Сохранить этот Шаблон Разрешений + + + Поиск + + + Найти Пользователя или Группу для добавления + + + + + + + + + Разрешения для: + + + + + + + + + + + + Пользовательские Записи и Группы + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.sk-SK.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.sk-SK.resx new file mode 100644 index 0000000..af497ce --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.sk-SK.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected subject + + + Remove + + + Remove entried for the selected subject + + + Save Permissions + + + Save this Subject's permissions + + + Search + + + Search for a User or Group to add + + + + + + + + + Permissions for: + + + + + + + + + + + + User Accounts and User Groups + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.sr-Latn-CS.resx new file mode 100644 index 0000000..af497ce --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.sr-Latn-CS.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected subject + + + Remove + + + Remove entried for the selected subject + + + Save Permissions + + + Save this Subject's permissions + + + Search + + + Search for a User or Group to add + + + + + + + + + Permissions for: + + + + + + + + + + + + User Accounts and User Groups + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.tr-TR.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.tr-TR.resx new file mode 100644 index 0000000..5d7486d --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.tr-TR.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + EKle + + + Seçili konuyu ekle + + + Kaldır + + + Seçili konu için girişi kaldır + + + İzinleri Kaydet + + + Konu'ya ait izinleri kaydet + + + Ara + + + Eklemek için bir kullanıcı veya gurup arayın + + + + + + + + + İzinlerin sahibi: + + + + + + + + + + + + Kullanıcılar ve Kullanıcı Gurupları + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.uk-UA.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.uk-UA.resx new file mode 100644 index 0000000..95ac8b3 --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.uk-UA.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Додати + + + Додати обране + + + Виключити + + + Виключити запис з обраного + + + Зберегти Дозволи + + + Зберегти цей Шаблон Дозволів + + + Пошук + + + Знайти Користувача чи Групу для додавання + + + + + + + + + Дозволи для: + + + + + + + + + + + + Записи Користувачів й їх Групи + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.zh-cn.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.zh-cn.resx new file mode 100644 index 0000000..613d9ef --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.zh-cn.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 添加 + + + 添加选中的主题 + + + 移除 + + + 移除选中的主题 + + + 保存权限 + + + 保存此主题的权限 + + + 搜索 + + + 搜索要添加的用户或组 + + + + + + + + + 权限: + + + + + + + + + + + + 用户帐户和用户组 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PermissionsManager.ascx.zh-tw.resx b/WebApplication/App_LocalResources/PermissionsManager.ascx.zh-tw.resx new file mode 100644 index 0000000..af497ce --- /dev/null +++ b/WebApplication/App_LocalResources/PermissionsManager.ascx.zh-tw.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add + + + Add the selected subject + + + Remove + + + Remove entried for the selected subject + + + Save Permissions + + + Save this Subject's permissions + + + Search + + + Search for a User or Group to add + + + + + + + + + Permissions for: + + + + + + + + + + + + User Accounts and User Groups + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/Popup.aspx.cs-CZ.resx new file mode 100644 index 0000000..160c1fd --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.cs-CZ.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Storno + + + Storno + + + Storno + + + Storno + + + Storno + + + OK + + + OK + + + OK + + + OK + + + OK + + + Otevřít odkaz v novém okně + + + Procházet přílohy stránky + + + Procházet přílohy stránky + + + Otevřít odkaz v novém okně + + + Otevřít odkaz v novém okně + + + Otevřít odkaz v novém okně + + + Anchor ID (volitelně) + + + Titulek odkazu (volitelně) + + + Odkaz na: + + + Cesta souboru (vyžadováno) + + + Titulek odkazu (volitelně) + + + Popis (volitelněl) + + + Odkaz (volitelněl) + + + Cesta/URL pro obrázek (vyžadováno) + + + Vložit: + + + Titulek odkazu (volitelně) + + + URL odkazu nebo email (vyžadováno) + + + Název stránky (vyžadováno) + + + Titulek odkazu (volitelně) + + + ScrewTurn Wiki + + + Odkaz na existující Anchor + + + Auto + + + Inline + + + Zarovnat vlevo + + + Check this to select the linked image using the browser + + + Check this to select the image using the browser + + + Zarovnat vpravo + + + Nový anchor + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.da-DK.resx b/WebApplication/App_LocalResources/Popup.aspx.da-DK.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.da-DK.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.de-DE.resx b/WebApplication/App_LocalResources/Popup.aspx.de-DE.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.de-DE.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.es-ES.resx b/WebApplication/App_LocalResources/Popup.aspx.es-ES.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.es-ES.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.fr-FR.resx b/WebApplication/App_LocalResources/Popup.aspx.fr-FR.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.fr-FR.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.hu-HU.resx b/WebApplication/App_LocalResources/Popup.aspx.hu-HU.resx new file mode 100644 index 0000000..d6534a1 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.hu-HU.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Mégse + + + + + + Mégse + + + + + + Mégse + + + + + + Mégse + + + + + + Mégse + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Link megnyitása új ablakban + + + + + + Csatolmányok böngészése + + + + + + Csatolmányok böngészése + + + + + + Link megnyitása új ablakban + + + + + + Link megnyitása új ablakban + + + + + + Link megnyitása új ablakban + + + + + + Könyvjelsző ID (kötelező) + + + Link címe (opcionális) + + + Link ide: + + + Fájl útvonala (kötelező) + + + Link címe (opcionális) + + + Leírás (opcionális) + + + Link (opcionális) + + + Kép útvonal/URL (kötelező) + + + Beilleszt: + + + Link címe (opcionális) + + + Link URL vagy Email cím (kötelező) + + + Lap neve (kötelező) + + + Link címe (opcionális) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link egy létező könyvjelzőhöz + + + + + + Auto + + + + + + Sorban + + + + + + Balrazárt + + + + + + + + + A böngészővel válaszd ki a linkelt képet + + + + + + A böngészővel válaszd ki a képet + + + Jobbrazárt + + + + + + Új könyvjelző + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.it-IT.resx b/WebApplication/App_LocalResources/Popup.aspx.it-IT.resx new file mode 100644 index 0000000..e7f9c0a --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.it-IT.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Annulla + + + + + + Annulla + + + + + + Annulla + + + + + + Annulla + + + + + + Annulla + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Apri link in una nuova finestra + + + + + + Sfoglia allegati + + + + + + Sfoglia allegati + + + + + + Apri link in una nuova finestra + + + + + + Apri link in una nuova finestra + + + + + + Apri link in una nuova finestra + + + + + + ID ancora (richiesto) + + + Titolo (facoltatico) + + + Link a: + + + Percorso (richiesto) + + + Titolo (facoltativo) + + + Descrizione (facoltativa) + + + Link (facoltativo) + + + Percorso/URL immagine (richiesto) + + + Inserisci: + + + Titolo (facoltativo) + + + URL o indirizzo email (richiesto) + + + Nome pagina (richiesto) + + + Titolo (facoltativo) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Linkad un'ancora esistente + + + + + + Auto + + + + + + Inline + + + + + + Allinea a sinistra + + + + + + + + + Output del browser + + + + + + Output del browser + + + Allinea a destra + + + + + + Nuova ancora + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.nb-NO.resx b/WebApplication/App_LocalResources/Popup.aspx.nb-NO.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.nb-NO.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.nl-NL.resx b/WebApplication/App_LocalResources/Popup.aspx.nl-NL.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.nl-NL.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.pl-PL.resx b/WebApplication/App_LocalResources/Popup.aspx.pl-PL.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.pl-PL.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.pt-BR.resx b/WebApplication/App_LocalResources/Popup.aspx.pt-BR.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.pt-BR.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.resx b/WebApplication/App_LocalResources/Popup.aspx.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.ro-RO.resx b/WebApplication/App_LocalResources/Popup.aspx.ro-RO.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.ro-RO.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.ru-RU.resx b/WebApplication/App_LocalResources/Popup.aspx.ru-RU.resx new file mode 100644 index 0000000..ee9e300 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.ru-RU.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Отмена + + + + + + Отмена + + + + + + Отмена + + + + + + Отмена + + + + + + Отмена + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Открыть ссылку в новом окне + + + + + + Просмотреть вложения страницы + + + + + + Просмотреть вложения страницы + + + + + + Открыть ссылку в новом окне + + + + + + Открыть ссылку в новом окне + + + + + + Открыть ссылку в новом окне + + + + + + Якорь ID (обязательно) + + + Подложка ссылки (не обязательно) + + + Ссылка: + + + Путь к файлу (обязательно) + + + Подложка ссылки (не обязательно) + + + Описание (не обязательно) + + + Ссылка (не обязательно) + + + Путь к изображению/URL (обязательно) + + + Вставка: + + + Подложка ссылки (не обязательно) + + + URL ссылки или Email (обязательно) + + + Имя страницы (обязательно) + + + Подложка ссылки (не обязательно) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Ссылка на существующий Якорь + + + + + + Авто + + + + + + В линию + + + + + + По левому краю + + + + + + + + + Отметьте, если для открытия изображения потребуется браузер + + + + + + Отметьте, если для открытия изображения потребуется браузер + + + По правому краю + + + + + + Новый Якорь + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.sk-SK.resx b/WebApplication/App_LocalResources/Popup.aspx.sk-SK.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.sk-SK.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/Popup.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.sr-Latn-CS.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.tr-TR.resx b/WebApplication/App_LocalResources/Popup.aspx.tr-TR.resx new file mode 100644 index 0000000..37ca803 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.tr-TR.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + İptal + + + + + + İptal + + + + + + İptal + + + + + + İptal + + + + + + İptal + + + + + + Tamam + + + + + + Tamam + + + + + + Tamam + + + + + + Tamam + + + + + + Tamam + + + + + + Linki yeni pencere aç + + + + + + Sayfa ilişiklerine gözat + + + + + + Sayfa ilişiklerine gözat + + + + + + Linki yeni pencere aç + + + + + + Linki yeni pencere aç + + + + + + Linki yeni pencere aç + + + + + + Yer işareti kimliği (gerekli) + + + Link başlığı (tercihli) + + + Link hedefi: + + + Dosya yolu (gerekli) + + + Link başlığı (tercihli) + + + Açıklama (tercihli) + + + Link (tercihli) + + + İmaj yolu veya URL'i (gerekli) + + + Ekle: + + + Link başlığı (tercihli) + + + Link URL'i veya Email adresi (gerekli) + + + Sayfa Adı (gerekli) + + + Link başlığı (tercihli) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Var olan yer işaretine link + + + + + + Kendiliğinden + + + + + + Satır içi + + + + + + Sola hizala + + + + + + + + + İmaj'ı tarayıcı vasıtasıyla seçmek için burayık işaretleyin + + + + + + İmaj'ı tarayıcı vasıtasıyla seçmek için burayık işaretleyin + + + Sağa hizala + + + + + + Yeni yer işareti + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.uk-UA.resx b/WebApplication/App_LocalResources/Popup.aspx.uk-UA.resx new file mode 100644 index 0000000..595d3af --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.uk-UA.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Відміна + + + + + + Відміна + + + + + + Відміна + + + + + + Відміна + + + + + + Відміна + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Відкрити посилання в новому вікні + + + + + + Переглянути вкладення до сторінки + + + + + + Переглянути вкладення до сторінки + + + + + + Відкрити посилання в новому вікні + + + + + + Відкрити посилання в новому вікні + + + + + + Відкрити посилання в новому вікні + + + + + + Якір ID (обов'язково) + + + Підкладка посилання (не обов'язково) + + + Посилання: + + + Шлях до файлу (обов'язково) + + + Підкладка посилання (не обов'язково) + + + Опис (не обов'язково) + + + Посилання (не обов'язково) + + + Шлях до зображення/URL (обов'язково) + + + Вставка: + + + Підкладка посилання (не обов'язково) + + + URL посилання чи Email (обов'язково) + + + Ім'я сторінки (обов'язково) + + + Підкладка посилання (не обов'язково) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Посилання на існуючий Якір + + + + + + Авто + + + + + + У лінію + + + + + + За лівим краєм + + + + + + + + + Вкажіть, якщо для відкриття зображення потрібен браузер + + + + + + Вкажіть, якщо для відкриття зображення потрібен браузер + + + За правим краєм + + + + + + Новий Якір + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.zh-cn.resx b/WebApplication/App_LocalResources/Popup.aspx.zh-cn.resx new file mode 100644 index 0000000..08491e4 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.zh-cn.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 取消 + + + + + + 取消 + + + + + + 取消 + + + + + + 取消 + + + + + + 取消 + + + + + + 确定 + + + + + + 确定 + + + + + + 确定 + + + + + + 确定 + + + + + + 确定 + + + + + + 在新窗口中打开链接 + + + + + + 浏览页面附件 + + + + + + 浏览页面附件 + + + + + + 在新窗口中打开链接 + + + + + + 在新窗口中打开链接 + + + + + + 在新窗口中打开链接 + + + + + + 段落链接 ID(必填) + + + 链接标题(可选) + + + 链接到: + + + 文件路径(必填) + + + 链接标题(可选) + + + 描述(可选) + + + 链接(可选) + + + 图像路径/网址(必填) + + + 插入: + + + 链接标题(可选) + + + 链接网址或电子邮件地址(必填) + + + 页面名称(必填) + + + 链接标题(可选) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + 链接到已有的段落链接 + + + + + + 自动 + + + + + + 嵌入 + + + + + + 左对齐 + + + + + + + + + 选中以使用浏览器选择被链接的图像 + + + + + + 选中以使用浏览器选择图像 + + + 右对齐 + + + + + + 新段落链接 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Popup.aspx.zh-tw.resx b/WebApplication/App_LocalResources/Popup.aspx.zh-tw.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/Popup.aspx.zh-tw.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.cs-CZ.resx new file mode 100644 index 0000000..5f82c27 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.cs-CZ.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Storno + + + Storno + + + Storno + + + Storno + + + Storno + + + OK + + + OK + + + OK + + + OK + + + OK + + + Otevřít odkaz v novém okně + + + Procházet přílohy stránky + + + Procházet přílohy stránky + + + Otevřít odkaz v novém okně + + + Otevřít odkaz v novém okně + + + Otevřít odkaz v novém okně + + + Anchor ID (required) + + + Titulek odkazu (volitelně) + + + Odkaz na: + + + Cesta souboru (vyžadováno) + + + Titulek odkazu (volitelně) + + + Popis (volitelněl) + + + Odkaz (volitelněl) + + + Cesta/URL pro obrázek (vyžadováno) + + + Vložit: + + + Titulek odkazu (volitelně) + + + URL odkazu nebo email (vyžadováno) + + + Název stránky (vyžadováno) + + + Titulek odkazu (volitelně) + + + ScrewTurn Wiki + + + Odkaz na existující Anchor + + + Auto + + + Inline + + + Zarovnat vlevo + + + Check this to select the linked image using the browser + + + Check this to select the image using the browser + + + Zarovnat vpravo + + + Nový Anchor + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.da-DK.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.da-DK.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.da-DK.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.de-DE.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.de-DE.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.de-DE.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.es-ES.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.es-ES.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.es-ES.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.fr-FR.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.fr-FR.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.fr-FR.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.hu-HU.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.hu-HU.resx new file mode 100644 index 0000000..d6534a1 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.hu-HU.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Mégse + + + + + + Mégse + + + + + + Mégse + + + + + + Mégse + + + + + + Mégse + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Link megnyitása új ablakban + + + + + + Csatolmányok böngészése + + + + + + Csatolmányok böngészése + + + + + + Link megnyitása új ablakban + + + + + + Link megnyitása új ablakban + + + + + + Link megnyitása új ablakban + + + + + + Könyvjelsző ID (kötelező) + + + Link címe (opcionális) + + + Link ide: + + + Fájl útvonala (kötelező) + + + Link címe (opcionális) + + + Leírás (opcionális) + + + Link (opcionális) + + + Kép útvonal/URL (kötelező) + + + Beilleszt: + + + Link címe (opcionális) + + + Link URL vagy Email cím (kötelező) + + + Lap neve (kötelező) + + + Link címe (opcionális) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link egy létező könyvjelzőhöz + + + + + + Auto + + + + + + Sorban + + + + + + Balrazárt + + + + + + + + + A böngészővel válaszd ki a linkelt képet + + + + + + A böngészővel válaszd ki a képet + + + Jobbrazárt + + + + + + Új könyvjelző + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.it-IT.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.it-IT.resx new file mode 100644 index 0000000..e7f9c0a --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.it-IT.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Annulla + + + + + + Annulla + + + + + + Annulla + + + + + + Annulla + + + + + + Annulla + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Apri link in una nuova finestra + + + + + + Sfoglia allegati + + + + + + Sfoglia allegati + + + + + + Apri link in una nuova finestra + + + + + + Apri link in una nuova finestra + + + + + + Apri link in una nuova finestra + + + + + + ID ancora (richiesto) + + + Titolo (facoltatico) + + + Link a: + + + Percorso (richiesto) + + + Titolo (facoltativo) + + + Descrizione (facoltativa) + + + Link (facoltativo) + + + Percorso/URL immagine (richiesto) + + + Inserisci: + + + Titolo (facoltativo) + + + URL o indirizzo email (richiesto) + + + Nome pagina (richiesto) + + + Titolo (facoltativo) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Linkad un'ancora esistente + + + + + + Auto + + + + + + Inline + + + + + + Allinea a sinistra + + + + + + + + + Output del browser + + + + + + Output del browser + + + Allinea a destra + + + + + + Nuova ancora + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.nb-NO.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.nb-NO.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.nb-NO.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.nl-NL.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.nl-NL.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.nl-NL.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.pl-PL.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.pl-PL.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.pl-PL.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.pt-BR.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.pt-BR.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.pt-BR.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.ro-RO.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.ro-RO.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.ro-RO.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.ru-RU.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.ru-RU.resx new file mode 100644 index 0000000..59d3cb8 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.ru-RU.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Отмена + + + + + + Отмена + + + + + + Отмена + + + + + + Отмена + + + + + + Отмена + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Открыть ссылку в новом окне + + + + + + Показать приложения к странице + + + + + + Показать приложения к странице + + + + + + Открыть ссылку в новом окне + + + + + + Открыть ссылку в новом окне + + + + + + Открыть ссылку в новом окне + + + + + + Якорь ID (обязательно) + + + Подложка ссылки (не обязательно) + + + Ссылка: + + + Путь к файлу (обязательно) + + + Подложка ссылки (не обязательно) + + + Описание (не обязательно) + + + Ссылка (не обязательно) + + + Путь к изображению/URL (обязательно) + + + Вставить: + + + Подложка ссылки (не обязательно) + + + URL ссылки или Email (обязательно) + + + Имя страницы (обязательно) + + + Подложка ссылки (не обязательно) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Ссылка на существующий Якорь + + + + + + Авто + + + + + + В линию + + + + + + По левому краю + + + + + + + + + Отметьте, если для открытия изображения требуется браузер + + + + + + Отметьте, если для открытия изображения требуется браузер + + + По правому краю + + + + + + Новый Якорь + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.sk-SK.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.sk-SK.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.sk-SK.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.sr-Latn-CS.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.tr-TR.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.tr-TR.resx new file mode 100644 index 0000000..a0f3999 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.tr-TR.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + İptal + + + + + + İptal + + + + + + İptal + + + + + + İptal + + + + + + İptal + + + + + + Tamam + + + + + + Tamam + + + + + + Tamam + + + + + + Tamam + + + + + + Tamam + + + + + + Bağlantıyı yeni pencerede aç + + + + + + Sayfa eklentilerine bak + + + + + + Sayfa eklentilerine bak + + + + + + Bağlantıyı yeni pencerede aç + + + + + + Bağlantıyı yeni pencerede aç + + + + + + Bağlantıyı yeni pencerede aç + + + + + + Link ID (gerekli) + + + Link Başlığı (opsiyonel) + + + Hedef: + + + Dosya yolu (gerekli) + + + Link Başlığı (opsiyonel) + + + Açıklama (opsiyonel) + + + Link (opsiyonel) + + + Resim yolu/URL (gerekli) + + + Ekle: + + + Link Başlığı (opsiyonel) + + + Link URL ya da e-posta adresi (gerekli) + + + Sayfa adı (gerekli) + + + Link Başlığı (opsiyonel) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Var olan bir konuma link + + + + + + Kendiliğinden + + + + + + Satır içi + + + + + + Sola hizala + + + + + + + + + Linklenmiş resmi seçmek için bunu işaretleyiniz + + + + + + Linklenmiş resmi seçmek için bunu işaretleyiniz + + + Sağa hizala + + + + + + Yeni yer imi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.uk-UA.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.uk-UA.resx new file mode 100644 index 0000000..65b4882 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.uk-UA.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Відміна + + + + + + Відміна + + + + + + Відміна + + + + + + Відміна + + + + + + Відміна + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Відкрити посилання в новому вікні + + + + + + Переглянути вкладення до сторінки + + + + + + Переглянути вкладення до сторінки + + + + + + Відкрити посилання в новому вікні + + + + + + Відкрити посилання в новому вікні + + + + + + Відкрити посилання в новому вікні + + + + + + Якір ID (обов'язково) + + + Підкладка посилання (не обов'язково) + + + Посилання: + + + Шлях до файлу (обов'язково) + + + Підкладка посилання (не обов'язково) + + + Опис (не обов'язково) + + + Посилання (не обов'язково) + + + Шлях до зображення/URL (обов'язково) + + + Додати: + + + Підкладка посилання (не обов'язково) + + + URL посилання чи Email (обов'язково) + + + Ім'я сторінки (обов'язково) + + + Підкладка посилання (не обов'язково) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Посилання на існуючий Якір + + + + + + Авто + + + + + + У лінію + + + + + + За лівим краєм + + + + + + + + + Вкажіть, якщо для відкриття зображення потрібен браузер + + + + + + Вкажіть, якщо для відкриття зображення потрібен браузер + + + За правим краєм + + + + + + Новий Якір + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.zh-cn.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.zh-cn.resx new file mode 100644 index 0000000..08491e4 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.zh-cn.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 取消 + + + + + + 取消 + + + + + + 取消 + + + + + + 取消 + + + + + + 取消 + + + + + + 确定 + + + + + + 确定 + + + + + + 确定 + + + + + + 确定 + + + + + + 确定 + + + + + + 在新窗口中打开链接 + + + + + + 浏览页面附件 + + + + + + 浏览页面附件 + + + + + + 在新窗口中打开链接 + + + + + + 在新窗口中打开链接 + + + + + + 在新窗口中打开链接 + + + + + + 段落链接 ID(必填) + + + 链接标题(可选) + + + 链接到: + + + 文件路径(必填) + + + 链接标题(可选) + + + 描述(可选) + + + 链接(可选) + + + 图像路径/网址(必填) + + + 插入: + + + 链接标题(可选) + + + 链接网址或电子邮件地址(必填) + + + 页面名称(必填) + + + 链接标题(可选) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + 链接到已有的段落链接 + + + + + + 自动 + + + + + + 嵌入 + + + + + + 左对齐 + + + + + + + + + 选中以使用浏览器选择被链接的图像 + + + + + + 选中以使用浏览器选择图像 + + + 右对齐 + + + + + + 新段落链接 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.zh-tw.resx b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.zh-tw.resx new file mode 100644 index 0000000..2869231 --- /dev/null +++ b/WebApplication/App_LocalResources/PopupWYSIWYG.aspx.zh-tw.resx @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + Cancel + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + OK + + + + + + Open link in new window + + + + + + Browse Page Attachments + + + + + + Browse Page Attachments + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Open link in new window + + + + + + Anchor ID (required) + + + Link Title (optional) + + + Link to: + + + File Path (required) + + + Link Title (optional) + + + Description (optional) + + + Link (optional) + + + Image Path/URL (required) + + + Insert: + + + Link Title (optional) + + + Link URL or Email address (required) + + + Page Name (required) + + + Link Title (optional) + + + + + + + + + + + + + + + ScrewTurn Wiki + + + + + + + + + + + + + + + Link to an existing Anchor + + + + + + Auto + + + + + + Inline + + + + + + Align Left + + + + + + + + + Check this to select the linked image using the browser + + + + + + Check this to select the image using the browser + + + Align Right + + + + + + New Anchor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/Post.aspx.cs-CZ.resx new file mode 100644 index 0000000..5da58f2 --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.cs-CZ.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Zrušit + + + + + + Poslat + + + + + + + + + + + + Předmět + + + Poslat zprávu + + + Untitled Page + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.da-DK.resx b/WebApplication/App_LocalResources/Post.aspx.da-DK.resx new file mode 100644 index 0000000..78eb0ed --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.da-DK.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Afbryd + + + + + + Send + + + + + + + + + + + + Emne + + + Send meddelse + + + Unavngiven + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.de-DE.resx b/WebApplication/App_LocalResources/Post.aspx.de-DE.resx new file mode 100644 index 0000000..a9b4902 --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.de-DE.resx @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Abbrechen + Cancel + + + + + + Senden + Send + + + + + + + + + + + + Betreff + Subject + + + Nachricht senden + Post a Message + + + Untitled Page + Untitled Page + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.es-ES.resx b/WebApplication/App_LocalResources/Post.aspx.es-ES.resx new file mode 100644 index 0000000..6bae8b5 --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.es-ES.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancelar + + + + + + Enviar + + + + + + + + + + + + Asunto + + + Postear un mensaje + + + Página Sin Titulo + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.fr-FR.resx b/WebApplication/App_LocalResources/Post.aspx.fr-FR.resx new file mode 100644 index 0000000..b550aa4 --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.fr-FR.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Annuler + + + + + + Envoyer + + + + + + + + + + + + Sujet + + + Poster un Message + + + Untitled Page + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.hu-HU.resx b/WebApplication/App_LocalResources/Post.aspx.hu-HU.resx new file mode 100644 index 0000000..9db6ada --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.hu-HU.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Mégse + + + + + + Küldés + + + + + + + + + + + + Tárgy + + + Üzenet küldése + + + Cím nélküli oldal + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.it-IT.resx b/WebApplication/App_LocalResources/Post.aspx.it-IT.resx new file mode 100644 index 0000000..a8da16b --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.it-IT.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Annulla + + + + + + Invia + + + + + + + + + + + + Oggetto + + + Inserisci un Messaggio + + + Untitled Page + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.nb-NO.resx b/WebApplication/App_LocalResources/Post.aspx.nb-NO.resx new file mode 100644 index 0000000..127a429 --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.nb-NO.resx @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Avbryt + Cancel + + + + + + Send + Send + + + + + + + + + + + + Emne + Subject + + + Send en melding + Post a Message + + + Side uten tittel + Untitled Page + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.nl-NL.resx b/WebApplication/App_LocalResources/Post.aspx.nl-NL.resx new file mode 100644 index 0000000..406f902 --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.nl-NL.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Annuleren + + + + + + Toevoegen + + + + + + + + + + + + Onderwerp + + + Bericht toevoegen + + + Untitled Page + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.pl-PL.resx b/WebApplication/App_LocalResources/Post.aspx.pl-PL.resx new file mode 100644 index 0000000..31c71d9 --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.pl-PL.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Anuluj + + + + + + Wyślij + + + + + + + + + + + + Temat + + + Wyślij wiadomość + + + Strona niezatytułowana + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.pt-BR.resx b/WebApplication/App_LocalResources/Post.aspx.pt-BR.resx new file mode 100644 index 0000000..8a6c34b --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.pt-BR.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancelar + + + + + + Enviar + + + + + + + + + + + + Assunto + + + Postar uma mensagem + + + Página sem título + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.resx b/WebApplication/App_LocalResources/Post.aspx.resx new file mode 100644 index 0000000..0b6c03e --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + + + + Send + + + + + + + + + + + + Subject + + + Post a Message + + + Untitled Page + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.ro-RO.resx b/WebApplication/App_LocalResources/Post.aspx.ro-RO.resx new file mode 100644 index 0000000..fd0bc2e --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.ro-RO.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Renunta + + + + + + Trimite + + + + + + + + + + + + Subiect + + + Posteaza un mesaj + + + Pagina fara titlu + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.ru-RU.resx b/WebApplication/App_LocalResources/Post.aspx.ru-RU.resx new file mode 100644 index 0000000..39574a4 --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.ru-RU.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Отмена + + + + + + Отправить + + + + + + + + + + + + Тема + + + Отправить Сообщение + + + Untitled Page + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.sk-SK.resx b/WebApplication/App_LocalResources/Post.aspx.sk-SK.resx new file mode 100644 index 0000000..04597dc --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.sk-SK.resx @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Zrušiť + Cancel + + + + + + Poslať + Send + + + + + + + + + + + + Predmet + Subject + + + Vložiť správu + Post a Message + + + Nepomenovaná stránka + Untitled Page + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/Post.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..34a153e --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.sr-Latn-CS.resx @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Otkaži + Cancel + + + + + + Pošalji + Send + + + + + + + + + + + + Naslov + Subject + + + Pošalji poruku + Post a Message + + + Neimenovana stranica + Untitled Page + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.tr-TR.resx b/WebApplication/App_LocalResources/Post.aspx.tr-TR.resx new file mode 100644 index 0000000..78e0109 --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.tr-TR.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + İptal + + + + + + Gönder + + + + + + + + + + + + Başlık + + + Bir mesaj gönder + + + Başlıksız Sayfa + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.uk-UA.resx b/WebApplication/App_LocalResources/Post.aspx.uk-UA.resx new file mode 100644 index 0000000..dc7a2d9 --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.uk-UA.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Відміна + + + + + + Надіслати + + + + + + + + + + + + Тема + + + Надіслати Повідомлення + + + Untitled Page + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.zh-cn.resx b/WebApplication/App_LocalResources/Post.aspx.zh-cn.resx new file mode 100644 index 0000000..e7c5695 --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.zh-cn.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 取消 + + + + + + 发送 + + + + + + + + + + + + 主题 + + + 发送消息 + + + 无标题页面 + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Post.aspx.zh-tw.resx b/WebApplication/App_LocalResources/Post.aspx.zh-tw.resx new file mode 100644 index 0000000..3ab25cd --- /dev/null +++ b/WebApplication/App_LocalResources/Post.aspx.zh-tw.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 取消 + + + + + + 發送 + + + + + + + + + + + + 主題 + + + 發送消息 + + + 無標題頁面 + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/Profile.aspx.cs-CZ.resx new file mode 100644 index 0000000..d34b027 --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.cs-CZ.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + + + + + + + + + + + + + + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Aktuální nastavení systému neumožňuje žádné změny osobních informací. + + + Uložit zobrazované jméno + + + Klikněte sem pro uložení zobrazovaného jména + + + Zobrazované jméno + + + Editovat Zobrazované jméno, Email a Heslo + + + Neplatné zobrazované jméno + + + Napište zobrazované jméno pro Vaše konto + + + Uložit + + + Jste členem následujících skupin: + + + Jazyky a časové pásma + + + Uložit + + + Emailové adresy nejsou stejné + + + Emailové adresy nejsou stejné + + + Neplatné heslo + + + Hesla nejsou stejná + + + Hesla nejsou stejná + + + Nastavení oznámení emailem + + + Je vyžadován email + + + Je vyžadován email + + + Je vyžadováno staré heslo + + + Je vyžadováno heslo + + + Je vyžadováno heslo + + + Neplatný email + + + Neplatné heslo + + + Potvrdit + + + Smazat účet + + + Uložit email + + + Klikněte zde pro uložení email adresy + + + Uložit heslo + + + Klikněte zde pro uložení nového hesla + + + Můžete smazat Váš účet kliknutím na tlačítko Smazat účet a potvrzením tlačítkem Potvrdit.<br /><b>Varování</b>: operace je nevratná. + + + Smazat účet + + + Vítejte ve Vašem profilu, + + + Email + + + Email (znovu) + + + Zde můžete editovat nastavení Vašeho profilu, jako emailovou adresu a heslo. Nemůžete změnit Vaše uživatelské jméno. + + + Staré heslo + + + Heslo + + + Heslo (znovu) + + + Uživatelský profil + + + Untitled Page + + + Napište zde emailovou adresu + + + Opakujte svoji emailovou adresu + + + Napište zde Vaše staré heslo + + + Napište zde nové heslo + + + Napište zde znovu nové heslo + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.da-DK.resx b/WebApplication/App_LocalResources/Profile.aspx.da-DK.resx new file mode 100644 index 0000000..f4848b4 --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.da-DK.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + Current system settings do not allow any change to your personal information. + + + + + + + + + + + + + + + Save Display Name + + + Click here to save your Display Name + + + Display Name + + + Edit Display Name, Email and Password + + + + + + + + + Invalid Display Name + + + + + + + + + + + + Type here a display name for your account + + + Save + + + + + + Save + + + + + + Email addresses are not equal + + + + + + + + + Email addresses are not equal + + + + + + + + + Incorrect Password + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + + + + You are member of the following groups: + + + + + + + + + Language and Time Zone + + + Email Notification Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Old Password is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Email address + + + + + + + + + Invalid Password + + + + + + + + + Konfirmer + + + + + + Slet konto + + + + + + Gem E-Mail + + + Klik her for at gemme E-mail + + + Gem kodeord + + + Klik her for at gemme dit nye kodeord + + + Du kan slette din konto ved at trykke slet og konformere det igen på konfirmer knappen.<br /><b>Advarsel</b>: Sletningen kan ikke gøres om!. + + + Slet konto + + + Velkommen til din profil + + + E-Mail + + + E-mail (gentag) + + + Her kan du ændre din profil, password m.m. Du kan ikke ændre dit bruger navn. + + + Gemmelt kodeord + + + Kodeord + + + Kodeord (gentag) + + + + + + + + + Bruger profil + + + + + + Unavngiven side + + + + + + Skriv din E-mail adresse her + + + + + + Gentag din E-mail adresse her + + + + + + Skriv dit nuværende kodeord her + + + + + + Skriv dit nye kodeord her + + + + + + Gentag dit kodeord her + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.de-DE.resx b/WebApplication/App_LocalResources/Profile.aspx.de-DE.resx new file mode 100644 index 0000000..75a9780 --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.de-DE.resx @@ -0,0 +1,452 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + Current system settings do not allow any change to your personal information. + + + + + + + + + + + + + + + Save Display Name + + + Click here to save your Display Name + + + Display Name + + + Edit Display Name, Email and Password + + + + + + + + + Invalid Display Name + + + + + + + + + + + + Type here a display name for your account + + + Save + + + + + + + + + You are member of the following groups: + + + + + + + + + Language and Time Zone + + + Save + + + + + + Email addresses are not equal + + + + + + + + + Email addresses are not equal + + + + + + + + + Incorrect Password + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Email Notification Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Old Password is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Email address + + + + + + + + + Invalid Password + + + + + + + + + Bestätigen + Confirm + + + + + + Zugang löschen + Delete Account + + + + + + Speichere Email + Save Email + + + Klicken Sie hier um die Email Adresse zu speichern + Click here to save your Email address + + + Speichere Passwort + Save Password + + + Klicken Sie hier um Ihr neues Kennwort zu speichern + Click here to save your new Password + + + Sie können Ihren Zugang löschen, in dem Sie auf die <i>Zugang löschen</i> Schaltfläche klicken. Zur Sicherheit muß die Aktion durch klick auf die Schaltfläche <i>Bestätigen</i> abgeschlossen werden.<br /><b>Warnung:</b> Dieser Vorgang kann nicht rückgängig gemacht werden. + You can delete your account by clicking on the Delete Account button and then confirming with the Confirm button.<br /><b>Warning</b>: the operation is irreversible. + + + Zugang löschen + Delete Account + + + Willkommen in Ihrem Profil, + Welcome to your Profile, + + + Email + + + Email (Wiederholung) + Email (repeat) + + + Hier können Sie Ihre Profilinformationen, wie Email Adresse und Passwort, ändern. Der Benutzername kann nicht geändert werden. + Here you can edit your Profile settings, such as Email address and Password. You cannot change your Username. + + + Altes Passwort + Old Password + + + Passwort + Password + + + Passwort (Wiederholung) + Password (repeat) + + + + + + + + + Benutzerprofil + User Profile + + + + + + Unbenannte Seite + Untitled Page + + + + + + Geben Sie hier Ihre Email Adresse ein + Type here your Email address + + + + + + Wiederholen Sie Ihre Email Adresse + Repeat your Email address + + + + + + Geben Sie hier Ihr aktuelles Passwort ein + + + + + + Geben Sie hier Ihr neues Passwort ein + Type here your new Password + + + + + + Wiederholen Sie Ihr neues Passwort + Repeat your new Password + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.es-ES.resx b/WebApplication/App_LocalResources/Profile.aspx.es-ES.resx new file mode 100644 index 0000000..e2f50f0 --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.es-ES.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + Current system settings do not allow any change to your personal information. + + + + + + + + + + + + + + + Save Display Name + + + Click here to save your Display Name + + + Display Name + + + Edit Display Name, Email and Password + + + + + + + + + Invalid Display Name + + + + + + + + + + + + Type here a display name for your account + + + Save + + + + + + + + + You are member of the following groups: + + + + + + + + + Language and Time Zone + + + Save + + + + + + Email addresses are not equal + + + + + + + + + Email addresses are not equal + + + + + + + + + Incorrect Password + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Email Notification Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Old Password is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Email address + + + + + + + + + Invalid Password + + + + + + + + + Confirmar + + + + + + Borrar Cuenta + + + + + + Guardar Email + + + Click aquí para guardar la dirección Email + + + Guardar Password + + + Click aquí para guardar la nueva Password + + + Puede borrar su cuenta haciendo click en el botón “Borrar Cuenta” y luego confirmando con el botón Confirmar.<br /><b>Advertencia</b>: esta operación es irreversible. + + + Borrar Cuenta + + + Bienvenido a su Perfil, + + + Email + + + Email (repetir) + + + Aquí puede modificar su Perfil, cambiar la dirección Email y la password. No podrá cambiar su nombre de usuario. + + + Password anterior + + + Password + + + Password (repetir) + + + + + + + + + Perfil del usuario + + + + + + Página sin titulo + + + + + + Escriba aquí su dirección Email + + + + + + Repita su dirección Email + + + + + + Escriba aquí su Password actual + + + + + + Escriba aquí su nuevo Password + + + + + + Repita su nuevo Password + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.fr-FR.resx b/WebApplication/App_LocalResources/Profile.aspx.fr-FR.resx new file mode 100644 index 0000000..c10a667 --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.fr-FR.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + Current system settings do not allow any change to your personal information. + + + + + + + + + + + + + + + Save Display Name + + + Click here to save your Display Name + + + Display Name + + + Edit Display Name, Email and Password + + + + + + + + + Invalid Display Name + + + + + + + + + + + + Type here a display name for your account + + + Save + + + + + + + + + You are member of the following groups: + + + + + + + + + Language and Time Zone + + + Save + + + + + + Email addresses are not equal + + + + + + + + + Email addresses are not equal + + + + + + + + + Incorrect Password + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Email Notification Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Old Password is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Email address + + + + + + + + + Invalid Password + + + + + + + + + Confirmer + + + + + + Supprimer le Compte + + + + + + Enregistrer l'E-mail + + + Cliquez ici pour enregistrer votre adresse E-mail + + + Enregistrer le Mot de Passe + + + Cliquez ici pour enregistrer votre nouveau Mot de Passe + + + Vous pouvez supprimer votre Compte en cliquant sur le bouton Supprimer le Compte, puis en confirmant avec le bouton Confirmer.<br /><b>Avis</b>: cette opération est irréversible. + + + Supprimer le Compte + + + Bienvenue dans votre Profil, + + + E-mail + + + E-mail (répéter) + + + Ici vous pouvez modifier les données de votre Profil, p.ex. votre adresse E-mail et Mot de Passe. Vous ne pouvez pas changer votre Nom Utilisateur. + + + Ancien Mot de Passe + + + Mot de Passe + + + Mot de Passe (répéter) + + + + + + + + + Profil Utilisateur + + + + + + Untitled Page + + + + + + Tapez ici votre adresse E-mail + + + + + + Répétez votre adresse E-mail + + + + + + Tapez ici votre Mot de Passe actuel + + + + + + Tapez ici votre nouveau Mot de Passe + + + + + + Répétez votre nouveau Mot de Passe + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.hu-HU.resx b/WebApplication/App_LocalResources/Profile.aspx.hu-HU.resx new file mode 100644 index 0000000..779c96d --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.hu-HU.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + Save Display Name + + + Click here to save your Display Name + + + Display Name + + + Current system settings do not allow any change to your personal information. + + + + + + + + + + + + + + + + + + + + + Invalid Display Name + + + + + + + + + + + + Type here a display name for your account + + + Mentés + + + + + + + + + Az alábbi csoportokba tartozol: + + + + + + + + + Nyelv és időzóna + + + Mentés + + + + + + Az email címek nem egyeznek + + + + + + + + + Az email címek nem egyeznek + + + + + + + + + Érvénytelen jelszó + + + + + + + + + A jelszavak nem egyeznek + + + + + + + + + A jelszavak nem egyeznek + + + + + + + + + Email cím és jelszó szerkesztése + + + Email értesítők beállításai + + + + + + + + + + + + + + + + + + + + + + + + + + + Az email cím kötelező + + + + + + + + + Az email cím kötelező + + + + + + + + + A régi jelszó kötelező + + + + + + + + + A jelszó kötelező + + + + + + + + + A jelszó kötelező + + + + + + + + + Érvénytelen email cím + + + + + + + + + Érvénytelen jelszó + + + + + + + + + Megerősítés + + + + + + Profil törlése + + + + + + Email mentése + + + Kattints ide az email cím mentéséhez + + + Jelszó mentése + + + Kattints ide az új jelszó mentéséhez + + + Törölheted a profilodat a profil törlésére kattintva, majd megerősítve. A folyamat nem vonható vissza! + + + Profil törlése + + + Üdvözöllek a profilodban + + + Email + + + Email (ismét) + + + Itt szerkesztheted a profilodat, pl email, jelszó. A felhasználónév nem változtatható + + + Régi jelszó + + + Jelszó + + + Jelszó (ismét) + + + + + + + + + Felhasználói profil + + + + + + Cím nélküli oldal + + + + + + Írd ide az email címed + + + + + + Ismételd meg az email címed + + + + + + Írd ide a jelszavad + + + + + + Írd ide az új jelszavad + + + + + + Ismételd meg az új jelszavad + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.it-IT.resx b/WebApplication/App_LocalResources/Profile.aspx.it-IT.resx new file mode 100644 index 0000000..87e7278 --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.it-IT.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ricevi una notifica via email ogni volta che un <b>messaggio</b> viene inserito in una pagina in uno dei seguenti namespace (solo discussioni a cui hai accesso): + + + Ricevi una notifica via email ogni volta che una <b>pagina</b> viene modificata nei seguenti namespace (solo pagine a cui hai accesso): + + + Le impostazioni di sistema non permettono modifiche al tuo profilo. + + + + + + + + + + + + + + + Salva nome visualizzato + + + Salva il tuo nome visualizzato + + + Nome visualizzato + + + Modifica nome visualizzato, email e password + + + + + + + + + Nome visualizzato non valido + + + + + + + + + + + + Digita qui un nome da visualizzare + + + Salva + + + + + + + + + Sei membro dei seguenti gruppi: + + + + + + + + + Lingua e fuso orario + + + Salva + + + + + + Indirizzi email non uguali + + + + + + + + + Indirizzi email non uguali + + + + + + + + + Password non corretta + + + + + + + + + Password non uguali + + + + + + + + + Password non uguali + + + + + + + + + Impostazioni notifiche email + + + + + + + + + + + + + + + + + + + + + + + + + + + email richiesta + + + + + + + + + Email richiesta + + + + + + + + + Vecchia password richiesta + + + + + + + + + Password richiesta + + + + + + + + + Password richiesta + + + + + + + + + Email non valida + + + + + + + + + Password non valida + + + + + + + + + Conferma + + + + + + Elimina Account + + + + + + Salva Email + + + Clicca qui per salvare il tuo indirizzo Email + + + Salva Password + + + Clicca qui per salvare la tua Password + + + Puoi eliminare il tuo Account cliccando sul pulsante Elimina Account e confermando cliccando il pulsante Conferma.<br /><b>Attenzione</b>: l'operazione è irreversibile. + + + Elimina Account + + + Benvenuto nel tuo Profilo, + + + Email + + + Email (ripeti) + + + Qui puoi modificare le impostazioni del tuo Account, come Email e Password. Non puoi cambiare il tuo Username. + + + Vecchia Password + + + Password + + + Password (ripeti) + + + + + + + + + Profilo Utente + + + + + + Untitled Page + + + + + + Digita qui il tuo nuovo indirizzo Email + + + + + + Ripeti il tuo nuovo indirizzo Email + + + + + + Digita qui la Password attuale + + + + + + Digita qui la tua nuova Password + + + + + + Ripeti la tua nuova Password + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.nb-NO.resx b/WebApplication/App_LocalResources/Profile.aspx.nb-NO.resx new file mode 100644 index 0000000..df96f63 --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.nb-NO.resx @@ -0,0 +1,452 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + Current system settings do not allow any change to your personal information. + + + + + + + + + + + + + + + Save Display Name + + + Click here to save your Display Name + + + Display Name + + + Edit Display Name, Email and Password + + + + + + + + + Invalid Display Name + + + + + + + + + + + + Type here a display name for your account + + + Save + + + + + + + + + You are member of the following groups: + + + + + + + + + Language and Time Zone + + + Save + + + + + + Email addresses are not equal + + + + + + + + + Email addresses are not equal + + + + + + + + + Incorrect Password + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Email Notification Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Old Password is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Email address + + + + + + + + + Invalid Password + + + + + + + + + Bekreft + Confirm + + + + + + Slett konto + Delete Account + + + + + + Lagre epost + Save Email + + + Klikk her for å lagre epost adressen din + Click here to save your Email address + + + Lagre passord + Save Password + + + Klikk her for å lagre det nye passordet ditt + Click here to save your new Password + + + Du kan slette kontoen din ved å klikke på slett konto kanppen og bekrefte ved å klikke på bekreft knappen. .<br /><b>Advarsel</b>: Handlingen kan ikke gjøres om. + You can delete your account by clicking on the Delete Account button and then confirming with the Confirm button.<br /><b>Warning</b>: the operation is irreversible. + + + Slett konto + Delete Account + + + Velkommen til din profil, + Welcome to your Profile, + + + Epost + + + Epost (gjenta) + Email (repeat) + + + Her kan du redigere profilinformasjon som epost adresse og passord. Du kan ikke endre brukernavnet ditt. + Here you can edit your Profile settings, such as Email address and Password. You cannot change your Username. + + + Gammelt passord + Old Password + + + Passord + Password + + + Passwort (Wiederholung) + Password (repeat) + + + + + + + + + Brukerprofil + User Profile + + + + + + Side uten tittel + Untitled Page + + + + + + Skriv inn din epost adressse + Type here your Email address + + + + + + Gjenta din epost adresse + Repeat your Email address + + + + + + Skriv inn ditt passord + + + + + + Skriv inn ditt nye passord + Type here your new Password + + + + + + Gjenta ditt nye passord + Repeat your new Password + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.nl-NL.resx b/WebApplication/App_LocalResources/Profile.aspx.nl-NL.resx new file mode 100644 index 0000000..7d8e87c --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.nl-NL.resx @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + Current system settings do not allow any change to your personal information. + + + + + + + + + + + + + + + Save Display Name + + + Click here to save your Display Name + + + Display Name + + + Edit Display Name, Email and Password + + + + + + + + + Invalid Display Name + + + + + + + + + + + + Type here a display name for your account + + + Save + + + + + + + + + You are member of the following groups: + + + + + + + + + Language and Time Zone + + + Save + + + + + + Email addresses are not equal + + + + + + + + + Email addresses are not equal + + + + + + + + + Incorrect Password + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Email Notification Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Old Password is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Email address + + + + + + + + + Invalid Password + + + + + + + + + Bevestigen + + + + + + Gebruiker verwijderen + + + + + + Bewaar e-mailadres + + + Klik hier om uw e-mailadres te bewaren. + + + Bewaar wachtwoord + + + Klik hier om uw wachtwoord te bewaren + + + U kunt uw gebruikersprofiel verwijderen door op de "Verwijder gebruiker" knop te klikken, u dient verwijderen daarna te bevestigen.<br /><b>Waarschuwing</b>: deze actie kan niet ongedaan gemaakt worden. + You can delete your account by clicking on the Delete Account button and then confirming with the Confirm button.<br /><b>Warning</b>: the operation is irreversible. + + + Verwijder gebruiker + + + Welkom bij uw gebruikersprofiel + + + E-mail + + + E-mail (herhalen) + + + Hier kunt u de instellingen van uw gebruikersprofiel, zoals e-mailadres en wachtwoord, aanpassen. U kunt uw gebruikersnaam niet aanpassen. + Here you can edit your Profile settings, such as Email address and Password. You cannot change your Username. + + + Oud wachtwoord + + + Wachtwoord + + + Wachtwoord (herhalen) + + + + + + + + + Gebruikersprofiel + + + + + + Untitled Page + + + + + + Typ hier uw e-mailadres + + + + + + Herhaal uw e-mailadres + + + + + + Typ hier uw huidige wachtwoord + + + + + + Typ hier uw nieuwe wachtwoord + + + + + + Herhaal uw nieuwe wachtwoord + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.pl-PL.resx b/WebApplication/App_LocalResources/Profile.aspx.pl-PL.resx new file mode 100644 index 0000000..5538871 --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.pl-PL.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + Save Display Name + + + Click here to save your Display Name + + + Save + + + + + + Save + + + + + + Email addresses are not equal + + + + + + + + + Email addresses are not equal + + + + + + + + + Incorrect Password + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Display Name + + + Edit Display Name, Email and Password + + + + + + You are member of the following groups: + + + + + + + + + Language and Time Zone + + + Current system settings do not allow any change to your personal information. + + + Email Notification Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Old Password is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email address + + + + + + + + + Invalid Password + + + + + + + + + + + + Type here a display name for your account + + + Zatwierdź + + + + + + Usuń konto + + + + + + Zapisz adres e-mail + + + Kliknij tutaj aby zapisać adres e-mail + + + Zapisz hasło + + + Kliknij tutaj aby zapisać hasło + + + Możesz usunąć swoje konto poprzez kliknięcie przycisku Usuń konto, a następnie przycisku Zatwierdź.<br /><b>Uwaga</b>: operacja jest nieodwracalna. + + + Usuń konto + + + Witamy na stronie Twojego profilu użytkownika + + + Adres e-mail + + + Powtórz adres e-mail + + + Tutaj możesz edytować swój profil użytkownika. Nie możesz jedynie zmienić swojej nazwy użytkownika. + + + Stare hasło + + + Hasło + + + Powtórz hasło + + + + + + + + + Profil użytkownika + + + + + + Strona niezatytułowana + + + + + + Wpisz tutaj swój adres e-mail + + + + + + Powtórz swój adres e-mail + + + + + + Wpisz tutaj swoje aktualne hasło + + + + + + Wpisz tutaj swoje nowe hasło + + + + + + Powtórz swoje nowe hasło + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.pt-BR.resx b/WebApplication/App_LocalResources/Profile.aspx.pt-BR.resx new file mode 100644 index 0000000..c3cd10f --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.pt-BR.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + Current system settings do not allow any change to your personal information. + + + + + + + + + + + + + + + Save Display Name + + + Click here to save your Display Name + + + Display Name + + + Edit Display Name, Email and Password + + + + + + + + + Invalid Display Name + + + + + + + + + + + + Type here a display name for your account + + + Save + + + + + + + + + You are member of the following groups: + + + + + + + + + Language and Time Zone + + + Save + + + + + + Email addresses are not equal + + + + + + + + + Email addresses are not equal + + + + + + + + + Incorrect Password + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Email Notification Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Old Password is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Email address + + + + + + + + + Invalid Password + + + + + + + + + Confirmar + + + + + + Apagar conta + + + + + + Salvar e-mail + + + Clique aqui para salvar seu endereço de e-mail + + + Salvar senha + + + Clique aqui para salvar sua nova senha + + + Você pode apagar a sua conta clicando na tecla apagar conta e então confirmando com o botão confirmar.<br /><b>Atenção</b>: a operação é irreversível. + + + Apagar conta + + + Bem-vindo ao seu perfil, + + + e-mail + + + e-mail (repetir) + + + Aqui você pode editar as suas configurações de perfil, tais como o endereço de e-mail e a senha. Você não pode mudar seu nome de usuário. + + + Senha anterior + + + Senha + + + Senha (repetir) + + + + + + + + + Perfil de usuário + + + + + + Página sem título + + + + + + Digite aqui seu endereço de e-mail + + + + + + Repita seu endereço de e-mail + + + + + + Digite aqui sua senha atual + + + + + + Digite aqui sua nova senha + + + + + + Repita sua nova senha + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.resx b/WebApplication/App_LocalResources/Profile.aspx.resx new file mode 100644 index 0000000..f8d9fb6 --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Confirm + + + + + + Delete Account + + + + + + Save Display Name + + + Click here to save your Display Name + + + Save Email + + + Click here to save your Email address + + + Save + + + + + + Save + + + + + + Save Password + + + Click here to save your new Password + + + Email addresses are not equal + + + + + + + + + Email addresses are not equal + + + + + + + + + Incorrect Password + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + You can delete your account by clicking on the Delete Account button and then confirming with the Confirm button.<br /><b>Warning</b>: the operation is irreversible. + + + Delete Account + + + Welcome to your Profile, + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + Display Name + + + Edit Display Name, Email and Password + + + Email + + + Email (repeat) + + + + + + You are member of the following groups: + + + Here you can edit your Profile settings, such as Email address and Password. You cannot change your Username. + + + + + + + + + Language and Time Zone + + + Current system settings do not allow any change to your personal information. + + + Email Notification Settings + + + + + + + + + Old Password + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + Password + + + Password (repeat) + + + + + + + + + + + + + + + + + + + + + User Profile + + + + + + + + + + + + Untitled Page + + + + + + + + + + + + + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Old Password is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email address + + + + + + + + + Invalid Password + + + + + + + + + + + + Type here a display name for your account + + + + + + Type here your Email address + + + + + + Repeat your Email address + + + + + + Type here your current Password + + + + + + Type here your new Password + + + + + + Repeat your new Password + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.ro-RO.resx b/WebApplication/App_LocalResources/Profile.aspx.ro-RO.resx new file mode 100644 index 0000000..47b6384 --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.ro-RO.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + Current system settings do not allow any change to your personal information. + + + + + + + + + + + + + + + Save Display Name + + + Click here to save your Display Name + + + Display Name + + + Edit Display Name, Email and Password + + + + + + + + + Invalid Display Name + + + + + + + + + + + + Type here a display name for your account + + + Save + + + + + + + + + You are member of the following groups: + + + + + + + + + Language and Time Zone + + + Save + + + + + + Email addresses are not equal + + + + + + + + + Email addresses are not equal + + + + + + + + + Incorrect Password + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Email Notification Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Old Password is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Email address + + + + + + + + + Invalid Password + + + + + + + + + Cofirma + + + + + + Sterge cont + + + + + + Salveaza e-mail + + + Click aici pentru a salva adresa de e-mail + + + Salveaza parola + + + Click aici pentru a salva noua ta parola + + + Puteti sterge contul dvs. printr-un click pe butonul Sterge cont urmat de o confirmare.<br /><b>Atentie</b>: operatiunea este ireversibila. + + + Sterge cont + + + Bine ati venit la profilul dumneavoastra, + + + E-mail + + + E-mail (repeat) + + + Aici puteti edita setarile din profiul dvs., cum ar fi adresa de E-mail si parola. Nu puteti schimba numele de utilizator. + + + Parola veche + + + Parola + + + Parola (din nou) + + + + + + + + + Profil utilizator + + + + + + Pagina fara titlu + + + + + + Tastati aici adresa dvs. de e-mail + + + + + + Repetati adresa de e-mail + + + + + + Tastati aici parola curenta + + + + + + Tastati aici noua parola + + + + + + Repetati noua parola + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.ru-RU.resx b/WebApplication/App_LocalResources/Profile.aspx.ru-RU.resx new file mode 100644 index 0000000..bc498dd --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.ru-RU.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Получать email-уведомления о публикациях <b>сообщений</b> размещённых на страницах следующих пространств имён (только в обсуждениях, к которым Вы имеете доступ): + + + Получать email-уведомления об обновлении <b>страниц</b> следующих пространств имён (только для страниц, к которым у Вас есть доступ): + + + Текущие настройки содержат запрет на изменение Вашей личной информации + + + + + + + + + + + + + + + Сохранить Отображаемое Имя + + + Нажмите здесь для сохранения Отображаемого Имени + + + Отображаемое Имя + + + Изменить Отображаемое Имя, Email и Паролль + + + + + + + + + Ошибочное Отображаемое Имя + + + + + + + + + + + + Укажите своё Отображаемое Имя + + + Сохранить + + + + + + + + + Вы являетесь членом таких Групп: + + + + + + + + + Выбор языка и Временной Зоны + + + Сохранить + + + + + + Email-адреса не совпадают + + + + + + + + + Email-адреса не совпадают + + + + + + + + + Неправильный пароль + + + + + + + + + Пароли не совпадают + + + + + + + + + Пароли не совпадают + + + + + + + + + Установки email-уведомлений + + + + + + + + + + + + + + + + + + + + + + + + + + + Email необходим + + + + + + + + + Email необходим + + + + + + + + + Требуется действующий пароль + + + + + + + + + Пароль необходим + + + + + + + + + Пароль необходим + + + + + + + + + Неверный email + + + + + + + + + Неправильный пароль + + + + + + + + + Подтвердить + + + + + + Удалить Запись + + + + + + Сохранить Email + + + Нажмите здесь, чтобы сохранить Email адрес + + + Сохранить Пароль + + + Нажмите здесь, чтобы сохранить Пароль + + + Вы можете удалить свою Учётную Запись, нажав на кнопку Удалить и подтвердить удаление, нажав на кнопку Подтвердить.<br /><b>Предупреждение</b>: эта операция необратима. + + + Удалить Запись + + + Ваш Профиль, + + + Email + + + Email (ещё раз) + + + Вы имеете возможность изменить данные своего Профиля, такие как Email-адрес и Пароль. У Вас нет возможности изменить своё Имя Пользователя. + + + Текущий Пароль + + + Пароль + + + Пароль (ещё раз) + + + + + + + + + Профиль Пользователя + + + + + + Untitled Page + + + + + + Введите свой Email-адрес + + + + + + Повторите ввод Email-адреса + + + + + + Введите свой текущий Пароль + + + + + + Введите свой новый Пароль + + + + + + Повторите ввод нового Пароля + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.sk-SK.resx b/WebApplication/App_LocalResources/Profile.aspx.sk-SK.resx new file mode 100644 index 0000000..be339a0 --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.sk-SK.resx @@ -0,0 +1,452 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + Current system settings do not allow any change to your personal information. + + + + + + + + + + + + + + + Save Display Name + + + Click here to save your Display Name + + + Display Name + + + Edit Display Name, Email and Password + + + + + + + + + Invalid Display Name + + + + + + + + + + + + Type here a display name for your account + + + Save + + + + + + + + + You are member of the following groups: + + + + + + + + + Language and Time Zone + + + Save + + + + + + Email addresses are not equal + + + + + + + + + Email addresses are not equal + + + + + + + + + Incorrect Password + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Email Notification Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Old Password is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Email address + + + + + + + + + Invalid Password + + + + + + + + + Potvrdiť + Confirm + + + + + + Zmazať konto + Delete Account + + + + + + Uložiť email + Save Email + + + Kliknite tu na uloženie Vašej emailovej adresy + Click here to save your Email address + + + Uložiť heslo + Save Password + + + Kliknite tu na uloženie Vašeho nového hesla + Click here to save your new Password + + + Svoje konto môžete zmazať kliknutím na tlačítko Zmazať konto a potvrdením kliknutím na potvrdzovacie tlačítko.<br /><b>Varovanie</b>: operácia je definitívna a nevratná + You can delete your account by clicking on the Delete Account button and then confirming with the Confirm button.<br /><b>Warning</b>: the operation is irreversible. + + + Zmazať konto + Delete Account + + + Vitajte vo svojom profile, + Welcome to your Profile, + + + Email + + + Email (potvrdenie) + Email (repeat) + + + Tu môžete meniť nastavenia Vašeho profilu, ako napríklad email alebo heslo. Nemôžete však meniť svoje prihlasovacie meno. + Here you can edit your Profile settings, such as Email address and Password. You cannot change your Username. + + + Pôvodné heslo + Old Password + + + Heslo + Password + + + Hesloe (potvrdenie) + Password (repeat) + + + + + + + + + Používateľský profil + User Profile + + + + + + Nepomenovaná stránka + Untitled Page + + + + + + Tu vložte Váš email + Type here your Email address + + + + + + Tu vložte Váš email pre potvrdenie + Repeat your Email address + + + + + + Tu vložte Vaše súčastné heslo + + + + + + Tu vložte Vaše nové heslo + Type here your new Password + + + + + + Tu vložte Vaše nové heslo pre potvrdenie + Repeat your new Password + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/Profile.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..b6bbb81 --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.sr-Latn-CS.resx @@ -0,0 +1,452 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + Current system settings do not allow any change to your personal information. + + + + + + + + + + + + + + + Save Display Name + + + Click here to save your Display Name + + + Display Name + + + Edit Display Name, Email and Password + + + + + + + + + Invalid Display Name + + + + + + + + + + + + Type here a display name for your account + + + Save + + + + + + + + + You are member of the following groups: + + + + + + + + + Language and Time Zone + + + Save + + + + + + Email addresses are not equal + + + + + + + + + Email addresses are not equal + + + + + + + + + Incorrect Password + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Email Notification Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Old Password is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Email address + + + + + + + + + Invalid Password + + + + + + + + + Stvarno Briši + Confirm + + + + + + Briši konto + Delete Account + + + + + + Sačuvaj email + Save Email + + + Kliknite ovde ako želite sačuvati email adresu + Click here to save your Email address + + + Sačuvaj šifru + Save Password + + + Kliknite ovde ako želite sačuvati vešu novu šifru + Click here to save your new Password + + + Možete obrisati vaš konto, klikom na <i>Briši Konto</i> dugme. Zbog sigurnosti morate potvrdom na dugme <i>Stvarno Briši</i> izvršiti.<br /><b>Upozorenje:</b> Posle ove operacije nema povratka podataka (no recovery). + You can delete your account by clicking on the Delete Account button and then confirming with the Confirm button.<br /><b>Warning</b>: the operation is irreversible. + + + Briši konto + Delete Account + + + Dobro došli na vaš konto, + Welcome to your Profile, + + + Email + + + Email (ponoviti) + Email (repeat) + + + Ovde možete izmeniti vaše podatke kao što su email, adresa itd... Korisničko ime se ne može menjati. + Here you can edit your Profile settings, such as Email address and Password. You cannot change your Username. + + + Stara šifra + Old Password + + + Šifra + Password + + + Šifra (ponoviti) + Password (repeat) + + + + + + + + + Korisnički profil + User Profile + + + + + + Neimenovana stranica + Untitled Page + + + + + + Unesite vašu email adresu + Type here your Email address + + + + + + Unesite ponovo vašu email adresu + Repeat your Email address + + + + + + Unesite vašu aktuelnu šifru + + + + + + Unesite vašu novu šifru + Type here your new Password + + + + + + Ponovite vašu novu šifru + Repeat your new Password + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.tr-TR.resx b/WebApplication/App_LocalResources/Profile.aspx.tr-TR.resx new file mode 100644 index 0000000..9071755 --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.tr-TR.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + Mevcut sistem ayarları, kişisel bilgilerinizde değişiklik yapmaya izin vermiyor. + + + + + + + + + + + + + + + Görünür Adı Kaydet + + + Görünür adınızı kaydetmek için tıklayın + + + Görünür ad + + + Görünür adı, emaili ve parolayı değiştirin + + + + + + + + + Geçersiz görünür ad + + + + + + + + + + + + Buraya hesabınız için bir görünür ad yazın + + + Kaydet + + + + + + + + + Aşağıdaki guruplara mensupsunuz: + + + + + + + + + Dil ve Saat Dilimi + + + Kaydet + + + + + + Email adresleri aynı değil + + + + + + + + + Email adresleri aynı değil + + + + + + + + + Geçersiz parola + + + + + + + + + Parolalar aynı değil + + + + + + + + + Parolalar aynı değil + + + + + + + + + Email Bildirim Ayarları + + + + + + + + + + + + + + + + + + + + + + + + + + + Email gereklidir + + + + + + + + + Email gereklidir + + + + + + + + + Eski parola gereklidir + + + + + + + + + Parola gereklidir + + + + + + + + + Parola gereklidir + + + + + + + + + Geçersiz email adresi + + + + + + + + + Geçersiz parola + + + + + + + + + Doğrula + + + + + + Hesabı Sil + + + + + + E-Posta adresini kaydet + + + E-Posta adresini kaydetmek için buraya basın + + + Şifreyi kaydet + + + Şifreyi kaydetmek için buraya basın + + + Hesabınızı "Hesabı Sil" tuşuna basıp "Doğrula" butonu ile onaylayarak silebilirsiniz. <br /><b>İkaz</b>: bu işlemin geri dönüşü yoktur. + + + Hesabı Sil + + + Profil sayfanıza hoş geldiniz, + + + E-Posta + + + E-Postal (tekrar) + + + Buradan e-posta adresi ve şifre gibi profil ayarlarınızı düzenleyebilirsiniz. Kullanıcı adınızı ise değiştiremiyorsunuz. + + + Şimdiki şifre + + + Şifre + + + Şifre (tekrar) + + + + + + + + + Kullanıcı Profili + + + + + + Başlıksız Sayfa + + + + + + Buraya e-posta adresinizi yazın + + + + + + Buraya e-posta adresinizi tekrar yazın + + + + + + Buraya şimdiki şifrenizi yazın + + + + + + Buraya yeni şifrenizi yazın + + + + + + Buraya yeni şifrenizi tekrar yazın + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.uk-UA.resx b/WebApplication/App_LocalResources/Profile.aspx.uk-UA.resx new file mode 100644 index 0000000..49c4f9b --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.uk-UA.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Отримувати email-повідомлення щодо <b>коментарів</b> які будуть розміщені на сторінках в таких просторах імен (тількі в обговореннях до яких у Вас є доступ): + + + Отримувати email-повідомлення щодо оновлення <b>сторінок</b> в наступних просторах імен (тільки у відношенні сторінок до яких Ви маєте доступ): + + + Поточні параметри системи не передбачають можливості щодо зміни Вашої персональної інформації. + + + + + + + + + + + + + + + Зберегти Ім'я для Відображення + + + Натисніть для збереження Вашого Імені для Відображення + + + Ім'я для Відображення + + + Змінити Ім'я для Відображення, Email та Пароль + + + + + + + + + Помилкове Ім'я для Відображення + + + + + + + + + + + + Введіть своє Ім'я для Відображення + + + Зберегти + + + + + + + + + Ви є членом таких Груп: + + + + + + + + + Вибір Мови та Часової Зони + + + Зберегти + + + + + + Email-адреси не співпадають + + + + + + + + + Email-адреси не співпадають + + + + + + + + + Невірний пароль + + + + + + + + + Паролі не співпадають + + + + + + + + + Паролі не співпадають + + + + + + + + + Налаштування email-повідомлень + + + + + + + + + + + + + + + + + + + + + + + + + + + Email обов'язковий + + + + + + + + + Email обов'язковий + + + + + + + + + Потрібен діючий пароль + + + + + + + + + Пароль обов'язково + + + + + + + + + Пароль обов'язково + + + + + + + + + Невірний email + + + + + + + + + Невірний email + + + + + + + + + Підтвердити + + + + + + Видалити Запис + + + + + + Зберегти Email + + + Натисніть, щоб зберегти Email адресу + + + Зберегти Пароль + + + Натисніть, щоб зберегти Пароль + + + Ви мате змогу знищити свій Запис Користувача, натиснувши Видалити та згодом натиснувши Підтвердити.<br /><b>Попередження</b>: така операція є незворотньою. + + + Видалити Запис + + + Ваш Профіль, + + + Email + + + Email (ще раз) + + + Ви маєте змогу змінити свою Email-адресу та Пароль. Ви не маєте змоги змінити своє Ім'я Користувача. + + + Поточний Пароль + + + Пароль + + + Пароль (ще раз) + + + + + + + + + Профіль Користувача + + + + + + Untitled Page + + + + + + Введіть свою Email-адресу + + + + + + Введіть свою Email-адресу, ще раз + + + + + + Введіть свій діючий Пароль + + + + + + Введіть свій новий Пароль + + + + + + Введіть свій новий Пароль, ще раз + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.zh-cn.resx b/WebApplication/App_LocalResources/Profile.aspx.zh-cn.resx new file mode 100644 index 0000000..3aec599 --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.zh-cn.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 当有<b>消息</b>发布在下列命名空间中的页面时(仅限您可以访问的讨论),接收电子邮件通知: + + + 当在下列命名空间中的<b>页面</b>更新时(仅限您可以访问的页面),接收电子邮件通知: + + + 当前系统设置不允许对个人信息作任何更改。 + + + + + + + + + + + + + + + 保存显示名称 + + + 点击这里保存您的显示名称 + + + 显示名称 + + + 编辑显示名称、电子邮件地址和密码 + + + + + + + + + 显示名称无效 + + + + + + + + + + + + 键入您帐户的显示名称 + + + 保存 + + + + + + + + + 您是下列组的成员: + + + + + + + + + 语言和时区 + + + 保存 + + + + + + 电子邮件地址不匹配 + + + + + + + + + 电子邮件地址不匹配 + + + + + + + + + 密码错误 + + + + + + + + + 密码不匹配 + + + + + + + + + 密码不匹配 + + + + + + + + + 电子邮件通知设置 + + + + + + + + + + + + + + + + + + + + + + + + + + + 需要电子邮件地址 + + + + + + + + + 需要电子邮件地址 + + + + + + + + + 需要旧密码 + + + + + + + + + 需要密码 + + + + + + + + + 需要密码 + + + + + + + + + 电子邮件地址无效 + + + + + + + + + 密码无效 + + + + + + + + + 确认 + + + + + + 删除帐户 + + + + + + 保存电子邮件地址 + + + 点击这里保存您的电子邮件地址 + + + 保存密码 + + + 点击这里保存您的密码 + + + 您可以通过先点击“删除帐户”按钮然后点击“确认”按钮进行确定以删除您的帐户。<br /><b>警告</b>:此操作不可逆。 + + + 删除帐户 + + + 欢迎访问您的用户概要, + + + 电子邮件地址 + + + 电子邮件地址(重复) + + + 在这里您可以编辑您的概要设置,例如电子邮件地址和密码。您不能更改您的用户名。 + + + 旧密码 + + + 密码 + + + 密码(重复) + + + + + + + + + 用户概要 + + + + + + 无标题页面 + + + + + + 输入您的电子邮件地址 + + + + + + 重复您的电子邮件地址 + + + + + + 输入您的当前密码 + + + + + + 输入您的新密码 + + + + + + 重复您的新密码 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Profile.aspx.zh-tw.resx b/WebApplication/App_LocalResources/Profile.aspx.zh-tw.resx new file mode 100644 index 0000000..504ba06 --- /dev/null +++ b/WebApplication/App_LocalResources/Profile.aspx.zh-tw.resx @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Receive an email notification whenever a <b>message</b> is posted for a page in the following namespaces (only discussions you have access to): + + + Receive an email notification whenever a <b>page</b> in the following namespaces is updated (only pages you have access to): + + + Current system settings do not allow any change to your personal information. + + + + + + + + + + + + + + + Save Display Name + + + Click here to save your Display Name + + + Display Name + + + Edit Display Name, Email and Password + + + + + + + + + Invalid Display Name + + + + + + + + + + + + Type here a display name for your account + + + Save + + + + + + + + + You are member of the following groups: + + + + + + + + + Language and Time Zone + + + Save + + + + + + Email addresses are not equal + + + + + + + + + Email addresses are not equal + + + + + + + + + Incorrect Password + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Email Notification Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Old Password is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Invalid Email address + + + + + + + + + Invalid Password + + + + + + + + + 確認 + + + + + + 刪除帳戶 + + + + + + 保存Email + + + 點擊這裏保存你的Email + + + 保存密碼 + + + 點擊這裏保存你的密碼 + + + 你通過先點擊“刪除帳戶”按鈕然後點擊“確定”按鈕進行確定以刪除帳戶。<br /><b>警告</b>:本操作不可逆。 + + + 刪除帳戶 + + + 歡迎查看你的用戶概要: + + + Email + + + Email (重複) + + + 這裏你可以編輯你的概要設置,例如Email位址和密碼。但不能更改用戶名。 + + + 原密碼 + + + 密碼 + + + 密碼(重複) + + + + + + + + + 用戶概要 + + + + + + 未命名頁面 + + + + + + 輸入你的Email位址 + + + + + + 重複你的Email地址 + + + + + + 輸入你當前的密碼 + + + + + + 輸入你的新密碼 + + + + + + 重複你的新密碼 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/Register.aspx.cs-CZ.resx new file mode 100644 index 0000000..ad5cd08 --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.cs-CZ.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Vytvořit konto + + + Klikněte zde pro vytvoření konta + + + Emaily nejsou stejné + + + Emaily nejsou stejné + + + Hesla nejsou stejná + + + Hesla nejsou stejná + + + Uživatelské jméno již existuje + + + Kontrolní text (ohled na velikost písmen): + + + Zobrazované jméno + + + Email + + + Email (znovu) + + + Heslo + + + Heslo (znovu) + + + Zde můžete vytvořit nové konto pro Wiki.<br /><b>Poznámka</b>: všechna pole jsou povinná . Email nebude v žádném případě publikován, ale bude viditelný Administrátorům. + + + Vytvořit nové konto + + + Uživatelské jméno + + + Untitled Page + + + Vyžadován Email + + + Vyžadován Email + + + Vyžadováno heslo + + + Vyžadováno heslo + + + Vyžadováno uživatelské jméno + + + Neplatné zobrazované jméno + + + Neplatný Email + + + Neplatné heslo + + + Neplatné uživatelské jméno + + + Napište zobrazované jméno pro Váš účet + + + Napište email adresu + + + Znovu email adresu + + + Zde napište heslo + + + Znovu heslo + + + Napište uživatelské jméno + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.da-DK.resx b/WebApplication/App_LocalResources/Register.aspx.da-DK.resx new file mode 100644 index 0000000..e8a1a9c --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.da-DK.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Emails are not equal + + + + + + + + + Emails are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Username already exists + + + + + + + + + + + + Display Name + + + + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + + + + Type here a display name for your account + + + Opret konto + + + Klik er for at oprette en konto + + + Kontrol tekst: + + + E-mail + + + E-mail (gentag) + + + Kodeord + + + Kodeord (gentag) + + + Her kan du oprette en ny konto til denne Wiki. <br /> <b> Bemærk </ b>: alle felter er obligatoriske. E-mail-adressen vil ikke blive offentliggjort på nogen måde, men det vil være synlig for administratorer. + + + + + + Opret en ny konto + + + Brugernavn + + + Side uden titel + + + + + + Skriv din E-mail adresse her + + + + + + Gentag din E-mail adresse her + + + + + + Skriv dit kodeord her + + + + + + Gentag dit kodeord her + + + + + + Skriv dit burgernavn her + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.de-DE.resx b/WebApplication/App_LocalResources/Register.aspx.de-DE.resx new file mode 100644 index 0000000..a58a39d --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.de-DE.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Create Account + + + Click here to create your Account + + + Emails are not equal + + + + + + + + + Emails are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Username already exists + + + + + + + + + + + + Control Text (case sensitive): + + + Display Name + + + Email + + + Email (repeat) + + + Password + + + Password (repeat) + + + Here you can create a new Account for this Wiki.<br /><b>Note</b>: all the fields are mandatory. The Email address will not be published in any way, but it will be visible to the Administrators. + + + + + + + + + Create a new Account + + + Username + + + Untitled Page + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + + + + Type here a display name for your account + + + + + + Type here your Email address + + + + + + Repeat your Email address + + + + + + Type here your Password + + + + + + Repeat your Password + + + + + + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.es-ES.resx b/WebApplication/App_LocalResources/Register.aspx.es-ES.resx new file mode 100644 index 0000000..a58a39d --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.es-ES.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Create Account + + + Click here to create your Account + + + Emails are not equal + + + + + + + + + Emails are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Username already exists + + + + + + + + + + + + Control Text (case sensitive): + + + Display Name + + + Email + + + Email (repeat) + + + Password + + + Password (repeat) + + + Here you can create a new Account for this Wiki.<br /><b>Note</b>: all the fields are mandatory. The Email address will not be published in any way, but it will be visible to the Administrators. + + + + + + + + + Create a new Account + + + Username + + + Untitled Page + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + + + + Type here a display name for your account + + + + + + Type here your Email address + + + + + + Repeat your Email address + + + + + + Type here your Password + + + + + + Repeat your Password + + + + + + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.fr-FR.resx b/WebApplication/App_LocalResources/Register.aspx.fr-FR.resx new file mode 100644 index 0000000..a58a39d --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.fr-FR.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Create Account + + + Click here to create your Account + + + Emails are not equal + + + + + + + + + Emails are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Username already exists + + + + + + + + + + + + Control Text (case sensitive): + + + Display Name + + + Email + + + Email (repeat) + + + Password + + + Password (repeat) + + + Here you can create a new Account for this Wiki.<br /><b>Note</b>: all the fields are mandatory. The Email address will not be published in any way, but it will be visible to the Administrators. + + + + + + + + + Create a new Account + + + Username + + + Untitled Page + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + + + + Type here a display name for your account + + + + + + Type here your Email address + + + + + + Repeat your Email address + + + + + + Type here your Password + + + + + + Repeat your Password + + + + + + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.hu-HU.resx b/WebApplication/App_LocalResources/Register.aspx.hu-HU.resx new file mode 100644 index 0000000..ab4234d --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.hu-HU.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Fiók létrehozása + + + Kattints ide a fiókod létrehozásához + + + Az email címek nem egyeznek + + + + + + + + + Az email címek nem egyeznek + + + + + + + + + A jelszavak nem egyeznek + + + + + + + + + A jelszavak nem egyeznek + + + + + + + + + A felhasználónév már foglalt + + + + + + + + + + + + Ellenőrző szöveg (kis-nagybetű számít): + + + Megjelenő név + + + Email + + + Email (ismét) + + + Jelszó + + + Jelszó (ismét) + + + Itt létrehozhatsz egy új fiókot a wikihez.<br /><b>Figyelem</b>: minden mező kötelező. Az email cím nem lesz nyilvános, de az adminisztrátorok láthatják. + + + + + + + + + Új fiók létrehozása + + + Felhasználói név + + + Cím nélküli oldal + + + + + + + + + Az email kötelező + + + + + + + + + Az email kötelező + + + + + + + + + A jelszó kötelező + + + + + + + + + A jelszó kötelező + + + + + + + + + A felhasználói név kötelező + + + + + + + + + Érvénytelen megjelenő név + + + + + + + + + Érvénytelen email + + + + + + + + + Érvénytelen jelszó + + + + + + + + + Érvénytelen felhasználói név + + + + + + + + + + + + Írd ide a megjelenő neved + + + + + + Írd ide az email címed + + + + + + Ismételd meg az email címed + + + + + + Írd ide a jelszavad + + + + + + Ismételd meg a jelszavad + + + + + + Írd ide a felhasználói neved + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.it-IT.resx b/WebApplication/App_LocalResources/Register.aspx.it-IT.resx new file mode 100644 index 0000000..ac249f7 --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.it-IT.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Crea account + + + Fai click per creare il tuo account + + + Email non uguali + + + + + + + + + Email non uguali + + + + + + + + + Password non uguali + + + + + + + + + Password non uguali + + + + + + + + + Username già esistente + + + + + + + + + + + + Testo di controllo (sensibile alle maiuscole): + + + Nome visualizzato: + + + Email + + + Email (ripeti) + + + Password + + + Password (ripeti) + + + In questa pagina puoi creare un account per questo wiki.<br /><b>Nota</b>: tutti i cambi sono obbligatori. L'indirizzo email non verrà pubblicato in alcun modo ma sarà visibile agli amministratori. + + + + + + + + + Crea un nuovo account + + + Username + + + Untitled Page + + + + + + + + + Email richiesta + + + + + + + + + Email richiesta + + + + + + + + + Password richiesta + + + + + + + + + Password richiesta + + + + + + + + + Username richiesto + + + + + + + + + Nome visualizzato non valido + + + + + + + + + Email non valida + + + + + + + + + Password non valida + + + + + + + + + Username non valido + + + + + + + + + + + + Digita qui il nome visualizzato + + + + + + Digita qui la tua email + + + + + + Ripeti la tua email + + + + + + Digita la tua password + + + + + + Ripeti la tua password + + + + + + Digita il tuo username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.nb-NO.resx b/WebApplication/App_LocalResources/Register.aspx.nb-NO.resx new file mode 100644 index 0000000..a58a39d --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.nb-NO.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Create Account + + + Click here to create your Account + + + Emails are not equal + + + + + + + + + Emails are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Username already exists + + + + + + + + + + + + Control Text (case sensitive): + + + Display Name + + + Email + + + Email (repeat) + + + Password + + + Password (repeat) + + + Here you can create a new Account for this Wiki.<br /><b>Note</b>: all the fields are mandatory. The Email address will not be published in any way, but it will be visible to the Administrators. + + + + + + + + + Create a new Account + + + Username + + + Untitled Page + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + + + + Type here a display name for your account + + + + + + Type here your Email address + + + + + + Repeat your Email address + + + + + + Type here your Password + + + + + + Repeat your Password + + + + + + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.nl-NL.resx b/WebApplication/App_LocalResources/Register.aspx.nl-NL.resx new file mode 100644 index 0000000..a58a39d --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.nl-NL.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Create Account + + + Click here to create your Account + + + Emails are not equal + + + + + + + + + Emails are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Username already exists + + + + + + + + + + + + Control Text (case sensitive): + + + Display Name + + + Email + + + Email (repeat) + + + Password + + + Password (repeat) + + + Here you can create a new Account for this Wiki.<br /><b>Note</b>: all the fields are mandatory. The Email address will not be published in any way, but it will be visible to the Administrators. + + + + + + + + + Create a new Account + + + Username + + + Untitled Page + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + + + + Type here a display name for your account + + + + + + Type here your Email address + + + + + + Repeat your Email address + + + + + + Type here your Password + + + + + + Repeat your Password + + + + + + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.pl-PL.resx b/WebApplication/App_LocalResources/Register.aspx.pl-PL.resx new file mode 100644 index 0000000..cef0bbf --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.pl-PL.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Emails are not equal + + + + + + + + + Emails are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Username already exists + + + + + + + + + + + + Display Name + + + + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + + + + Type here a display name for your account + + + Utwórz konto + + + Kliknij tutaj aby utworzyć swoje konto + + + Tekst kontrolny: + + + Adres e-mail + + + Powtórz asdres e-mail + + + Hasło + + + Powtórz hasło + + + Tutaj możesz utworzyć nowe konto użytkownika serwisu Wiki.<br /><b>Uwaga</b>: wszystkie pola są obowiązkowe. Adres e-mail nie będzie nigdzie przekazywany, ani publikowany, jednakże będzie on widoczny dla administratorów serwisu. + + + + + + Utwórz nowe konto + + + Nazwa użytkownika + + + Strona niezatytułowana + + + + + + Wpisz tutaj swój adres e-mail + + + + + + Powtórz swój adres e-mail + + + + + + Wpisz tutaj swoje hasło + + + + + + Powtórz swoje hasło + + + + + + Wpisz tutaj swoją nazwę użytkownika + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.pt-BR.resx b/WebApplication/App_LocalResources/Register.aspx.pt-BR.resx new file mode 100644 index 0000000..a58a39d --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.pt-BR.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Create Account + + + Click here to create your Account + + + Emails are not equal + + + + + + + + + Emails are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Username already exists + + + + + + + + + + + + Control Text (case sensitive): + + + Display Name + + + Email + + + Email (repeat) + + + Password + + + Password (repeat) + + + Here you can create a new Account for this Wiki.<br /><b>Note</b>: all the fields are mandatory. The Email address will not be published in any way, but it will be visible to the Administrators. + + + + + + + + + Create a new Account + + + Username + + + Untitled Page + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + + + + Type here a display name for your account + + + + + + Type here your Email address + + + + + + Repeat your Email address + + + + + + Type here your Password + + + + + + Repeat your Password + + + + + + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.resx b/WebApplication/App_LocalResources/Register.aspx.resx new file mode 100644 index 0000000..a58a39d --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Create Account + + + Click here to create your Account + + + Emails are not equal + + + + + + + + + Emails are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Username already exists + + + + + + + + + + + + Control Text (case sensitive): + + + Display Name + + + Email + + + Email (repeat) + + + Password + + + Password (repeat) + + + Here you can create a new Account for this Wiki.<br /><b>Note</b>: all the fields are mandatory. The Email address will not be published in any way, but it will be visible to the Administrators. + + + + + + + + + Create a new Account + + + Username + + + Untitled Page + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + + + + Type here a display name for your account + + + + + + Type here your Email address + + + + + + Repeat your Email address + + + + + + Type here your Password + + + + + + Repeat your Password + + + + + + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.ro-RO.resx b/WebApplication/App_LocalResources/Register.aspx.ro-RO.resx new file mode 100644 index 0000000..a58a39d --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.ro-RO.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Create Account + + + Click here to create your Account + + + Emails are not equal + + + + + + + + + Emails are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Username already exists + + + + + + + + + + + + Control Text (case sensitive): + + + Display Name + + + Email + + + Email (repeat) + + + Password + + + Password (repeat) + + + Here you can create a new Account for this Wiki.<br /><b>Note</b>: all the fields are mandatory. The Email address will not be published in any way, but it will be visible to the Administrators. + + + + + + + + + Create a new Account + + + Username + + + Untitled Page + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + + + + Type here a display name for your account + + + + + + Type here your Email address + + + + + + Repeat your Email address + + + + + + Type here your Password + + + + + + Repeat your Password + + + + + + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.ru-RU.resx b/WebApplication/App_LocalResources/Register.aspx.ru-RU.resx new file mode 100644 index 0000000..4b8a9d9 --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.ru-RU.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Создать учётную запись + + + Нажмите здесь, для создания Учётной Записи + + + Email-ы не совпадают + + + + + + + + + Email-ы не совпадают + + + + + + + + + Пароли не совпадают + + + + + + + + + Пароли не совпадают + + + + + + + + + Такой Логин зарезервирован + + + + + + + + + + + + Защита от авторегистраций (регистрозависимо): + + + НикНейм + + + Email + + + Повторите Email + + + Пароль + + + Повторите пароль + + + Создавайте новую Учётную Запись в этой Wiki.<br /><b>Примечание</b>: все поля обязательны к заполнению. Email никому не будет доступен, кроме Администраторов этой Wiki. + + + + + + + + + Создать новую Учётную Запись + + + Логин + + + Untitled Page + + + + + + + + + Email обязателен + + + + + + + + + Email обязателен + + + + + + + + + Требуется пароль + + + + + + + + + Требуется пароль + + + + + + + + + Логин обязателен + + + + + + + + + Неверный НикНейм + + + + + + + + + Неправильный Email + + + + + + + + + Неправильный пароль + + + + + + + + + Неправильный Логин + + + + + + + + + + + + Введите сюда свой НикНейм, будет отображаться в Wiki + + + + + + Укажите свой Email + + + + + + Введите свой Email ещё раз + + + + + + Введите свой Пароль + + + + + + Введите свой Пароль ещё раз + + + + + + Введите свой Логин + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.sk-SK.resx b/WebApplication/App_LocalResources/Register.aspx.sk-SK.resx new file mode 100644 index 0000000..a58a39d --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.sk-SK.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Create Account + + + Click here to create your Account + + + Emails are not equal + + + + + + + + + Emails are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Username already exists + + + + + + + + + + + + Control Text (case sensitive): + + + Display Name + + + Email + + + Email (repeat) + + + Password + + + Password (repeat) + + + Here you can create a new Account for this Wiki.<br /><b>Note</b>: all the fields are mandatory. The Email address will not be published in any way, but it will be visible to the Administrators. + + + + + + + + + Create a new Account + + + Username + + + Untitled Page + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + + + + Type here a display name for your account + + + + + + Type here your Email address + + + + + + Repeat your Email address + + + + + + Type here your Password + + + + + + Repeat your Password + + + + + + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/Register.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..a58a39d --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.sr-Latn-CS.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Create Account + + + Click here to create your Account + + + Emails are not equal + + + + + + + + + Emails are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Username already exists + + + + + + + + + + + + Control Text (case sensitive): + + + Display Name + + + Email + + + Email (repeat) + + + Password + + + Password (repeat) + + + Here you can create a new Account for this Wiki.<br /><b>Note</b>: all the fields are mandatory. The Email address will not be published in any way, but it will be visible to the Administrators. + + + + + + + + + Create a new Account + + + Username + + + Untitled Page + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + + + + Type here a display name for your account + + + + + + Type here your Email address + + + + + + Repeat your Email address + + + + + + Type here your Password + + + + + + Repeat your Password + + + + + + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.tr-TR.resx b/WebApplication/App_LocalResources/Register.aspx.tr-TR.resx new file mode 100644 index 0000000..d62f71e --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.tr-TR.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Hesap Oluştur + + + Hesap oluşturmak için tıklayınız + + + Email'ler aynı değil + + + + + + + + + Email'ler aynı değil + + + + + + + + + Parolalar aynı değil + + + + + + + + + Parolalar aynı değil + + + + + + + + + Kullanıcı adı mevcut + + + + + + + + + + + + Kontrol metni (Büyük/küçük harfe dikkat): + + + Görünür Ad + + + Email + + + Email (tekrar) + + + Parola + + + Parola (tekrar) + + + Buradan Wiki için yeni bir hesap oluşturabilirsiniz. Tüm sahalara giriş zorunludur. Email adresi ise ifşa edilmeyecektir. + + + + + + + + + Yeni hesap oluştur + + + Kullanıcı adı + + + Başlıksız Sayfa + + + + + + + + + Email gereklidir + + + + + + + + + Email gereklidir + + + + + + + + + Parola gereklidir + + + + + + + + + Parola gereklidir + + + + + + + + + Kullanıcı adı gereklidir + + + + + + + + + Geçersiz görünür ad + + + + + + + + + Geçersiz email + + + + + + + + + Geçersiz parola + + + + + + + + + Geçersiz kullanıcı adı + + + + + + + + + + + + Buraya hesabınız için bir görünür ad belirtiniz + + + + + + Email adresinizi yazınız + + + + + + Repeat your Email address + + + + + + Buraya parolanızı yazınız + + + + + + Parolanızı tekrar deiniz + + + + + + Kullanıcı adınızı yazınız + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.uk-UA.resx b/WebApplication/App_LocalResources/Register.aspx.uk-UA.resx new file mode 100644 index 0000000..e7ab290 --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.uk-UA.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Створити обліковий запис + + + Натисніть для створення Запису Користувача + + + Email-и не співпадають + + + + + + + + + Email-и не співпадають + + + + + + + + + Паролі не співпадають + + + + + + + + + Паролі не співпадають + + + + + + + + + Такий Логін зарезервований + + + + + + + + + + + + Захист від автореєстрацій (регістрозалежно): + + + НікНейм + + + Email + + + Email, ще раз + + + Пароль + + + Пароль, ще раз + + + Створюйте новий Запис Користувача цієї Wiki.<br /><b>Примітка</b>: всі поля обов'язкові до заповнення. Email не буде доступний нікому окрім Адміністраторів цієї Wiki. + + + + + + + + + Створити новий Запис Користувача + + + Логін + + + Untitled Page + + + + + + + + + Email обов'язковий + + + + + + + + + Email обов'язковий + + + + + + + + + Портібен пароль + + + + + + + + + Портібен пароль + + + + + + + + + Логін обов'язковий + + + + + + + + + Помилковий НікНейм + + + + + + + + + Помилковий Email + + + + + + + + + Помилковий пароль + + + + + + + + + Помилковий Логін + + + + + + + + + + + + Введіть свій НікНейм, що буде ідентифкувати Вас в цій Wiki + + + + + + Вкажіть свій Email + + + + + + Вкажіть свій Email, ще раз + + + + + + Введіть свій Пароль + + + + + + Введіть свій Пароль, ще раз + + + + + + Введіть свій Логін + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.zh-cn.resx b/WebApplication/App_LocalResources/Register.aspx.zh-cn.resx new file mode 100644 index 0000000..c8d5c5b --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.zh-cn.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 创建帐户 + + + 点击这里创建您的帐户 + + + 电子邮件地址不匹配 + + + + + + + + + 电子邮件地址不匹配 + + + + + + + + + 密码不匹配 + + + + + + + + + 密码不匹配 + + + + + + + + + 用户名已存在 + + + + + + + + + + + + 验证码(大小写敏感): + + + 显示名称 + + + 电子邮件地址 + + + 电子邮件地址(重复) + + + 密码 + + + 密码(重复) + + + 在这里您可以创建新帐户。<br /><b>注意</b>:所有区域都是必填的。电子邮件地址不会以任何形式被公布,但对管理员可见。 + + + + + + + + + 创建新帐户 + + + 用户名 + + + 无标题页面 + + + + + + + + + 需要电子邮件地址 + + + + + + + + + 需要电子邮件地址 + + + + + + + + + 需要密码 + + + + + + + + + 需要密码 + + + + + + + + + 需要用户名 + + + + + + + + + 显示名称无效 + + + + + + + + + 电子邮件地址无效 + + + + + + + + + 密码无效 + + + + + + + + + 用户名无效 + + + + + + + + + + + + 键入您帐户的显示名称 + + + + + + 键入您的电子邮件地址 + + + + + + 重复您的电子邮件地址 + + + + + + 键入您的密码 + + + + + + 重复您的密码 + + + + + + 键入您的用户名 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Register.aspx.zh-tw.resx b/WebApplication/App_LocalResources/Register.aspx.zh-tw.resx new file mode 100644 index 0000000..a58a39d --- /dev/null +++ b/WebApplication/App_LocalResources/Register.aspx.zh-tw.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Create Account + + + Click here to create your Account + + + Emails are not equal + + + + + + + + + Emails are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Passwords are not equal + + + + + + + + + Username already exists + + + + + + + + + + + + Control Text (case sensitive): + + + Display Name + + + Email + + + Email (repeat) + + + Password + + + Password (repeat) + + + Here you can create a new Account for this Wiki.<br /><b>Note</b>: all the fields are mandatory. The Email address will not be published in any way, but it will be visible to the Administrators. + + + + + + + + + Create a new Account + + + Username + + + Untitled Page + + + + + + + + + Email is required + + + + + + + + + Email is required + + + + + + + + + Password is required + + + + + + + + + Password is required + + + + + + + + + Username is required + + + + + + + + + Invalid Display Name + + + + + + + + + Invalid Email + + + + + + + + + Invalid Password + + + + + + + + + Invalid Username + + + + + + + + + + + + Type here a display name for your account + + + + + + Type here your Email address + + + + + + Repeat your Email address + + + + + + Type here your Password + + + + + + Repeat your Password + + + + + + Type here your Username + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/Search.aspx.cs-CZ.resx new file mode 100644 index 0000000..0549ceb --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.cs-CZ.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Search Files and Attachments + + + + + + Hledat ve všech jmenných prostorech a kategoriích + + + Zde můžete prohledávat stránky aktuálního jmeného prostoru, jejich příloh a souborů nahraných do systému.<br /><b>Poznámka</b>: budou zobrazené výsledky jen na které máte práva ke čtení. + + + Jít + + + Nekategorizované stránky + + + Filtr dle kategorie + + + Vyhledávání + + + Titulek + + + Všechna slova + + + Nejméně jedno slovo + + + Přesná fráze + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.da-DK.resx b/WebApplication/App_LocalResources/Search.aspx.da-DK.resx new file mode 100644 index 0000000..5f090a5 --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.da-DK.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Search Files and Attachments + + + + + + Search in all Namespaces and all Categories + + + + + + Uncategorized Pages + + + + + + Filter by Category + + + Here you can search through the pages of this Namespace, their attachments and the files uploaded to the system.<br /><b>Note</b>: the results will only display the items you have permissions to read. + + + + + + Search Engine + + + All words + + + + + + At least one word + + + + + + Exact phrase + + + + + + + + + + + + Start + + + Klik her for at starte søgningen + + + + + + Side uden titel + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.de-DE.resx b/WebApplication/App_LocalResources/Search.aspx.de-DE.resx new file mode 100644 index 0000000..2c30afe --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.de-DE.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Search Files and Attachments + + + + + + Search in all Namespaces and all Categories + + + + + + Here you can search through the pages of this Namespace, their attachments and the files uploaded to the system.<br /><b>Note</b>: the results will only display the items you have permissions to read. + + + Go + + + + + + Uncategorized Pages + + + + + + Filter by Category + + + + + + Search Engine + + + + + + Title + + + All words + + + + + + At least one word + + + + + + Exact phrase + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.es-ES.resx b/WebApplication/App_LocalResources/Search.aspx.es-ES.resx new file mode 100644 index 0000000..2c30afe --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.es-ES.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Search Files and Attachments + + + + + + Search in all Namespaces and all Categories + + + + + + Here you can search through the pages of this Namespace, their attachments and the files uploaded to the system.<br /><b>Note</b>: the results will only display the items you have permissions to read. + + + Go + + + + + + Uncategorized Pages + + + + + + Filter by Category + + + + + + Search Engine + + + + + + Title + + + All words + + + + + + At least one word + + + + + + Exact phrase + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.fr-FR.resx b/WebApplication/App_LocalResources/Search.aspx.fr-FR.resx new file mode 100644 index 0000000..2c30afe --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.fr-FR.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Search Files and Attachments + + + + + + Search in all Namespaces and all Categories + + + + + + Here you can search through the pages of this Namespace, their attachments and the files uploaded to the system.<br /><b>Note</b>: the results will only display the items you have permissions to read. + + + Go + + + + + + Uncategorized Pages + + + + + + Filter by Category + + + + + + Search Engine + + + + + + Title + + + All words + + + + + + At least one word + + + + + + Exact phrase + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.hu-HU.resx b/WebApplication/App_LocalResources/Search.aspx.hu-HU.resx new file mode 100644 index 0000000..bfcc1af --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.hu-HU.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Search Files and Attachments + + + + + + Search in all Namespaces and all Categories + + + + + + Itt kereshetsz a névtér lapjai és a csatolmányai közt.<br /><b>Figyelem</b>: csak azok az oldalak jelennek meg, amelyekhez jogosultságod van. + + + Mehet + + + + + + Kategória nélküli lapok + + + + + + Szűrés kategória alapján + + + + + + Keresés + + + + + + Cím + + + Minden szó + + + + + + Legalább egy szó + + + + + + Pontos kifejezés + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.it-IT.resx b/WebApplication/App_LocalResources/Search.aspx.it-IT.resx new file mode 100644 index 0000000..0abdacc --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.it-IT.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cerca tra file ed allegati + + + + + + Cerca in tutti i namespace e tutte le categorie + + + + + + E' possibile eseguire una ricerca tra le pagine di questo namespace, gli allegati ed i file caricati nel wiki.<br /><b>Nota</b>: i risultati mostrano solo gli elementi a cui tu puoi accedere. + + + Vai + + + + + + Pagine non categorizzate + + + + + + Filtro per categoria + + + + + + Motore di ricerca + + + + + + Title + + + Tutte le parole + + + + + + Almeno una parola + + + + + + Frase esatta + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.nb-NO.resx b/WebApplication/App_LocalResources/Search.aspx.nb-NO.resx new file mode 100644 index 0000000..2c30afe --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.nb-NO.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Search Files and Attachments + + + + + + Search in all Namespaces and all Categories + + + + + + Here you can search through the pages of this Namespace, their attachments and the files uploaded to the system.<br /><b>Note</b>: the results will only display the items you have permissions to read. + + + Go + + + + + + Uncategorized Pages + + + + + + Filter by Category + + + + + + Search Engine + + + + + + Title + + + All words + + + + + + At least one word + + + + + + Exact phrase + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.nl-NL.resx b/WebApplication/App_LocalResources/Search.aspx.nl-NL.resx new file mode 100644 index 0000000..2c30afe --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.nl-NL.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Search Files and Attachments + + + + + + Search in all Namespaces and all Categories + + + + + + Here you can search through the pages of this Namespace, their attachments and the files uploaded to the system.<br /><b>Note</b>: the results will only display the items you have permissions to read. + + + Go + + + + + + Uncategorized Pages + + + + + + Filter by Category + + + + + + Search Engine + + + + + + Title + + + All words + + + + + + At least one word + + + + + + Exact phrase + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.pl-PL.resx b/WebApplication/App_LocalResources/Search.aspx.pl-PL.resx new file mode 100644 index 0000000..4c74a4a --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.pl-PL.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Search Files and Attachments + + + + + + Uncategorized Pages + + + + + + Filter by Category + + + Here you can search through the pages of this Namespace, their attachments and the files uploaded to the system.<br /><b>Note</b>: the results will only display the items you have permissions to read. + + + + + + Search Engine + + + All words + + + + + + At least one word + + + + + + Exact phrase + + + + + + + + + + + + Search in all Namespaces and all Categories + + + + + + Go + + + Kliknij tutaj aby rozpocząć wyszukiwanie + + + + + + Strona niezatytułowana + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.pt-BR.resx b/WebApplication/App_LocalResources/Search.aspx.pt-BR.resx new file mode 100644 index 0000000..2c30afe --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.pt-BR.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Search Files and Attachments + + + + + + Search in all Namespaces and all Categories + + + + + + Here you can search through the pages of this Namespace, their attachments and the files uploaded to the system.<br /><b>Note</b>: the results will only display the items you have permissions to read. + + + Go + + + + + + Uncategorized Pages + + + + + + Filter by Category + + + + + + Search Engine + + + + + + Title + + + All words + + + + + + At least one word + + + + + + Exact phrase + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.resx b/WebApplication/App_LocalResources/Search.aspx.resx new file mode 100644 index 0000000..84204ae --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Go + + + + + + Uncategorized Pages + + + + + + Filter by Category + + + Here you can search through the pages of this Namespace, their attachments and the files uploaded to the system.<br /><b>Note</b>: the results will only display the items you have permissions to read. + + + + + + Search Engine + + + + + + Title + + + All words + + + + + + At least one word + + + + + + Exact phrase + + + + + + + + + + + + Search in all Namespaces and all Categories + + + + + + Search Files and Attachments + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.ro-RO.resx b/WebApplication/App_LocalResources/Search.aspx.ro-RO.resx new file mode 100644 index 0000000..2c30afe --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.ro-RO.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Search Files and Attachments + + + + + + Search in all Namespaces and all Categories + + + + + + Here you can search through the pages of this Namespace, their attachments and the files uploaded to the system.<br /><b>Note</b>: the results will only display the items you have permissions to read. + + + Go + + + + + + Uncategorized Pages + + + + + + Filter by Category + + + + + + Search Engine + + + + + + Title + + + All words + + + + + + At least one word + + + + + + Exact phrase + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.ru-RU.resx b/WebApplication/App_LocalResources/Search.aspx.ru-RU.resx new file mode 100644 index 0000000..846ea08 --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.ru-RU.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Поиск Файлов и Вложений + + + + + + Поиск во всех Пространствах Имён и всех Категориях + + + + + + Осуществляйте поиск страниц, вложений и файлов этого Пространства Имён.<br /><b>Ограничение</b>: в поисковых результатах отобразятся только те файлы, к которым у Вас есть доступ. + + + Вперёд + + + + + + Страницы без категорий + + + + + + Фильтр по категориям + + + + + + Поисковик Wiki + + + + + + Заголовок + + + Все слова + + + + + + Хотя бы одно слово + + + + + + Фраза целиком + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.sk-SK.resx b/WebApplication/App_LocalResources/Search.aspx.sk-SK.resx new file mode 100644 index 0000000..2c30afe --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.sk-SK.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Search Files and Attachments + + + + + + Search in all Namespaces and all Categories + + + + + + Here you can search through the pages of this Namespace, their attachments and the files uploaded to the system.<br /><b>Note</b>: the results will only display the items you have permissions to read. + + + Go + + + + + + Uncategorized Pages + + + + + + Filter by Category + + + + + + Search Engine + + + + + + Title + + + All words + + + + + + At least one word + + + + + + Exact phrase + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/Search.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..2c30afe --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.sr-Latn-CS.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Search Files and Attachments + + + + + + Search in all Namespaces and all Categories + + + + + + Here you can search through the pages of this Namespace, their attachments and the files uploaded to the system.<br /><b>Note</b>: the results will only display the items you have permissions to read. + + + Go + + + + + + Uncategorized Pages + + + + + + Filter by Category + + + + + + Search Engine + + + + + + Title + + + All words + + + + + + At least one word + + + + + + Exact phrase + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.tr-TR.resx b/WebApplication/App_LocalResources/Search.aspx.tr-TR.resx new file mode 100644 index 0000000..bdb4302 --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.tr-TR.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Search Files and Attachments + + + + + + Search in all Namespaces and all Categories + + + + + + Buradan şu anki ad alanına ait sayfalar ve ilişik dosyaları içerisinde arama yapabilirsiniz. Not: Sadece okuma izniniz olan sonuçlar gösterilecektir. + + + Git + + + + + + Kategorilenmemiş sayfalar + + + + + + Kategoriye göre filtrele + + + + + + Arama motoru + + + + + + Başlık + + + Tüm sözcükler + + + + + + En azından bir sözcük + + + + + + Tam ifade + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.uk-UA.resx b/WebApplication/App_LocalResources/Search.aspx.uk-UA.resx new file mode 100644 index 0000000..d9d8e19 --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.uk-UA.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Пошук Файлів та Вкладень + + + + + + Пошук у всіх Просторах Імен та Категоріях + + + + + + Здійснюйте пошук сторінок, вкладень та файлів цього Простору Імен.<br /><b>Обмеження</b>: в пошуковому результаті знайдуть відображення тільки ті файли до яких Ви маєте доступ (у відповідності із Правами Доступу). + + + Вперед + + + + + + Сторінки без Категорій + + + + + + Фільтр за Категоріями + + + + + + Пошукова Машина Wiki + + + + + + Заголовок + + + Усі слова + + + + + + Хоча б одне слово + + + + + + Речення повністю + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.zh-cn.resx b/WebApplication/App_LocalResources/Search.aspx.zh-cn.resx new file mode 100644 index 0000000..5d32394 --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.zh-cn.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 搜索文件和附件 + + + + + + 在所有命名空间和分类中搜索 + + + + + + 在这里您可以搜索此命名空间中的页面、页面附件以及上传到系统的文件。<br /><b>注意</b>:搜索结果只显示您有阅读权限的项目。 + + + 搜索 + + + + + + 未分类页面 + + + + + + 按分类筛选 + + + + + + 搜索引擎 + + + + + + 标题 + + + 全部字词 + + + + + + 至少一个字词 + + + + + + 完整字句 + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Search.aspx.zh-tw.resx b/WebApplication/App_LocalResources/Search.aspx.zh-tw.resx new file mode 100644 index 0000000..2c30afe --- /dev/null +++ b/WebApplication/App_LocalResources/Search.aspx.zh-tw.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Search Files and Attachments + + + + + + Search in all Namespaces and all Categories + + + + + + Here you can search through the pages of this Namespace, their attachments and the files uploaded to the system.<br /><b>Note</b>: the results will only display the items you have permissions to read. + + + Go + + + + + + Uncategorized Pages + + + + + + Filter by Category + + + + + + Search Engine + + + + + + Title + + + All words + + + + + + At least one word + + + + + + Exact phrase + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/Upload.aspx.cs-CZ.resx new file mode 100644 index 0000000..b0cd5d3 --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.cs-CZ.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Zde můžete spravovat soubory a adresáře uložené ve Wiki. + + + Správa souborů + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.da-DK.resx b/WebApplication/App_LocalResources/Upload.aspx.da-DK.resx new file mode 100644 index 0000000..005c3c8 --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.da-DK.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Here you can manage files and directories stored in the Wiki. + + + Fil håndtering + + + Side uden titel + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.de-DE.resx b/WebApplication/App_LocalResources/Upload.aspx.de-DE.resx new file mode 100644 index 0000000..f134fe2 --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.de-DE.resx @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Here you can manage files and directories stored in the Wiki. + + + Dateimanagement + File Management + + + Unbenannte Seite + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.es-ES.resx b/WebApplication/App_LocalResources/Upload.aspx.es-ES.resx new file mode 100644 index 0000000..9cf4e97 --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.es-ES.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Here you can manage files and directories stored in the Wiki. + + + Administración de Archivos + + + Página sin titulo + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.fr-FR.resx b/WebApplication/App_LocalResources/Upload.aspx.fr-FR.resx new file mode 100644 index 0000000..34d4965 --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.fr-FR.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Here you can manage files and directories stored in the Wiki. + + + Gestion de Fichiers + + + Page sans titre + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.hu-HU.resx b/WebApplication/App_LocalResources/Upload.aspx.hu-HU.resx new file mode 100644 index 0000000..fd0cf91 --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.hu-HU.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Itt szerkesztheted a wiki fájljait, könyvtárait. + + + Fájl menedzsment + + + Cím nélküli oldal + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.it-IT.resx b/WebApplication/App_LocalResources/Upload.aspx.it-IT.resx new file mode 100644 index 0000000..b2b01ca --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.it-IT.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Gestione file e directory del wiki. + + + Gestione File + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.nb-NO.resx b/WebApplication/App_LocalResources/Upload.aspx.nb-NO.resx new file mode 100644 index 0000000..83be682 --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.nb-NO.resx @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Here you can manage files and directories stored in the Wiki. + + + Filhåndtering + File Management + + + Side uten tittel + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.nl-NL.resx b/WebApplication/App_LocalResources/Upload.aspx.nl-NL.resx new file mode 100644 index 0000000..fa64591 --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.nl-NL.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Here you can manage files and directories stored in the Wiki. + + + Bestandsbeheer + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.pl-PL.resx b/WebApplication/App_LocalResources/Upload.aspx.pl-PL.resx new file mode 100644 index 0000000..d355996 --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.pl-PL.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Here you can manage files and directories stored in the Wiki. + + + Zarządzanie plikami + + + Strona niezatytułowana + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.pt-BR.resx b/WebApplication/App_LocalResources/Upload.aspx.pt-BR.resx new file mode 100644 index 0000000..6d9df5e --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.pt-BR.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Here you can manage files and directories stored in the Wiki. + + + Gerenciamento de arquivos + + + Página sem título + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.resx b/WebApplication/App_LocalResources/Upload.aspx.resx new file mode 100644 index 0000000..7b70e10 --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Here you can manage files and directories stored in the Wiki. + + + File Management + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.ro-RO.resx b/WebApplication/App_LocalResources/Upload.aspx.ro-RO.resx new file mode 100644 index 0000000..28cafb1 --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.ro-RO.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Here you can manage files and directories stored in the Wiki. + + + Management fisiere + + + Pagina fara titlu + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.ru-RU.resx b/WebApplication/App_LocalResources/Upload.aspx.ru-RU.resx new file mode 100644 index 0000000..0c0f329 --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.ru-RU.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Место управления файлами и папками этой Wiki. + + + Управление файлами + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.sk-SK.resx b/WebApplication/App_LocalResources/Upload.aspx.sk-SK.resx new file mode 100644 index 0000000..09d4eeb --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.sk-SK.resx @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Here you can manage files and directories stored in the Wiki. + + + Správa súborov + File Management + + + Nepomenovaná stránka + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/Upload.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..69a6cdc --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.sr-Latn-CS.resx @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Here you can manage files and directories stored in the Wiki. + + + File Management + File Management + + + Neimenovana stranica + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.tr-TR.resx b/WebApplication/App_LocalResources/Upload.aspx.tr-TR.resx new file mode 100644 index 0000000..48ad323 --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.tr-TR.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Buradan Wiki'deki dosya ve sayfaları yönetebilirsiniz + + + Dosya Yönetimi + + + Başlıksız Sayfa + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.uk-UA.resx b/WebApplication/App_LocalResources/Upload.aspx.uk-UA.resx new file mode 100644 index 0000000..2149619 --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.uk-UA.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Місце керування файлами та папками цієї Wiki. + + + Керування файлами + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.zh-cn.resx b/WebApplication/App_LocalResources/Upload.aspx.zh-cn.resx new file mode 100644 index 0000000..523bc5a --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.zh-cn.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 在这里您可以管理存储在维基中的文件和目录。 + + + 文件管理 + + + 无标题页面 + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/Upload.aspx.zh-tw.resx b/WebApplication/App_LocalResources/Upload.aspx.zh-tw.resx new file mode 100644 index 0000000..007c439 --- /dev/null +++ b/WebApplication/App_LocalResources/Upload.aspx.zh-tw.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Here you can manage files and directories stored in the Wiki. + + + 文件管理 + + + Untitled Page + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.cs-CZ.resx b/WebApplication/App_LocalResources/User.aspx.cs-CZ.resx new file mode 100644 index 0000000..669085d --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.cs-CZ.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Poslat + + + Nedávné aktivity uživatele: + + + Můžete zaslat email pro uživatele.<b>Poznámka</b>: vaše emailová adresa nebude odhalena. + + + Zaslat zprávu + + + <i>Nejsou nedávné aktivity</i> + + + Předmět + + + ##NAME## - detail uživatele + + + Titulek + + + Je vyžadováno tělo + + + Je vyžadován předmět + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.da-DK.resx b/WebApplication/App_LocalResources/User.aspx.da-DK.resx new file mode 100644 index 0000000..8f44eb2 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.da-DK.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Send + + + + + + Recent activity by this user: + + + + + + You can send an email message to this user. <b>Note</b>: your email address won't be disclosed. + + + Send a Message + + + <i>No recent activity</i> + + + + + + + + + + + + Subject + + + ##NAME## - User's Details + + + Title + + + + + + + + + Body is required + + + + + + + + + Subject is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.de-DE.resx b/WebApplication/App_LocalResources/User.aspx.de-DE.resx new file mode 100644 index 0000000..8f44eb2 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.de-DE.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Send + + + + + + Recent activity by this user: + + + + + + You can send an email message to this user. <b>Note</b>: your email address won't be disclosed. + + + Send a Message + + + <i>No recent activity</i> + + + + + + + + + + + + Subject + + + ##NAME## - User's Details + + + Title + + + + + + + + + Body is required + + + + + + + + + Subject is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.es-ES.resx b/WebApplication/App_LocalResources/User.aspx.es-ES.resx new file mode 100644 index 0000000..8f44eb2 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.es-ES.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Send + + + + + + Recent activity by this user: + + + + + + You can send an email message to this user. <b>Note</b>: your email address won't be disclosed. + + + Send a Message + + + <i>No recent activity</i> + + + + + + + + + + + + Subject + + + ##NAME## - User's Details + + + Title + + + + + + + + + Body is required + + + + + + + + + Subject is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.fr-FR.resx b/WebApplication/App_LocalResources/User.aspx.fr-FR.resx new file mode 100644 index 0000000..8f44eb2 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.fr-FR.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Send + + + + + + Recent activity by this user: + + + + + + You can send an email message to this user. <b>Note</b>: your email address won't be disclosed. + + + Send a Message + + + <i>No recent activity</i> + + + + + + + + + + + + Subject + + + ##NAME## - User's Details + + + Title + + + + + + + + + Body is required + + + + + + + + + Subject is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.hu-HU.resx b/WebApplication/App_LocalResources/User.aspx.hu-HU.resx new file mode 100644 index 0000000..8f44eb2 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.hu-HU.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Send + + + + + + Recent activity by this user: + + + + + + You can send an email message to this user. <b>Note</b>: your email address won't be disclosed. + + + Send a Message + + + <i>No recent activity</i> + + + + + + + + + + + + Subject + + + ##NAME## - User's Details + + + Title + + + + + + + + + Body is required + + + + + + + + + Subject is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.it-IT.resx b/WebApplication/App_LocalResources/User.aspx.it-IT.resx new file mode 100644 index 0000000..a7af8bd --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.it-IT.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Invia + + + + + + Attività recenti di questo utente: + + + + + + Puoi inviare un messaggio a questo utente. <b>Nota</b>: il tuo indirizzo email non verrà rivelato. + + + Invia un messaggio + + + <i>Nessuna attività recente</i> + + + + + + + + + + + + Oggetto + + + ##NAME## - Dettagli utente + + + Titolo + + + + + + + + + Corpo richiesto + + + + + + + + + Oggetto richiesto + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.nb-NO.resx b/WebApplication/App_LocalResources/User.aspx.nb-NO.resx new file mode 100644 index 0000000..8f44eb2 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.nb-NO.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Send + + + + + + Recent activity by this user: + + + + + + You can send an email message to this user. <b>Note</b>: your email address won't be disclosed. + + + Send a Message + + + <i>No recent activity</i> + + + + + + + + + + + + Subject + + + ##NAME## - User's Details + + + Title + + + + + + + + + Body is required + + + + + + + + + Subject is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.nl-NL.resx b/WebApplication/App_LocalResources/User.aspx.nl-NL.resx new file mode 100644 index 0000000..8f44eb2 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.nl-NL.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Send + + + + + + Recent activity by this user: + + + + + + You can send an email message to this user. <b>Note</b>: your email address won't be disclosed. + + + Send a Message + + + <i>No recent activity</i> + + + + + + + + + + + + Subject + + + ##NAME## - User's Details + + + Title + + + + + + + + + Body is required + + + + + + + + + Subject is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.pl-PL.resx b/WebApplication/App_LocalResources/User.aspx.pl-PL.resx new file mode 100644 index 0000000..8f44eb2 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.pl-PL.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Send + + + + + + Recent activity by this user: + + + + + + You can send an email message to this user. <b>Note</b>: your email address won't be disclosed. + + + Send a Message + + + <i>No recent activity</i> + + + + + + + + + + + + Subject + + + ##NAME## - User's Details + + + Title + + + + + + + + + Body is required + + + + + + + + + Subject is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.pt-BR.resx b/WebApplication/App_LocalResources/User.aspx.pt-BR.resx new file mode 100644 index 0000000..8f44eb2 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.pt-BR.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Send + + + + + + Recent activity by this user: + + + + + + You can send an email message to this user. <b>Note</b>: your email address won't be disclosed. + + + Send a Message + + + <i>No recent activity</i> + + + + + + + + + + + + Subject + + + ##NAME## - User's Details + + + Title + + + + + + + + + Body is required + + + + + + + + + Subject is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.resx b/WebApplication/App_LocalResources/User.aspx.resx new file mode 100644 index 0000000..8f44eb2 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Send + + + + + + Recent activity by this user: + + + + + + You can send an email message to this user. <b>Note</b>: your email address won't be disclosed. + + + Send a Message + + + <i>No recent activity</i> + + + + + + + + + + + + Subject + + + ##NAME## - User's Details + + + Title + + + + + + + + + Body is required + + + + + + + + + Subject is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.ro-RO.resx b/WebApplication/App_LocalResources/User.aspx.ro-RO.resx new file mode 100644 index 0000000..8f44eb2 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.ro-RO.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Send + + + + + + Recent activity by this user: + + + + + + You can send an email message to this user. <b>Note</b>: your email address won't be disclosed. + + + Send a Message + + + <i>No recent activity</i> + + + + + + + + + + + + Subject + + + ##NAME## - User's Details + + + Title + + + + + + + + + Body is required + + + + + + + + + Subject is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.ru-RU.resx b/WebApplication/App_LocalResources/User.aspx.ru-RU.resx new file mode 100644 index 0000000..c4cb2b5 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.ru-RU.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Отправить + + + + + + Недавняя активность Пользователя: + + + + + + Вы имеете возможность отправить сообщение в адрес этого Пользователя. <b>Примечание</b>: Ваш email останется (ему) неизвестным. + + + Отправить Сообщение + + + <i>Активности не было</i> + + + + + + + + + + + + Тема + + + ##NAME## - пользовательские сведения + + + Название + + + + + + + + + Текст необходим + + + + + + + + + Тема сообщения обязательна + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.sk-SK.resx b/WebApplication/App_LocalResources/User.aspx.sk-SK.resx new file mode 100644 index 0000000..8f44eb2 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.sk-SK.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Send + + + + + + Recent activity by this user: + + + + + + You can send an email message to this user. <b>Note</b>: your email address won't be disclosed. + + + Send a Message + + + <i>No recent activity</i> + + + + + + + + + + + + Subject + + + ##NAME## - User's Details + + + Title + + + + + + + + + Body is required + + + + + + + + + Subject is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.sr-Latn-CS.resx b/WebApplication/App_LocalResources/User.aspx.sr-Latn-CS.resx new file mode 100644 index 0000000..8f44eb2 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.sr-Latn-CS.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Send + + + + + + Recent activity by this user: + + + + + + You can send an email message to this user. <b>Note</b>: your email address won't be disclosed. + + + Send a Message + + + <i>No recent activity</i> + + + + + + + + + + + + Subject + + + ##NAME## - User's Details + + + Title + + + + + + + + + Body is required + + + + + + + + + Subject is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.tr-TR.resx b/WebApplication/App_LocalResources/User.aspx.tr-TR.resx new file mode 100644 index 0000000..aee9c30 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.tr-TR.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Gönder + + + + + + Kullanıcının son zamanlardaki aktiviteleri: + + + + + + Kullanıcıya e-mali gönderebilirsiniz. <b>Not</b>: e-mail adresiniz ifşa edilmeyecek. + + + Mesaj Gönder + + + <i>Son zamanlarda bir aktivite yok</i> + + + + + + + + + + + + Konu + + + ##NAME## - Kullanıcı Detayları + + + Başlık + + + + + + + + + Mesaj gövdesi gereklidir + + + + + + + + + Konu gereklidir + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.uk-UA.resx b/WebApplication/App_LocalResources/User.aspx.uk-UA.resx new file mode 100644 index 0000000..52a8feb --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.uk-UA.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Надіслати + + + + + + Нещодавня активність Користувача: + + + + + + Ви маєте можливість надіслати цьому Користувачу email-повідомлення. <b>Примітка</b>: Ваша адреса залишиться (йому) невідомою. + + + Надіслати Повідомлення + + + <i>Активність відсутня</i> + + + + + + + + + + + + Тема + + + ##NAME## - відомості Користувача + + + Назва + + + + + + + + + Текст обов'язковий + + + + + + + + + Тема потрібна + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.zh-cn.resx b/WebApplication/App_LocalResources/User.aspx.zh-cn.resx new file mode 100644 index 0000000..f414f6f --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.zh-cn.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 发送 + + + + + + 此用户最近的活动: + + + + + + 您可以给此用户发送电子邮件。<b>注意</b>:您的电子邮件地址不会被公开。 + + + 发送消息 + + + <i>无最近活动</i> + + + + + + + + + + + + 主题 + + + ##NAME## - 用户详情 + + + 标题 + + + + + + + + + 需要内容 + + + + + + + + + 需要主题 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/App_LocalResources/User.aspx.zh-tw.resx b/WebApplication/App_LocalResources/User.aspx.zh-tw.resx new file mode 100644 index 0000000..8f44eb2 --- /dev/null +++ b/WebApplication/App_LocalResources/User.aspx.zh-tw.resx @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Send + + + + + + Recent activity by this user: + + + + + + You can send an email message to this user. <b>Note</b>: your email address won't be disclosed. + + + Send a Message + + + <i>No recent activity</i> + + + + + + + + + + + + Subject + + + ##NAME## - User's Details + + + Title + + + + + + + + + Body is required + + + + + + + + + Subject is required + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebApplication/AttachmentManager.ascx b/WebApplication/AttachmentManager.ascx new file mode 100644 index 0000000..e11d73c --- /dev/null +++ b/WebApplication/AttachmentManager.ascx @@ -0,0 +1,110 @@ +<%@ Control Language="C#" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.AttachmentManager" Codebehind="AttachmentManager.ascx.cs" %> + + + + + +
    + +
    + + +
    + + +
    +

    +
    +
    + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      
    -<%# Eval("Name") %><%# Eval("Size") %><%# Eval("Downloads") %> + + • + + <%# ((bool)Eval("Editable") ? "• " + ScrewTurn.Wiki.Properties.Messages.Edit + "" : "")%> +
    -<%# Eval("Name") %><%# Eval("Size") %><%# Eval("Downloads") %> + + • + + <%# ((bool)Eval("Editable") ? "• " + ScrewTurn.Wiki.Properties.Messages.Edit + "" : "")%> +
    + +
    + +
    + +
    +

    +
    +
    + + + + + + + +
    + +
    diff --git a/WebApplication/AttachmentManager.ascx.cs b/WebApplication/AttachmentManager.ascx.cs new file mode 100644 index 0000000..7469aef --- /dev/null +++ b/WebApplication/AttachmentManager.ascx.cs @@ -0,0 +1,299 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class AttachmentManager : System.Web.UI.UserControl { + + private IFilesStorageProviderV30 provider; + + private bool canDownload = false; + private bool canUpload = false; + private bool canDelete = false; + private bool isAdmin = false; + + protected void Page_Load(object sender, EventArgs e) { + + if(!Page.IsPostBack) { + // Localized strings for JavaScript + StringBuilder sb = new StringBuilder(); + sb.Append(@"\n"); + lblStrings.Text = sb.ToString(); + + // Setup upload information (max file size, allowed file types) + lblUploadFilesInfo.Text = lblUploadFilesInfo.Text.Replace("$1", Tools.BytesToString(Settings.MaxFileSize * 1024)); + sb = new StringBuilder(); + string[] aft = Settings.AllowedFileTypes; + for(int i = 0; i < aft.Length; i++) { + sb.Append(aft[i].ToUpper()); + if(i != aft.Length - 1) sb.Append(", "); + } + lblUploadFilesInfo.Text = lblUploadFilesInfo.Text.Replace("$2", sb.ToString()); + + // Load Providers + foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) { + ListItem item = new ListItem(prov.Information.Name, prov.GetType().FullName); + if(item.Value == Settings.DefaultFilesProvider) { + item.Selected = true; + } + lstProviders.Items.Add(item); + } + + if(CurrentPage == null) btnUpload.Enabled = false; + } + + // Set provider + provider = Collectors.FilesProviderCollector.GetProvider(lstProviders.SelectedValue); + + if(!Page.IsPostBack) { + rptItems.DataBind(); + } + + DetectPermissions(); + + // Setup buttons and controls + if(!canUpload) { + btnUpload.Enabled = false; + } + if(!canDelete) { + chkOverwrite.Enabled = false; + } + } + + /// + /// Detects the permissions of the current user. + /// + private void DetectPermissions() { + if(CurrentPage != null) { + string currentUser = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + canDownload = AuthChecker.CheckActionForPage(CurrentPage, Actions.ForPages.DownloadAttachments, currentUser, currentGroups); + canUpload = AuthChecker.CheckActionForPage(CurrentPage, Actions.ForPages.UploadAttachments, currentUser, currentGroups); + canDelete = AuthChecker.CheckActionForPage(CurrentPage, Actions.ForPages.DeleteAttachments, currentUser, currentGroups); + isAdmin = Array.Find(currentGroups, delegate(string g) { return g == Settings.AdministratorsGroup; }) != null; + } + else { + canDownload = false; + canUpload = false; + canDelete = false; + isAdmin = false; + } + lstProviders.Visible = isAdmin; + } + + /// + /// Gets or sets the PageInfo object. + /// + /// This property must be set at page load. + public PageInfo CurrentPage { + get { return Pages.FindPage(ViewState["CP"] as string); } + set { + if(value == null) ViewState["CP"] = null; + else ViewState["CP"] = value.FullName; + btnUpload.Enabled = value != null; + lblNoUpload.Visible = !btnUpload.Enabled; + DetectPermissions(); + rptItems.DataBind(); + } + } + + protected void rptItems_DataBinding(object sender, EventArgs e) { + provider = Collectors.FilesProviderCollector.GetProvider(lstProviders.SelectedValue); + + if(provider == null || CurrentPage == null) { + return; + } + + // Build a DataTable containing the proper information + DataTable table = new DataTable("Items"); + + table.Columns.Add("Name"); + table.Columns.Add("Size"); + table.Columns.Add("Editable", typeof(bool)); + table.Columns.Add("Page"); + table.Columns.Add("Link"); + table.Columns.Add("Downloads"); + table.Columns.Add("CanDelete", typeof(bool)); + table.Columns.Add("CanDownload", typeof(bool)); + + string[] attachments = provider.ListPageAttachments(CurrentPage); + foreach(string s in attachments) { + FileDetails details = provider.GetPageAttachmentDetails(CurrentPage, s); + + DataRow row = table.NewRow(); + string ext = Path.GetExtension(s).ToLowerInvariant(); + row["Name"] = s; + row["Size"] = Tools.BytesToString(details.Size); + row["Editable"] = canUpload && canDelete && (ext == ".jpg" || ext == ".jpeg" || ext == ".png"); + row["Page"] = CurrentPage.FullName; + if(canDownload) { + row["Link"] = "GetFile.aspx?File=" + Tools.UrlEncode(s) + "&AsStreamAttachment=1&Provider=" + + provider.GetType().FullName + "&IsPageAttachment=1&Page=" + + Tools.UrlEncode(CurrentPage.FullName) + "&NoHit=1"; + } + else { + row["Link"] = ""; + } + row["Downloads"] = details.RetrievalCount.ToString(); + row["CanDelete"] = canDelete; + row["CanDownload"] = canDownload; + table.Rows.Add(row); + } + + rptItems.DataSource = table; + } + + protected void btnRefresh_Click(object sender, EventArgs e) { + rptItems.DataBind(); + } + + protected void btnUpload_Click(object sender, EventArgs e) { + if(canUpload) { + lblUploadResult.Text = ""; + if(fileUpload.HasFile) { + if(fileUpload.FileBytes.Length > Settings.MaxFileSize * 1024) { + lblUploadResult.Text = Properties.Messages.FileTooBig; + lblUploadResult.CssClass = "resulterror"; + } + else { + // Check file extension + string[] aft = Settings.AllowedFileTypes; + bool allowed = false; + + if(aft.Length > 0 && aft[0] == "*") allowed = true; + else { + string ext = Path.GetExtension(fileUpload.FileName); + if(ext == null) ext = ""; + if(ext.StartsWith(".")) ext = ext.Substring(1).ToLowerInvariant(); + foreach(string ft in aft) { + if(ft == ext) { + allowed = true; + break; + } + } + } + + if(!allowed) { + lblUploadResult.Text = Properties.Messages.InvalidFileType; + lblUploadResult.CssClass = "resulterror"; + } + else { + // Store attachment + bool done = provider.StorePageAttachment(CurrentPage, fileUpload.FileName, fileUpload.FileContent, chkOverwrite.Checked); + if(!done) { + lblUploadResult.Text = Properties.Messages.CannotStoreFile; + lblUploadResult.CssClass = "resulterror"; + } + else { + Host.Instance.OnAttachmentActivity(provider.GetType().FullName, + fileUpload.FileName, CurrentPage.FullName, null, FileActivity.AttachmentUploaded); + } + rptItems.DataBind(); + } + } + } + else { + lblUploadResult.Text = Properties.Messages.FileVoid; + lblUploadResult.CssClass = "resulterror"; + } + } + } + + protected void lstProviders_SelectedIndexChanged(object sender, EventArgs e) { + provider = Collectors.FilesProviderCollector.GetProvider(lstProviders.SelectedValue); + rptItems.DataBind(); + } + + protected void rptItems_ItemCommand(object sender, RepeaterCommandEventArgs e) { + // Raised when a ButtonField is clicked + + switch(e.CommandName) { + case "Rename": + if(canDelete) { + pnlRename.Visible = true; + lblItem.Text = (string)e.CommandArgument; + txtNewName.Text = (string)e.CommandArgument; + rptItems.Visible = false; + } + break; + case "Delete": + if(canDelete) { + // Delete Attachment + bool d = provider.DeletePageAttachment(CurrentPage, (string)e.CommandArgument); + + if(d) { + Host.Instance.OnAttachmentActivity(provider.GetType().FullName, + (string)e.CommandArgument, CurrentPage.FullName, null, FileActivity.AttachmentDeleted); + } + + rptItems.DataBind(); + } + break; + } + } + + protected void btnRename_Click(object sender, EventArgs e) { + if(canDelete) { + lblRenameResult.Text = ""; + + // Ensure that the extension is not changed (security) + string previousExtension = Path.GetExtension(lblItem.Text); + string newExtension = Path.GetExtension(txtNewName.Text); + if(string.IsNullOrEmpty(newExtension)) { + newExtension = previousExtension; + txtNewName.Text += previousExtension; + } + + if(newExtension.ToLowerInvariant() != previousExtension.ToLowerInvariant()) { + txtNewName.Text += previousExtension; + } + + bool done = true; + if(txtNewName.Text.ToLowerInvariant() != lblItem.Text.ToLowerInvariant()) { + done = provider.RenamePageAttachment(CurrentPage, lblItem.Text, txtNewName.Text); + } + + if(done) { + pnlRename.Visible = false; + rptItems.Visible = true; + rptItems.DataBind(); + + Host.Instance.OnAttachmentActivity(provider.GetType().FullName, + txtNewName.Text, CurrentPage.FullName, lblItem.Text, FileActivity.AttachmentRenamed); + } + else { + lblRenameResult.Text = Properties.Messages.CannotRenameItem; + lblRenameResult.CssClass = "resulterror"; + } + } + } + + protected void btnCancel_Click(object sender, EventArgs e) { + pnlRename.Visible = false; + rptItems.Visible = true; + lblRenameResult.Text = ""; + } + + } + +} diff --git a/WebApplication/AttachmentManager.ascx.designer.cs b/WebApplication/AttachmentManager.ascx.designer.cs new file mode 100644 index 0000000..aacea16 --- /dev/null +++ b/WebApplication/AttachmentManager.ascx.designer.cs @@ -0,0 +1,169 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4016 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AttachmentManager { + + /// + /// lblStrings control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblStrings; + + /// + /// lstProviders control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstProviders; + + /// + /// btnRefresh control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.LinkButton btnRefresh; + + /// + /// pnlRename control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlRename; + + /// + /// lblRename control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRename; + + /// + /// lblItem control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblItem; + + /// + /// txtNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtNewName; + + /// + /// btnRename control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnRename; + + /// + /// btnCancel control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnCancel; + + /// + /// lblRenameResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblRenameResult; + + /// + /// rptItems control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Repeater rptItems; + + /// + /// lblUploadFilesInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblUploadFilesInfo; + + /// + /// chkOverwrite control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CheckBox chkOverwrite; + + /// + /// fileUpload control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.FileUpload fileUpload; + + /// + /// btnUpload control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnUpload; + + /// + /// lblNoUpload control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblNoUpload; + + /// + /// lblUploadResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblUploadResult; + } +} diff --git a/WebApplication/AttachmentViewer.ascx b/WebApplication/AttachmentViewer.ascx new file mode 100644 index 0000000..890b014 --- /dev/null +++ b/WebApplication/AttachmentViewer.ascx @@ -0,0 +1,34 @@ +<%@ Control Language="C#" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.AttachmentViewer" Codebehind="AttachmentViewer.ascx.cs" %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     
    -<%# Eval("Name") %><%# Eval("Size") %>
    -<%# Eval("Name") %><%# Eval("Size") %>
    + +
    diff --git a/WebApplication/AttachmentViewer.ascx.cs b/WebApplication/AttachmentViewer.ascx.cs new file mode 100644 index 0000000..70155b3 --- /dev/null +++ b/WebApplication/AttachmentViewer.ascx.cs @@ -0,0 +1,63 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Collections.Generic; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class AttachmentViewer : System.Web.UI.UserControl { + + private PageInfo pageInfo; + + protected void Page_Load(object sender, EventArgs e) { + if(!Page.IsPostBack) { + rptItems.DataBind(); + } + } + + /// + /// Gets or sets the PageInfo object. + /// + /// This property must be set at page load. + public PageInfo PageInfo { + get { return pageInfo; } + set { pageInfo = value; } + } + + protected void rptItems_DataBinding(object sender, EventArgs e) { + if(pageInfo == null) return; + + // Build a DataTable containing the proper information + DataTable table = new DataTable("Items"); + + table.Columns.Add("Name"); + table.Columns.Add("Size"); + table.Columns.Add("Link"); + + foreach(IFilesStorageProviderV30 provider in Collectors.FilesProviderCollector.AllProviders) { + string[] attachments = provider.ListPageAttachments(pageInfo); + foreach(string s in attachments) { + DataRow row = table.NewRow(); + row["Name"] = s; + row["Size"] = Tools.BytesToString(provider.GetPageAttachmentDetails(pageInfo, s).Size); + row["Link"] = "GetFile.aspx?File=" + Tools.UrlEncode(s) + "&AsStreamAttachment=1&Provider=" + + provider.GetType().FullName + "&IsPageAttachment=1&Page=" + Tools.UrlEncode(pageInfo.FullName); + table.Rows.Add(row); + } + } + + rptItems.DataSource = table; + } + + } + +} diff --git a/WebApplication/AttachmentViewer.ascx.designer.cs b/WebApplication/AttachmentViewer.ascx.designer.cs new file mode 100644 index 0000000..95f1fee --- /dev/null +++ b/WebApplication/AttachmentViewer.ascx.designer.cs @@ -0,0 +1,25 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4016 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class AttachmentViewer { + + /// + /// rptItems control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Repeater rptItems; + } +} diff --git a/WebApplication/Captcha.ascx b/WebApplication/Captcha.ascx new file mode 100644 index 0000000..b404b24 --- /dev/null +++ b/WebApplication/Captcha.ascx @@ -0,0 +1,10 @@ +<%@ Control Language="C#" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.Captcha" Codebehind="Captcha.ascx.cs" %> + +
    +
    + + * + * +
    \ No newline at end of file diff --git a/WebApplication/Captcha.ascx.cs b/WebApplication/Captcha.ascx.cs new file mode 100644 index 0000000..e1e35fd --- /dev/null +++ b/WebApplication/Captcha.ascx.cs @@ -0,0 +1,50 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; + +namespace ScrewTurn.Wiki { + + public partial class Captcha : UserControl { + + protected void Page_Load(object sender, EventArgs e) { + if(!Page.IsPostBack) { + rfvCaptcha.ErrorMessage = Properties.Messages.RequiredField; + rfvCaptcha.ToolTip = Properties.Messages.RequiredField; + cvCaptcha.ErrorMessage = Properties.Messages.WrongControlText; + cvCaptcha.ToolTip = Properties.Messages.WrongControlText; + } + + if(!Page.IsPostBack) { + // Generate captcha string + Random r = new Random(); + string c = ""; + c += (char)r.Next(49, 58); // 1 - 9 (not 0) + c += (char)r.Next(65, 79); // A - N (not O) + c += (char)r.Next(97, 111); // a - n (not o) + c += (char)r.Next(49, 58); // 1 - 9 (not 0) + c += (char)r.Next(80, 91); // P - Z + c += (char)r.Next(112, 123); // p - z + Session["__Captcha"] = c; + } + } + + protected void cvCaptcha_ServerValidate(object source, ServerValidateEventArgs args) { + if(!Settings.DisableCaptchaControl) { + args.IsValid = txtCaptcha.Text == (string)Session["__Captcha"]; + } + else { + args.IsValid = true; + } + } + + } + +} diff --git a/WebApplication/Captcha.ascx.designer.cs b/WebApplication/Captcha.ascx.designer.cs new file mode 100644 index 0000000..c792169 --- /dev/null +++ b/WebApplication/Captcha.ascx.designer.cs @@ -0,0 +1,52 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4016 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class Captcha { + + /// + /// imgCaptcha control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Image imgCaptcha; + + /// + /// txtCaptcha control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtCaptcha; + + /// + /// rfvCaptcha control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RequiredFieldValidator rfvCaptcha; + + /// + /// cvCaptcha control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CustomValidator cvCaptcha; + } +} diff --git a/WebApplication/Captcha.aspx b/WebApplication/Captcha.aspx new file mode 100644 index 0000000..410a270 --- /dev/null +++ b/WebApplication/Captcha.aspx @@ -0,0 +1 @@ +<%@ Page Language="C#" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.CaptchaPage" Codebehind="Captcha.aspx.cs" %> diff --git a/WebApplication/Captcha.aspx.cs b/WebApplication/Captcha.aspx.cs new file mode 100644 index 0000000..1840933 --- /dev/null +++ b/WebApplication/Captcha.aspx.cs @@ -0,0 +1,52 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using System.Drawing; + +namespace ScrewTurn.Wiki { + + // No BasePage because compression/language selection are not needed + public partial class CaptchaPage : Page { + + protected void Page_Load(object sender, EventArgs e) { + Response.Clear(); + Response.ContentType = "image/jpeg"; + + string s = (string)Session["__Captcha"]; + + Font f = new Font("Times New Roman", 30, FontStyle.Italic | FontStyle.Bold, GraphicsUnit.Pixel); + StringFormat format = new StringFormat(); + format.Alignment = StringAlignment.Center; + format.LineAlignment = StringAlignment.Center; + + Bitmap bmp = new Bitmap(150, 40, System.Drawing.Imaging.PixelFormat.Format24bppRgb); + Graphics g = Graphics.FromImage(bmp); + g.Clear(Color.White); + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; + + g.DrawString(s, f, Brushes.Black, new RectangleF(0, 0, bmp.Width, bmp.Height), format); + + for(int i = -2; i < bmp.Width / 10; i++) { + g.DrawLine(Pens.OrangeRed, i * 10, 0, i * 10 + 20, bmp.Height); + } + for(int i = -2; i < bmp.Width / 10 + 10; i++) { + g.DrawLine(Pens.Blue, i * 10, 0, i * 10 - 60, bmp.Height); + } + + g.Dispose(); + + bmp.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg); + } + + } + +} diff --git a/WebApplication/Captcha.aspx.designer.cs b/WebApplication/Captcha.aspx.designer.cs new file mode 100644 index 0000000..7975fc9 --- /dev/null +++ b/WebApplication/Captcha.aspx.designer.cs @@ -0,0 +1,16 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.1434 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class CaptchaPage { + } +} diff --git a/WebApplication/Category.aspx b/WebApplication/Category.aspx new file mode 100644 index 0000000..c069c27 --- /dev/null +++ b/WebApplication/Category.aspx @@ -0,0 +1,10 @@ +<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.Category" Title="Untitled Page" Culture="auto" meta:resourcekey="PageResource1" UICulture="auto" Codebehind="Category.aspx.cs" %> + + + +

    +

    +
    + + +
    diff --git a/WebApplication/Category.aspx.cs b/WebApplication/Category.aspx.cs new file mode 100644 index 0000000..182da73 --- /dev/null +++ b/WebApplication/Category.aspx.cs @@ -0,0 +1,96 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Collections.Generic; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class Category : BasePage { + + private NamespaceInfo currentNamespace = null; + + protected void Page_Load(object sender, EventArgs e) { + Page.Title = Properties.Messages.CategoryTitle + " - " + Settings.WikiTitle; + + LoginTools.VerifyReadPermissionsForCurrentNamespace(); + + currentNamespace = DetectNamespaceInfo(); + + PrintCat(); + } + + public void PrintCat() { + StringBuilder sb = new StringBuilder(); + sb.Append("
    "); + + sb.Append(""); + lblCatList.Text = sb.ToString(); + } + + } + +} diff --git a/WebApplication/Category.aspx.designer.cs b/WebApplication/Category.aspx.designer.cs new file mode 100644 index 0000000..78b11a6 --- /dev/null +++ b/WebApplication/Category.aspx.designer.cs @@ -0,0 +1,43 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.3074 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class Category { + + /// + /// lblCategories control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCategories; + + /// + /// lblDescription control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDescription; + + /// + /// lblCatList control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCatList; + } +} diff --git a/WebApplication/ClientImageBrowser.ascx b/WebApplication/ClientImageBrowser.ascx new file mode 100644 index 0000000..d7788c2 --- /dev/null +++ b/WebApplication/ClientImageBrowser.ascx @@ -0,0 +1,20 @@ +<%@ Control Language="C#" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.ClientImageBrowser" Codebehind="ClientImageBrowser.ascx.cs" %> + + + + + + diff --git a/WebApplication/ClientImageBrowser.ascx.cs b/WebApplication/ClientImageBrowser.ascx.cs new file mode 100644 index 0000000..4b487e9 --- /dev/null +++ b/WebApplication/ClientImageBrowser.ascx.cs @@ -0,0 +1,157 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Collections.Generic; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using System.Text; + +namespace ScrewTurn.Wiki { + + public partial class ClientImageBrowser : System.Web.UI.UserControl { + + private const string ClientBrowserItems = "ClientBrowserItems"; + + private string leafCssClass = ""; + private string nodeCssClass = ""; + private string nodeContent = ""; + private string upCssClass = ""; + private string upLevelContent = ""; + + protected void Page_Load(object sender, EventArgs e) { + Render(); + } + + /// + /// Removes all the items in the browser and re-populates it. + /// + public void PopulateBrowser() { + if(Populate == null) ViewState[ClientBrowserItems] = new List(); + else ViewState[ClientBrowserItems] = Populate(this, new PopulateEventArgs()); + + Render(); + } + + private void Render() { + lblStrings.Text = string.Format("", BuildSubTreeContainerID(0)); + + List items = (List)ViewState[ClientBrowserItems]; + if(items == null) return; + + StringBuilder sb = new StringBuilder(); + sb.Append(@"
    "); + int iteration = 0; + RenderSubTree(items, sb, ref iteration, BuildSubTreeContainerID(0)); + sb.Append("
    "); + lblContent.Text = sb.ToString(); + } + + private void RenderSubTree(List items, StringBuilder sb, ref int iteration, string parentId) { + // This method generates the client markup and JavaScript that contains a tree of images + // The sub-trees are NOT rendered as nested elements + + StringBuilder temp = new StringBuilder(); + + string id = BuildSubTreeContainerID(iteration); + + sb.AppendFormat(@"
    ", id, (iteration != 0 ? @" style=""display: none;""" : "")); + + if(iteration != 0) { + sb.AppendFormat(@"", + upCssClass, parentId, Properties.Messages.UpLevel, upLevelContent + Properties.Messages.UpLevel); + } + + foreach(TreeElement item in items) { + iteration++; // Before invoking RenderSubTree recursively! + // Render item + RenderItem(item, sb, iteration); + if(item.SubItems.Count > 0) { + RenderSubTree(item.SubItems, temp, ref iteration, id); + } + } + + sb.Append("
    "); + + sb.Append(temp.ToString()); + } + + private void RenderItem(TreeElement item, StringBuilder sb, int iteration) { + if(item.SubItems.Count > 0) { + // Expanding link + string containerId = BuildSubTreeContainerID(iteration); + sb.AppendFormat(@"", + nodeCssClass, containerId, item.Name, nodeContent + item.Text); + } + else { + // Action link + sb.AppendFormat(@"", + leafCssClass, item.OnClientClick, item.Name, item.Text); + } + } + + private string BuildSubTreeContainerID(int iteration) { + return string.Format("sub_{0}_{1}", ID, iteration); + } + + /// + /// Gets or sets the CSS Class for leaf items. + /// + public string LeafCssClass { + get { return leafCssClass; } + set { leafCssClass = value; } + } + + /// + /// Gets or sets the CSS Class for folder items. + /// + public string NodeCssClass { + get { return nodeCssClass; } + set { nodeCssClass = value; } + } + + /// + /// Gets or sets the content for folder items. + /// + public string NodeContent { + get { return nodeContent; } + set { nodeContent = value; } + } + + /// + /// Gets or sets the CSS Class for "Up one level" nodes. + /// + public string UpCssClass { + get { return upCssClass; } + set { upCssClass = value; } + } + + /// + /// Gets or sets the content for "Up one level" nodes. + /// + public string UpLevelContent { + get { return upLevelContent; } + set { upLevelContent = value; } + } + + /// + /// Delegate used for handling the Populate event. + /// + /// The object that fired the event. + /// The event arguments. + /// A list of items contained in the expanded sub-tree. + public delegate List PopulateEventHandler(object sender, PopulateEventArgs e); + + /// + /// Occurs when a sub-tree is populated. + /// + public event PopulateEventHandler Populate; + + } + +} diff --git a/WebApplication/ClientImageBrowser.ascx.designer.cs b/WebApplication/ClientImageBrowser.ascx.designer.cs new file mode 100644 index 0000000..c6b0693 --- /dev/null +++ b/WebApplication/ClientImageBrowser.ascx.designer.cs @@ -0,0 +1,34 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.1434 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class ClientImageBrowser { + + /// + /// lblStrings control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblStrings; + + /// + /// lblContent control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblContent; + } +} diff --git a/WebApplication/ClientTree.ascx b/WebApplication/ClientTree.ascx new file mode 100644 index 0000000..a0fdc28 --- /dev/null +++ b/WebApplication/ClientTree.ascx @@ -0,0 +1,14 @@ +<%@ Control Language="C#" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.ClientTree" Codebehind="ClientTree.ascx.cs" %> + + + + diff --git a/WebApplication/ClientTree.ascx.cs b/WebApplication/ClientTree.ascx.cs new file mode 100644 index 0000000..3ed3d4a --- /dev/null +++ b/WebApplication/ClientTree.ascx.cs @@ -0,0 +1,118 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Collections.Generic; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using System.Text; + +namespace ScrewTurn.Wiki { + + public partial class ClientTree : System.Web.UI.UserControl { + + private const string ClientTreeItems = "ClientTreeItems"; + + private string leafCssClass = ""; + private string nodeCssClass = ""; + private string containerCssClass = ""; + + protected void Page_Load(object sender, EventArgs e) { + Render(); + } + + /// + /// Removes all the items in the tree and re-populates it. + /// + public void PopulateTree() { + // Use ViewState to cache data + if(Populate == null) ViewState[ClientTreeItems] = new List(); + else ViewState[ClientTreeItems] = Populate(this, new PopulateEventArgs()); + + Render(); + } + + private void Render() { + List items = (List)ViewState[ClientTreeItems]; + if(items == null) return; + + StringBuilder sb = new StringBuilder(); + sb.Append(@"
    "); + int iteration = 0; + RenderSubTree(items, sb, ref iteration); + sb.Append("
    "); + lblContent.Text = sb.ToString(); + } + + private void RenderSubTree(List items, StringBuilder sb, ref int iteration) { + // This method generates the client markup and JavaScript that contains a tree of items + // The sub-trees are rendered as nested elements (DIVs) + foreach(TreeElement item in items) { + iteration++; // Before invoking RenderSubTree recursively! + // Render item + if(item.SubItems.Count > 0) { + // Expanding link + string containerId = BuildSubTreeContainerID(iteration); + sb.AppendFormat(@"{3}", + nodeCssClass, containerId, item.Name, item.Text); + sb.AppendFormat(@"
    ", containerId, containerCssClass); + RenderSubTree(item.SubItems, sb, ref iteration); + sb.Append("
    "); + } + else { + // Action link + sb.AppendFormat(@"{3}", + leafCssClass, item.OnClientClick, item.Name, item.Text); + } + } + } + + private string BuildSubTreeContainerID(int iteration) { + return string.Format("sub_{0}_{1}", ID, iteration); + } + + /// + /// Gets or sets the CSS Class for leaf items. + /// + public string LeafCssClass { + get { return leafCssClass; } + set { leafCssClass = value; } + } + + /// + /// Gets or sets the CSS Class for folder items. + /// + public string NodeCssClass { + get { return nodeCssClass; } + set { nodeCssClass = value; } + } + + /// + /// Gets or sets the CSS Class for containers. + /// + public string ContainerCssClass { + get { return containerCssClass; } + set { containerCssClass = value; } + } + + /// + /// Delegate used for handling the Populate event. + /// + /// The object that fired the event. + /// The event arguments. + /// A list of items contained in the expanded sub-tree. + public delegate List PopulateEventHandler(object sender, PopulateEventArgs e); + + /// + /// Occurs when a sub-tree is populated. + /// + public event PopulateEventHandler Populate; + + } + +} diff --git a/WebApplication/ClientTree.ascx.designer.cs b/WebApplication/ClientTree.ascx.designer.cs new file mode 100644 index 0000000..4c1269e --- /dev/null +++ b/WebApplication/ClientTree.ascx.designer.cs @@ -0,0 +1,25 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.1434 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class ClientTree { + + /// + /// lblContent control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblContent; + } +} diff --git a/WebApplication/Code/BasePage.cs b/WebApplication/Code/BasePage.cs new file mode 100644 index 0000000..aa00ec6 --- /dev/null +++ b/WebApplication/Code/BasePage.cs @@ -0,0 +1,120 @@ + +using System; +using System.Configuration; +using System.Globalization; +using System.IO.Compression; +using System.Threading; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public class BasePage : Page { + + public BasePage() { + } + + protected override void OnInit(EventArgs e) { + base.OnInit(e); + + // Mitigate Cross-Site Request Forgery (CSRF/XSRF) attacks + ViewStateUserKey = Session.SessionID; + } + + protected override void OnLoad(EventArgs e) { + base.OnLoad(e); + + // Bypass compression if the current request was made by Anthem.NET + if(HttpContext.Current.Request["Anthem_CallBack"] != null) return; + + // Request might not be initialized -> use HttpContext + string ua = HttpContext.Current.Request.UserAgent != null ? HttpContext.Current.Request.UserAgent.ToLowerInvariant() : ""; + if(Settings.EnableHttpCompression && !ua.Contains("konqueror") && !ua.Contains("safari")) { + if(Request.Headers["Accept-encoding"] != null && Request.Headers["Accept-encoding"].Contains("gzip")) { + Response.Filter = new GZipStream(Response.Filter, CompressionMode.Compress, true); + Response.AppendHeader("Content-encoding", "gzip"); + Response.AppendHeader("Vary", "Content-encoding"); + //Response.Write("HTTP Compression Enabled (GZip)"); + } + else if(Request.Headers["Accept-encoding"] != null && Request.Headers["Accept-encoding"].Contains("deflate")) { + Response.Filter = new DeflateStream(Response.Filter, CompressionMode.Compress, true); + Response.AppendHeader("Content-encoding", "deflate"); + Response.AppendHeader("Vary", "Content-encoding"); + //Response.Write("HTTP Compression Enabled (Deflate)"); + } + } + } + + protected override void InitializeCulture() { + // First, look for hard-stored user preferences + // If they are not available, look at the cookie + + string culture = Preferences.LoadLanguageFromUserData(); + if(culture == null) culture = Preferences.LoadLanguageFromCookie(); + + if(culture != null) { + Thread.CurrentThread.CurrentCulture = new CultureInfo(culture); + Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture); + } + else { + try { + if(Settings.DefaultLanguage.Equals("-")) { + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); + Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US"); + } + else { + Thread.CurrentThread.CurrentCulture = new CultureInfo(Settings.DefaultLanguage); + Thread.CurrentThread.CurrentUICulture = new CultureInfo(Settings.DefaultLanguage); + } + } + catch { + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); + Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US"); + } + } + //Response.Write("Culture: " + Thread.CurrentThread.CurrentCulture.Name + "
    "); + //Response.Write("UICulture: " + Thread.CurrentThread.CurrentUICulture.Name + "
    "); + } + + /// + /// Detects the correct object associated to the current page using the Page and NS parameters in the query string. + /// + /// true to load the default page of the specified namespace when Page is not specified, false otherwise. + /// If Page is specified and exists, the correct , otherwise null if loadDefault is false, + /// or the object representing the default page of the specified namespace if loadDefault is true. + protected PageInfo DetectPageInfo(bool loadDefault) { + return Tools.DetectCurrentPageInfo(loadDefault); + } + + /// + /// Detects the full name of the current page using the Page and NS parameters in the query string. + /// + /// The full name of the page, regardless of the existence of the page. + protected string DetectFullName() { + return Tools.DetectCurrentFullName(); + } + + /// + /// Detects the correct object associated to the current namespace using the NS parameter in the query string. + /// + /// The correct object, or null. + protected NamespaceInfo DetectNamespaceInfo() { + return Tools.DetectCurrentNamespaceInfo(); + } + + /// + /// Detects the name of the current namespace using the NS parameter in the query string. + /// + /// The name of the namespace, or an empty string. + protected string DetectNamespace() { + return Tools.DetectCurrentNamespace(); + } + + } + +} diff --git a/WebApplication/Code/Compressor.cs b/WebApplication/Code/Compressor.cs new file mode 100644 index 0000000..ec6de57 --- /dev/null +++ b/WebApplication/Code/Compressor.cs @@ -0,0 +1,56 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; + +namespace ScrewTurn.Wiki { + + /// + /// Allows to compress and decompress byte streams in memory. + /// + public static class Compressor { + + /// + /// Compresses data. + /// + /// The data to compress. + /// The compressed data. + public static byte[] Compress(byte[] data) { + using(MemoryStream output = new MemoryStream()) { + using(GZipStream gzip = new GZipStream(output, CompressionMode.Compress, true)) { + gzip.Write(data, 0, data.Length); + gzip.Close(); + } + return output.ToArray(); + } + } + + /// + /// Decompresses data. + /// + /// The data to decompress. + /// The decompressed data. + public static byte[] Decompress(byte[] data) { + using(MemoryStream input = new MemoryStream()) { + input.Write(data, 0, data.Length); + input.Position = 0; + using(GZipStream gzip = new GZipStream(input, CompressionMode.Decompress, true)) { + using(MemoryStream output = new MemoryStream()) { + byte[] buff = new byte[64]; + int read = -1; + read = gzip.Read(buff, 0, buff.Length); + while(read > 0) { + output.Write(buff, 0, read); + read = gzip.Read(buff, 0, buff.Length); + } + gzip.Close(); + return output.ToArray(); + } + } + } + } + + } + +} diff --git a/WebApplication/Code/ExtendedPageInfo.cs b/WebApplication/Code/ExtendedPageInfo.cs new file mode 100644 index 0000000..c9f7763 --- /dev/null +++ b/WebApplication/Code/ExtendedPageInfo.cs @@ -0,0 +1,85 @@ + +using System; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Contains extended information about a Page. + /// + public class ExtendedPageInfo { + + private PageInfo pageInfo; + private string title, creator, lastAuthor; + private DateTime modificationDateTime; + private int messageCount; + + /// + /// Initializes a new instance of the class. + /// + /// The object. + /// The title of the page. + /// The modification date/time. + /// The creator. + /// The last author. + public ExtendedPageInfo(PageInfo pageInfo, string title, DateTime modificationDateTime, string creator, string lastAuthor) { + this.pageInfo = pageInfo; + this.title = FormattingPipeline.PrepareTitle(title, false, FormattingContext.PageContent, pageInfo); + this.modificationDateTime = modificationDateTime; + this.creator = creator; + this.lastAuthor = lastAuthor; + this.messageCount = Pages.GetMessageCount(pageInfo); + } + + /// + /// Gets the PageInfo object. + /// + public PageInfo PageInfo { + get { return pageInfo; } + } + + /// + /// Gets the title of the page. + /// + public string Title { + get { return title; } + } + + /// + /// Gets the creation date/time. + /// + public DateTime CreationDateTime { + get { return pageInfo.CreationDateTime; } + } + + /// + /// Gets the modification date/time. + /// + public DateTime ModificationDateTime { + get { return modificationDateTime; } + } + + /// + /// Gets the creator. + /// + public string Creator { + get { return creator; } + } + + /// + /// Gets the last author. + /// + public string LastAuthor { + get { return lastAuthor; } + } + + /// + /// Gets the number of messages. + /// + public int MessageCount { + get { return messageCount; } + } + + } + +} diff --git a/WebApplication/Code/LoginTools.cs b/WebApplication/Code/LoginTools.cs new file mode 100644 index 0000000..52da22d --- /dev/null +++ b/WebApplication/Code/LoginTools.cs @@ -0,0 +1,125 @@ + +using System; +using System.Collections.Generic; +using System.Web; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + /// + /// Implements login tools. + /// + public static class LoginTools { + + /// + /// The login key. + /// + public const string LoginKey = "LoginKey"; + + /// + /// The username. + /// + public const string Username = "Username"; + + /// + /// A logout flag. + /// + public const string Logout = "Logout"; + + /// + /// Tries to automatically login the current user. + /// + public static void TryAutoLogin() { + if(SessionFacade.LoginKey == null && HttpContext.Current.Request.Cookies[Settings.LoginCookieName] != null) { + string username = HttpContext.Current.Request.Cookies[Settings.LoginCookieName].Values[Username]; + string key = HttpContext.Current.Request.Cookies[Settings.LoginCookieName].Values[LoginKey]; + + // Try cookie login + UserInfo user = Users.TryCookieLogin(username, key); + if(user != null) { + SetupSession(user); + Log.LogEntry("User " + user.Username + " logged in through cookie", EntryType.General, Log.SystemUsername); + TryRedirect(false); + } + else { + // Cookie is not valid, delete it + SetLoginCookie("", "", DateTime.Now.AddYears(-1)); + SetupSession(null); + } + } + else if(HttpContext.Current.Session[Logout] == null) { // Check for filtered autologin + // If no cookie is available, try to autologin through providers + UserInfo user = Users.TryAutoLogin(HttpContext.Current); + if(user != null) { + SetupSession(user); + Log.LogEntry("User " + user.Username + " logged in via " + user.Provider.GetType().FullName + " autologin", EntryType.General, Log.SystemUsername); + TryRedirect(false); + } + } + } + + /// + /// Sets up a user session. + /// + /// The user (null for anonymous). + public static void SetupSession(UserInfo user) { + if(user != null) { + SessionFacade.LoginKey = Users.ComputeLoginKey(user.Username, user.Email, user.DateTime); + SessionFacade.CurrentUsername = user.Username; + + HttpContext.Current.Session[Logout] = null; // No session facade because this key is used only in this page + } + else { + SessionFacade.LoginKey = null; + SessionFacade.CurrentUsername = null; + } + } + + /// + /// Tries to redirect the user to any specified URL. + /// + /// A value indicating whether to redirect to the home page if no explicit redirect URL is found. + public static void TryRedirect(bool goHome) { + if(HttpContext.Current.Request["Redirect"] != null) { + string target = HttpContext.Current.Request["Redirect"]; + if(target.StartsWith("http:") || target.StartsWith("https:")) HttpContext.Current.Response.Redirect(target); + else UrlTools.Redirect(UrlTools.BuildUrl(target)); + } + else if(goHome) UrlTools.Redirect(UrlTools.BuildUrl("Default.aspx")); + } + + /// + /// Sets the login cookie. + /// + /// The username. + /// The login key. + /// The expiration date/time. + public static void SetLoginCookie(string username, string loginKey, DateTime expiration) { + HttpCookie cookie = new HttpCookie(Settings.LoginCookieName); + cookie.Expires = expiration; + cookie.Path = Settings.CookiePath; + cookie.Values.Add(LoginKey, loginKey); + cookie.Values.Add(Username, username); + HttpContext.Current.Response.Cookies.Add(cookie); + } + + /// + /// Verifies read permissions for the current user, redirecting to the appropriate page if no valid permissions are found. + /// + public static void VerifyReadPermissionsForCurrentNamespace() { + string currentUsername = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + + bool canViewNamespace = AuthChecker.CheckActionForNamespace( + Tools.DetectCurrentNamespaceInfo(), Actions.ForNamespaces.ReadPages, + currentUsername, currentGroups); + + if(!canViewNamespace) { + if(SessionFacade.CurrentUsername == null) UrlTools.Redirect("Login.aspx?Redirect=" + Tools.UrlEncode(HttpContext.Current.Request.Url.ToString())); + else UrlTools.Redirect("AccessDenied.aspx"); + } + } + + } + +} diff --git a/WebApplication/Code/PageSortingTools.cs b/WebApplication/Code/PageSortingTools.cs new file mode 100644 index 0000000..bfe7663 --- /dev/null +++ b/WebApplication/Code/PageSortingTools.cs @@ -0,0 +1,436 @@ + +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace ScrewTurn.Wiki { + + /// + /// Implements methods for sorting pages. + /// + public static class PageSortingTools { + + /// + /// Sorts pages. + /// + /// The pages list to sort. + /// The sorting method. + /// true to sort in reverse order. + /// The sorted list, divided in relevant groups. + public static SortedDictionary> Sort(ExtendedPageInfo[] pages, SortingMethod sortBy, bool reverse) { + switch(sortBy) { + case SortingMethod.Title: + return SortByTitle(pages, reverse); + case SortingMethod.Creator: + return SortByCreator(pages, reverse); + case SortingMethod.User: + return SortByUser(pages, reverse); + case SortingMethod.DateTime: + return SortByDateTime(pages, reverse); + case SortingMethod.Creation: + return SortByCreation(pages, reverse); + default: + throw new NotSupportedException("Invalid sorting method"); + } + } + + /// + /// Sorts pages by title. + /// + /// The pages. + /// true to sort in reverse order. + /// The sorted list, divided in relevant groups (#, A, B, etc.). + private static SortedDictionary> SortByTitle(ExtendedPageInfo[] pages, bool reverse) { + ExtendedPageInfo[] temp = new ExtendedPageInfo[pages.Length]; + Array.Copy(pages, temp, pages.Length); + Array.Sort(temp, delegate(ExtendedPageInfo p1, ExtendedPageInfo p2) { + string t1 = p1.Title, t2 = p2.Title; + if(!reverse) return string.Compare(t1, t2, false, CultureInfo.CurrentCulture); + else return string.Compare(t2, t1, false, CultureInfo.CurrentCulture); + }); + + SortedDictionary> result = + new SortedDictionary>(new CharComparer(reverse)); + + foreach(ExtendedPageInfo p in temp) { + char first = GetFirstChar(p.Title); + if(!char.IsLetter(first)) { + if(!result.ContainsKey('#')) result.Add('#', new List(20)); + result['#'].Add(p); + } + else { + if(!result.ContainsKey(first)) result.Add(first, new List(20)); + result[first].Add(p); + } + } + + SortedDictionary> finalResult = + new SortedDictionary>(new SortingGroupComparer(reverse)); + foreach(char key in result.Keys) { + finalResult.Add(new SortingGroup(GetLetterNumber(key), key.ToString(), key), result[key]); + } + return finalResult; + } + + private static char GetFirstChar(string value) { + return value.ToUpper(CultureInfo.CurrentCulture)[0]; + } + + /// + /// Sorts pages by last author. + /// + /// The pages. + /// true to sort in reverse order. + /// The sorted list, divided in relevant groups (#, A, B, etc.). + private static SortedDictionary> SortByUser(ExtendedPageInfo[] pages, bool reverse) { + ExtendedPageInfo[] temp = new ExtendedPageInfo[pages.Length]; + Array.Copy(pages, temp, pages.Length); + Array.Sort(temp, delegate(ExtendedPageInfo p1, ExtendedPageInfo p2) { + string u1 = p1.LastAuthor, u2 = p2.LastAuthor; + if(!reverse) return string.Compare(u1, u2, false, CultureInfo.CurrentCulture); + else return string.Compare(u2, u1, false, CultureInfo.CurrentCulture); + }); + + SortedDictionary> result = + new SortedDictionary>(new CharComparer(reverse)); + + foreach(ExtendedPageInfo p in temp) { + char first = GetFirstChar(p.LastAuthor); + if(!char.IsLetter(first)) { + if(!result.ContainsKey('#')) result.Add('#', new List(20)); + result['#'].Add(p); + } + else { + if(!result.ContainsKey(first)) result.Add(first, new List(20)); + result[first].Add(p); + } + } + + SortedDictionary> finalResult = + new SortedDictionary>(new SortingGroupComparer(reverse)); + foreach(char key in result.Keys) { + finalResult.Add(new SortingGroup(GetLetterNumber(key), key.ToString(), key), result[key]); + } + return finalResult; + } + + /// + /// Sorts pages by creator. + /// + /// The pages. + /// true to sort in reverse order. + /// The sorted list, divided in relevant groups (#, A, B, etc.). + private static SortedDictionary> SortByCreator(ExtendedPageInfo[] pages, bool reverse) { + ExtendedPageInfo[] temp = new ExtendedPageInfo[pages.Length]; + Array.Copy(pages, temp, pages.Length); + Array.Sort(temp, delegate(ExtendedPageInfo p1, ExtendedPageInfo p2) { + string u1 = p1.Creator, u2 = p2.Creator; + if(!reverse) return string.Compare(u1, u2, false, CultureInfo.CurrentCulture); + else return string.Compare(u2, u1, false, CultureInfo.CurrentCulture); + }); + + SortedDictionary> result = + new SortedDictionary>(new CharComparer(reverse)); + + foreach(ExtendedPageInfo p in temp) { + char first = GetFirstChar(p.Creator); + if(!char.IsLetter(first)) { + if(!result.ContainsKey('#')) result.Add('#', new List(20)); + result['#'].Add(p); + } + else { + if(!result.ContainsKey(first)) result.Add(first, new List(20)); + result[first].Add(p); + } + } + + SortedDictionary> finalResult = + new SortedDictionary>(new SortingGroupComparer(reverse)); + foreach(char key in result.Keys) { + finalResult.Add(new SortingGroup(GetLetterNumber(key), key.ToString(), key), result[key]); + } + return finalResult; + } + + /// + /// Sorts pages by modification date/time. + /// + /// The pages. + /// true to sort in reverse order. + /// The sorted list, divided in relevant groups. + private static SortedDictionary> SortByDateTime(ExtendedPageInfo[] pages, bool reverse) { + ExtendedPageInfo[] temp = new ExtendedPageInfo[pages.Length]; + Array.Copy(pages, temp, pages.Length); + Array.Sort(temp, delegate(ExtendedPageInfo p1, ExtendedPageInfo p2) { + if(!reverse) return p1.ModificationDateTime.CompareTo(p2.ModificationDateTime); + else return p2.ModificationDateTime.CompareTo(p1.ModificationDateTime); + }); + + SortedDictionary> result = + new SortedDictionary>(new DateTimeComparer(reverse)); + + Dictionary labels = new Dictionary(); + + foreach(ExtendedPageInfo p in temp) { + string label; + DateTime marker = GetMarkerDate(p.ModificationDateTime, out label); + if(!result.ContainsKey(marker)) { + result.Add(marker, new List(20)); + labels.Add(marker, label); + } + result[marker].Add(p); + } + + SortedDictionary> finalResult = + new SortedDictionary>(new SortingGroupComparer(reverse)); + foreach(DateTime key in result.Keys) { + finalResult.Add(new SortingGroup(key.DayOfYear + key.Year * 1000, labels[key], key), result[key]); + } + return finalResult; + } + + /// + /// Sorts pages by creation date/time. + /// + /// The pages. + /// true to sort in reverse order. + /// The sorted list, divided in relevant groups. + private static SortedDictionary> SortByCreation(ExtendedPageInfo[] pages, bool reverse) { + ExtendedPageInfo[] temp = new ExtendedPageInfo[pages.Length]; + Array.Copy(pages, temp, pages.Length); + Array.Sort(temp, delegate(ExtendedPageInfo p1, ExtendedPageInfo p2) { + if(!reverse) return p1.CreationDateTime.CompareTo(p2.CreationDateTime); + else return p2.CreationDateTime.CompareTo(p1.CreationDateTime); + }); + + SortedDictionary> result = + new SortedDictionary>(new DateTimeComparer(reverse)); + + Dictionary labels = new Dictionary(); + + foreach(ExtendedPageInfo p in temp) { + string label; + DateTime marker = GetMarkerDate(p.CreationDateTime, out label); + if(!result.ContainsKey(marker)) { + result.Add(marker, new List(20)); + labels.Add(marker, label); + } + result[marker].Add(p); + } + + SortedDictionary> finalResult = + new SortedDictionary>(new SortingGroupComparer(reverse)); + foreach(DateTime key in result.Keys) { + finalResult.Add(new SortingGroup(key.DayOfYear + key.Year * 1000, labels[key], key), result[key]); + } + return finalResult; + } + + private static int GetLetterNumber(char c) { + // Only # and letters allowed + c = char.ToUpperInvariant(c); + if(c == '#') return 0; + else return c - 64; + } + + private static DateTime GetMarkerDate(DateTime dt, out string label) { + DateTime now = DateTime.Now; + + // Today + if(dt.Date == now.Date) { + label = Properties.Messages.Today; + return new DateTime(now.Year, now.Month, now.Day); + } + + // Yesterday + DateTime yesterday = now.AddDays(-1); + if(dt.Date == yesterday.Date) { + label = Properties.Messages.Yesterday; + return new DateTime(yesterday.Year, yesterday.Month, yesterday.Day); + } + + // Earlier this week + DateTime thisWeek = now; + while(thisWeek.DayOfWeek != DayOfWeek.Monday) thisWeek = thisWeek.AddDays(-1); + if(dt.Year == thisWeek.Year && dt.Month == thisWeek.Month && dt.Day >= thisWeek.Day) { + label = Properties.Messages.EarlierThisWeek; + return new DateTime(thisWeek.Year, thisWeek.Month, thisWeek.Day); + } + + // Earlier this month + DateTime thisMonth = now; + while(thisMonth.Day != 1) thisMonth = thisMonth.AddDays(-1); + if(dt.Year == thisMonth.Year && dt.Month == thisMonth.Month) { + label = Properties.Messages.EarlierThisMonth + " (" + thisMonth.ToString("MMMM") + ")"; + return new DateTime(thisMonth.Year, thisMonth.Month, thisMonth.Day); + } + + // Last month + DateTime lastMonth = now.AddMonths(-1); + while(lastMonth.Day != 1) lastMonth = lastMonth.AddDays(-1); + if(dt.Year == lastMonth.Year && dt.Month == lastMonth.Month) { + label = Properties.Messages.LastMonth + " (" + lastMonth.ToString("MMMM") + ")"; + return new DateTime(lastMonth.Year, lastMonth.Month, lastMonth.Day); + } + + label = Properties.Messages.Older; + return DateTime.MinValue; + } + + } + + /// + /// Lists legal page sorting methods. + /// + public enum SortingMethod { + /// + /// Sort by title. + /// + Title, + /// + /// Sort by creator. + /// + Creator, + /// + /// Sort by last author. + /// + User, + /// + /// Sort by creation date/time. + /// + Creation, + /// + /// Sort by modification date/time. + /// + DateTime + } + + /// + /// Describes a sorting group. + /// + public class SortingGroup { + + private int number; + private string label; + private object tag; + + /// + /// Initializes a new instance of the SortingGroup class. + /// + /// The group number. + /// The group label. + /// The group tag. + public SortingGroup(int number, string label, object tag) { + this.number = number; + this.label = label; + this.tag = tag; + } + + /// + /// Gets the group number. + /// + public int Number { + get { return number; } + } + + /// + /// Gets the group label. + /// + public string Label { + get { return label; } + } + + /// + /// Gets the group tag. + /// + public object Tag { + get { return tag; } + } + + } + + /// + /// Implements a Sorting Group comparer. + /// + public class SortingGroupComparer : IComparer { + + private bool reverse; + + /// + /// Initializes a new instance of the SortingGroupComparer class. + /// + /// true to perform the comparison in reverse order. + public SortingGroupComparer(bool reverse) { + this.reverse = reverse; + } + + /// + /// Compares two Sorting Groups. + /// + /// The first Sorting Group. + /// The second Sorting Group. + /// The comparison result. + public int Compare(SortingGroup x, SortingGroup y) { + if(!reverse) return x.Number.CompareTo(y.Number); + else return y.Number.CompareTo(x.Number); + } + + } + + /// + /// Implements a char comparer. + /// + public class CharComparer : IComparer { + + private bool reverse; + + /// + /// Initializes a new instance of the CharComparer class. + /// + /// true to perform a reverse comparison. + public CharComparer(bool reverse) { + this.reverse = reverse; + } + + /// + /// Compares two chars. + /// + /// The first char. + /// The second char. + /// The comparison result. + public int Compare(char x, char y) { + if(!reverse) return x.CompareTo(y); + else return y.CompareTo(x); + } + + } + + /// + /// Implements a date/time comparer. + /// + public class DateTimeComparer : IComparer { + + private bool reverse; + + /// + /// Initializes a new instance of the DateTimeComparer class. + /// + /// true to perform reverse comparison. + public DateTimeComparer(bool reverse) { + this.reverse = reverse; + } + + /// + /// Compares two date/times. + /// + /// The first date/time. + /// The second date/time. + /// The comparison result. + public int Compare(DateTime x, DateTime y) { + if(!reverse) return x.CompareTo(y); + else return y.CompareTo(x); + } + + } + +} diff --git a/WebApplication/Code/ResourceExchanger.cs b/WebApplication/Code/ResourceExchanger.cs new file mode 100644 index 0000000..6c146a7 --- /dev/null +++ b/WebApplication/Code/ResourceExchanger.cs @@ -0,0 +1,41 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using System.Resources; +using System.Reflection; + +namespace ScrewTurn.Wiki { + + /// + /// Implements a Resource Exchanger. + /// + public class ResourceExchanger : IResourceExchanger { + + private ResourceManager manager; + + /// + /// Initialises a new instance of the ResourceExchanger class. + /// + public ResourceExchanger() { + manager = new ResourceManager("ScrewTurn.Wiki.Properties.Messages", typeof(Properties.Messages).Assembly); + } + + /// + /// Gets a Resource String. + /// + /// The Name of the Resource. + /// The Resource String. + public string GetResource(string name) { + return manager.GetString(name); + } + + } + +} diff --git a/WebApplication/Code/TreeElement.cs b/WebApplication/Code/TreeElement.cs new file mode 100644 index 0000000..b757d2b --- /dev/null +++ b/WebApplication/Code/TreeElement.cs @@ -0,0 +1,88 @@ + +using System; +using System.Collections.Generic; + +namespace ScrewTurn.Wiki { + + /// + /// Defines an item or element in an tree structure. + /// + public class TreeElement { + + private string name, text, onClientClick; + private List subItems; + + /// + /// Initializes a new instance of the TreeElement class. + /// + /// The name of the item. + /// The text of the item. + /// The JavaScript to execute on client click. + /// The sub-items. + public TreeElement(string name, string text, string onClientClick, List subItems) { + this.name = name; + this.text = text; + this.onClientClick = onClientClick; + this.subItems = subItems; + } + + /// + /// Initializes a new instance of the TreeElement class. + /// + /// The name of the item. + /// The text of the item. + /// The JavaScript to execute on client click. + public TreeElement(string name, string text, string onClientClick) + : this(name, text, onClientClick, new List()) { } + + /// + /// Initializes a new instance of the TreeElement class. + /// + /// The name of the item. + /// The text of the item. + /// The sub-items. + public TreeElement(string name, string text, List subItems) + : this(name, text, "", subItems) { } + + /// + /// Gets or sets the name of the item. + /// + public string Name { + get { return name; } + set { this.name = value; } + } + + /// + /// Gets or sets the text of the item. + /// + public string Text { + get { return text; } + set { text = value; } + } + + /// + /// Gets or sets the JavaScript to execute on client click. + /// + public string OnClientClick { + get { return onClientClick; } + set { onClientClick = value; } + } + + /// + /// Gets or sets the SubItems. + /// + public List SubItems { + get { return subItems; } + set { subItems = value; } + } + + } + + /// + /// Contains the event arguments for the Populate event. + /// + public class PopulateEventArgs : EventArgs { + + } + +} diff --git a/WebApplication/Default.aspx b/WebApplication/Default.aspx new file mode 100644 index 0000000..214d284 --- /dev/null +++ b/WebApplication/Default.aspx @@ -0,0 +1,275 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ScrewTurn.Wiki.DefaultPage" culture="auto" meta:resourcekey="PageResource2" uiculture="auto" %> + +<%@ Register TagPrefix="st" TagName="AttachmentViewer" Src="~/AttachmentViewer.ascx" %> + + + + + + + +
    + + +
    + + + + + + + + + +
    + +

    + + + +

    + +
    + +
    + +
    + +
    + +
    + +
    + + +
    + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + +
    + +
    + +
    + + + +
    + +
    + +
    + + + +
    + + + +
    diff --git a/WebApplication/Default.aspx.cs b/WebApplication/Default.aspx.cs new file mode 100644 index 0000000..23c11b7 --- /dev/null +++ b/WebApplication/Default.aspx.cs @@ -0,0 +1,711 @@ + +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.UI; +using System.Web.UI.WebControls; +using ScrewTurn.Wiki.PluginFramework; +using System.Text; + +namespace ScrewTurn.Wiki { + + public partial class DefaultPage : BasePage { + + private PageInfo currentPage = null; + private PageContent currentContent = null; + + private bool discussMode = false; + private bool viewCodeMode = false; + + protected void Page_Load(object sender, EventArgs e) { + + discussMode = Request["Discuss"] != null; + viewCodeMode = Request["Code"] != null && !discussMode; + if(!Settings.EnableViewPageCodeFeature) viewCodeMode = false; + + currentPage = DetectPageInfo(true); + + VerifyAndPerformRedirects(); + + // The following actions are verified: + // - View content (redirect to AccessDenied) + // - Edit or Edit with Approval (for button display) + // - Any Administrative activity (Rollback/Admin/Perms) (for button display) + // - Download attachments (for button display - download permissions are also checked in GetFile) + // - View discussion (for button display in content mode) + // - Post discussion (for button display in discuss mode) + + string currentUsername = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + + bool canView = AuthChecker.CheckActionForPage(currentPage, Actions.ForPages.ReadPage, currentUsername, currentGroups); + bool canEdit = false; + bool canEditWithApproval = false; + Pages.CanEditPage(currentPage, currentUsername, currentGroups, out canEdit, out canEditWithApproval); + if(canEditWithApproval && canEdit) canEditWithApproval = false; + bool canDownloadAttachments = AuthChecker.CheckActionForPage(currentPage, Actions.ForPages.DownloadAttachments, currentUsername, currentGroups); + bool canSetPerms = AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManagePermissions, currentUsername, currentGroups); + bool canAdmin = AuthChecker.CheckActionForPage(currentPage, Actions.ForPages.ManagePage, currentUsername, currentGroups); + bool canViewDiscussion = AuthChecker.CheckActionForPage(currentPage, Actions.ForPages.ReadDiscussion, currentUsername, currentGroups); + bool canPostDiscussion = AuthChecker.CheckActionForPage(currentPage, Actions.ForPages.PostDiscussion, currentUsername, currentGroups); + bool canManageDiscussion = AuthChecker.CheckActionForPage(currentPage, Actions.ForPages.ManageDiscussion, currentUsername, currentGroups); + + if(!canView) { + if(SessionFacade.LoginKey == null) UrlTools.Redirect("Login.aspx?Redirect=" + Tools.UrlEncode(Request.Url.ToString())); + else UrlTools.Redirect(UrlTools.BuildUrl("AccessDenied.aspx")); + } + attachmentViewer.Visible = canDownloadAttachments; + + attachmentViewer.PageInfo = currentPage; + currentContent = Content.GetPageContent(currentPage, true); + + pnlPageInfo.Visible = Settings.EnablePageInfoDiv; + + SetupTitles(); + + SetupToolbarLinks(canEdit || canEditWithApproval, canViewDiscussion, canPostDiscussion, canDownloadAttachments, canAdmin, canAdmin, canSetPerms); + + SetupLabels(); + SetupPrintAndRssLinks(); + SetupMetaInformation(); + VerifyAndPerformPageRedirection(); + SetupRedirectionSource(); + SetupNavigationPaths(); + SetupAdjacentPages(); + + SessionFacade.Breadcrumbs.AddPage(currentPage); + SetupBreadcrumbsTrail(); + + SetupDoubleClickHandler(); + + SetupEmailNotification(); + + SetupPageContent(canPostDiscussion, canManageDiscussion); + } + + /// + /// Verifies the need for a redirect and performs it. + /// + private void VerifyAndPerformRedirects() { + if(currentPage == null) { + UrlTools.Redirect(UrlTools.BuildUrl("PageNotFound.aspx?Page=", Tools.UrlEncode(DetectFullName()))); + } + if(Request["Edit"] == "1") { + UrlTools.Redirect(UrlTools.BuildUrl("Edit.aspx?Page=", Tools.UrlEncode(currentPage.FullName))); + } + if(Request["History"] == "1") { + UrlTools.Redirect(UrlTools.BuildUrl("History.aspx?Page=", Tools.UrlEncode(currentPage.FullName))); + } + } + + /// + /// Sets the titles used in the page. + /// + private void SetupTitles() { + string title = FormattingPipeline.PrepareTitle(currentContent.Title, false, FormattingContext.PageContent, currentPage); + Page.Title = title + " - " + Settings.WikiTitle; + lblPageTitle.Text = title; + } + + /// + /// Sets the content and visibility of all toolbar links. + /// + /// A value indicating whether the current user can edit the page. + /// A value indicating whether the current user can view the page discussion. + /// A value indicating whether the current user can post messages in the page discussion. + /// A value indicating whether the current user can download attachments. + /// A value indicating whether the current user can rollback the page. + /// A value indicating whether the current user can perform at least one administration task. + /// A value indicating whether the current user can set page permissions. + private void SetupToolbarLinks(bool canEdit, bool canViewDiscussion, bool canPostMessages, + bool canDownloadAttachments, bool canRollback, bool canAdmin, bool canSetPerms) { + + lblDiscussLink.Visible = !discussMode && !viewCodeMode && canViewDiscussion; + if(lblDiscussLink.Visible) { + lblDiscussLink.Text = string.Format(@"{1} ({2})", + Properties.Messages.Discuss, Properties.Messages.Discuss, Pages.GetMessageCount(currentPage)); + } + + lblEditLink.Visible = Settings.EnablePageToolbar && !discussMode && !viewCodeMode && canEdit; + if(lblEditLink.Visible) { + lblEditLink.Text = string.Format(@"{2}", + Properties.Messages.EditThisPage, + UrlTools.BuildUrl("Edit.aspx?Page=", Tools.UrlEncode(currentPage.FullName)), + Properties.Messages.Edit); + } + + if(Settings.EnablePageToolbar && Settings.EnableViewPageCodeFeature) { + lblViewCodeLink.Visible = !discussMode && !viewCodeMode && !canEdit; + if(lblViewCodeLink.Visible) { + lblViewCodeLink.Text = string.Format(@"{1}", + Properties.Messages.ViewPageCode, Properties.Messages.ViewPageCode); + } + } + else lblViewCodeLink.Visible = false; + + lblHistoryLink.Visible = Settings.EnablePageToolbar && !discussMode && !viewCodeMode && canViewDiscussion; + if(lblHistoryLink.Visible) { + lblHistoryLink.Text = string.Format(@"{2}", + Properties.Messages.ViewPageHistory, + UrlTools.BuildUrl("History.aspx?Page=", Tools.UrlEncode(currentPage.FullName)), + Properties.Messages.History); + } + + int attachmentCount = GetAttachmentCount(); + lblAttachmentsLink.Visible = canDownloadAttachments && !discussMode && !viewCodeMode && attachmentCount > 0; + if(lblAttachmentsLink.Visible) { + lblAttachmentsLink.Text = string.Format(@"{1}", + Properties.Messages.Attachments, Properties.Messages.Attachments); + } + attachmentViewer.Visible = lblAttachmentsLink.Visible; + + int bakCount = GetBackupCount(); + lblAdminToolsLink.Visible = Settings.EnablePageToolbar && !discussMode && !viewCodeMode && + ((canRollback && bakCount > 0)|| canAdmin || canSetPerms); + if(lblAdminToolsLink.Visible) { + lblAdminToolsLink.Text = string.Format(@"{1}", + Properties.Messages.AdminTools, Properties.Messages.Admin); + + if(canRollback && bakCount > 0) { + lblRollbackPage.Text = string.Format(@"{2}", + Tools.UrlEncode(currentPage.FullName), + Properties.Messages.RollbackThisPage, Properties.Messages.Rollback); + } + else lblRollbackPage.Visible = false; + + if(canAdmin) { + lblAdministratePage.Text = string.Format(@"{2}", + Tools.UrlEncode(currentPage.FullName), + Properties.Messages.AdministrateThisPage, Properties.Messages.Administrate); + } + else lblAdministratePage.Visible = false; + + if(canSetPerms) { + lblSetPagePermissions.Text = string.Format(@"{2}", + Tools.UrlEncode(currentPage.FullName), + Properties.Messages.SetPermissionsForThisPage, Properties.Messages.Permissions); + } + else lblSetPagePermissions.Visible = false; + } + + lblPostMessageLink.Visible = discussMode && !viewCodeMode && canPostMessages; + if(lblPostMessageLink.Visible) { + lblPostMessageLink.Text = string.Format(@"{2}", + Properties.Messages.PostMessage, + UrlTools.BuildUrl("Post.aspx?Page=", Tools.UrlEncode(currentPage.FullName)), + Properties.Messages.PostMessage); + } + + lblBackLink.Visible = discussMode || viewCodeMode; + if(lblBackLink.Visible) { + lblBackLink.Text = string.Format(@"{2}", + Properties.Messages.Back, + UrlTools.BuildUrl(Tools.UrlEncode(currentPage.FullName), Settings.PageExtension, "?NoRedirect=1"), + Properties.Messages.Back); + } + } + + /// + /// Gets the number of backups for the current page. + /// + /// The number of backups. + private int GetBackupCount() { + return Pages.GetBackups(currentPage).Count; + } + + /// + /// Gets the number of attachments for the current page. + /// + /// The number of attachments. + private int GetAttachmentCount() { + int count = 0; + foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) { + count += prov.ListPageAttachments(currentPage).Length; + } + return count; + } + + /// + /// Sets the content and visibility of all labels used in the page. + /// + private void SetupLabels() { + if(discussMode) { + lblModified.Visible = false; + lblModifiedDateTime.Visible = false; + lblBy.Visible = false; + lblAuthor.Visible = false; + lblCategorizedAs.Visible = false; + lblPageCategories.Visible = false; + lblNavigationPaths.Visible = false; + lblDiscussedPage.Text = "" + FormattingPipeline.PrepareTitle(currentContent.Title, false, FormattingContext.PageContent, currentPage) + ""; + } + else { + lblPageDiscussionFor.Visible = false; + lblDiscussedPage.Visible = false; + + lblModifiedDateTime.Text = + Preferences.AlignWithTimezone(currentContent.LastModified).ToString(Settings.DateTimeFormat); + lblAuthor.Text = Users.UserLink(currentContent.User); + lblPageCategories.Text = GetFormattedPageCategories(); + } + } + + /// + /// Sets the Print and RSS links. + /// + private void SetupPrintAndRssLinks() { + if(!viewCodeMode) { + lblPrintLink.Text = string.Format(@"{2}", + UrlTools.BuildUrl("Print.aspx?Page=", Tools.UrlEncode(currentPage.FullName), discussMode ? "&Discuss=1" : ""), + Properties.Messages.PrinterFriendlyVersion, Properties.Messages.Print); + + if(Settings.RssFeedsMode != RssFeedsMode.Disabled) { + lblRssLink.Text = string.Format(@"RSS", + UrlTools.BuildUrl("RSS.aspx?Page=", Tools.UrlEncode(currentPage.FullName), discussMode ? "&Discuss=1" : ""), + discussMode ? Properties.Messages.RssForThisDiscussion : Properties.Messages.RssForThisPage, + discussMode ? " class=\"discuss\"" : ""); + } + else lblRssLink.Visible = false; + } + else { + lblPrintLink.Visible = false; + lblRssLink.Visible = false; + } + } + + /// + /// Gets the categories for the current page, already formatted for display. + /// + /// The categories, formatted for display. + private string GetFormattedPageCategories() { + CategoryInfo[] categories = Pages.GetCategoriesForPage(currentPage); + if(categories.Length == 0) { + return string.Format(@"{2}", + GetCategoryLink("-"), + Properties.Messages.Uncategorized, Properties.Messages.Uncategorized); + } + else { + StringBuilder sb = new StringBuilder(categories.Length * 10); + for(int i = 0; i < categories.Length; i++) { + sb.AppendFormat(@"{2}", + GetCategoryLink(categories[i].FullName), + NameTools.GetLocalName(categories[i].FullName), + NameTools.GetLocalName(categories[i].FullName)); + if(i != categories.Length - 1) sb.Append(", "); + } + return sb.ToString(); + } + } + + /// + /// Gets the link to a category. + /// + /// The full name of the category. + /// The link URL. + private string GetCategoryLink(string category) { + return UrlTools.BuildUrl("AllPages.aspx?Cat=", Tools.UrlEncode(category)); + } + + /// + /// Sets the content of the META description and keywords for the current page. + /// + private void SetupMetaInformation() { + // Set keywords and description + if(currentContent.Keywords != null && currentContent.Keywords.Length > 0) { + Literal lit = new Literal(); + lit.Text = string.Format("", PrintKeywords(currentContent.Keywords)); + Page.Header.Controls.Add(lit); + } + if(!string.IsNullOrEmpty(currentContent.Description)) { + Literal lit = new Literal(); + lit.Text = string.Format("", currentContent.Description); + Page.Header.Controls.Add(lit); + } + } + + /// + /// Prints the keywords in a CSV list. + /// + /// + /// + private string PrintKeywords(string[] keywords) { + StringBuilder sb = new StringBuilder(50); + for(int i = 0; i < keywords.Length; i++) { + sb.Append(keywords[i]); + if(i != keywords.Length - 1) sb.Append(", "); + } + return sb.ToString(); + } + + /// + /// Verifies the need for a page redirection, and performs it when appropriate. + /// + private void VerifyAndPerformPageRedirection() { + if(currentPage == null) return; + + // Force formatting so that the destination can be detected + Content.GetFormattedPageContent(currentPage, true); + + PageInfo dest = Redirections.GetDestination(currentPage); + if(dest == null) return; + + if(dest != null) { + if(Request["NoRedirect"] != "1") { + UrlTools.Redirect(dest.FullName + Settings.PageExtension + "?From=" + currentPage.FullName); + } + else { + // Write redirection hint + StringBuilder sb = new StringBuilder(); + sb.Append(@""); + Literal literal = new Literal(); + literal.Text = sb.ToString(); + plhContent.Controls.Add(literal); + } + } + } + + /// + /// Sets the breadcrumbs trail, if appropriate. + /// + private void SetupBreadcrumbsTrail() { + if(Settings.DisableBreadcrumbsTrail || discussMode || viewCodeMode) { + lblBreadcrumbsTrail.Visible = false; + return; + } + + StringBuilder sb = new StringBuilder(1000); + + sb.Append(@"
    "); + + PageInfo[] pageTrail = SessionFacade.Breadcrumbs.AllPages; + int min = 3; + if(pageTrail.Length < 3) min = pageTrail.Length; + + sb.Append(@"
    "); + if(pageTrail.Length > 3) { + // Write hyperLink + sb.Append(@"("); + sb.Append(pageTrail.Length.ToString()); + sb.Append(") "); + } + + for(int i = pageTrail.Length - min; i < pageTrail.Length; i++) { + AppendBreadcrumb(sb, pageTrail[i], "s"); + } + sb.Append("
    "); + + sb.Append(@"
    "); + // Write hyperLink + sb.Append(@"[X] "); + for(int i = 0; i < pageTrail.Length; i++) { + AppendBreadcrumb(sb, pageTrail[i], "f"); + } + sb.Append("
    "); + + sb.Append("
    "); + + lblBreadcrumbsTrail.Text = sb.ToString(); + } + + /// + /// Appends a breadbrumb trail element. + /// + /// The destination . + /// The page to append. + /// The drop-down menu ID prefix. + private void AppendBreadcrumb(StringBuilder sb, PageInfo page, string dpPrefix) { + PageNameComparer comp = new PageNameComparer(); + PageContent pc = Content.GetPageContent(page, true); + + string id = AppendBreadcrumbDropDown(sb, page, dpPrefix); + + string nspace = NameTools.GetNamespace(page.FullName); + + sb.Append("» "); + if(comp.Compare(page, currentPage) == 0) sb.Append(""); + sb.AppendFormat(@"{1}", + Tools.UrlEncode(page.FullName) + Settings.PageExtension, + FormattingPipeline.PrepareTitle(pc.Title, false, FormattingContext.PageContent, currentPage) + (string.IsNullOrEmpty(nspace) ? "" : (" (" + NameTools.GetNamespace(page.FullName) + ")")), + (id != null ? @" onmouseover=""javascript:return __ShowDropDown(event, '" + id + @"', this);""" : ""), + (id != null ? @" id=""lnk" + id + @"""" : ""), + (id != null ? @" onmouseout=""javascript:return __HideDropDown('" + id + @"');""" : "")); + if(comp.Compare(page, currentPage) == 0) sb.Append(""); + sb.Append(" "); + } + + /// + /// Appends the drop-down menu DIV with outgoing links for a page. + /// + /// The destination . + /// The page. + /// The drop-down menu DIV ID prefix. + /// The DIV ID, or null if no target pages were found. + private string AppendBreadcrumbDropDown(StringBuilder sb, PageInfo page, string dbPrefix) { + // Build outgoing links list + // Generate list DIV + // Return DIV's ID + + string[] outgoingLinks = Pages.GetPageOutgoingLinks(page); + if(outgoingLinks == null || outgoingLinks.Length == 0) return null; + + string id = dbPrefix + Guid.NewGuid().ToString(); + + StringBuilder buffer = new StringBuilder(300); + + buffer.AppendFormat(@"
    ", id); + bool pageAdded = false; + foreach(string link in outgoingLinks) { + PageInfo target = Pages.FindPage(link); + if(target != null) { + pageAdded = true; + PageContent cont = Content.GetPageContent(target, true); + + string title = FormattingPipeline.PrepareTitle(cont.Title, false, FormattingContext.PageContent, currentPage); + + buffer.AppendFormat(@"{2}", link, Settings.PageExtension, title, title); + } + } + buffer.Append("
    "); + + sb.Insert(0, buffer.ToString()); + + if(pageAdded) return id; + else return null; + } + + /// + /// Sets the redirection source page link, if appropriate. + /// + private void SetupRedirectionSource() { + if(Request["From"] != null) { + + PageInfo source = Pages.FindPage(Request["From"]); + + if(source != null) { + StringBuilder sb = new StringBuilder(300); + sb.Append(@""); + + lblRedirectionSource.Text = sb.ToString(); + } + else lblRedirectionSource.Visible = false; + } + else lblRedirectionSource.Visible = false; + } + + /// + /// Sets the navigation paths label. + /// + private void SetupNavigationPaths() { + string[] paths = NavigationPaths.PathsPerPage(currentPage); + + string currentPath = Request["NavPath"]; + if(!string.IsNullOrEmpty(currentPath)) currentPath = currentPath.ToLowerInvariant(); + + if(!discussMode && !viewCodeMode && paths.Length > 0) { + StringBuilder sb = new StringBuilder(500); + sb.Append(Properties.Messages.Paths); + sb.Append(": "); + for(int i = 0; i < paths.Length; i++) { + NavigationPath path = NavigationPaths.Find(paths[i]); + if(path != null) { + if(currentPath != null && paths[i].ToLowerInvariant().Equals(currentPath)) sb.Append(""); + + sb.Append(@""); + sb.Append(NameTools.GetLocalName(path.FullName)); + sb.Append(""); + + if(currentPath != null && paths[i].ToLowerInvariant().Equals(currentPath)) sb.Append(""); + if(i != paths.Length - 1) sb.Append(", "); + } + } + + lblNavigationPaths.Text = sb.ToString(); + } + else lblNavigationPaths.Visible = false; + } + + /// + /// Prepares the previous and next pages link for navigation paths. + /// + /// The previous page link. + /// The next page link. + private void SetupAdjacentPages() { + StringBuilder prev = new StringBuilder(50), next = new StringBuilder(50); + + if(Request["NavPath"] != null) { + NavigationPath path = NavigationPaths.Find(Request["NavPath"]); + + if(path != null) { + int idx = Array.IndexOf(path.Pages, currentPage.FullName); + if(idx != -1) { + if(idx > 0) { + PageInfo prevPage = Pages.FindPage(path.Pages[idx - 1]); + prev.Append(@"« "); + } + if(idx < path.Pages.Length - 1) { + PageInfo nextPage = Pages.FindPage(path.Pages[idx + 1]); + next.Append(@" »"); + } + } + } + } + + if(prev.Length > 0) { + lblPreviousPage.Text = prev.ToString(); + } + else lblPreviousPage.Visible = false; + + if(next.Length > 0) { + lblNextPage.Text = next.ToString(); + } + else lblNextPage.Visible = false; + } + + /// + /// Sets the JavaScript double-click editing handler. + /// + private void SetupDoubleClickHandler() { + if(Settings.EnableDoubleClickEditing && !discussMode && !viewCodeMode) { + StringBuilder sb = new StringBuilder(200); + sb.Append(@""); + + lblDoubleClickHandler.Text = sb.ToString(); + } + else lblDoubleClickHandler.Visible = false; + } + + /// + /// Sets the email notification button. + /// + private void SetupEmailNotification() { + if(SessionFacade.LoginKey != null && SessionFacade.CurrentUsername != "admin") { + bool pageChanges; + bool discussionMessages; + + UserInfo user = Users.FindUser(SessionFacade.CurrentUsername); + if(user.Provider.UsersDataReadOnly) { + btnEmailNotification.Visible = false; + return; + } + + Users.GetEmailNotification(user, currentPage, out pageChanges, out discussionMessages); + + bool active = false; + if(discussMode) { + active = discussionMessages; + } + else { + active = pageChanges; + } + + if(active) { + btnEmailNotification.CssClass = "activenotification" + (discussMode ? " discuss" : ""); + btnEmailNotification.ToolTip = Properties.Messages.EmailNotificationsAreActive; + } + else { + btnEmailNotification.CssClass = "inactivenotification" + (discussMode ? " discuss" : ""); + btnEmailNotification.ToolTip = Properties.Messages.ClickToEnableEmailNotifications; + } + } + else btnEmailNotification.Visible = false; + } + + protected void btnEmailNotification_Click(object sender, EventArgs e) { + bool pageChanges; + bool discussionMessages; + + UserInfo user = Users.FindUser(SessionFacade.CurrentUsername); + Users.GetEmailNotification(user, currentPage, out pageChanges, out discussionMessages); + + if(discussMode) { + Users.SetEmailNotification(user, currentPage, pageChanges, !discussionMessages); + } + else { + Users.SetEmailNotification(user, currentPage, !pageChanges, discussionMessages); + } + + SetupEmailNotification(); + } + + /// + /// Sets the actual page content, based on the current view mode (normal, discussion, view code). + /// + /// A value indicating whether the current user can post messages. + /// A value indicating whether the current user can manage the discussion. + private void SetupPageContent(bool canPostMessages, bool canManageDiscussion) { + if(!discussMode && !viewCodeMode) { + Literal literal = new Literal(); + literal.Text = Content.GetFormattedPageContent(currentPage, true); + plhContent.Controls.Add(literal); + } + else if(!discussMode && viewCodeMode) { + if(Settings.EnableViewPageCodeFeature) { + Literal literal = new Literal(); + StringBuilder sb = new StringBuilder(currentContent.Content.Length + 100); + sb.Append(@""); + literal.Text = sb.ToString(); + plhContent.Controls.Add(literal); + } + } + else if(discussMode && !viewCodeMode) { + PageDiscussion discussion = LoadControl("~/PageDiscussion.ascx") as PageDiscussion; + discussion.CurrentPage = currentPage; + discussion.CanPostMessages = canPostMessages; + discussion.CanManageDiscussion = canManageDiscussion; + plhContent.Controls.Add(discussion); + } + } + + } + +} diff --git a/WebApplication/Default.aspx.designer.cs b/WebApplication/Default.aspx.designer.cs new file mode 100644 index 0000000..607a43e --- /dev/null +++ b/WebApplication/Default.aspx.designer.cs @@ -0,0 +1,304 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class DefaultPage { + + /// + /// lblDiscussLink control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDiscussLink; + + /// + /// lblEditLink control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblEditLink; + + /// + /// lblViewCodeLink control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblViewCodeLink; + + /// + /// lblHistoryLink control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblHistoryLink; + + /// + /// lblAttachmentsLink control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblAttachmentsLink; + + /// + /// lblAdminToolsLink control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblAdminToolsLink; + + /// + /// lblPostMessageLink control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPostMessageLink; + + /// + /// lblBackLink control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblBackLink; + + /// + /// lblPreviousPage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPreviousPage; + + /// + /// lblPageTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPageTitle; + + /// + /// lblNextPage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNextPage; + + /// + /// lblPrintLink control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPrintLink; + + /// + /// lblRssLink control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRssLink; + + /// + /// btnEmailNotification control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.ImageButton btnEmailNotification; + + /// + /// pnlPageInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlPageInfo; + + /// + /// lblModified control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblModified; + + /// + /// lblModifiedDateTime control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblModifiedDateTime; + + /// + /// lblBy control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblBy; + + /// + /// lblAuthor control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblAuthor; + + /// + /// lblNavigationPaths control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNavigationPaths; + + /// + /// lblCategorizedAs control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCategorizedAs; + + /// + /// lblPageCategories control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPageCategories; + + /// + /// lblPageDiscussionFor control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPageDiscussionFor; + + /// + /// lblDiscussedPage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDiscussedPage; + + /// + /// lblBreadcrumbsTrail control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblBreadcrumbsTrail; + + /// + /// lblRedirectionSource control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRedirectionSource; + + /// + /// plhContent control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.PlaceHolder plhContent; + + /// + /// lblDoubleClickHandler control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDoubleClickHandler; + + /// + /// attachmentViewer control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.AttachmentViewer attachmentViewer; + + /// + /// lblRollbackPage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRollbackPage; + + /// + /// lblAdministratePage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblAdministratePage; + + /// + /// lblSetPagePermissions control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblSetPagePermissions; + } +} diff --git a/WebApplication/Diff.aspx b/WebApplication/Diff.aspx new file mode 100644 index 0000000..880e56c --- /dev/null +++ b/WebApplication/Diff.aspx @@ -0,0 +1,13 @@ +<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.Diff" Title="Untitled Page" Codebehind="Diff.aspx.cs" %> + + + +

    + - + +

    +
    + + + +
    diff --git a/WebApplication/Diff.aspx.cs b/WebApplication/Diff.aspx.cs new file mode 100644 index 0000000..aa11f69 --- /dev/null +++ b/WebApplication/Diff.aspx.cs @@ -0,0 +1,114 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.IO; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using ScrewTurn.Wiki.PluginFramework; +using System.Text; + +namespace ScrewTurn.Wiki { + + public partial class Diff : BasePage { + + protected void Page_Load(object sender, EventArgs e) { + Page.Title = Properties.Messages.DiffTitle + " - " + Settings.WikiTitle; + + PrintDiff(); + } + + public void PrintDiff() { + if(Request["Page"] == null || Request["Rev1"] == null || Request["Rev2"] == null) { + Redirect(); + return; + } + + StringBuilder sb = new StringBuilder(); + + PageInfo page = Pages.FindPage(Request["Page"]); + if(page == null) { + Redirect(); + return; + } + + bool canView = AuthChecker.CheckActionForPage(page, Actions.ForPages.ReadPage, + SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames()); + if(!canView) UrlTools.Redirect("AccessDenied.aspx"); + + int rev1 = -1; + int rev2 = -1; + string rev1Text = ""; + string rev2Text = ""; + + PageContent rev1Content = null; + PageContent rev2Content = null; + bool draft = false; + + // Load rev1 content + if(int.TryParse(Request["Rev1"], out rev1)) { + rev1Content = Pages.GetBackupContent(page, rev1); + rev1Text = rev1.ToString(); + if(rev1Content == null) Redirect(); + } + else { + // Look for current + if(Request["Rev1"].ToLowerInvariant() == "current") { + rev1Content = Content.GetPageContent(page, false); + rev1Text = Properties.Messages.Current; + } + else Redirect(); + } + + if(int.TryParse(Request["Rev2"], out rev2)) { + rev2Content = Pages.GetBackupContent(page, rev2); + rev2Text = rev2.ToString(); + if(rev2Content == null) Redirect(); + } + else { + // Look for current or draft + if(Request["Rev2"].ToLowerInvariant() == "current") { + rev2Content = Content.GetPageContent(page, false); + rev2Text = Properties.Messages.Current; + } + else if(Request["Rev2"].ToLowerInvariant() == "draft") { + rev2Content = Pages.GetDraft(page); + rev2Text = Properties.Messages.Draft; + draft = true; + if(rev2Content == null) Redirect(); + } + else Redirect(); + } + + PageContent content = Content.GetPageContent(page, true); + + lblTitle.Text = Properties.Messages.DiffingPageTitle.Replace("##PAGETITLE##", + FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.PageContent, page)).Replace("##REV1##", rev1Text).Replace("##REV2##", rev2Text); + + lblBack.Text = string.Format(@"« {1}", + UrlTools.BuildUrl("History.aspx?Page=", Tools.UrlEncode(Request["Page"]), "&Rev1=", Request["Rev1"], "&Rev2=", Request["Rev2"]), + Properties.Messages.Back); + lblBack.Visible = !draft; + + sb.Append(Properties.Messages.DiffColorKey); + sb.Append("

    "); + + string result = DiffTools.DiffRevisions(rev1Content.Content, rev2Content.Content); + + sb.Append(result); + + lblDiff.Text = sb.ToString(); + } + + private void Redirect() { + UrlTools.RedirectHome(); + } + + } + +} diff --git a/WebApplication/Diff.aspx.designer.cs b/WebApplication/Diff.aspx.designer.cs new file mode 100644 index 0000000..9903f2b --- /dev/null +++ b/WebApplication/Diff.aspx.designer.cs @@ -0,0 +1,43 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.3053 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class Diff { + + /// + /// lblBack control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblBack; + + /// + /// lblTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblTitle; + + /// + /// lblDiff control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDiff; + } +} diff --git a/WebApplication/Edit.aspx b/WebApplication/Edit.aspx new file mode 100644 index 0000000..6e9285d --- /dev/null +++ b/WebApplication/Edit.aspx @@ -0,0 +1,196 @@ +<%@ Page Language="C#" MasterPageFile="~/MasterPageSA.master" AutoEventWireup="true" CodeBehind="Edit.aspx.cs" Inherits="ScrewTurn.Wiki.Edit" ValidateRequest="false" culture="auto" meta:resourcekey="PageResource2" uiculture="auto" %> + +<%@ Register TagPrefix="st" TagName="Editor" Src="~/Editor.ascx" %> +<%@ Register TagPrefix="st" TagName="Captcha" Src="~/Captcha.ascx" %> +<%@ Register TagPrefix="st" TagName="AttachmentManager" Src="~/AttachmentManager.ascx" %> + + + + + + + + . + + + + + + + + + + + +
    +
    + + + + + +
    + +
    + +
    + + + +
    +
    + +
    +
    + +
    +
    + +
    + +
    + + + + +
    +
    + + +
    +
    +
    + + +
    + +
    + + + + + +
    + +
    + +
    + + + • + + +
    +
    +
    + + + + + + +
    + +
    + + + + + +
    +
    +

    +
    + +
    + +
    + +
    + + + + +
    +
    +
    + +
    +

    +
    +
    +
    + +
    + +
    + +
    +

    + +
    + + + + + + + +
    + +
    diff --git a/WebApplication/Edit.aspx.cs b/WebApplication/Edit.aspx.cs new file mode 100644 index 0000000..17c262c --- /dev/null +++ b/WebApplication/Edit.aspx.cs @@ -0,0 +1,746 @@ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using ScrewTurn.Wiki.PluginFramework; +using System.Text; +using System.Text.RegularExpressions; +using System.Linq; + +namespace ScrewTurn.Wiki { + + public partial class Edit : BasePage { + + private PageInfo currentPage = null; + private PageContent currentContent = null; + private bool isDraft = false; + private int currentSection = -1; + + private bool canEdit = false; + private bool canEditWithApproval = false; + private bool canCreateNewPages = false; + private bool canCreateNewCategories = false; + private bool canManagePageCategories = false; + private bool canDownloadAttachments = false; + + /// + /// Detects the permissions for the current user. + /// + /// currentPage should be set before calling this method. + private void DetectPermissions() { + string currentUser = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + + if(currentPage != null) { + Pages.CanEditPage(currentPage, currentUser, currentGroups, out canEdit, out canEditWithApproval); + canCreateNewPages = false; // Least privilege + canCreateNewCategories = AuthChecker.CheckActionForNamespace(Pages.FindNamespace(NameTools.GetNamespace(currentPage.FullName)), + Actions.ForNamespaces.ManageCategories, currentUser, currentGroups); + canManagePageCategories = AuthChecker.CheckActionForPage(currentPage, Actions.ForPages.ManageCategories, currentUser, currentGroups); + canDownloadAttachments = AuthChecker.CheckActionForPage(currentPage, Actions.ForPages.DownloadAttachments, currentUser, currentGroups); + } + else { + NamespaceInfo ns = DetectNamespaceInfo(); + canCreateNewPages = AuthChecker.CheckActionForNamespace(ns, Actions.ForNamespaces.CreatePages, currentUser, currentGroups); + canCreateNewCategories = AuthChecker.CheckActionForNamespace(ns, Actions.ForNamespaces.ManageCategories, currentUser, currentGroups); + canManagePageCategories = canCreateNewCategories; + canDownloadAttachments = AuthChecker.CheckActionForNamespace(ns, Actions.ForNamespaces.DownloadAttachments, currentUser, currentGroups); + } + } + + protected void Page_Load(object sender, EventArgs e) { + + Page.Title = Properties.Messages.EditTitle + " - " + Settings.WikiTitle; + + lblEditNotice.Text = Formatter.FormatPhase3(Formatter.Format(Settings.Provider.GetMetaDataItem( + MetaDataItem.EditNotice, DetectNamespace()), false, FormattingContext.Other, null), FormattingContext.Other, null); + + // Prepare page unload warning + string ua = Request.UserAgent; + if(!string.IsNullOrEmpty(ua)) { + ua = ua.ToLowerInvariant(); + StringBuilder sbua = new StringBuilder(50); + sbua.Append(@""); + lblUnloadPage.Text = sbua.ToString(); + } + + if(!Page.IsPostBack) { + PopulateCategories(new CategoryInfo[0]); + + if(Settings.AutoGeneratePageNames) { + pnlPageName.Visible = false; + pnlManualName.Visible = true; + } + } + + // Load requested page, if any + if(Request["Page"] != null) { + string name = Request["Page"]; + + currentPage = Pages.FindPage(name); + + // If page already exists, load the content and disable page name, + // otherwise pre-fill page name + if(currentPage != null) { + // Look for a draft + currentContent = Pages.GetDraft(currentPage); + + if(currentContent == null) { + // No cache because the page will be probably modified in a few minutes + currentContent = Content.GetPageContent(currentPage, false); + } + else isDraft = true; + + // Set current page for editor and attachment manager + editor.CurrentPage = currentPage; + attachmentManager.CurrentPage = currentPage; + + if(!int.TryParse(Request["Section"], out currentSection)) currentSection = -1; + + // Fill data, if not posted back + if(!Page.IsPostBack) { + // Set keywords, description + SetKeywords(currentContent.Keywords); + txtDescription.Text = currentContent.Description; + + txtName.Text = NameTools.GetLocalName(currentPage.FullName); + txtName.Enabled = false; + pnlPageName.Visible = false; + pnlManualName.Visible = false; + + PopulateCategories(Pages.GetCategoriesForPage(currentPage)); + + txtTitle.Text = currentContent.Title; + + // Manage section, if appropriate (disable if draft) + if(!isDraft && currentSection != -1) { + int startIndex, len; + string dummy = ""; + ExtractSection(currentContent.Content, currentSection, out startIndex, out len, out dummy); + editor.SetContent(currentContent.Content.Substring(startIndex, len), Settings.UseVisualEditorAsDefault); + } + else { + // Select default editor view (WikiMarkup or Visual) and populate content + editor.SetContent(currentContent.Content, Settings.UseVisualEditorAsDefault); + } + } + } + else { + // Pre-fill name, if not posted back + if(!Page.IsPostBack) { + // Set both name and title, as the NAME was provided from the query-string and must be preserved + pnlPageName.Visible = true; + pnlManualName.Visible = false; + txtName.Text = NameTools.GetLocalName(name); + txtTitle.Text = txtName.Text; + editor.SetContent(LoadTemplateIfAppropriate(), Settings.UseVisualEditorAsDefault); + } + } + } + else { + if(!Page.IsPostBack) { + chkMinorChange.Visible = false; + chkSaveAsDraft.Visible = false; + + editor.SetContent(LoadTemplateIfAppropriate(), Settings.UseVisualEditorAsDefault); + } + } + + // Here is centralized all permissions-checking code + DetectPermissions(); + + // Verify the following permissions: + // - if new page, check for page creation perms + // - else, check for editing perms + // - full edit or edit with approval + // - categories management + // - attachment manager + // - CAPTCHA if enabled and user is anonymous + // ---> recheck every time an action is performed + + if(currentPage == null) { + // Check permissions for creating new pages + if(!canCreateNewPages) { + if(SessionFacade.LoginKey == null) UrlTools.Redirect("Login.aspx?Redirect=" + Tools.UrlEncode(Request.Url.ToString())); + else UrlTools.Redirect("AccessDenied.aspx"); + } + } + else { + // Check permissions for editing current page + if(!canEdit && !canEditWithApproval) { + if(SessionFacade.LoginKey == null) UrlTools.Redirect("Login.aspx?Redirect=" + Tools.UrlEncode(Request.Url.ToString())); + else UrlTools.Redirect("AccessDenied.aspx"); + } + } + + if(!canEdit && canEditWithApproval) { + // Hard-wire status of draft and minor change checkboxes + chkMinorChange.Enabled = false; + chkSaveAsDraft.Enabled = false; + chkSaveAsDraft.Checked = true; + } + + // Setup categories + lstCategories.Enabled = canManagePageCategories; + pnlCategoryCreation.Visible = canCreateNewCategories; + + // Setup attachment manager (require at least download permissions) + attachmentManager.Visible = canDownloadAttachments; + + // CAPTCHA + pnlCaptcha.Visible = SessionFacade.LoginKey == null && !Settings.DisableCaptchaControl; + captcha.Visible = pnlCaptcha.Visible; + + // Moderation notice + pnlApprovalRequired.Visible = !canEdit && canEditWithApproval; + + // Check and manage editing collisions + ManageEditingCollisions(); + + // Display draft status + if(!Page.IsPostBack) { + ManageDraft(); + } + + // Setup session refresh iframe + PrintSessionRefresh(); + } + + /// + /// Loads a content template when the query strings specifies it. + /// + /// The content of the selected template. + private string LoadTemplateIfAppropriate() { + if(string.IsNullOrEmpty(Request["Template"])) return ""; + ContentTemplate template = Templates.Find(Request["Template"]); + if(template == null) return ""; + else { + lblAutoTemplate.Text = lblAutoTemplate.Text.Replace("##TEMPLATE##", template.Name); + pnlAutoTemplate.Visible = true; + return template.Content; + } + } + + protected void btnAutoTemplateOK_Click(object sender, EventArgs e) { + pnlAutoTemplate.Visible = false; + } + + /// + /// Prints the session refresh code in the page. + /// + public void PrintSessionRefresh() { + StringBuilder sb = new StringBuilder(50); + sb.Append(@""); + + lblSessionRefresh.Text = sb.ToString(); + } + + /// + /// Verifies for editing collisions, and if no collision is found, "locks" the page + /// + private void ManageEditingCollisions() { + if(currentPage == null) return; + + lblRefreshLink.Text = @"" + Properties.Messages.Refresh + " »"; + + string username = Request.UserHostAddress; + if(SessionFacade.LoginKey != null) username = SessionFacade.CurrentUsername; + + if(Collisions.IsPageBeingEdited(currentPage, username)) { + pnlCollisions.Visible = true; + lblConcurrentEditingUsername.Text = "(" + Users.UserLink(Collisions.WhosEditing(currentPage)) + ")"; + if(Settings.DisableConcurrentEditing) { + lblSaveDisabled.Visible = true; + lblSaveDangerous.Visible = false; + btnSave.Enabled = false; + btnSaveAndContinue.Enabled = false; + } + else { + lblSaveDisabled.Visible = false; + lblSaveDangerous.Visible = true; + btnSave.Enabled = true; + btnSaveAndContinue.Enabled = true; + } + } + else { + pnlCollisions.Visible = false; + btnSave.Enabled = true; + btnSaveAndContinue.Enabled = true; + Collisions.RenewEditingSession(currentPage, username); + } + } + + /// + /// Manages the draft status display. + /// + private void ManageDraft() { + if(isDraft) { + chkSaveAsDraft.Checked = true; + chkMinorChange.Enabled = false; + pnlDraft.Visible = true; + lblDraftInfo.Text = lblDraftInfo.Text.Replace("##USER##", + Users.UserLink(currentContent.User, true)).Replace("##DATETIME##", + Preferences.AlignWithTimezone(currentContent.LastModified).ToString(Settings.DateTimeFormat)).Replace("##VIEWCHANGES##", + string.Format("{1}", UrlTools.BuildUrl("Diff.aspx?Page=", + Tools.UrlEncode(currentPage.FullName), "&Rev1=Current&Rev2=Draft"), + Properties.Messages.ViewChanges)); + } + else { + pnlDraft.Visible = false; + } + } + + /// + /// Populates the categories for the current namespace and provider, selecting the ones specified. + /// + /// The categories to select. + private void PopulateCategories(CategoryInfo[] toSelect) { + IPagesStorageProviderV30 provider = FindAppropriateProvider(); + List cats = Pages.GetCategories(DetectNamespaceInfo()); + lstCategories.Items.Clear(); + foreach(CategoryInfo c in cats) { + if(c.Provider == provider) { + ListItem itm = new ListItem(NameTools.GetLocalName(c.FullName), c.FullName); + if(Array.Find(toSelect, delegate(CategoryInfo s) { return s.FullName == c.FullName; }) != null) + itm.Selected = true; + lstCategories.Items.Add(itm); + } + } + } + + protected void btnManualName_Click(object sender, EventArgs e) { + pnlPageName.Visible = true; + pnlManualName.Visible = false; + txtName.Text = GenerateAutoName(txtTitle.Text); + pnlManualName.UpdateAfterCallBack = true; + pnlPageName.UpdateAfterCallBack = true; + } + + /// + /// Generates an automatic page name. + /// + /// The page title. + /// The name. + private static string GenerateAutoName(string title) { + // Replace all non-alphanumeric characters with dashes + if(title.Length == 0) return ""; + + StringBuilder buffer = new StringBuilder(title.Length); + + foreach(char ch in title.Normalize(NormalizationForm.FormD).Replace("\"", "").Replace("'", "")) { + var unicat = char.GetUnicodeCategory(ch); + if(unicat == System.Globalization.UnicodeCategory.LowercaseLetter || + unicat == System.Globalization.UnicodeCategory.UppercaseLetter || + unicat == System.Globalization.UnicodeCategory.DecimalDigitNumber) { + buffer.Append(ch); + } + else if(unicat != System.Globalization.UnicodeCategory.NonSpacingMark) buffer.Append("-"); + } + + while(buffer.ToString().IndexOf("--") >= 0) { + buffer.Replace("--", "-"); + } + + return buffer.ToString().Trim('-'); + } + + /// + /// Finds the start and end positions of a section of the content. + /// + /// The content. + /// The section ID. + /// The index of the first character of the section. + /// The length of the section. + /// The anchor ID of the section. + private static void ExtractSection(string content, int section, out int start, out int len, out string anchor) { + List hPos = Formatter.DetectHeaders(content); + start = 0; + len = content.Length; + anchor = ""; + int level = -1; + bool found = false; + for(int i = 0; i < hPos.Count; i++) { + if(hPos[i].ID == section) { + start = hPos[i].Index; + len = len - start; + level = hPos[i].Level; // Level is used to edit the current section AND all the subsections + // Set the anchor value so that it's possible to redirect the user to the just edited section + anchor = Formatter.BuildHAnchor(hPos[i].Text); + found = true; + break; + } + } + if(found) { + int diff = len; + for(int i = 0; i < hPos.Count; i++) { + if(hPos[i].Index > start && // Next section (Hx) + hPos[i].Index - start < diff && // The nearest section + hPos[i].Level <= level) { // Of the same level or higher + len = hPos[i].Index - start - 1; + diff = hPos[i].Index - start; + } + } + } + } + + protected void btnCancel_Click(object sender, EventArgs e) { + if(currentPage != null) UrlTools.Redirect(Tools.UrlEncode(currentPage.FullName) + Settings.PageExtension); + else UrlTools.Redirect(UrlTools.BuildUrl("Default.aspx")); + } + + protected void cvName1_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = !txtName.Enabled || Pages.IsValidName(txtName.Text); + } + + protected void cvName2_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = !txtName.Enabled || Pages.FindPage(NameTools.GetFullName(DetectNamespace(), txtName.Text)) == null; + } + + protected void chkMinorChange_CheckedChanged(object sender, EventArgs e) { + if(chkMinorChange.Checked) { + // Save as draft is not available + chkSaveAsDraft.Checked = false; + chkSaveAsDraft.Enabled = false; + } + else { + chkSaveAsDraft.Enabled = true; + } + } + + protected void chkSaveAsDraft_CheckedChanged(object sender, EventArgs e) { + if(chkSaveAsDraft.Checked) { + // Minor change is not available + chkMinorChange.Checked = false; + chkMinorChange.Enabled = false; + } + else { + chkMinorChange.Enabled = true; + } + } + + /// + /// Finds the appropriate provider to use for operations. + /// + /// The provider. + private IPagesStorageProviderV30 FindAppropriateProvider() { + IPagesStorageProviderV30 provider = null; + + if(currentPage != null) provider = currentPage.Provider; + else { + NamespaceInfo currentNamespace = DetectNamespaceInfo(); + provider = + currentNamespace == null ? + Collectors.PagesProviderCollector.GetProvider(Settings.DefaultPagesProvider) : + currentNamespace.Provider; + } + + return provider; + } + + protected void btnSave_Click(object sender, EventArgs e) { + bool wasVisible = pnlPageName.Visible; + pnlPageName.Visible = true; + + if(!wasVisible && Settings.AutoGeneratePageNames && txtName.Enabled) { + txtName.Text = GenerateAutoName(txtTitle.Text); + } + + Page.Validate("nametitle"); + Page.Validate("captcha"); + if(!Page.IsValid) { + if(!rfvTitle.IsValid || !rfvName.IsValid || !cvName1.IsValid || !cvName2.IsValid) { + pnlPageName.Visible = true; + pnlManualName.Visible = false; + pnlPageName.UpdateAfterCallBack = true; + pnlManualName.UpdateAfterCallBack = true; + } + + return; + } + + pnlPageName.Visible = wasVisible; + + // Check permissions + if(currentPage == null) { + // Check permissions for creating new pages + if(!canCreateNewPages) UrlTools.Redirect("AccessDenied.aspx"); + } + else { + // Check permissions for editing current page + if(!canEdit && !canEditWithApproval) UrlTools.Redirect("AccessDenied.aspx"); + } + + chkMinorChange.Visible = true; + chkSaveAsDraft.Visible = true; + + // Verify edit with approval + if(!canEdit && canEditWithApproval) { + chkSaveAsDraft.Checked = true; + } + + // Check for scripts (Administrators can always add SCRIPT tags) + if(!SessionFacade.GetCurrentGroupNames().Contains(Settings.AdministratorsGroup) && !Settings.ScriptTagsAllowed) { + Regex r = new Regex(@"\", RegexOptions.Compiled | RegexOptions.IgnoreCase); + if(r.Match(editor.GetContent()).Success) { + lblResult.Text = @"" + Properties.Messages.ScriptDetected + ""; + return; + } + } + + bool redirect = true; + if(sender == btnSaveAndContinue) redirect = false; + + lblResult.Text = ""; + lblResult.CssClass = ""; + + string username = ""; + if(SessionFacade.LoginKey == null) username = Request.UserHostAddress; + else username = SessionFacade.CurrentUsername; + + IPagesStorageProviderV30 provider = FindAppropriateProvider(); + + // Create list of selected categories + List categories = new List(); + for(int i = 0; i < lstCategories.Items.Count; i++) { + if(lstCategories.Items[i].Selected) { + CategoryInfo cat = Pages.FindCategory(lstCategories.Items[i].Value); + + // Sanity check + if(cat.Provider == provider) categories.Add(cat); + } + } + + txtComment.Text = txtComment.Text.Trim(); + txtDescription.Text = txtDescription.Text.Trim(); + + SaveMode saveMode = SaveMode.Backup; + if(chkSaveAsDraft.Checked) saveMode = SaveMode.Draft; + if(chkMinorChange.Checked) saveMode = SaveMode.Normal; + + if(txtName.Enabled) { + // Find page, if inexistent create it + PageInfo pg = Pages.FindPage(NameTools.GetFullName(DetectNamespace(), txtName.Text), provider); + if(pg == null) { + Pages.CreatePage(DetectNamespaceInfo(), txtName.Text, provider); + pg = Pages.FindPage(NameTools.GetFullName(DetectNamespace(), txtName.Text), provider); + saveMode = SaveMode.Normal; + } + Log.LogEntry("Page update requested for " + txtName.Text, EntryType.General, username); + + Pages.ModifyPage(pg, txtTitle.Text, username, DateTime.Now, txtComment.Text, editor.GetContent(), + GetKeywords(), txtDescription.Text, saveMode); + + // Save categories binding + Pages.Rebind(pg, categories.ToArray()); + + // If not a draft, remove page draft + if(saveMode != SaveMode.Draft) { + Pages.DeleteDraft(currentPage); + isDraft = false; + } + else isDraft = true; + + ManageDraft(); + + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.PageSaved; + + // This is a new page, so only who has page management permissions can execute this code + // No notification must be sent for drafts awaiting approval + if(redirect) { + Collisions.CancelEditingSession(pg, username); + string target = UrlTools.BuildUrl(Tools.UrlEncode(txtName.Text), Settings.PageExtension, "?NoRedirect=1"); + UrlTools.Redirect(target); + } + else { + // Disable PageName, because the name cannot be changed anymore + txtName.Enabled = false; + pnlManualName.Visible = false; + } + } + else { + // Used for redirecting to a specific section after editing it + string anchor = ""; + + if(currentPage == null) currentPage = Pages.FindPage(NameTools.GetFullName(DetectNamespace(), txtName.Text)); + + // Save data + Log.LogEntry("Page update requested for " + currentPage.FullName, EntryType.General, username); + if(!isDraft && currentSection != -1) { + PageContent cont = Content.GetPageContent(currentPage, false); + StringBuilder sb = new StringBuilder(cont.Content.Length); + int start, len; + ExtractSection(cont.Content, currentSection, out start, out len, out anchor); + if(start > 0) sb.Append(cont.Content.Substring(0, start)); + sb.Append(editor.GetContent()); + if(start + len < cont.Content.Length - 1) sb.Append(cont.Content.Substring(start + len)); + Pages.ModifyPage(currentPage, txtTitle.Text, username, DateTime.Now, txtComment.Text, sb.ToString(), + GetKeywords(), txtDescription.Text, saveMode); + } + else { + Pages.ModifyPage(currentPage, txtTitle.Text, username, DateTime.Now, txtComment.Text, editor.GetContent(), + GetKeywords(), txtDescription.Text, saveMode); + } + + // Save Categories binding + Pages.Rebind(currentPage, categories.ToArray()); + + // If not a draft, remove page draft + if(saveMode != SaveMode.Draft) { + Pages.DeleteDraft(currentPage); + isDraft = false; + } + else isDraft = true; + + ManageDraft(); + + lblResult.CssClass = "resultok"; + lblResult.Text = Properties.Messages.PageSaved; + + // This code is executed every time the page is saved, even when "Save & Continue" is clicked + // This causes a draft approval notification to be sent multiple times for the same page, + // but this is the only solution because the user might navigate away from the page after + // clicking "Save & Continue" but not "Save" or "Cancel" - in other words, it is necessary + // to take every chance to send a notification because no more chances might be available + if(!canEdit && canEditWithApproval) { + Pages.SendEmailNotificationForDraft(currentPage, txtTitle.Text, txtComment.Text, username); + } + + if(redirect) { + Collisions.CancelEditingSession(currentPage, username); + string target = UrlTools.BuildUrl(Tools.UrlEncode(currentPage.FullName), Settings.PageExtension, "?NoRedirect=1", + (!string.IsNullOrEmpty(anchor) ? ("#" + anchor) : "")); + UrlTools.Redirect(target); + } + } + } + + /// + /// Gets the keywords entered in the appropriate textbox. + /// + /// The keywords. + private string[] GetKeywords() { + string[] keywords = txtKeywords.Text.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + for(int i = 0; i < keywords.Length; i++) { + keywords[i] = keywords[i].Trim(); + } + return keywords; + } + + /// + /// Sets a set of keywords in the appropriate textbox. + /// + /// The keywords. + private void SetKeywords(string[] keywords) { + if(keywords == null || keywords.Length == 0) txtKeywords.Text = ""; + + StringBuilder sb = new StringBuilder(50); + for(int i = 0; i < keywords.Length; i++) { + sb.Append(keywords[i]); + if(i != keywords.Length - 1) sb.Append(","); + } + txtKeywords.Text = sb.ToString(); + } + + protected void cvCategory1_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = Pages.IsValidName(txtCategory.Text); + } + + protected void cvCategory2_ServerValidate(object sender, ServerValidateEventArgs e) { + e.IsValid = Pages.FindCategory(NameTools.GetFullName(DetectNamespace(), txtCategory.Text)) == null; + } + + protected void btnCreateCategory_Click(object sender, EventArgs e) { + if(canManagePageCategories) { + lblCategoryResult.Text = ""; + lblCategoryResult.CssClass = ""; + + string fullName = NameTools.GetFullName(DetectNamespace(), txtCategory.Text); + Pages.CreateCategory(DetectNamespaceInfo(), txtCategory.Text, FindAppropriateProvider()); + + // Save selected categories + List selected = new List(); + for(int i = 0; i < lstCategories.Items.Count; i++) { + if(lstCategories.Items[i].Selected) { + selected.Add(Pages.FindCategory(lstCategories.Items[i].Value)); + } + } + + PopulateCategories(selected.ToArray()); + + // Re-select previously selected categories + for(int i = 0; i < lstCategories.Items.Count; i++) { + if(selected.Find(delegate(CategoryInfo c) { return c.FullName == lstCategories.Items[i].Value; }) != null) { + lstCategories.Items[i].Selected = true; + } + if(lstCategories.Items[i].Value == fullName) lstCategories.Items[i].Selected = true; + } + txtCategory.Text = ""; + } + } + + protected void btnTemplates_Click(object sender, EventArgs e) { + pnlTemplates.Visible = true; + btnTemplates.Visible = false; + pnlAutoTemplate.Visible = false; + + // Load templates + lstTemplates.Items.Clear(); + lstTemplates.Items.Add(new ListItem(Properties.Messages.SelectTemplate, "")); + foreach(ContentTemplate temp in Templates.GetTemplates()) { + lstTemplates.Items.Add(new ListItem(temp.Name, temp.Name)); + } + } + + protected void lstTemplates_SelectedIndexChanged(object sender, EventArgs e) { + ContentTemplate template = Templates.Find(lstTemplates.SelectedValue); + + if(template != null) { + lblTemplatePreview.Text = template.Content; + btnUseTemplate.Visible = true; + } + else { + lblTemplatePreview.Text = ""; + btnUseTemplate.Visible = false; + } + } + + protected void btnUseTemplate_Click(object sender, EventArgs e) { + ContentTemplate template = Templates.Find(lstTemplates.SelectedValue); + + editor.SetContent(template.Content, Settings.UseVisualEditorAsDefault); + btnCancelTemplate_Click(sender, e); + } + + protected void btnCancelTemplate_Click(object sender, EventArgs e) { + pnlTemplates.Visible = false; + btnTemplates.Visible = true; + } + + } + +} diff --git a/WebApplication/Edit.aspx.designer.cs b/WebApplication/Edit.aspx.designer.cs new file mode 100644 index 0000000..a43f73a --- /dev/null +++ b/WebApplication/Edit.aspx.designer.cs @@ -0,0 +1,592 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class Edit { + + /// + /// pnlCollisions control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlCollisions; + + /// + /// lblConcurrentEditing control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblConcurrentEditing; + + /// + /// lblConcurrentEditingUsername control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblConcurrentEditingUsername; + + /// + /// lblSaveDangerous control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblSaveDangerous; + + /// + /// lblSaveDisabled control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblSaveDisabled; + + /// + /// lblRefreshLink control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRefreshLink; + + /// + /// pnlAnonymous control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlAnonymous; + + /// + /// lblIpLogged control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblIpLogged; + + /// + /// pnlDraft control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlDraft; + + /// + /// lblDraftInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDraftInfo; + + /// + /// lblDraftInfo2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDraftInfo2; + + /// + /// pnlApprovalRequired control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlApprovalRequired; + + /// + /// lblApprovalRequiredInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblApprovalRequiredInfo; + + /// + /// lblEditNotice control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblEditNotice; + + /// + /// btnSave control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnSave; + + /// + /// btnSaveAndContinue control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnSaveAndContinue; + + /// + /// btnCancel control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnCancel; + + /// + /// chkMinorChange control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkMinorChange; + + /// + /// chkSaveAsDraft control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkSaveAsDraft; + + /// + /// lblResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblResult; + + /// + /// pnlPageName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlPageName; + + /// + /// lblName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblName; + + /// + /// txtName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtName; + + /// + /// rfvName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RequiredFieldValidator rfvName; + + /// + /// cvName1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CustomValidator cvName1; + + /// + /// cvName2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CustomValidator cvName2; + + /// + /// pnlManualName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlManualName; + + /// + /// btnManualName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.LinkButton btnManualName; + + /// + /// lblTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblTitle; + + /// + /// txtTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtTitle; + + /// + /// rfvTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RequiredFieldValidator rfvTitle; + + /// + /// btnTemplates control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.LinkButton btnTemplates; + + /// + /// pnlTemplates control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlTemplates; + + /// + /// lstTemplates control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstTemplates; + + /// + /// lblTemplatePreview control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblTemplatePreview; + + /// + /// btnUseTemplate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.LinkButton btnUseTemplate; + + /// + /// btnCancelTemplate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.LinkButton btnCancelTemplate; + + /// + /// pnlAutoTemplate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlAutoTemplate; + + /// + /// lblAutoTemplate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblAutoTemplate; + + /// + /// btnAutoTemplateOK control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.LinkButton btnAutoTemplateOK; + + /// + /// editor control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.Editor editor; + + /// + /// pnlCaptcha control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlCaptcha; + + /// + /// captcha control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.Captcha captcha; + + /// + /// lblCategories control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCategories; + + /// + /// lstCategories control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBoxList lstCategories; + + /// + /// pnlCategoryCreation control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlCategoryCreation; + + /// + /// lblNewCategory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNewCategory; + + /// + /// txtCategory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtCategory; + + /// + /// btnCreateCategory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnCreateCategory; + + /// + /// rfvCategory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RequiredFieldValidator rfvCategory; + + /// + /// cvCategory1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CustomValidator cvCategory1; + + /// + /// cvCategory2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CustomValidator cvCategory2; + + /// + /// lblCategoryResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblCategoryResult; + + /// + /// lblMeta control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblMeta; + + /// + /// lblKeywords control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblKeywords; + + /// + /// txtKeywords control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtKeywords; + + /// + /// lblDescription control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDescription; + + /// + /// txtDescription control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtDescription; + + /// + /// lblComment control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblComment; + + /// + /// txtComment control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtComment; + + /// + /// lblAttachmentManager control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblAttachmentManager; + + /// + /// attachmentManager control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.AttachmentManager attachmentManager; + + /// + /// lblSessionRefresh control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblSessionRefresh; + + /// + /// lblUnloadPage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblUnloadPage; + } +} diff --git a/WebApplication/Editor.ascx b/WebApplication/Editor.ascx new file mode 100644 index 0000000..b337b94 --- /dev/null +++ b/WebApplication/Editor.ascx @@ -0,0 +1,657 @@ +<%@ Control Language="C#" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.Editor" Codebehind="Editor.ascx.cs" %> + +<%@ Register TagName="ClientImageBrowser" TagPrefix="st" Src="~/ClientImageBrowser.ascx" %> + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    +
    + + + +
    + +
    + +
    + +
    +
    + +
    + + + + + + + + + + diff --git a/WebApplication/Editor.ascx.cs b/WebApplication/Editor.ascx.cs new file mode 100644 index 0000000..1c24d8c --- /dev/null +++ b/WebApplication/Editor.ascx.cs @@ -0,0 +1,266 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Web.UI; +using System.Web.UI.WebControls; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class Editor : System.Web.UI.UserControl { + + private PageInfo currentPage = null; + private bool inWYSIWYG = false; + + protected void Page_Load(object sender, EventArgs e) { + if(!Page.IsPostBack) { + NamespaceInfo currentNamespace = Tools.DetectCurrentNamespaceInfo(); + string currentNamespaceName = currentNamespace != null ? currentNamespace.Name + "." : ""; + StringBuilder sb = new StringBuilder(200); + sb.Append(""); + lblStrings.Text = sb.ToString(); + + if(ViewState["ToolbarVisible"] == null) ViewState["ToolbarVisible"] = true; + + InitToolbar(); + } + + if(mlvEditor.ActiveViewIndex == 1) inWYSIWYG = true; + else inWYSIWYG = false; + + //SelectTab(0); + if(ViewState["Tab"] != null) SelectTab((int)ViewState["Tab"]); + + LoadSnippets(); + + PrintCustomSpecialTags(); + } + + private void InitToolbar() { + if(!ToolbarVisible) { + lblToolbarInit.Text = ""; + } + else lblToolbarInit.Text = ""; + } + + /// + /// Selects the active tab. + /// + /// The index of the active tab: + /// - 0: WikiMarkup + /// - 1: Visual + /// - 2: Preview. + private void SelectTab(int index) { + btnWikiMarkup.CssClass = "tabbutton"; + btnVisual.CssClass = "tabbutton"; + btnPreview.CssClass = "tabbutton"; + mlvEditor.ActiveViewIndex = index; + switch(index) { + case 0: + btnWikiMarkup.CssClass = "tabbuttonactive"; + break; + case 1: + btnVisual.CssClass = "tabbuttonactive"; + break; + case 2: + btnPreview.CssClass = "tabbuttonactive"; + break; + } + ViewState["Tab"] = index; + } + + #region Tabs Management + + /// + /// Gets or sets a value indicating wherher the Visual tab is enabled. + /// + public bool VisualVisible { + get { return btnVisual.Visible; } + set { btnVisual.Visible = value; } + } + + /// + /// Gets or sets a value indicating whether the Preview tab is enabled. + /// + public bool PreviewVisible { + get { return btnPreview.Visible; } + set { btnPreview.Visible = value; } + } + + /// + /// Gets or sets a value indicating whether the toolbar is visible. + /// + public bool ToolbarVisible { + get { return (bool)ViewState["ToolbarVisible"]; } + set { + ViewState["ToolbarVisible"] = value; + InitToolbar(); + } + } + + protected void btnWikiMarkup_Click(object sender, EventArgs e) { + SelectTab(0); + + //added for WYSIWYG + //if last view was WYSIWYG take text from WYSIWYG to Markup + if(inWYSIWYG) + txtMarkup.Text = ReverseFormatter.ReverseFormat(lblWYSIWYG.Text); + //end + } + + protected void btnVisual_Click(object sender, EventArgs e) { + SelectTab(1); + + //added for WYSIWYG + //lblWYSIWYG.Text = FormattingPipeline.FormatWithPhase1And2(txtMarkup.Text, null); + string[] links = null; + lblWYSIWYG.Text = Formatter.Format(txtMarkup.Text.Replace("<", "<").Replace(">", ">"), + false, FormattingContext.Unknown, null, out links, true); + //end + } + + protected void btnPreview_Click(object sender, EventArgs e) { + SelectTab(2); + + //added for WYSIWYG + //if last view was WYSIWYG take text from WYSIWYG to Preview + //in both cases I need to synchronize WYSIWYG and Markup view + if(inWYSIWYG) { + lblPreview.Text = lblWYSIWYG.Text; + txtMarkup.Text = ReverseFormatter.ReverseFormat(lblWYSIWYG.Text); + } + else { + lblPreview.Text = FormattingPipeline.FormatWithPhase3(FormattingPipeline.FormatWithPhase1And2(txtMarkup.Text, false, FormattingContext.Unknown, null), + FormattingContext.Unknown, null); + //lblWYSIWYG.Text = lblPreview.Text; + string[] links = null; + lblWYSIWYG.Text = Formatter.Format(txtMarkup.Text, false, FormattingContext.Unknown, null, out links, true); + } + //end + } + + #endregion + + #region Menus Management + + /// + /// Prints the custom special tags. + /// + private void PrintCustomSpecialTags() { + Dictionary tags = Host.Instance.CustomSpecialTags; + StringBuilder sb = new StringBuilder(100); + foreach(string key in tags.Keys) { + switch(tags[key].Item) { + case ToolbarItem.SpecialTag: + sb.AppendFormat("{1}", + tags[key].Value, key); + break; + case ToolbarItem.SpecialTagWrap: + string[] t = tags[key].Value.Split('|'); + sb.AppendFormat("{2}", + t[0], t[1], key); + break; + } + } + lblCustomSpecialTags.Text = sb.ToString(); + } + + /// + /// Loads and prints the snippets. + /// + private void LoadSnippets() { + StringBuilder sb = new StringBuilder(1000); + foreach(Snippet s in Snippets.GetSnippets()) { + string[] parameters = Snippets.ExtractParameterNames(s); + int paramCount = parameters.Length; + string label; + if(paramCount == 0) { + label = s.Name; + sb.AppendFormat(@"{0}", + label, s.Name); + } + else { + label = string.Format("{0} ({1} {2})", s.Name, paramCount, Properties.Messages.Parameters); + sb.AppendFormat(@"{0}", + label, s.Name, GetParametersPlaceHolders(parameters)); + } + } + if(sb.Length == 0) sb.Append("" + Properties.Messages.NoSnippets + ""); + lblSnippets.Text = sb.ToString(); + } + + /// + /// Gets the placeholder for snippet parameters. + /// + /// The parameters. + /// The snippet placeholder/template. + private static string GetParametersPlaceHolders(string[] parameters) { + if(parameters.Length == 0) return ""; + else { + StringBuilder sb = new StringBuilder(20); + foreach(string param in parameters) { + sb.AppendFormat("| {0} = PLACE YOUR VALUE HERE\\r\\n", param); + } + /*for(int i = 1; i <= paramCount; i++) { + sb.Append("|P"); + sb.Append(i.ToString()); + }*/ + return sb.ToString(); + } + } + + #endregion + + #region Public I/O + + /// + /// Sets the edited content. + /// + /// The content. + /// true if the visual editor must be used, false otherwise. + public void SetContent(string content, bool useVisual) { + txtMarkup.Text = content; + if(useVisual) btnVisual_Click(this, null); + else btnWikiMarkup_Click(this, null); + /*if(useVisual) { + txtMarkup.Text = content; + lblWYSIWYG.Text = content; + SelectTab(1); + } + else { + txtMarkup.Text = content; + SelectTab(0); + }*/ + } + + /// + /// Gets the edited content. + /// + /// The content. + public string GetContent() { + if(inWYSIWYG) return ReverseFormatter.ReverseFormat(lblWYSIWYG.Text); + else return txtMarkup.Text; + } + + /// + /// Gets or sets the current Page (if any), i.e. the page that is being edited. + /// + /// This property is used for enabling the "link attachment" feature in the editor. + /// If the current Page is null, the feature is disabled. + public PageInfo CurrentPage { + get { return currentPage; } + set { currentPage = value; } + } + + #endregion + + } + +} diff --git a/WebApplication/Editor.ascx.designer.cs b/WebApplication/Editor.ascx.designer.cs new file mode 100644 index 0000000..8e69b2d --- /dev/null +++ b/WebApplication/Editor.ascx.designer.cs @@ -0,0 +1,187 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4016 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class Editor { + + /// + /// lblStrings control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblStrings; + + /// + /// btnWikiMarkup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnWikiMarkup; + + /// + /// btnVisual control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnVisual; + + /// + /// btnPreview control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnPreview; + + /// + /// mlvEditor control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.MultiView mlvEditor; + + /// + /// viwStandard control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.View viwStandard; + + /// + /// btnBiggerMarkup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.ImageButton btnBiggerMarkup; + + /// + /// btnSmallerMarkup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.ImageButton btnSmallerMarkup; + + /// + /// txtMarkup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtMarkup; + + /// + /// viwVisual control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.View viwVisual; + + /// + /// btnBiggerMarkupVisual control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.ImageButton btnBiggerMarkupVisual; + + /// + /// btnSmallerMarkupVisual control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.ImageButton btnSmallerMarkupVisual; + + /// + /// lblWYSIWYG control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox lblWYSIWYG; + + /// + /// viwPreview control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.View viwPreview; + + /// + /// lblPreviewWarning control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblPreviewWarning; + + /// + /// lblPreview control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblPreview; + + /// + /// lblSnippets control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblSnippets; + + /// + /// lblCustomSpecialTags control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCustomSpecialTags; + + /// + /// lblToolbarInit control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblToolbarInit; + } +} diff --git a/WebApplication/Error.aspx b/WebApplication/Error.aspx new file mode 100644 index 0000000..4bf09d8 --- /dev/null +++ b/WebApplication/Error.aspx @@ -0,0 +1,27 @@ +<%@ Page Language="C#" MasterPageFile="~/MasterPageSA.master" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.Error" Title="Untitled Page" Culture="auto" meta:resourcekey="PageResource1" UICulture="auto" Codebehind="Error.aspx.cs" %> + + + +

    + + + + + +
    + Error + +

    + +

    +
    + + +

    +

    +
    +
    + +
    diff --git a/WebApplication/Error.aspx.cs b/WebApplication/Error.aspx.cs new file mode 100644 index 0000000..c045dc5 --- /dev/null +++ b/WebApplication/Error.aspx.cs @@ -0,0 +1,34 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; + +namespace ScrewTurn.Wiki { + + public partial class Error : BasePage { + + protected void Page_Load(object sender, EventArgs e) { + Page.Title = Properties.Messages.ErrorTitle + " - " + Settings.WikiTitle; + + Exception ex = Session["LastError"] as Exception; + if(ex != null && SessionFacade.LoginKey != null && + AdminMaster.CanManageConfiguration(SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames())) { + + lblException.Text = ex.ToString(); + } + else { + pnlException.Visible = false; + } + Session["LastError"] = null; + } + + } + +} diff --git a/WebApplication/Error.aspx.designer.cs b/WebApplication/Error.aspx.designer.cs new file mode 100644 index 0000000..c73469c --- /dev/null +++ b/WebApplication/Error.aspx.designer.cs @@ -0,0 +1,61 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.3053 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class Error { + + /// + /// lblError control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblError; + + /// + /// lblDescription control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDescription; + + /// + /// pnlException control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlException; + + /// + /// lblAdmin control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblAdmin; + + /// + /// lblException control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblException; + } +} diff --git a/WebApplication/FileManager.ascx b/WebApplication/FileManager.ascx new file mode 100644 index 0000000..73cfcda --- /dev/null +++ b/WebApplication/FileManager.ascx @@ -0,0 +1,150 @@ +<%@ Control Language="C#" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.FileManager" Codebehind="FileManager.ascx.cs" %> + +<%@ Register TagPrefix="st" TagName="PermissionsManager" Src="~/PermissionsManager.ascx" %> + + + + + +
    + +
    + + + + +
    + + +
    +

    +
    +
    + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      
    " alt="-" /><%# Eval("Name") %> + <%# Eval("Size") %><%# Eval("Downloads") %><%# Eval("WikiMarkupLink") %> + + • + + <%# ((bool)Eval("Editable") ? "• " + ScrewTurn.Wiki.Properties.Messages.Edit + "" : "")%> +
    " alt="-" /><%# Eval("Name") %> + <%# Eval("Size") %><%# Eval("Downloads") %><%# Eval("WikiMarkupLink") %> + + • + + <%# ((bool)Eval("Editable") ? "• " + ScrewTurn.Wiki.Properties.Messages.Edit + "" : "")%> +
    + +
    +
    + + +
    + +
    + +
    +

    +
    + + + +
    +
    + + +
    +

    +

    +
    +
    + + + + + + +
    +
    + +
    + + +
    +

    + +
    +
    +
    + +
    diff --git a/WebApplication/FileManager.ascx.cs b/WebApplication/FileManager.ascx.cs new file mode 100644 index 0000000..105fa03 --- /dev/null +++ b/WebApplication/FileManager.ascx.cs @@ -0,0 +1,596 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class FileManager : System.Web.UI.UserControl { + + private IFilesStorageProviderV30 provider = null; + + bool canList = false; + bool canDownload = false; + bool canUpload = false; + bool canCreateDirs = false; + bool canDeleteFiles = false; + bool canDeleteDirs = false; + bool canSetPerms = false; + bool isAdmin = false; + + protected void Page_Load(object sender, EventArgs e) { + + if(!Page.IsPostBack) { + permissionsManager.CurrentResourceName = "/"; + + // Localized strings for JavaScript + StringBuilder sb = new StringBuilder(); + sb.Append(@"\n"); + lblStrings.Text = sb.ToString(); + + // Setup upload information (max file size, allowed file types) + lblUploadFilesInfo.Text = lblUploadFilesInfo.Text.Replace("$1", Tools.BytesToString(Settings.MaxFileSize * 1024)); + sb = new StringBuilder(); + string[] aft = Settings.AllowedFileTypes; + for(int i = 0; i < aft.Length; i++) { + sb.Append(aft[i].ToUpper()); + if(i != aft.Length - 1) sb.Append(", "); + } + lblUploadFilesInfo.Text = lblUploadFilesInfo.Text.Replace("$2", sb.ToString()); + + LoadProviders(); + + permissionsManager.CurrentFilesProvider = lstProviders.SelectedValue; + + // See if a dir is specified in query string + if(Request["Dir"] != null) { + string currDir = Request["Dir"]; + if(!currDir.StartsWith("/")) currDir = "/" + currDir; + if(!currDir.EndsWith("/")) currDir += "/"; + CurrentDirectory = currDir; + } + } + + // Set provider + provider = Collectors.FilesProviderCollector.GetProvider(lstProviders.SelectedValue); + + // The following actions are verified ***FOR THE CURRENT DIRECTORY***: + // - List contents + // - Download files + // - Upload files + // - Create directories + // - Delete/Rename files -> hide/show buttons in repeater + // - Delete/Rename directories --> hide/show buttons in repeater + // - Manage Permissions -> avoid setting permissionsManager.CurrentResourceName/CurrentFilesProvider if not authorized + // - Member of Administrators -> hide/show provider selection + // ---> recheck everywhere an action is performed + + DetectPermissions(); + + if(!Page.IsPostBack) { + rptItems.DataBind(); + } + + PopulateBreadcrumb(); + + SetupControlsForPermissions(); + } + + /// + /// Loads the providers. + /// + private void LoadProviders() { + lstProviders.Items.Clear(); + foreach(IFilesStorageProviderV30 prov in Collectors.FilesProviderCollector.AllProviders) { + ListItem item = new ListItem(prov.Information.Name, prov.GetType().FullName); + if(item.Value == Settings.DefaultFilesProvider) { + item.Selected = true; + } + lstProviders.Items.Add(item); + } + } + + /// + /// Detects the permissions of the current user for the current directory. + /// + private void DetectPermissions() { + string currentUser = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + + canList = AuthChecker.CheckActionForDirectory(provider, CurrentDirectory, Actions.ForDirectories.List, currentUser, currentGroups); + canDownload = AuthChecker.CheckActionForDirectory(provider, CurrentDirectory, Actions.ForDirectories.DownloadFiles, currentUser, currentGroups); + canUpload = AuthChecker.CheckActionForDirectory(provider, CurrentDirectory, Actions.ForDirectories.UploadFiles, currentUser, currentGroups); + canCreateDirs = AuthChecker.CheckActionForDirectory(provider, CurrentDirectory, Actions.ForDirectories.CreateDirectories, currentUser, currentGroups); + canDeleteFiles = AuthChecker.CheckActionForDirectory(provider, CurrentDirectory, Actions.ForDirectories.DeleteFiles, currentUser, currentGroups); + canDeleteDirs = AuthChecker.CheckActionForDirectory(provider, CurrentDirectory, Actions.ForDirectories.DeleteDirectories, currentUser, currentGroups); + canSetPerms = AuthChecker.CheckActionForGlobals(Actions.ForGlobals.ManagePermissions, currentUser, currentGroups); + isAdmin = Array.Find(currentGroups, delegate(string g) { return g == Settings.AdministratorsGroup; }) != null; + } + + /// + /// Returns to the root. + /// + public void GoToRoot() { + CurrentDirectory = "/"; + + DetectPermissions(); + + rptItems.DataBind(); + PopulateBreadcrumb(); + } + + protected void rptItems_DataBinding(object sender, EventArgs e) { + permissionsManager.CurrentResourceName = CurrentDirectory; + permissionsManager.CurrentFilesProvider = lstProviders.SelectedValue; + + // Build a DataTable containing the proper information + DataTable table = new DataTable("Items"); + + table.Columns.Add("Type"); + table.Columns.Add("Name"); + table.Columns.Add("Size"); + table.Columns.Add("WikiMarkupLink"); + table.Columns.Add("Link"); + table.Columns.Add("Editable", typeof(bool)); + table.Columns.Add("FullPath"); + table.Columns.Add("Downloads"); + table.Columns.Add("CanDelete", typeof(bool)); + table.Columns.Add("CanDownload", typeof(bool)); + + if(!canList) { + lblNoList.Visible = true; + rptItems.DataSource = table; // This is empty + return; + } + lblNoList.Visible = false; + + string currDir = CurrentDirectory; + + string[] dirs = provider.ListDirectories(currDir); + + string currentUser = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + + foreach(string s in dirs) { + bool canListThisSubDir = AuthChecker.CheckActionForDirectory(provider, s, Actions.ForDirectories.List, currentUser, currentGroups); + + DataRow row = table.NewRow(); + row["Type"] = "D"; + row["Name"] = GetItemName(s)/* + "/"*/; + row["Size"] = "(" + ((int)(provider.ListFiles(s).Length + provider.ListDirectories(s).Length)).ToString() + ")"; + row["WikiMarkupLink"] = " "; + row["Link"] = ""; + row["Editable"] = false; + row["FullPath"] = s; + row["Downloads"] = " "; + row["CanDelete"] = canDeleteDirs; + row["CanDownload"] = canListThisSubDir; + table.Rows.Add(row); + } + + string[] files = provider.ListFiles(currDir); + foreach(string s in files) { + FileDetails details = provider.GetFileDetails(s); + + DataRow row = table.NewRow(); + string ext = Path.GetExtension(s).ToLowerInvariant(); + row["Type"] = "F"; + row["Name"] = GetItemName(s); + row["Size"] = Tools.BytesToString(details.Size); + row["WikiMarkupLink"] = "{UP}" + Tools.UrlEncode(s); + if(canDownload) { + row["Link"] = "GetFile.aspx?File=" + Tools.UrlEncode(s) + "&AsStreamAttachment=1&Provider=" + + provider.GetType().FullName + "&NoHit=1"; + } + else { + row["Link"] = ""; + } + row["Editable"] = canUpload && canDeleteFiles && (ext == ".jpg" || ext == ".jpeg" || ext == ".png"); + row["FullPath"] = s; + row["Downloads"] = details.RetrievalCount.ToString(); + row["CanDelete"] = canDeleteFiles; + row["CanDownload"] = canDownload; + table.Rows.Add(row); + } + + rptItems.DataSource = table; + } + + /// + /// Deletes the permissions of a directory. + /// + /// The directory. + private void DeletePermissions(string directory) { + if(!directory.StartsWith("/")) directory = "/" + directory; + if(!directory.EndsWith("/")) directory = directory + "/"; + + foreach(string sub in provider.ListDirectories(directory)) { + DeletePermissions(sub); + } + + AuthWriter.ClearEntriesForDirectory(provider, directory); + } + + protected void rptItems_ItemCommand(object sender, RepeaterCommandEventArgs e) { + string item = (string)e.CommandArgument; + + switch(e.CommandName) { + case "Dir": + EnterDirectory(GetItemName(item)); + break; + case "Rename": + // Hide all directory-specific controls + // Permissions are verified in btnRename_Click + pnlRename.Visible = true; + pnlNewDirectory.Visible = false; + pnlUpload.Visible = false; + pnlPermissions.Visible = false; + lstProviders.Visible = false; + lblItem.Text = GetItemName(item) + (item.EndsWith("/") ? "/" : ""); + txtNewName.Text = GetItemName(item); + rptItems.Visible = false; + break; + case "Delete": + if(item.EndsWith("/")) { + if(canDeleteDirs) { + // Delete Directory + DeletePermissions(item); + bool d = provider.DeleteDirectory(item); + + if(d) { + Host.Instance.OnDirectoryActivity(provider.GetType().FullName, + item, null, FileActivity.DirectoryDeleted); + } + } + } + else { + if(canDeleteFiles) { + // Delete File + bool d = provider.DeleteFile(item); + + if(d) { + Host.Instance.OnFileActivity(provider.GetType().FullName, + item, null, FileActivity.FileDeleted); + } + } + } + rptItems.DataBind(); + break; + } + } + + /// + /// Tries to enter a directory. + /// + /// The provider. + /// The full path of the directory. + public void TryEnterDirectory(string provider, string directory) { + if(string.IsNullOrEmpty(directory) || string.IsNullOrEmpty(provider)) return; + + if(!directory.StartsWith("/")) directory = "/" + directory; + if(!directory.EndsWith("/")) directory += "/"; + + LoadProviders(); + + IFilesStorageProviderV30 realProvider = Collectors.FilesProviderCollector.GetProvider(provider); + if(realProvider == null) return; + this.provider = realProvider; + + // Detect existence + try { + realProvider.ListDirectories(directory); + } + catch(ArgumentException) { + return; + } + + bool canListThisSubDir = AuthChecker.CheckActionForDirectory(realProvider, directory, Actions.ForDirectories.List, + SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames()); + if(!canListThisSubDir) { + return; + } + + lstProviders.SelectedIndex = -1; + foreach(ListItem item in lstProviders.Items) { + if(item.Value == provider) { + item.Selected = true; + break; + } + } + //lstProviders_SelectedIndexChanged(this, null); + + string parent = "/"; + string trimmedDirectory = directory.TrimEnd('/'); + if(trimmedDirectory.Length > 0) { + int lastSlash = trimmedDirectory.LastIndexOf("/"); + if(lastSlash != -1) { + parent = "/" + trimmedDirectory.Substring(0, lastSlash) + "/"; + } + } + + if(parent != directory) { + CurrentDirectory = parent; + EnterDirectory(Tools.ExtractDirectoryName(directory)); + } + } + + /// + /// Enters a sub-directory of the current directory. + /// + /// The name of the current directory. + private void EnterDirectory(string name) { + string newDirectory = CurrentDirectory + name + "/"; + bool canListThisSubDir = AuthChecker.CheckActionForDirectory(provider, newDirectory, Actions.ForDirectories.List, + SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames()); + if(!canListThisSubDir) { + return; + } + + CurrentDirectory += name + "/"; + + DetectPermissions(); + + rptItems.DataBind(); + PopulateBreadcrumb(); + } + + /// + /// Gets the name of an item. + /// + /// The full path of the item (file or directory). + /// The name of the item. + private string GetItemName(string path) { + path = path.Trim('/'); + if(path.Contains("/")) { + path = path.Substring(path.LastIndexOf("/") + 1); + } + return path; + } + + /// + /// Gets or sets the current directory. + /// + private string CurrentDirectory { + get { + if(ViewState["CurrDir"] != null) return (string)ViewState["CurrDir"]; + else return "/"; + } + set { ViewState["CurrDir"] = value; } + } + + protected void lnkRoot_Click(object sender, EventArgs e) { + GoToRoot(); + } + + private void linkButton_Click(object sender, EventArgs e) { + Anthem.LinkButton lnk = sender as Anthem.LinkButton; + if(lnk != null) { + CurrentDirectory = lnk.CommandArgument; + + DetectPermissions(); + + rptItems.DataBind(); + PopulateBreadcrumb(); + } + } + + /// + /// Populates the directory breadcrumb placeholder. See also methods linkButton_Click e lnkRoot_Click. + /// + private void PopulateBreadcrumb() { + plhDirectory.Controls.Clear(); + // Get all directories by splitting the content of txtCurrentDirectory + string[] dirs = CurrentDirectory.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + + // Add a LinkButton and a "/" label for each directory + string current = "/"; + for(int i = 0; i < dirs.Length; i++) { + Anthem.LinkButton lnk = new Anthem.LinkButton(); + lnk.ID = "lnkDir" + i.ToString(); + lnk.Text = dirs[i]; + current += dirs[i] + "/"; + lnk.CommandArgument = current; + lnk.ToolTip = current; + lnk.Click += new EventHandler(linkButton_Click); + plhDirectory.Controls.Add(lnk); + + Anthem.Label lbl = new Anthem.Label(); + lbl.ID = "lblDir" + i.ToString(); + lbl.Text = " / "; + plhDirectory.Controls.Add(lbl); + } + } + + /// + /// Sets the controls depending on the current permissions. + /// + private void SetupControlsForPermissions() { + // Setup buttons and controls visibility + chkOverwrite.Enabled = canDeleteFiles; + lstProviders.Visible = isAdmin; + + pnlUpload.Visible = canUpload; + pnlNewDirectory.Visible = canCreateDirs; + pnlPermissions.Visible = canSetPerms; + permissionsManager.Visible = canSetPerms; + if(!canSetPerms) { + permissionsManager.CurrentResourceName = ""; + permissionsManager.CurrentFilesProvider = ""; + } + } + + /// + /// Recursively moves permissions from the old (renamed) directory to the new one. + /// + /// The old directory name. + /// The new directory name. + private void MovePermissions(string oldDirectory, string newDirectory) { + if(!oldDirectory.StartsWith("/")) oldDirectory = "/" + oldDirectory; + if(!oldDirectory.EndsWith("/")) oldDirectory = oldDirectory + "/"; + if(!newDirectory.StartsWith("/")) newDirectory = "/" + newDirectory; + if(!newDirectory.EndsWith("/")) newDirectory = newDirectory + "/"; + + foreach(string sub in provider.ListDirectories(oldDirectory)) { + string subNew = newDirectory + sub.Substring(oldDirectory.Length); + MovePermissions(sub, subNew); + } + + AuthWriter.ClearEntriesForDirectory(provider, newDirectory); + AuthWriter.ProcessDirectoryRenaming(provider, oldDirectory, newDirectory); + } + + protected void btnRename_Click(object sender, EventArgs e) { + lblRenameResult.Text = ""; + bool done = false; + if(lblItem.Text.EndsWith("/")) { + if(canDeleteDirs) { + MovePermissions(CurrentDirectory + lblItem.Text, CurrentDirectory + txtNewName.Text); + + done = provider.RenameDirectory(CurrentDirectory + lblItem.Text, CurrentDirectory + txtNewName.Text); + + if(done) { + Host.Instance.OnDirectoryActivity(provider.GetType().FullName, + CurrentDirectory + txtNewName.Text, CurrentDirectory + lblItem.Text, FileActivity.DirectoryRenamed); + } + } + } + else { + if(canDeleteFiles) { + // Ensure that the extension is not changed (security) + string previousExtension = Path.GetExtension(lblItem.Text); + string newExtension = Path.GetExtension(txtNewName.Text); + if(string.IsNullOrEmpty(newExtension)) { + newExtension = previousExtension; + txtNewName.Text += previousExtension; + } + + if(newExtension.ToLowerInvariant() != previousExtension.ToLowerInvariant()) { + txtNewName.Text += previousExtension; + } + + done = true; + if(txtNewName.Text.ToLowerInvariant() != lblItem.Text.ToLowerInvariant()) { + done = provider.RenameFile(CurrentDirectory + lblItem.Text, CurrentDirectory + txtNewName.Text); + + if(done) { + Host.Instance.OnFileActivity(provider.GetType().FullName, + CurrentDirectory + txtNewName.Text, CurrentDirectory + lblItem.Text, FileActivity.FileRenamed); + } + } + } + } + if(done) { + pnlRename.Visible = false; + rptItems.Visible = true; + rptItems.DataBind(); + SetupControlsForPermissions(); + } + else { + lblRenameResult.Text = Properties.Messages.CannotRenameItem; + lblRenameResult.CssClass = "resulterror"; + } + } + + protected void btnCancel_Click(object sender, EventArgs e) { + pnlRename.Visible = false; + rptItems.Visible = true; + SetupControlsForPermissions(); + lblRenameResult.Text = ""; + } + + protected void btnNewDirectory_Click(object sender, EventArgs e) { + if(canCreateDirs) { + lblNewDirectoryResult.Text = ""; + txtNewDirectoryName.Text = txtNewDirectoryName.Text.Trim('/'); + AuthWriter.ClearEntriesForDirectory(provider, CurrentDirectory + txtNewDirectoryName.Text + "/"); + bool done = provider.CreateDirectory(CurrentDirectory, txtNewDirectoryName.Text); + if(!done) { + lblNewDirectoryResult.CssClass = "resulterror"; + lblNewDirectoryResult.Text = Properties.Messages.CannotCreateNewDirectory; + } + else { + txtNewDirectoryName.Text = ""; + + Host.Instance.OnDirectoryActivity(provider.GetType().FullName, + CurrentDirectory + txtNewDirectoryName.Text + "/", null, FileActivity.DirectoryCreated); + } + rptItems.DataBind(); + } + } + + protected void btnUpload_Click(object sender, EventArgs e) { + if(canUpload && (chkOverwrite.Checked && canDeleteFiles || !chkOverwrite.Checked)) { + lblUploadResult.Text = ""; + if(fileUpload.HasFile) { + if(fileUpload.FileBytes.Length > Settings.MaxFileSize * 1024) { + lblUploadResult.Text = Properties.Messages.FileTooBig; + lblUploadResult.CssClass = "resulterror"; + } + else { + // Check file extension + string[] aft = Settings.AllowedFileTypes; + bool allowed = false; + + if(aft.Length > 0 && aft[0] == "*") allowed = true; + else { + string ext = Path.GetExtension(fileUpload.FileName); + if(ext == null) ext = ""; + if(ext.StartsWith(".")) ext = ext.Substring(1).ToLowerInvariant(); + foreach(string ft in aft) { + if(ft == ext) { + allowed = true; + break; + } + } + } + + if(!allowed) { + lblUploadResult.Text = Properties.Messages.InvalidFileType; + lblUploadResult.CssClass = "resulterror"; + } + else { + // Store file + bool done = provider.StoreFile(CurrentDirectory + fileUpload.FileName, fileUpload.FileContent, chkOverwrite.Checked); + if(!done) { + lblUploadResult.Text = Properties.Messages.CannotStoreFile; + lblUploadResult.CssClass = "resulterror"; + } + else { + Host.Instance.OnFileActivity(provider.GetType().FullName, + CurrentDirectory + fileUpload.FileName, null, FileActivity.FileUploaded); + } + rptItems.DataBind(); + } + } + } + else { + lblUploadResult.Text = Properties.Messages.FileVoid; + lblUploadResult.CssClass = "resulterror"; + } + } + } + + protected void lstProviders_SelectedIndexChanged(object sender, EventArgs e) { + provider = Collectors.FilesProviderCollector.GetProvider(lstProviders.SelectedValue); + GoToRoot(); + } + + } + +} diff --git a/WebApplication/FileManager.ascx.designer.cs b/WebApplication/FileManager.ascx.designer.cs new file mode 100644 index 0000000..38fd7b9 --- /dev/null +++ b/WebApplication/FileManager.ascx.designer.cs @@ -0,0 +1,286 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4016 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class FileManager { + + /// + /// lblStrings control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblStrings; + + /// + /// lstProviders control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.DropDownList lstProviders; + + /// + /// lnkRoot control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.LinkButton lnkRoot; + + /// + /// lblRoot control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblRoot; + + /// + /// plhDirectory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.PlaceHolder plhDirectory; + + /// + /// pnlRename control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlRename; + + /// + /// lblRename control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRename; + + /// + /// lblItem control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblItem; + + /// + /// txtNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtNewName; + + /// + /// btnRename control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnRename; + + /// + /// btnCancel control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnCancel; + + /// + /// lblRenameResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblRenameResult; + + /// + /// rptItems control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Repeater rptItems; + + /// + /// lblNoList control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblNoList; + + /// + /// pnlNewDirectory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlNewDirectory; + + /// + /// lblNewDirectoryTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblNewDirectoryTitle; + + /// + /// lblDirectoryName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDirectoryName; + + /// + /// txtNewDirectoryName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtNewDirectoryName; + + /// + /// btnNewDirectory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Button btnNewDirectory; + + /// + /// lblNewDirectoryResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblNewDirectoryResult; + + /// + /// pnlUpload control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlUpload; + + /// + /// lblUploadFiles control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblUploadFiles; + + /// + /// lblUploadFilesInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblUploadFilesInfo; + + /// + /// chkOverwrite control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CheckBox chkOverwrite; + + /// + /// fileUpload control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.FileUpload fileUpload; + + /// + /// btnUpload control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnUpload; + + /// + /// lblUploadResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblUploadResult; + + /// + /// pnlPermissions control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Panel pnlPermissions; + + /// + /// lblManagePermissions control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblManagePermissions; + + /// + /// permissionsManager control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::ScrewTurn.Wiki.PermissionsManager permissionsManager; + } +} diff --git a/WebApplication/GPL.txt b/WebApplication/GPL.txt new file mode 100644 index 0000000..f90922e --- /dev/null +++ b/WebApplication/GPL.txt @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/WebApplication/GetFile.aspx b/WebApplication/GetFile.aspx new file mode 100644 index 0000000..f2c6bbb --- /dev/null +++ b/WebApplication/GetFile.aspx @@ -0,0 +1 @@ +<%@ Page Language="C#" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.GetFile" Codebehind="GetFile.aspx.cs" %> diff --git a/WebApplication/GetFile.aspx.cs b/WebApplication/GetFile.aspx.cs new file mode 100644 index 0000000..692c81f --- /dev/null +++ b/WebApplication/GetFile.aspx.cs @@ -0,0 +1,201 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using System.Globalization; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + // No BasePage because compression/language selection are not needed + public partial class GetFile : Page { + + protected void Page_Load(object sender, EventArgs e) { + string filename = Request["File"]; + + if(filename == null) { + Response.StatusCode = 404; + Response.Write(Properties.Messages.FileNotFound); + return; + } + + // Remove ".." sequences that might be a security issue + filename = filename.Replace("..", ""); + + bool isPageAttachment = !string.IsNullOrEmpty(Request["Page"]); + PageInfo pageInfo = isPageAttachment ? Pages.FindPage(Request["Page"]) : null; + if(isPageAttachment && pageInfo == null) { + Response.StatusCode = 404; + Response.Write(Properties.Messages.FileNotFound); + return; + } + + IFilesStorageProviderV30 provider; + + if(!string.IsNullOrEmpty(Request["Provider"])) provider = Collectors.FilesProviderCollector.GetProvider(Request["Provider"]); + else { + if(isPageAttachment) provider = FilesAndAttachments.FindPageAttachmentProvider(pageInfo, filename); + else provider = FilesAndAttachments.FindFileProvider(filename); + } + + if(provider == null) { + Response.StatusCode = 404; + Response.Write("File not found."); + return; + } + + // Use canonical path format (leading with /) + if(!isPageAttachment) { + if(!filename.StartsWith("/")) filename = "/" + filename; + filename = filename.Replace("\\", "/"); + } + + bool countHit = CountHit(filename); + + // Verify permissions + bool canDownload = false; + + if(isPageAttachment) { + canDownload = AuthChecker.CheckActionForPage(pageInfo, Actions.ForPages.DownloadAttachments, + SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames()); + } + else { + string dir = Tools.GetDirectoryName(filename); + canDownload = AuthChecker.CheckActionForDirectory(provider, dir, + Actions.ForDirectories.DownloadFiles, SessionFacade.GetCurrentUsername(), + SessionFacade.GetCurrentGroupNames()); + } + if(!canDownload) { + Response.StatusCode = 401; + return; + } + + long size = -1; + + FileDetails details = null; + if(isPageAttachment) details = provider.GetPageAttachmentDetails(pageInfo, filename); + else details = provider.GetFileDetails(filename); + + if(details != null) size = details.Size; + else { + Log.LogEntry("Attempted to download an inexistent file/attachment (" + (pageInfo != null ? pageInfo.FullName + "/" : "") + filename + ")", EntryType.Warning, Log.SystemUsername); + Response.StatusCode = 404; + Response.Write("File not found."); + return; + } + + string mime = ""; + try { + string ext = Path.GetExtension(filename); + if(ext.StartsWith(".")) ext = ext.Substring(1).ToLower(); // Remove trailing dot + mime = GetMimeType(ext); + } + catch { + // ext is null -> no mime type -> abort + Response.Write(filename + "
    "); + Response.StatusCode = 404; + Response.Write("File not found."); + //mime = "application/octet-stream"; + return; + } + + // Prepare response + Response.Clear(); + Response.AddHeader("content-type", mime); + if(Request["AsStreamAttachment"] != null) { + Response.AddHeader("content-disposition", "attachment;filename=\"" + Path.GetFileName(filename) + "\""); + } + else { + Response.AddHeader("content-disposition", "inline;filename=\"" + Path.GetFileName(filename) + "\""); + } + Response.AddHeader("content-length", size.ToString()); + + bool retrieved = false; + if(isPageAttachment) { + try { + retrieved = provider.RetrievePageAttachment(pageInfo, filename, Response.OutputStream, countHit); + } + catch(ArgumentException ex) { + Log.LogEntry("Attempted to download an inexistent attachment (" + pageInfo.FullName + "/" + filename + ")\n" + ex.ToString(), EntryType.Warning, Log.SystemUsername); + } + } + else { + try { + retrieved = provider.RetrieveFile(filename, Response.OutputStream, countHit); + } + catch(ArgumentException ex) { + Log.LogEntry("Attempted to download an inexistent file/attachment (" + filename + ")\n" + ex.ToString(), EntryType.Warning, Log.SystemUsername); + } + } + + if(!retrieved) { + Response.StatusCode = 404; + Response.Write("File not found."); + return; + } + + // Set the cache duration accordingly to the file date/time + //Response.AddFileDependency(filename); + //Response.Cache.SetETagFromFileDependencies(); + //Response.Cache.SetLastModifiedFromFileDependencies(); + Response.Cache.SetETag(filename.GetHashCode().ToString() + "-" + size.ToString()); + Response.Cache.SetCacheability(HttpCacheability.Public); + Response.Cache.SetSlidingExpiration(true); + Response.Cache.SetValidUntilExpires(true); + Response.Cache.VaryByParams["File"] = true; + Response.Cache.VaryByParams["Provider"] = true; + Response.Cache.VaryByParams["Page"] = true; + Response.Cache.VaryByParams["IsPageAttachment"] = true; + } + + private string GetMimeType(string ext) { + string mime = ""; + if(MimeTypes.Types.TryGetValue(ext, out mime)) return mime; + else return "application/octet-stream"; + } + + /// + /// Gets a value indicating whether or not to count the hit. + /// + /// The name of the file. + /// true if the hit must be counted, false otherwise. + private bool CountHit(string file) { + bool result = Request["NoHit"] != "1"; + + if(!result) return false; + else { + FileDownloadCountFilterMode mode = Settings.FileDownloadCountFilterMode; + if(mode == FileDownloadCountFilterMode.CountAll) return true; + else { + string[] allowedExtensions = Settings.FileDownloadCountFilter; + string extension = Path.GetExtension(file); + if(string.IsNullOrEmpty(extension)) return false; + else extension = extension.Trim('.').ToLowerInvariant(); + + bool found = false; + foreach(string ex in allowedExtensions) { + if(ex == extension) { + found = true; + break; + } + } + + if(found && mode == FileDownloadCountFilterMode.CountSpecifiedExtensions) return true; + else if(!found && mode == FileDownloadCountFilterMode.ExcludeSpecifiedExtensions) return true; + else return false; + } + } + } + + } + +} diff --git a/WebApplication/GetFile.aspx.designer.cs b/WebApplication/GetFile.aspx.designer.cs new file mode 100644 index 0000000..fc54315 --- /dev/null +++ b/WebApplication/GetFile.aspx.designer.cs @@ -0,0 +1,16 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.1434 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class GetFile { + } +} diff --git a/WebApplication/Global.asax b/WebApplication/Global.asax new file mode 100644 index 0000000..e474e0c --- /dev/null +++ b/WebApplication/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="ScrewTurn.Wiki.Global" Language="C#" %> diff --git a/WebApplication/Global.asax.cs b/WebApplication/Global.asax.cs new file mode 100644 index 0000000..0b32458 --- /dev/null +++ b/WebApplication/Global.asax.cs @@ -0,0 +1,106 @@ + +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.Security; +using System.Web.SessionState; +using System.Text; + +namespace ScrewTurn.Wiki { + + public class Global : System.Web.HttpApplication { + + protected void Application_Start(object sender, EventArgs e) { + // Nothing to do (see Application_BeginRequest). + } + + protected void Session_Start(object sender, EventArgs e) { + // Increment # of online users and setup a new breadcrumbs manager + // TODO: avoid to increment # of online users when session is not InProc + ScrewTurn.Wiki.Cache.OnlineUsers++; + } + + protected void Application_BeginRequest(object sender, EventArgs e) { + if(Application["StartupOK"] == null) { + Application.Lock(); + if(Application["StartupOK"] == null) { + // Setup Resource Exchanger + ScrewTurn.Wiki.Exchanger.ResourceExchanger = new ScrewTurn.Wiki.ResourceExchanger(); + ScrewTurn.Wiki.StartupTools.Startup(); + + // All is OK, proceed with normal startup operations + Application["StartupOK"] = "OK"; + } + Application.UnLock(); + } + + ScrewTurn.Wiki.UrlTools.RouteCurrentRequest(); + } + + protected void Application_AcquireRequestState(object sender, EventArgs e) { + if(HttpContext.Current.Session != null) { + // This should be performed on EndRequest, but Session is not available there + SessionCache.ClearData(HttpContext.Current.Session.SessionID); + + // Try to automatically login the user through the cookie + ScrewTurn.Wiki.LoginTools.TryAutoLogin(); + } + } + + protected void Application_AuthenticateRequest(object sender, EventArgs e) { + // Nothing to do + } + + /// + /// Logs an error. + /// + /// The error. + private void LogError(Exception ex) { + //if(ex.InnerException != null) ex = ex.InnerException; + try { + ScrewTurn.Wiki.Log.LogEntry(HttpContext.Current.Request.Url.ToString() + "\n" + + ex.Source + " thrown " + ex.GetType().FullName + "\n" + ex.Message + "\n" + ex.StackTrace, + ScrewTurn.Wiki.PluginFramework.EntryType.Error, ScrewTurn.Wiki.Log.SystemUsername); + } + catch { } + } + + protected void Application_Error(object sender, EventArgs e) { + // Retrieve last error and log it, redirecting to Error.aspx (avoiding infinite loops) + + Exception ex = Server.GetLastError(); + + HttpException httpEx = ex as HttpException; + if(httpEx != null) { + // Try to redirect an inexistent .aspx page to a probably existing .ashx page + if(httpEx.GetHttpCode() == 404) { + string page = System.IO.Path.GetFileNameWithoutExtension(Request.PhysicalPath); + ScrewTurn.Wiki.UrlTools.Redirect(page + ScrewTurn.Wiki.Settings.PageExtension); + return; + } + } + + LogError(ex); + string url = ""; + try { + url = HttpContext.Current.Request.Url.ToString(); + } + catch { } + EmailTools.NotifyError(ex, url); + Session["LastError"] = Server.GetLastError(); + if(!Request.PhysicalPath.ToLowerInvariant().Contains("error.aspx")) ScrewTurn.Wiki.UrlTools.Redirect("Error.aspx"); + } + + protected void Session_End(object sender, EventArgs e) { + // Decrement # of online users (only works when session is InProc) + ScrewTurn.Wiki.Cache.OnlineUsers--; + } + + protected void Application_End(object sender, EventArgs e) { + // Try to cleanly shutdown the application and providers + ScrewTurn.Wiki.StartupTools.Shutdown(); + } + + } + +} diff --git a/WebApplication/History.aspx b/WebApplication/History.aspx new file mode 100644 index 0000000..bd4f2c9 --- /dev/null +++ b/WebApplication/History.aspx @@ -0,0 +1,57 @@ +<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.History" Title="Untitled Page" Culture="auto" meta:resourcekey="PageResource1" UICulture="auto" Codebehind="History.aspx.cs" %> + + + +

    +

    + + + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    # 
    <%# Eval("Revision") %><%# Eval("Title") %><%# Eval("SavedOn") %><%# Eval("SavedBy") %><%# Eval("Comment") %>
    <%# Eval("Revision") %><%# Eval("Title") %><%# Eval("SavedOn") %><%# Eval("SavedBy") %><%# Eval("Comment") %>
    + +
    + +
    diff --git a/WebApplication/History.aspx.cs b/WebApplication/History.aspx.cs new file mode 100644 index 0000000..8ff4fc7 --- /dev/null +++ b/WebApplication/History.aspx.cs @@ -0,0 +1,253 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using ScrewTurn.Wiki.PluginFramework; +using System.Text; + +namespace ScrewTurn.Wiki { + + public partial class History : BasePage { + + private PageInfo page; + private PageContent content; + private bool canRollback; + + protected void Page_Load(object sender, EventArgs e) { + Page.Title = Properties.Messages.HistoryTitle + " - " + Settings.WikiTitle; + + page = Pages.FindPage(Request["Page"]); + + if(page != null) { + canRollback = AuthChecker.CheckActionForPage(page, Actions.ForPages.ManagePage, + SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames()); + + content = Content.GetPageContent(page, true); + lblTitle.Text = Properties.Messages.PageHistory + ": " + FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.PageContent, page); + + bool canView = AuthChecker.CheckActionForPage(page, Actions.ForPages.ReadPage, + SessionFacade.GetCurrentUsername(), SessionFacade.GetCurrentGroupNames()); + if(!canView) UrlTools.Redirect("AccessDenied.aspx"); + } + else { + lblTitle.Text = Properties.Messages.PageNotFound; + return; + } + + if(!Page.IsPostBack && page != null) { + List revisions = Pages.GetBackups(page); + revisions.Reverse(); + // Populate dropdown lists + lstRev1.Items.Clear(); + lstRev2.Items.Clear(); + lstRev2.Items.Add(new ListItem(Properties.Messages.Current, "Current")); + if(Request["Rev2"] != null && Request["Rev2"].Equals(lstRev2.Items[0].Value)) + lstRev2.SelectedIndex = 0; + for(int i = 0; i < revisions.Count; i++) { + lstRev1.Items.Add(new ListItem(revisions[i].ToString(), revisions[i].ToString())); + lstRev2.Items.Add(new ListItem(revisions[i].ToString(), revisions[i].ToString())); + if(Request["Rev1"] != null && Request["Rev1"].Equals(lstRev1.Items[i].Value)) + lstRev1.SelectedIndex = i; + if(Request["Rev2"] != null && Request["Rev2"].Equals(lstRev2.Items[i + 1].Value)) + lstRev2.SelectedIndex = i + 1; + } + if(revisions.Count == 0) btnCompare.Enabled = false; + } + + PrintHistory(); + } + + /// + /// Prints the history. + /// + public void PrintHistory() { + if(page == null) return; + + StringBuilder sb = new StringBuilder(); + + if(Request["Revision"] == null) { + // Show version list + List revisions = Pages.GetBackups(page); + revisions.Reverse(); + + List result = new List(revisions.Count + 1); + + result.Add(new RevisionRow(-1, Content.GetPageContent(page, false), false)); + + foreach(int rev in revisions) { + PageContent content = Pages.GetBackupContent(page, rev); + + result.Add(new RevisionRow(rev, content, canRollback)); + } + + rptHistory.DataSource = result; + rptHistory.DataBind(); + } + else { + int rev = -1; + if(!int.TryParse(Request["Revision"], out rev)) UrlTools.Redirect(page.FullName + Settings.PageExtension); + + List backups = Pages.GetBackups(page); + if(!backups.Contains(rev)) { + UrlTools.Redirect(page.FullName + Settings.PageExtension); + return; + } + PageContent revision = Pages.GetBackupContent(page, rev); + sb.Append(@"
    "); + sb.Append(@"

    "); + if(rev > 0) { + sb.Append(@"« "); + sb.Append(Properties.Messages.OlderRevision); + sb.Append(""); + } + else { + sb.Append("« "); + sb.Append(Properties.Messages.OlderRevision); + } + + sb.Append(@" - "); + sb.Append(Properties.Messages.BackToHistory); + sb.Append(" - "); + + if(rev < backups.Count - 1) { + sb.Append(@""); + sb.Append(Properties.Messages.NewerRevision); + sb.Append(" »"); + } + else { + sb.Append(@""); + sb.Append(Properties.Messages.CurrentRevision); + sb.Append(""); + } + sb.Append("


    "); + + sb.Append(@"

    "); + sb.Append(Properties.Messages.PageRevision); + sb.Append(": "); + sb.Append(Preferences.AlignWithTimezone(revision.LastModified).ToString(Settings.DateTimeFormat)); + sb.Append("


    "); + + sb.Append(FormattingPipeline.FormatWithPhase3(FormattingPipeline.FormatWithPhase1And2(revision.Content, + false, FormattingContext.PageContent, page).Replace(Formatter.EditSectionPlaceHolder, ""), FormattingContext.PageContent, page)); + } + + lblHistory.Text = sb.ToString(); + } + + protected void rptHistory_ItemCommand(object sender, CommandEventArgs e) { + if(e.CommandName == "Rollback") { + if(!canRollback) return; + int rev = int.Parse(e.CommandArgument as string); + + Log.LogEntry("Page rollback requested for " + page.FullName + " to rev. " + rev.ToString(), EntryType.General, SessionFacade.GetCurrentUsername()); + Pages.Rollback(page, rev); + + PrintHistory(); + } + } + + protected void btnCompare_Click(object sender, EventArgs e) { + UrlTools.Redirect(UrlTools.BuildUrl("Diff.aspx?Page=", Tools.UrlEncode(page.FullName), "&Rev1=", lstRev1.SelectedValue, + "&Rev2=", lstRev2.SelectedValue)); + } + + } + + /// + /// Represents a page revision for display purposes. + /// + public class RevisionRow { + + private string page, revision, title, savedOn, savedBy, comment; + private bool canRollback; + + /// + /// Initializes a new instance of the class. + /// + /// The revision (-1 for current). + /// The original page content. + /// A value indicating whether the current user can rollback the page. + public RevisionRow(int revision, PageContent content, bool canRollback) { + this.page = content.PageInfo.FullName; + if(revision == -1) this.revision = Properties.Messages.Current; + else this.revision = revision.ToString(); + title = FormattingPipeline.PrepareTitle(content.Title, false, FormattingContext.PageContent, content.PageInfo); + savedOn = Preferences.AlignWithTimezone(content.LastModified).ToString(Settings.DateTimeFormat); + savedBy = content.User; + comment = content.Comment; + this.canRollback = canRollback; + } + + /// + /// Gets the page name. + /// + public string Page { + get { return page; } + } + + /// + /// Gets the revision. + /// + public string Revision { + get { return revision; } + } + + /// + /// Gets the title. + /// + public string Title { + get { return title; } + } + + /// + /// Gets the save date/time. + /// + public string SavedOn { + get { return savedOn; } + } + + /// + /// Gets the revision author. + /// + public string SavedBy { + get { return savedBy; } + } + + /// + /// Gets the comment. + /// + public string Comment { + get { return comment; } + } + + /// + /// Gets a value indicating whether the current user can rollback the page. + /// + public bool CanRollback { + get { return canRollback; } + } + + } + +} diff --git a/WebApplication/History.aspx.designer.cs b/WebApplication/History.aspx.designer.cs new file mode 100644 index 0000000..c8a76d1 --- /dev/null +++ b/WebApplication/History.aspx.designer.cs @@ -0,0 +1,79 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.3074 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class History { + + /// + /// lblTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblTitle; + + /// + /// lblCompare control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCompare; + + /// + /// lstRev1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.DropDownList lstRev1; + + /// + /// lstRev2 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.DropDownList lstRev2; + + /// + /// btnCompare control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnCompare; + + /// + /// lblHistory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblHistory; + + /// + /// rptHistory control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Repeater rptHistory; + } +} diff --git a/WebApplication/IFrameEditor.aspx b/WebApplication/IFrameEditor.aspx new file mode 100644 index 0000000..1eb2c08 --- /dev/null +++ b/WebApplication/IFrameEditor.aspx @@ -0,0 +1,11 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="IFrameEditor.aspx.cs" Inherits="ScrewTurn.Wiki.IFrameEditor" %> + + + + + + Editor + + + + diff --git a/WebApplication/IFrameEditor.aspx.cs b/WebApplication/IFrameEditor.aspx.cs new file mode 100644 index 0000000..bd01e9a --- /dev/null +++ b/WebApplication/IFrameEditor.aspx.cs @@ -0,0 +1,26 @@ + +using System; +using System.Collections; +using System.Configuration; +using System.Data; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.HtmlControls; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; + +namespace ScrewTurn.Wiki { + + public partial class IFrameEditor : BasePage { + + protected void Page_Load(object sender, EventArgs e) { + // Inject the proper stylesheet in page head + Literal l = new Literal(); + l.Text = Tools.GetIncludes(DetectNamespace()); + Page.Header.Controls.Add(l); + } + + } + +} diff --git a/WebApplication/IFrameEditor.aspx.designer.cs b/WebApplication/IFrameEditor.aspx.designer.cs new file mode 100644 index 0000000..a544faf --- /dev/null +++ b/WebApplication/IFrameEditor.aspx.designer.cs @@ -0,0 +1,16 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4016 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class IFrameEditor { + } +} diff --git a/WebApplication/ImageEditor.aspx b/WebApplication/ImageEditor.aspx new file mode 100644 index 0000000..6bc0c9c --- /dev/null +++ b/WebApplication/ImageEditor.aspx @@ -0,0 +1,156 @@ +<%@ Page Language="C#" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.ImageEditor" Codebehind="ImageEditor.aspx.cs" culture="auto" meta:resourcekey="PageResource1" uiculture="auto" %> + + + + + + Image Editor + + + + + + + +
    +
    + +
    + +
    + +
    +

    + +
    + + + +
    + + +
    + % +
    + +
    + +
    + + x + + +
    + + + + +
    +
    + +
    +
    +
    +
    +
    + +
    + +
    + + + +
    +
    + +
    +
    + + +
    + +
    +
    +
    +
    + + + +
    +
    + +
    +
    + + + + + diff --git a/WebApplication/ImageEditor.aspx.cs b/WebApplication/ImageEditor.aspx.cs new file mode 100644 index 0000000..f5edb94 --- /dev/null +++ b/WebApplication/ImageEditor.aspx.cs @@ -0,0 +1,377 @@ + +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using System.IO; +using System.Drawing; +using System.Drawing.Drawing2D; +using ScrewTurn.Wiki; +using ScrewTurn.Wiki.PluginFramework; +using System.Drawing.Imaging; + +namespace ScrewTurn.Wiki { + + public partial class ImageEditor : BasePage { + + private MemoryStream resultMemStream = null; + private string file = ""; + private string page = ""; + private IFilesStorageProviderV30 provider = null; + + protected void Page_Load(object sender, EventArgs e) { + SetProvider(); + SetInputData(); + + string currentUser = SessionFacade.GetCurrentUsername(); + string[] currentGroups = SessionFacade.GetCurrentGroupNames(); + string dir = Tools.GetDirectoryName(file); + + // Verify permissions + bool canUpload = AuthChecker.CheckActionForDirectory(provider, dir, + Actions.ForDirectories.UploadFiles, currentUser, currentGroups); + bool canDeleteFiles = AuthChecker.CheckActionForDirectory(provider, dir, + Actions.ForDirectories.DeleteFiles, currentUser, currentGroups); + + if(!canUpload || !canDeleteFiles) UrlTools.Redirect("AccessDenied.aspx"); + + // Inject the proper stylesheet in page head + Literal l = new Literal(); + l.Text = Tools.GetIncludes(DetectNamespace()); + Page.Header.Controls.Add(l); + + ResizeImage(); + } + + private void SetProvider() { + string p = Request["Provider"]; + if(string.IsNullOrEmpty(p)) { + p = Settings.DefaultFilesProvider; + } + provider = Collectors.FilesProviderCollector.GetProvider(p); + } + + private void SetInputData() { + page = Request["page"]; + + file = Request["File"]; + if(string.IsNullOrEmpty(file)) { + Response.Write("No file specified."); + return; + } + file = file.Replace("..", ""); + } + + private int GetSelectedRotation() { + if(rdo90CW.Checked) return 90; + else if(rdo90CCW.Checked) return 270; + else if(rdo180.Checked) return 180; + else return 0; + } + + private void ResizeImage() { + SetProvider(); + SetInputData(); + + // Contains the image bytes + MemoryStream ms = new MemoryStream(1048576); + + // Load from provider + if(string.IsNullOrEmpty(page)) { + if(!provider.RetrieveFile(file, ms, false)) { + Response.StatusCode = 404; + Response.Write("File not found."); + return; + } + } + else { + PageInfo info = Pages.FindPage(page); + if(info == null) { + Response.StatusCode = 404; + Response.WriteFile("Page not found."); + return; + } + + if(!provider.RetrievePageAttachment(info, file, ms, false)) { + Response.StatusCode = 404; + Response.WriteFile("File not found."); + return; + } + } + + // Setup new file name and image format + if(!Page.IsPostBack) { + string ext = Path.GetExtension(file); + txtNewName.Text = Path.GetFileNameWithoutExtension(file) + "-2" + ext; + switch(ext.ToLowerInvariant()) { + case ".jpg": + case ".jpeg": + rdoPng.Checked = false; + rdoJpegHigh.Checked = true; + rdoJpegMedium.Checked = false; + break; + default: + rdoPng.Checked = true; + rdoJpegHigh.Checked = false; + rdoJpegMedium.Checked = false; + break; + } + + } + + ms.Seek(0, SeekOrigin.Begin); + + // Load the source image + System.Drawing.Image source = System.Drawing.Image.FromStream(ms); + + lblCurrentSize.Text = string.Format("{0}x{1}", source.Width, source.Height); + if(!Page.IsPostBack) { + txtWidth.Text = source.Width.ToString(); + txtHeight.Text = source.Height.ToString(); + } + + // Put dimension in script + lblDimensions.Text = ""; + + int resultWidth = source.Width; + int resultHeight = source.Height; + + if(rdoPercentage.Checked) { + // Resize by percentage + int dim = 100; + if(string.IsNullOrEmpty(txtPercentage.Text)) dim = 100; + else { + try { + dim = int.Parse(txtPercentage.Text); + } + catch(FormatException) { } + } + + // Possible final preview dimensions + resultWidth = source.Width * dim / 100; + resultHeight = source.Height * dim / 100; + } + else { + // Resize by pixel + if(!string.IsNullOrEmpty(txtWidth.Text) && !string.IsNullOrEmpty(txtHeight.Text)) { + try { + resultWidth = int.Parse(txtWidth.Text); + resultHeight = int.Parse(txtHeight.Text); + } + catch(FormatException) { } + } + } + + int rotation = GetSelectedRotation(); + + // Draw preview + if(resultWidth > 290 || resultHeight > 290) { + int previewWidth = resultWidth; + int previewHeight = resultHeight; + + // Max preview dimension 290x290 + if(resultWidth > resultHeight) { + previewWidth = 290; + previewHeight = (int)((float)290 / (float)resultWidth * (float)resultHeight); + lblScale.Text = string.Format("{0:N0}", (float)290 / (float)resultWidth * 100); + } + else { + previewHeight = 290; + previewWidth = (int)((float)290 / (float)resultHeight * (float)resultWidth); + lblScale.Text = string.Format("{0:N0}", (float)290 / (float)resultHeight * 100); + } + imgPreview.ImageUrl = "Thumb.aspx?File=" + Request["File"] + + @"&Size=imgeditprev&Width=" + previewWidth + @"&Height=" + previewHeight + + @"&Page=" + (string.IsNullOrEmpty(page) ? "" : page) + + (!rdoNoRotation.Checked ? ("&Rot=" + rotation.ToString()) : ""); + } + else { + lblScale.Text = "100"; + imgPreview.ImageUrl = "Thumb.aspx?File=" + Request["File"] + + @"&Size=imgeditprev&Width=" + resultWidth + @"&Height=" + resultHeight + + @"&Page=" + (string.IsNullOrEmpty(page) ? "" : page) + + (!rdoNoRotation.Checked ? ("&Rot=" + rotation.ToString()) : ""); + } + + // Destination bitmap + Bitmap result = new Bitmap( + rotation != 90 && rotation != 270 ? resultWidth : resultHeight, + rotation != 90 && rotation != 270 ? resultHeight : resultWidth, + System.Drawing.Imaging.PixelFormat.Format32bppArgb); + + // Get Graphics object for destination bitmap + Graphics g = Graphics.FromImage(result); + g.Clear(Color.Transparent); + + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; + g.SmoothingMode = SmoothingMode.HighQuality; + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear; + + g.TranslateTransform(result.Width / 2, result.Height / 2); + g.RotateTransform(rotation); + g.TranslateTransform(-result.Width / 2, -result.Height / 2); + + // Draw bitmap + g.DrawImage(source, GetImageRectangle(result.Width, result.Height, + rotation != 90 && rotation != 270 ? source.Width : source.Height, + rotation != 90 && rotation != 270 ? source.Height : source.Width, + rotation == 90 || rotation == 270)); + + // Prepare encoder parameters + string format = GetCurrentFormat(); + resultMemStream = new MemoryStream(1048576); + + // Only JPEG and PNG images are editable + if(format == "image/jpeg") { + EncoderParameters encoderParams = new EncoderParameters(1); + encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, rdoJpegHigh.Checked ? 100L : 60L); + + result.Save(resultMemStream, GetEncoderInfo(format), encoderParams); + } + else { + result.Save(resultMemStream, ImageFormat.Png); + } + + resultMemStream.Seek(0, SeekOrigin.Begin); + + // Dispose of source and result bitmaps + source.Dispose(); + g.Dispose(); + result.Dispose(); + } + + private static ImageCodecInfo GetEncoderInfo(string mimeType) { + int j; + ImageCodecInfo[] encoders; + encoders = ImageCodecInfo.GetImageEncoders(); + for(j = 0; j < encoders.Length; ++j) { + if(encoders[j].MimeType == mimeType) + return encoders[j]; + } + return null; + } + + private string GetCurrentFormat() { + if(chkNewName.Checked) { + if(rdoPng.Checked) return "image/png"; + else return "image/jpeg"; + } + else { + switch(Path.GetExtension(file).ToLowerInvariant()) { + case ".jpg": + case ".jpeg": + return "image/jpeg"; + default: + return "image/png"; + } + } + } + + private Rectangle GetImageRectangle(int targetW, int targetH, int w, int h, bool swapped) { + if(w == h) { + // Square + return new Rectangle(0, 0, targetW, targetH); + } + else if(w > h) { + // Landscape + float scale = (float)targetW / (float)w; + if(targetW > w) scale = 1; + int width = (int)(w * scale); + int height = (int)(h * scale); + + if(swapped) { + int temp = width; + width = height; + height = temp; + } + + return new Rectangle((targetW - width) / 2, (targetH - height) / 2, width, height); + } + else { + // Portrait + float scale = (float)targetH / (float)h; + if(targetH > h) scale = 1; + int width = (int)(w * scale); + int height = (int)(h * scale); + + if(swapped) { + int temp = width; + width = height; + height = temp; + } + + return new Rectangle((targetW - width) / 2, (targetH - height) / 2, width, height); + } + } + + protected void btnPreview_Click(object sender, EventArgs e) { + //ResizeImage(); + } + + protected void btnSave_Click(object sender, EventArgs e) { + ResizeImage(); + bool done = false; + + string targetName = chkNewName.Checked ? txtNewName.Text : Path.GetFileName(file); + bool overwrite = !chkNewName.Checked; + + if(targetName.Length == 0 || Path.GetFileNameWithoutExtension(targetName).Length == 0) { + lblResult.CssClass = "resulterror"; + lblResult.Text = Properties.Messages.InvalidFileName; + return; + } + + if(!overwrite) { + // Force extension + targetName = Path.ChangeExtension(targetName, + rdoPng.Checked ? "png" : "jpg"); + } + + if(string.IsNullOrEmpty(page)) { + string path = (file.LastIndexOf('/') > 0) ? file.Substring(0, file.LastIndexOf('/') + 1) : ""; + done = provider.StoreFile(path + targetName, resultMemStream, overwrite); + } + else { + done = provider.StorePageAttachment(Pages.FindPage(page), targetName, resultMemStream, overwrite); + } + + if(done) { + lblResult.Text = Properties.Messages.FileSaved; + lblResult.CssClass = "resultok"; + } + else { + lblResult.Text = Properties.Messages.CouldNotSaveFile; + lblResult.CssClass = "resulterror"; + } + } + + protected void chkNewName_CheckedChanged(object sender, EventArgs e) { + txtNewName.Enabled = chkNewName.Checked; + rdoPng.Enabled = chkNewName.Checked; + rdoJpegHigh.Enabled = chkNewName.Checked; + rdoJpegMedium.Enabled = chkNewName.Checked; + } + + protected void rdoFormat_CheckedChanged(object sender, EventArgs e) { + string ext = ""; + if(rdoPng.Checked) { + ext = ".png"; + } + else { + ext = ".jpg"; + } + txtNewName.Text = Path.ChangeExtension(txtNewName.Text, ext); + } + + } + +} diff --git a/WebApplication/ImageEditor.aspx.designer.cs b/WebApplication/ImageEditor.aspx.designer.cs new file mode 100644 index 0000000..fadeaef --- /dev/null +++ b/WebApplication/ImageEditor.aspx.designer.cs @@ -0,0 +1,295 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ScrewTurn.Wiki { + + + public partial class ImageEditor { + + /// + /// lblDimensions control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblDimensions; + + /// + /// frmImageEditor control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.HtmlControls.HtmlForm frmImageEditor; + + /// + /// imgPreview control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Image imgPreview; + + /// + /// lblOptions control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblOptions; + + /// + /// lblCurrentSizeInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCurrentSizeInfo; + + /// + /// lblCurrentSize control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCurrentSize; + + /// + /// lblCurrentSizePixels control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblCurrentSizePixels; + + /// + /// rdoPercentage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RadioButton rdoPercentage; + + /// + /// txtPercentage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtPercentage; + + /// + /// rdoPixel control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RadioButton rdoPixel; + + /// + /// txtWidth control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtWidth; + + /// + /// txtHeight control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtHeight; + + /// + /// lblSizePixels control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblSizePixels; + + /// + /// chkAspectRatio control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.CheckBox chkAspectRatio; + + /// + /// lblRotate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal lblRotate; + + /// + /// rdoNoRotation control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RadioButton rdoNoRotation; + + /// + /// rdo90CW control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RadioButton rdo90CW; + + /// + /// rdo90CCW control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RadioButton rdo90CCW; + + /// + /// rdo180 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.RadioButton rdo180; + + /// + /// btnPreview control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.LinkButton btnPreview; + + /// + /// lblScalePre control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblScalePre; + + /// + /// lblScale control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.Label lblScale; + + /// + /// lblScalePost control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblScalePost; + + /// + /// btnSave control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnSave; + + /// + /// btnCancel control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnCancel; + + /// + /// lblResult control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblResult; + + /// + /// chkNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.CheckBox chkNewName; + + /// + /// txtNewName control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.TextBox txtNewName; + + /// + /// rdoPng control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoPng; + + /// + /// rdoJpegHigh control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoJpegHigh; + + /// + /// rdoJpegMedium control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::Anthem.RadioButton rdoJpegMedium; + } +} diff --git a/WebApplication/Images/Alert.png b/WebApplication/Images/Alert.png new file mode 100644 index 0000000..628cf2d Binary files /dev/null and b/WebApplication/Images/Alert.png differ diff --git a/WebApplication/Images/ArrowRight.png b/WebApplication/Images/ArrowRight.png new file mode 100644 index 0000000..662a973 Binary files /dev/null and b/WebApplication/Images/ArrowRight.png differ diff --git a/WebApplication/Images/Blank.png b/WebApplication/Images/Blank.png new file mode 100644 index 0000000..bb0aedd Binary files /dev/null and b/WebApplication/Images/Blank.png differ diff --git a/WebApplication/Images/Comment.png b/WebApplication/Images/Comment.png new file mode 100644 index 0000000..57f16e6 Binary files /dev/null and b/WebApplication/Images/Comment.png differ diff --git a/WebApplication/Images/DatePick.png b/WebApplication/Images/DatePick.png new file mode 100644 index 0000000..9acc866 Binary files /dev/null and b/WebApplication/Images/DatePick.png differ diff --git a/WebApplication/Images/Dir.png b/WebApplication/Images/Dir.png new file mode 100644 index 0000000..3e71bfa Binary files /dev/null and b/WebApplication/Images/Dir.png differ diff --git a/WebApplication/Images/Directory.png b/WebApplication/Images/Directory.png new file mode 100644 index 0000000..ecb7ff1 Binary files /dev/null and b/WebApplication/Images/Directory.png differ diff --git a/WebApplication/Images/DirectoryUp.png b/WebApplication/Images/DirectoryUp.png new file mode 100644 index 0000000..341e287 Binary files /dev/null and b/WebApplication/Images/DirectoryUp.png differ diff --git a/WebApplication/Images/Editor/Anchor.png b/WebApplication/Images/Editor/Anchor.png new file mode 100644 index 0000000..556ab2b Binary files /dev/null and b/WebApplication/Images/Editor/Anchor.png differ diff --git a/WebApplication/Images/Editor/BR.png b/WebApplication/Images/Editor/BR.png new file mode 100644 index 0000000..95704fb Binary files /dev/null and b/WebApplication/Images/Editor/BR.png differ diff --git a/WebApplication/Images/Editor/Bold.png b/WebApplication/Images/Editor/Bold.png new file mode 100644 index 0000000..889ae80 Binary files /dev/null and b/WebApplication/Images/Editor/Bold.png differ diff --git a/WebApplication/Images/Editor/Box.png b/WebApplication/Images/Editor/Box.png new file mode 100644 index 0000000..8443c23 Binary files /dev/null and b/WebApplication/Images/Editor/Box.png differ diff --git a/WebApplication/Images/Editor/Code.png b/WebApplication/Images/Editor/Code.png new file mode 100644 index 0000000..9017372 Binary files /dev/null and b/WebApplication/Images/Editor/Code.png differ diff --git a/WebApplication/Images/Editor/Comment.png b/WebApplication/Images/Editor/Comment.png new file mode 100644 index 0000000..0a52c7d Binary files /dev/null and b/WebApplication/Images/Editor/Comment.png differ diff --git a/WebApplication/Images/Editor/Directory.png b/WebApplication/Images/Editor/Directory.png new file mode 100644 index 0000000..784e8fa Binary files /dev/null and b/WebApplication/Images/Editor/Directory.png differ diff --git a/WebApplication/Images/Editor/EditorBigger.png b/WebApplication/Images/Editor/EditorBigger.png new file mode 100644 index 0000000..7cb6fd3 Binary files /dev/null and b/WebApplication/Images/Editor/EditorBigger.png differ diff --git a/WebApplication/Images/Editor/EditorSmaller.png b/WebApplication/Images/Editor/EditorSmaller.png new file mode 100644 index 0000000..e98cd3f Binary files /dev/null and b/WebApplication/Images/Editor/EditorSmaller.png differ diff --git a/WebApplication/Images/Editor/Escape.png b/WebApplication/Images/Editor/Escape.png new file mode 100644 index 0000000..de2ecb6 Binary files /dev/null and b/WebApplication/Images/Editor/Escape.png differ diff --git a/WebApplication/Images/Editor/File.png b/WebApplication/Images/Editor/File.png new file mode 100644 index 0000000..813f712 Binary files /dev/null and b/WebApplication/Images/Editor/File.png differ diff --git a/WebApplication/Images/Editor/Folder.png b/WebApplication/Images/Editor/Folder.png new file mode 100644 index 0000000..ce0ef11 Binary files /dev/null and b/WebApplication/Images/Editor/Folder.png differ diff --git a/WebApplication/Images/Editor/FolderUp.png b/WebApplication/Images/Editor/FolderUp.png new file mode 100644 index 0000000..1c9155d Binary files /dev/null and b/WebApplication/Images/Editor/FolderUp.png differ diff --git a/WebApplication/Images/Editor/H1.png b/WebApplication/Images/Editor/H1.png new file mode 100644 index 0000000..9c122e9 Binary files /dev/null and b/WebApplication/Images/Editor/H1.png differ diff --git a/WebApplication/Images/Editor/H2.png b/WebApplication/Images/Editor/H2.png new file mode 100644 index 0000000..fbd8765 Binary files /dev/null and b/WebApplication/Images/Editor/H2.png differ diff --git a/WebApplication/Images/Editor/H3.png b/WebApplication/Images/Editor/H3.png new file mode 100644 index 0000000..c7836cf Binary files /dev/null and b/WebApplication/Images/Editor/H3.png differ diff --git a/WebApplication/Images/Editor/H4.png b/WebApplication/Images/Editor/H4.png new file mode 100644 index 0000000..4e929ea Binary files /dev/null and b/WebApplication/Images/Editor/H4.png differ diff --git a/WebApplication/Images/Editor/HR.png b/WebApplication/Images/Editor/HR.png new file mode 100644 index 0000000..8dd1da1 Binary files /dev/null and b/WebApplication/Images/Editor/HR.png differ diff --git a/WebApplication/Images/Editor/Image.png b/WebApplication/Images/Editor/Image.png new file mode 100644 index 0000000..9008eda Binary files /dev/null and b/WebApplication/Images/Editor/Image.png differ diff --git a/WebApplication/Images/Editor/Img.png b/WebApplication/Images/Editor/Img.png new file mode 100644 index 0000000..db5f85c Binary files /dev/null and b/WebApplication/Images/Editor/Img.png differ diff --git a/WebApplication/Images/Editor/Italic.png b/WebApplication/Images/Editor/Italic.png new file mode 100644 index 0000000..8482ac8 Binary files /dev/null and b/WebApplication/Images/Editor/Italic.png differ diff --git a/WebApplication/Images/Editor/Link.png b/WebApplication/Images/Editor/Link.png new file mode 100644 index 0000000..00c6062 Binary files /dev/null and b/WebApplication/Images/Editor/Link.png differ diff --git a/WebApplication/Images/Editor/NoBr.png b/WebApplication/Images/Editor/NoBr.png new file mode 100644 index 0000000..764ed7b Binary files /dev/null and b/WebApplication/Images/Editor/NoBr.png differ diff --git a/WebApplication/Images/Editor/NoWiki.png b/WebApplication/Images/Editor/NoWiki.png new file mode 100644 index 0000000..631718e Binary files /dev/null and b/WebApplication/Images/Editor/NoWiki.png differ diff --git a/WebApplication/Images/Editor/OL.png b/WebApplication/Images/Editor/OL.png new file mode 100644 index 0000000..33b0b8d Binary files /dev/null and b/WebApplication/Images/Editor/OL.png differ diff --git a/WebApplication/Images/Editor/PageLink.png b/WebApplication/Images/Editor/PageLink.png new file mode 100644 index 0000000..0f736d5 Binary files /dev/null and b/WebApplication/Images/Editor/PageLink.png differ diff --git a/WebApplication/Images/Editor/Pre.png b/WebApplication/Images/Editor/Pre.png new file mode 100644 index 0000000..49425f4 Binary files /dev/null and b/WebApplication/Images/Editor/Pre.png differ diff --git a/WebApplication/Images/Editor/PreEsc.png b/WebApplication/Images/Editor/PreEsc.png new file mode 100644 index 0000000..647d2b2 Binary files /dev/null and b/WebApplication/Images/Editor/PreEsc.png differ diff --git a/WebApplication/Images/Editor/Preview.png b/WebApplication/Images/Editor/Preview.png new file mode 100644 index 0000000..108eb98 Binary files /dev/null and b/WebApplication/Images/Editor/Preview.png differ diff --git a/WebApplication/Images/Editor/Progress.gif b/WebApplication/Images/Editor/Progress.gif new file mode 100644 index 0000000..529e72f Binary files /dev/null and b/WebApplication/Images/Editor/Progress.gif differ diff --git a/WebApplication/Images/Editor/Refresh.png b/WebApplication/Images/Editor/Refresh.png new file mode 100644 index 0000000..1dc7f50 Binary files /dev/null and b/WebApplication/Images/Editor/Refresh.png differ diff --git a/WebApplication/Images/Editor/Snippet.png b/WebApplication/Images/Editor/Snippet.png new file mode 100644 index 0000000..9514edc Binary files /dev/null and b/WebApplication/Images/Editor/Snippet.png differ diff --git a/WebApplication/Images/Editor/SpecialTags.png b/WebApplication/Images/Editor/SpecialTags.png new file mode 100644 index 0000000..6e88a1b Binary files /dev/null and b/WebApplication/Images/Editor/SpecialTags.png differ diff --git a/WebApplication/Images/Editor/Striked.png b/WebApplication/Images/Editor/Striked.png new file mode 100644 index 0000000..612058a Binary files /dev/null and b/WebApplication/Images/Editor/Striked.png differ diff --git a/WebApplication/Images/Editor/Subscript.png b/WebApplication/Images/Editor/Subscript.png new file mode 100644 index 0000000..1a2b010 Binary files /dev/null and b/WebApplication/Images/Editor/Subscript.png differ diff --git a/WebApplication/Images/Editor/Superscript.png b/WebApplication/Images/Editor/Superscript.png new file mode 100644 index 0000000..2fb2a7c Binary files /dev/null and b/WebApplication/Images/Editor/Superscript.png differ diff --git a/WebApplication/Images/Editor/Symbol.png b/WebApplication/Images/Editor/Symbol.png new file mode 100644 index 0000000..5075ec6 Binary files /dev/null and b/WebApplication/Images/Editor/Symbol.png differ diff --git a/WebApplication/Images/Editor/TranspBG.png b/WebApplication/Images/Editor/TranspBG.png new file mode 100644 index 0000000..1a1c598 Binary files /dev/null and b/WebApplication/Images/Editor/TranspBG.png differ diff --git a/WebApplication/Images/Editor/UL.png b/WebApplication/Images/Editor/UL.png new file mode 100644 index 0000000..4a8672b Binary files /dev/null and b/WebApplication/Images/Editor/UL.png differ diff --git a/WebApplication/Images/Editor/Underlined.png b/WebApplication/Images/Editor/Underlined.png new file mode 100644 index 0000000..90d0df2 Binary files /dev/null and b/WebApplication/Images/Editor/Underlined.png differ diff --git a/WebApplication/Images/Error.png b/WebApplication/Images/Error.png new file mode 100644 index 0000000..07b8704 Binary files /dev/null and b/WebApplication/Images/Error.png differ diff --git a/WebApplication/Images/File.png b/WebApplication/Images/File.png new file mode 100644 index 0000000..0ad07dd Binary files /dev/null and b/WebApplication/Images/File.png differ diff --git a/WebApplication/Images/Filter.png b/WebApplication/Images/Filter.png new file mode 100644 index 0000000..a9925a0 Binary files /dev/null and b/WebApplication/Images/Filter.png differ diff --git a/WebApplication/Images/Icon.ico b/WebApplication/Images/Icon.ico new file mode 100644 index 0000000..727471d Binary files /dev/null and b/WebApplication/Images/Icon.ico differ diff --git a/WebApplication/Images/InputError.png b/WebApplication/Images/InputError.png new file mode 100644 index 0000000..14d6567 Binary files /dev/null and b/WebApplication/Images/InputError.png differ diff --git a/WebApplication/Images/LogError.png b/WebApplication/Images/LogError.png new file mode 100644 index 0000000..14d6567 Binary files /dev/null and b/WebApplication/Images/LogError.png differ diff --git a/WebApplication/Images/LogGeneral.png b/WebApplication/Images/LogGeneral.png new file mode 100644 index 0000000..6d0df07 Binary files /dev/null and b/WebApplication/Images/LogGeneral.png differ diff --git a/WebApplication/Images/LogWarning.png b/WebApplication/Images/LogWarning.png new file mode 100644 index 0000000..32226d2 Binary files /dev/null and b/WebApplication/Images/LogWarning.png differ diff --git a/WebApplication/Images/PoweredBy.png b/WebApplication/Images/PoweredBy.png new file mode 100644 index 0000000..f66a8c9 Binary files /dev/null and b/WebApplication/Images/PoweredBy.png differ diff --git a/WebApplication/Images/Refresh.png b/WebApplication/Images/Refresh.png new file mode 100644 index 0000000..0de2656 Binary files /dev/null and b/WebApplication/Images/Refresh.png differ diff --git a/WebApplication/Images/Rename.png b/WebApplication/Images/Rename.png new file mode 100644 index 0000000..d67742b Binary files /dev/null and b/WebApplication/Images/Rename.png differ diff --git a/WebApplication/Images/SearchIcon.ico b/WebApplication/Images/SearchIcon.ico new file mode 100644 index 0000000..4e438e6 Binary files /dev/null and b/WebApplication/Images/SearchIcon.ico differ diff --git a/WebApplication/Images/Trash.png b/WebApplication/Images/Trash.png new file mode 100644 index 0000000..4d3ef82 Binary files /dev/null and b/WebApplication/Images/Trash.png differ diff --git a/WebApplication/Images/Up.png b/WebApplication/Images/Up.png new file mode 100644 index 0000000..81673d4 Binary files /dev/null and b/WebApplication/Images/Up.png differ diff --git a/WebApplication/Images/Void.png b/WebApplication/Images/Void.png new file mode 100644 index 0000000..e10cbef Binary files /dev/null and b/WebApplication/Images/Void.png differ diff --git a/WebApplication/Images/Wait.gif b/WebApplication/Images/Wait.gif new file mode 100644 index 0000000..529e72f Binary files /dev/null and b/WebApplication/Images/Wait.gif differ diff --git a/WebApplication/Import.aspx b/WebApplication/Import.aspx new file mode 100644 index 0000000..6cfe961 --- /dev/null +++ b/WebApplication/Import.aspx @@ -0,0 +1,78 @@ +<%@ Page ValidateRequest="false" Async="true" AsyncTimeout="180" Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" Inherits="ScrewTurn.Wiki.Import" Title="Untitled Page" Codebehind="Import.aspx.cs" %> + + + +

    +

    +
    +

    Proxy address/port

    + + +
    +
    + + + + + + +
    +

    + + Import a single Page + Import a whole Wiki + Import a block of Text + +
    +

    + + MediaWiki + FlexWiki + +
    + +
    +

    + + + +

    +
    +

    + +

    + + +
    + +
    + + +

    +
    +

    + + +
    +

    +
    + +
    + +
    + + +

    +
    + + +
    + + +

    +
    + +
    +
    + +
    diff --git a/WebApplication/Import.aspx.cs b/WebApplication/Import.aspx.cs new file mode 100644 index 0000000..e205ba7 --- /dev/null +++ b/WebApplication/Import.aspx.cs @@ -0,0 +1,349 @@ +using System; +using System.Data; +using System.Configuration; +using System.Collections; +using System.Web; +using System.Web.Security; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.WebControls.WebParts; +using System.Web.UI.HtmlControls; +using System.Net; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Collections.Generic; +using ScrewTurn.Wiki.ImportWiki; +using ScrewTurn.Wiki.PluginFramework; + +namespace ScrewTurn.Wiki { + + public partial class Import : BasePage { + + private HttpWebRequest request; + private delegate void delegatePageDownload(); + + protected void Page_Load(object sender, EventArgs e) { + if(Array.Find(SessionFacade.GetCurrentGroupNames(), delegate(string g) { return g == Settings.AdministratorsGroup; }) == null) { + UrlTools.Redirect("AccessDenied.aspx"); + } + + Page.Title = "Import - " + Settings.WikiTitle; + } + + protected void lstOperation_SelectedIndexChanged(object sender, EventArgs e) { + switch(lstOperation.SelectedValue.ToUpperInvariant()) { + case "PAGE": + mlwImport.ActiveViewIndex = 0; + break; + case "WIKI": + mlwImport.ActiveViewIndex = 1; + break; + case "TEXT": + mlwImport.ActiveViewIndex = 2; + break; + } + } + + protected void lstWiki_SelectedIndexChanged(object sender, EventArgs e) { + switch(lstWiki.SelectedValue.ToUpperInvariant()) { + case "MEDIA": + lblWikiUrl.Text = "Wiki URL, in the form http://www.yourserver.com/w/index.php"; + lblPageUrl.Text = "Wiki URL, in the form http://www.yourserver.com/w/index.php"; + break; + case "FLEX": + lblWikiUrl.Text = "Wiki URL, in the form http://www.yourserver.com/"; + lblPageUrl.Text = "Wiki URL, in the form http://www.yourserver.com/"; + break; + } + } + + protected void btnGo_click(object sender, EventArgs e) { + switch(lstOperation.SelectedValue.ToUpperInvariant()) { + case "PAGE": + PageAsyncTask task = new PageAsyncTask( + new BeginEventHandler(BeginPageRequest), + new EndEventHandler(EndPageRequest), + new EndEventHandler(TimeoutPageRequest), + null); + RegisterAsyncTask(task); + break; + case "WIKI": + AddOnPreRenderCompleteAsync( + new BeginEventHandler(BeginPagesListRequest), + new EndEventHandler(EndPagesListRequest) + ); + break; + case "TEXT": + mlwImport.ActiveViewIndex = 3; + ITranslator translator = new Translator(); + txtTranslated.Text = translator.Translate(txtText.Text); + break; + } + } + + private string PageRequest(string url) { + request = (HttpWebRequest)WebRequest.Create(url); + SetProxyAndUserAgent(request); + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + StreamReader reader = new StreamReader(response.GetResponseStream()); + return reader.ReadToEnd(); + } + + private void SetProxyAndUserAgent(HttpWebRequest req) { + req.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.7) Gecko/20060909 Firefox/1.5.0.7"; + string addr = null; + int port = -1; + if(txtProxyAddress.Text.Length > 0) addr = txtProxyAddress.Text; + if(txtProxyPort.Text.Length > 0) port = int.Parse(txtProxyPort.Text); + + if(addr != null) { + if(port > 0) req.Proxy = new WebProxy(addr, port); + else req.Proxy = new WebProxy(addr); + } + else req.Proxy = null; + } + + private void savePage(string text) { + string pageName = txtPageName.Text.Replace(":", "_").Replace("/", "_").Replace(@"\", "_").Replace('?', '_'); + string pageTitle = txtPageName.Text; + Log.LogEntry("Page " + pageName + " created with import whole wiki", EntryType.General, "import"); + + PageInfo pg = Pages.FindPage(pageName); + SaveMode saveMode = SaveMode.Backup; + if(pg == null) { + Pages.CreatePage(null as string, pageName); + pg = Pages.FindPage(pageName); + saveMode = SaveMode.Normal; + } + Log.LogEntry("Page update requested for " + pageName, EntryType.General, "import"); + Pages.ModifyPage(pg, pageTitle, "import", DateTime.Now, "", text, null, null, saveMode); + } + + #region TranslateAll + + protected void btnTranslateAll_click(object sender, EventArgs e) { + AddOnPreRenderCompleteAsync( + new BeginEventHandler(PageDownload), + new EndEventHandler(endPageDownload) + ); + } + + private IAsyncResult PageDownload(object sender, EventArgs e, AsyncCallback ac, object state) { + IAsyncResult ar = null; + return ac.BeginInvoke(ar, ac, null); + } + + private void endPageDownload(IAsyncResult ar) { + for(int i = 0; i < pageList.Items.Count; i++) { + if(pageList.Items[i].Selected) { + string url = ""; + Regex textarea = null; + Match match = null; + ITranslator translator = null; + if(lstWiki.SelectedValue.ToUpperInvariant() == "MEDIA") { + url = txtWikiUrl.Text + "?title=" + pageList.Items[i].Value + "&action=edit"; + textarea = new Regex(@"(?<=(\])*?)\>)(.|\s)+?(?=(\<\/textarea\>))"); + translator = new Translator(); + } + if(lstWiki.SelectedValue.ToUpperInvariant() == "FLEX") { + if(txtWikiUrl.Text.EndsWith("/")) url = txtWikiUrl.Text + "wikiedit.aspx?topic=" + pageList.Items[i].Value; + else url = txtWikiUrl.Text + "/wikiedit.aspx?topic=" + pageList.Items[i].Value; + textarea = new Regex(@"(?<=(\