Merge recipe -> dev

--HG--
branch : dev
This commit is contained in:
Suha Can
2011-02-22 17:39:29 -08:00
72 changed files with 2574 additions and 282 deletions

View File

@@ -0,0 +1,38 @@
using Orchard.Commands;
using Orchard.ContentManagement;
using Orchard.Core.Navigation.Models;
namespace Orchard.Core.Navigation.Commands {
public class MenuCommands : DefaultOrchardCommandHandler {
private readonly IContentManager _contentManager;
public MenuCommands(IContentManager contentManager) {
_contentManager = contentManager;
}
[OrchardSwitch]
public string MenuPosition { get; set; }
[OrchardSwitch]
public string MenuText { get; set; }
[OrchardSwitch]
public string Url { get; set; }
[OrchardSwitch]
public bool OnMainMenu { get; set; }
[CommandName("menuitem create")]
[CommandHelp("menuitem create /MenuPosition:<position> /MenuText:<text> /Url:<url> [/OnMainMenu:true|false]\r\n\t" + "Creates a new menu item")]
[OrchardSwitches("MenuPosition,MenuText,Url,OnMainMenu")]
public void Create() {
var menuItem = _contentManager.Create("MenuItem");
menuItem.As<MenuPart>().MenuPosition = MenuPosition;
menuItem.As<MenuPart>().MenuText = T(MenuText).ToString();
menuItem.As<MenuPart>().OnMainMenu = OnMainMenu;
menuItem.As<MenuItemPart>().Url = Url;
Context.Output.WriteLine(T("Menu item created successfully.").Text);
}
}
}

View File

@@ -104,6 +104,7 @@
<Compile Include="Contents\Settings\ContentPartSettings.cs" />
<Compile Include="Contents\Shapes.cs" />
<Compile Include="Contents\ViewModels\PublishContentViewModel.cs" />
<Compile Include="Navigation\Commands\MenuCommands.cs" />
<Compile Include="Navigation\Services\MainMenuNavigationProvider.cs" />
<Compile Include="Routable\Events\ISlugEventHandler.cs" />
<Compile Include="Routable\ResourceManifest.cs" />

View File

@@ -2,6 +2,7 @@
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Orchard.Blogs.Models;
using Orchard.Commands;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
@@ -12,6 +13,7 @@ using Orchard.Core.Routable.Services;
using Orchard.Security;
using Orchard.Blogs.Services;
using Orchard.Core.Navigation.Services;
using Orchard.Settings;
namespace Orchard.Blogs.Commands {
public class BlogCommands : DefaultOrchardCommandHandler {
@@ -19,16 +21,19 @@ namespace Orchard.Blogs.Commands {
private readonly IMembershipService _membershipService;
private readonly IBlogService _blogService;
private readonly IMenuService _menuService;
private readonly ISiteService _siteService;
public BlogCommands(
IContentManager contentManager,
IMembershipService membershipService,
IBlogService blogService,
IMenuService menuService) {
IMenuService menuService,
ISiteService siteService) {
_contentManager = contentManager;
_membershipService = membershipService;
_blogService = blogService;
_menuService = menuService;
_siteService = siteService;
}
[OrchardSwitch]
@@ -43,13 +48,22 @@ namespace Orchard.Blogs.Commands {
[OrchardSwitch]
public string Title { get; set; }
[OrchardSwitch]
public string Description { get; set; }
[OrchardSwitch]
public string MenuText { get; set; }
[OrchardSwitch]
public bool Homepage { get; set; }
[CommandName("blog create")]
[CommandHelp("blog create /Slug:<slug> /Title:<title> /Owner:<username> [/MenuText:<menu text>]\r\n\t" + "Creates a new Blog")]
[OrchardSwitches("Slug,Title,Owner,MenuText")]
[CommandHelp("blog create /Slug:<slug> /Title:<title> [/Owner:<username>] [/Description:<description>] [/MenuText:<menu text>] [/Homepage:true|false]\r\n\t" + "Creates a new Blog")]
[OrchardSwitches("Slug,Title,Owner,Description,MenuText,Homepage")]
public string Create() {
if (String.IsNullOrEmpty(Owner)) {
Owner = _siteService.GetSiteSettings().SuperUser;
}
var owner = _membershipService.GetUser(Owner);
if ( owner == null ) {
@@ -65,6 +79,10 @@ namespace Orchard.Blogs.Commands {
blog.As<RoutePart>().Slug = Slug;
blog.As<RoutePart>().Path = Slug;
blog.As<RoutePart>().Title = Title;
blog.As<RoutePart>().PromoteToHomePage = Homepage;
if (!String.IsNullOrEmpty(Description)) {
blog.As<BlogPart>().Description = Description;
}
if ( !String.IsNullOrWhiteSpace(MenuText) ) {
blog.As<MenuPart>().OnMainMenu = true;
blog.As<MenuPart>().MenuPosition = _menuService.Get().Select(menuPart => menuPart.MenuPosition).Max() + 1 + ".0";

View File

@@ -2,5 +2,24 @@
namespace Orchard.Comments.Models {
public class CommentSettingsPart : ContentPart<CommentSettingsPartRecord> {
public bool ModerateComments {
get { return Record.ModerateComments; }
set { Record.ModerateComments = value; }
}
public bool EnableSpamProtection {
get { return Record.EnableSpamProtection; }
set { Record.EnableSpamProtection = value; }
}
public string AkismetKey {
get { return Record.AkismetKey; }
set { Record.AkismetKey = value; }
}
public string AkismetUrl {
get { return Record.AkismetUrl; }
set { Record.AkismetUrl = value; }
}
}
}

View File

@@ -0,0 +1,94 @@
using System;
using Orchard.Commands;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Common.Models;
using Orchard.Core.Routable.Models;
using Orchard.Security;
using Orchard.Settings;
namespace Orchard.Pages.Commands {
public class PageCommands : DefaultOrchardCommandHandler {
private readonly IContentManager _contentManager;
private readonly IMembershipService _membershipService;
private readonly ISiteService _siteService;
public PageCommands(IContentManager contentManager, IMembershipService membershipService, ISiteService siteService) {
_contentManager = contentManager;
_membershipService = membershipService;
_siteService = siteService;
}
[OrchardSwitch]
public string Slug { get; set; }
[OrchardSwitch]
public string Title { get; set; }
[OrchardSwitch]
public string Path { get; set; }
[OrchardSwitch]
public string Text { get; set; }
[OrchardSwitch]
public string Owner { get; set; }
[OrchardSwitch]
public bool Homepage { get; set; }
[OrchardSwitch]
public bool Publish { get; set; }
[OrchardSwitch]
public bool UseWelcomeText { get; set; }
[CommandName("page create")]
[CommandHelp("page create /Slug:<slug> /Title:<title> /Path:<path> [/Text:<text>] [/Owner:<username>] [/Homepage:true|false] [/Publish:true|false] [/UseWelcomeText:true|false]\r\n\t" + "Creates a new page")]
[OrchardSwitches("Slug,Title,Path,Text,Owner,Homepage,Publish,UseWelcomeText")]
public void Create() {
if (String.IsNullOrEmpty(Owner)) {
Owner = _siteService.GetSiteSettings().SuperUser;
}
var owner = _membershipService.GetUser(Owner);
var page = _contentManager.Create("Page", VersionOptions.Draft);
page.As<RoutePart>().Title = Title;
page.As<RoutePart>().Path = Path;
page.As<RoutePart>().Slug = Slug;
page.As<RoutePart>().PromoteToHomePage = Homepage;
page.As<ICommonPart>().Owner = owner;
var text = String.Empty;
if (UseWelcomeText) {
text = T(
@"<p>You've successfully setup your Orchard Site and this is the homepage of your new site.
Here are a few things you can look at to get familiar with the application.
Once you feel confident you don't need this anymore, you can
<a href=""Admin/Contents/Edit/{0}"">remove it by going into editing mode</a>
and replacing it with whatever you want.</p>
<p>First things first - You'll probably want to <a href=""Admin/Settings"">manage your settings</a>
and configure Orchard to your liking. After that, you can head over to
<a href=""Admin/Themes"">manage themes to change or install new themes</a>
and really make it your own. Once you're happy with a look and feel, it's time for some content.
You can start creating new custom content types or start from the built-in ones by
<a href=""Admin/Contents/Create/Page"">adding a page</a>, or <a href=""Admin/Navigation"">managing your menus.</a></p>
<p>Finally, Orchard has been designed to be extended. It comes with a few built-in
modules such as pages and blogs or themes. If you're looking to add additional functionality,
you can do so by creating your own module or by installing one that somebody else built.
Modules are created by other users of Orchard just like you so if you feel up to it,
<a href=""http://orchardproject.net/contribution"">please consider participating</a>.</p>
<p>Thanks for using Orchard The Orchard Team </p>", page.Id).Text;
}
else {
if (!String.IsNullOrEmpty(Text)) {
text = Text;
}
}
page.As<BodyPart>().Text = text;
if (Publish) {
_contentManager.Publish(page);
}
Context.Output.WriteLine(T("Page created successfully.").Text);
}
}
}

View File

@@ -38,6 +38,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AdminMenu.cs" />
<Compile Include="Commands\PageCommands.cs" />
<Compile Include="Migrations.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

View File

@@ -0,0 +1,23 @@
using System.Web.Mvc;
using Orchard.Localization;
using Orchard.Recipes.Services;
namespace Orchard.Recipes.Controllers {
public class RecipesController : Controller {
private readonly IRecipeJournal _recipeJournal;
public RecipesController(IRecipeJournal recipeJournal) {
_recipeJournal = recipeJournal;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public ActionResult RecipeExecutionStatus (string executionId) {
var recipeStatus = _recipeJournal.GetRecipeStatus(executionId);
var model = recipeStatus;
return View(model);
}
}
}

View File

@@ -0,0 +1,12 @@
Name: Recipes
AntiForgery: enabled
Author: The Orchard Team
Website: http://orchardproject.net
Version: 1.0.20
OrchardVersion: 1.0.20
Description: Provides Orchard Recipes.
Features:
Orchard.Recipes:
Name: Recipes
Description: Implementation of Orchard recipes.
Category: Core

View File

@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{FC1D74E8-7A4D-48F4-83DE-95C6173780C4}</ProjectGuid>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Orchard.Recipes</RootNamespace>
<AssemblyName>Orchard.Recipes</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>..\..\..\OrchardBasicCorrectness.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Autofac">
<HintPath>..\..\..\..\lib\autofac\Autofac.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Web" />
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
</ItemGroup>
<ItemGroup>
<Content Include="Module.txt" />
<Content Include="web.config" />
</ItemGroup>
<ItemGroup>
<Compile Include="Controllers\RecipesController.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RecipeHandlers\CommandRecipeHandler.cs" />
<Compile Include="RecipeHandlers\FeatureRecipeHandler.cs" />
<Compile Include="RecipeHandlers\MetadataRecipeHandler.cs" />
<Compile Include="RecipeHandlers\MigrationRecipeHandler.cs" />
<Compile Include="RecipeHandlers\ModuleRecipeHandler.cs" />
<Compile Include="RecipeHandlers\SettingsRecipeHandler.cs" />
<Compile Include="RecipeHandlers\ThemeRecipeHandler.cs" />
<Compile Include="Routes.cs" />
<Compile Include="Services\RecipeHarvester.cs" />
<Compile Include="Services\RecipeJournalManager.cs" />
<Compile Include="Services\RecipeManager.cs" />
<Compile Include="Services\RecipeParser.cs" />
<Compile Include="Services\RecipeScheduler.cs" />
<Compile Include="Services\RecipeStepExecutor.cs" />
<Compile Include="Services\RecipeStepQueue.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Orchard\Orchard.Framework.csproj">
<Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project>
<Name>Orchard.Framework</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Modules\Orchard.Modules.csproj">
<Project>{17F86780-9A1F-4AA1-86F1-875EEC2730C7}</Project>
<Name>Orchard.Modules</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Packaging\Orchard.Packaging.csproj">
<Project>{DFD137A2-DDB5-4D22-BE0D-FA9AD4C8B059}</Project>
<Name>Orchard.Packaging</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Themes\Orchard.Themes.csproj">
<Project>{CDE24A24-01D3-403C-84B9-37722E18DFB7}</Project>
<Name>Orchard.Themes</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>57675</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>
</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>True</UseCustomServer>
<CustomServerUrl>http://orchard.codeplex.com/</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Orchard.Recipes")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyProduct("Orchard")]
[assembly: AssemblyCopyright("Copyright © Outercurve Foundation 2009")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("9f7f12bd-c74c-48c0-a378-c9d26dfe2c5c")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.20")]
[assembly: AssemblyFileVersion("1.0.20")]
[assembly: SecurityTransparent]

View File

@@ -0,0 +1,201 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Orchard.Commands;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
namespace Orchard.Recipes.RecipeHandlers {
public class CommandRecipeHandler : IRecipeHandler {
private readonly ICommandManager _commandManager;
private readonly CommandParser _commandParser;
public CommandRecipeHandler (ICommandManager commandManager) {
_commandManager = commandManager;
_commandParser = new CommandParser();
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
ILogger Logger { get; set; }
/*
<Command>
command1
command2
command3
</Command>
*/
// run Orchard commands.
public void ExecuteRecipeStep(RecipeContext recipeContext) {
if (!String.Equals(recipeContext.RecipeStep.Name, "Command", StringComparison.OrdinalIgnoreCase)) {
return;
}
var commands =
recipeContext.RecipeStep.Step.Value
.Split(new[] {"\r\n", "\n"}, StringSplitOptions.RemoveEmptyEntries)
.Select(commandEntry => commandEntry.Trim());
foreach (var command in commands) {
if (!String.IsNullOrEmpty(command)) {
var commandParameters = _commandParser.ParseCommandParameters(command);
var input = new StringReader("");
var output = new StringWriter();
_commandManager.Execute(new CommandParameters { Arguments = commandParameters.Arguments, Input = input, Output = output, Switches = commandParameters.Switches });
}
}
recipeContext.Executed = true;
}
}
// Utility class for parsing lines of commands.
// Note: This lexer handles double quotes pretty harshly by design.
// In case you needed them in your arguments, hopefully single quotes work for you as a replacement on the receiving end.
class CommandParser {
public CommandParameters ParseCommandParameters(string command) {
var args = SplitArgs(command);
var arguments = new List<string>();
var result = new CommandParameters {
Switches = new Dictionary<string, string>()
};
foreach (var arg in args) {
// Switch?
if (arg[0] == '/') {
int index = arg.IndexOf(':');
var switchName = (index < 0 ? arg.Substring(1) : arg.Substring(1, index - 1));
var switchValue = (index < 0 || index >= arg.Length ? string.Empty : arg.Substring(index + 1));
if (string.IsNullOrEmpty(switchName))
{
throw new ArgumentException(string.Format("Invalid switch syntax: \"{0}\". Valid syntax is /<switchName>[:<switchValue>].", arg));
}
result.Switches.Add(switchName, switchValue);
}
else {
arguments.Add(arg);
}
}
result.Arguments = arguments;
return result;
}
class State {
private readonly string _commandLine;
private readonly StringBuilder _stringBuilder;
private readonly List<string> _arguments;
private int _index;
public State(string commandLine) {
_commandLine = commandLine;
_stringBuilder = new StringBuilder();
_arguments = new List<string>();
}
public StringBuilder StringBuilder { get { return _stringBuilder; } }
public bool EOF { get { return _index >= _commandLine.Length; } }
public char Current { get { return _commandLine[_index]; } }
public IEnumerable<string> Arguments { get { return _arguments; } }
public void AddArgument() {
_arguments.Add(StringBuilder.ToString());
StringBuilder.Clear();
}
public void AppendCurrent() {
StringBuilder.Append(Current);
}
public void Append(char ch) {
StringBuilder.Append(ch);
}
public void MoveNext() {
if (!EOF)
_index++;
}
}
/// <summary>
/// Implement the same logic as found at
/// http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
/// The 3 special characters are quote, backslash and whitespaces, in order
/// of priority.
/// The semantics of a quote is: whatever the state of the lexer, copy
/// all characters verbatim until the next quote or EOF.
/// The semantics of backslash is: If the next character is a backslash or a quote,
/// copy the next character. Otherwise, copy the backslash and the next character.
/// The semantics of whitespace is: end the current argument and move on to the next one.
/// </summary>
private static IEnumerable<string> SplitArgs(string commandLine) {
var state = new State(commandLine);
while (!state.EOF) {
switch (state.Current) {
case '"':
ProcessQuote(state);
break;
case '\\':
ProcessBackslash(state);
break;
case ' ':
case '\t':
if (state.StringBuilder.Length > 0)
state.AddArgument();
state.MoveNext();
break;
default:
state.AppendCurrent();
state.MoveNext();
break;
}
}
if (state.StringBuilder.Length > 0)
state.AddArgument();
return state.Arguments;
}
private static void ProcessQuote(State state) {
state.MoveNext();
while (!state.EOF) {
if (state.Current == '"') {
state.MoveNext();
break;
}
state.AppendCurrent();
state.MoveNext();
}
state.AddArgument();
}
private static void ProcessBackslash(State state) {
state.MoveNext();
if (state.EOF) {
state.Append('\\');
return;
}
if (state.Current == '"') {
state.Append('"');
state.MoveNext();
}
else {
state.Append('\\');
state.AppendCurrent();
state.MoveNext();
}
}
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.Environment.Features;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Modules.Services;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
namespace Orchard.Recipes.RecipeHandlers {
public class FeatureRecipeHandler : IRecipeHandler {
private readonly IFeatureManager _featureManager;
private readonly IModuleService _moduleService;
public FeatureRecipeHandler(IFeatureManager featureManager, IModuleService moduleService) {
_featureManager = featureManager;
_moduleService = moduleService;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
ILogger Logger { get; set; }
// <Feature enable="f1,f2,f3" disable="f4" />
// Enable/Disable features.
public void ExecuteRecipeStep(RecipeContext recipeContext) {
if (!String.Equals(recipeContext.RecipeStep.Name, "Feature", StringComparison.OrdinalIgnoreCase)) {
return;
}
var featuresToEnable = new List<string>();
var featuresToDisable = new List<string>();
foreach (var attribute in recipeContext.RecipeStep.Step.Attributes()) {
if (String.Equals(attribute.Name.LocalName, "disable", StringComparison.OrdinalIgnoreCase)) {
featuresToDisable = ParseFeatures(attribute.Value);
}
else if (String.Equals(attribute.Name.LocalName, "enable", StringComparison.OrdinalIgnoreCase)) {
featuresToEnable = ParseFeatures(attribute.Value);
}
else {
Logger.Error("Unrecognized attribute {0} encountered in step Feature. Skipping.", attribute.Name.LocalName);
}
}
var availableFeatures = _featureManager.GetAvailableFeatures().Select(x => x.Id).ToArray();
foreach (var featureName in featuresToDisable) {
if (!availableFeatures.Contains(featureName)) {
throw new InvalidOperationException(string.Format("Could not disable feature {0} because it was not found.", featureName));
}
}
foreach (var featureName in featuresToEnable) {
if (!availableFeatures.Contains(featureName)) {
throw new InvalidOperationException(string.Format("Could not enable feature {0} because it was not found.", featureName));
}
}
if (featuresToDisable.Count != 0) {
_moduleService.DisableFeatures(featuresToDisable, true);
}
if (featuresToEnable.Count != 0) {
_moduleService.EnableFeatures(featuresToEnable, true);
}
recipeContext.Executed = true;
}
private static List<string> ParseFeatures(string csv) {
return csv.Split(',')
.Select(value => value.Trim())
.Where(sanitizedValue => !String.IsNullOrEmpty(sanitizedValue))
.ToList();
}
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Xml;
using Orchard.ContentManagement.MetaData;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
namespace Orchard.Recipes.RecipeHandlers {
public class MetadataRecipeHandler : IRecipeHandler {
private readonly IContentDefinitionManager _contentDefinitionManager;
private readonly IContentDefinitionReader _contentDefinitionReader;
public MetadataRecipeHandler(IContentDefinitionManager contentDefinitionManager, IContentDefinitionReader contentDefinitionReader) {
_contentDefinitionManager = contentDefinitionManager;
_contentDefinitionReader = contentDefinitionReader;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
ILogger Logger { get; set; }
/*
<Metadata>
<Types>
<Blog creatable="true">
<Body format="abodyformat"/>
</Blog>
</Types>
<Parts>
</Parts>
</Metadata>
*/
// Set type settings and attach parts to types.
// Create dynamic parts.
public void ExecuteRecipeStep(RecipeContext recipeContext) {
if (!String.Equals(recipeContext.RecipeStep.Name, "Metadata", StringComparison.OrdinalIgnoreCase)) {
return;
}
foreach (var metadataElement in recipeContext.RecipeStep.Step.Elements()) {
switch (metadataElement.Name.LocalName) {
case "Types":
foreach (var element in metadataElement.Elements()) {
var typeElement = element;
var typeName = XmlConvert.DecodeName(element.Name.LocalName);
_contentDefinitionManager.AlterTypeDefinition(typeName, alteration => _contentDefinitionReader.Merge(typeElement, alteration));
}
break;
case "Parts":
// create dynamic part.
foreach (var element in metadataElement.Elements()) {
var partElement = element;
var partName = XmlConvert.DecodeName(element.Name.LocalName);
_contentDefinitionManager.AlterPartDefinition(partName, alteration => _contentDefinitionReader.Merge(partElement, alteration));
}
break;
default:
Logger.Error("Unrecognized element {0} encountered in step Metadata. Skipping.", metadataElement.Name.LocalName);
break;
}
}
recipeContext.Executed = true;
}
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.Data.Migration;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
namespace Orchard.Recipes.RecipeHandlers {
public class MigrationRecipeHandler : IRecipeHandler {
private readonly IDataMigrationManager _dataMigrationManager;
public MigrationRecipeHandler(IDataMigrationManager dataMigrationManager) {
_dataMigrationManager = dataMigrationManager;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
ILogger Logger { get; set; }
// <Migration features="f1, f2" />
// <Migration features="*" />
// Run migration for features.
public void ExecuteRecipeStep(RecipeContext recipeContext) {
if (!String.Equals(recipeContext.RecipeStep.Name, "Migration", StringComparison.OrdinalIgnoreCase)) {
return;
}
bool runAll = false;
var features = new List<string>();
foreach (var attribute in recipeContext.RecipeStep.Step.Attributes()) {
if (String.Equals(attribute.Name.LocalName, "features", StringComparison.OrdinalIgnoreCase)) {
features = ParseFeatures(attribute.Value);
if (features.Contains("*"))
runAll = true;
}
else {
Logger.Error("Unrecognized attribute {0} encountered in step Migration. Skipping.", attribute.Name.LocalName);
}
}
if (runAll) {
foreach (var feature in _dataMigrationManager.GetFeaturesThatNeedUpdate()) {
_dataMigrationManager.Update(feature);
}
}
else {
_dataMigrationManager.Update(features);
}
// run migrations
recipeContext.Executed = true;
}
private static List<string> ParseFeatures(string csv) {
return csv.Split(',')
.Select(value => value.Trim())
.Where(sanitizedValue => !String.IsNullOrEmpty(sanitizedValue))
.ToList();
}
}
}

View File

@@ -0,0 +1,108 @@
using System;
using System.Linq;
using System.Web.Hosting;
using Orchard.Data.Migration;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Modules.Services;
using Orchard.Packaging.Models;
using Orchard.Packaging.Services;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
namespace Orchard.Recipes.RecipeHandlers {
public class ModuleRecipeHandler : IRecipeHandler {
private readonly IPackagingSourceManager _packagingSourceManager;
private readonly IPackageManager _packageManager;
private readonly IExtensionManager _extensionManager;
private readonly IModuleService _moduleService;
private readonly IDataMigrationManager _dataMigrationManager;
public ModuleRecipeHandler(
IPackagingSourceManager packagingSourceManager,
IPackageManager packageManager,
IExtensionManager extensionManager,
IModuleService moduleService,
IDataMigrationManager dataMigrationManager) {
_packagingSourceManager = packagingSourceManager;
_packageManager = packageManager;
_extensionManager = extensionManager;
_moduleService = moduleService;
_dataMigrationManager = dataMigrationManager;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
ILogger Logger { get; set; }
// <Module name="module1" [repository="somerepo"] version="1.1" />
// install modules from feed.
public void ExecuteRecipeStep(RecipeContext recipeContext) {
if (!String.Equals(recipeContext.RecipeStep.Name, "Module", StringComparison.OrdinalIgnoreCase)) {
return;
}
string name = null, version = null, repository = null;
foreach (var attribute in recipeContext.RecipeStep.Step.Attributes()) {
if (String.Equals(attribute.Name.LocalName, "name", StringComparison.OrdinalIgnoreCase)) {
name = attribute.Value;
}
else if (String.Equals(attribute.Name.LocalName, "version", StringComparison.OrdinalIgnoreCase)) {
version = attribute.Value;
}
else if (String.Equals(attribute.Name.LocalName, "repository", StringComparison.OrdinalIgnoreCase)) {
repository = attribute.Value;
}
else {
throw new InvalidOperationException(string.Format("Unrecognized attribute {0} encountered in step Module.", attribute.Name.LocalName));
}
}
if (name == null) {
throw new InvalidOperationException("Name is required in a Module declaration in a recipe file.");
}
// download and install module from the orchard feed or a custom feed if repository is specified.
bool enforceVersion = version != null;
bool installed = false;
PackagingSource packagingSource = _packagingSourceManager.GetSources().FirstOrDefault();
if (repository != null) {
enforceVersion = false;
packagingSource = new PackagingSource {FeedTitle = repository, FeedUrl = repository};
}
foreach (var packagingEntry in _packagingSourceManager.GetExtensionList(packagingSource)) {
if (String.Equals(packagingEntry.Title, name, StringComparison.OrdinalIgnoreCase)) {
if (enforceVersion && !String.Equals(packagingEntry.Version, version, StringComparison.OrdinalIgnoreCase)) {
continue;
}
var extensions = _extensionManager.AvailableExtensions();
if (extensions.Where(extension =>
DefaultExtensionTypes.IsModule(extension.ExtensionType) &&
String.Equals(packagingEntry.Title, extension.Name, StringComparison.OrdinalIgnoreCase)).Any()) {
throw new InvalidOperationException(string.Format("Module {0} already exists.", name));
}
_packageManager.Install(packagingEntry.PackageId, packagingEntry.Version, packagingSource.FeedUrl, HostingEnvironment.MapPath("~/"));
foreach (
var features in
from extensionDescriptor in extensions
where String.Equals(extensionDescriptor.Name, packagingEntry.Title, StringComparison.OrdinalIgnoreCase)
select extensionDescriptor.Features.Select(f => f.Name).ToArray()) {
_moduleService.EnableFeatures(features);
_dataMigrationManager.Update(features);
installed = true;
}
break;
}
}
if (!installed) {
throw new InvalidOperationException(string.Format("Module {0} was not found in the specified location.", name));
}
recipeContext.Executed = true;
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Xml;
using System.Xml.Linq;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
using Orchard.Settings;
namespace Orchard.Recipes.RecipeHandlers {
public class SettingsRecipeHandler : IRecipeHandler {
private readonly ISiteService _siteService;
public SettingsRecipeHandler(ISiteService siteService) {
_siteService = siteService;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
ILogger Logger { get; set; }
/*
<Settings>
<SiteSettingsPart PageSize="30" />
<CommentSettingsPart ModerateComments="true" />
</Settings>
*/
// Set site and part settings.
public void ExecuteRecipeStep(RecipeContext recipeContext) {
if (!String.Equals(recipeContext.RecipeStep.Name, "Settings", StringComparison.OrdinalIgnoreCase)) {
return;
}
var site = _siteService.GetSiteSettings();
foreach (var element in recipeContext.RecipeStep.Step.Elements()) {
var partName = XmlConvert.DecodeName(element.Name.LocalName);
foreach (var contentPart in site.ContentItem.Parts) {
if (!String.Equals(contentPart.PartDefinition.Name, partName, StringComparison.OrdinalIgnoreCase)) {
continue;
}
foreach (var attribute in element.Attributes()) {
SetSetting(attribute, contentPart);
}
}
}
recipeContext.Executed = true;
}
private static void SetSetting(XAttribute attribute, ContentPart contentPart) {
var attributeName = attribute.Name.LocalName;
var attributeValue = attribute.Value;
var property = contentPart.GetType().GetProperty(attributeName);
if (property == null) {
throw new InvalidOperationException(string.Format("Could set setting {0} for part {1} because it was not found.", attributeName, contentPart.PartDefinition.Name));
}
var propertyType = property.PropertyType;
if (propertyType == typeof(string)) {
property.SetValue(contentPart, attributeValue, null);
}
else if (propertyType == typeof(bool)) {
property.SetValue(contentPart, Boolean.Parse(attributeValue), null);
}
else if (propertyType == typeof(int)) {
property.SetValue(contentPart, Int32.Parse(attributeValue), null);
}
else {
throw new InvalidOperationException(string.Format("Could set setting {0} for part {1} because its type is not supported. Settings should be integer,boolean or string.", attributeName, contentPart.PartDefinition.Name));
}
}
}
}

View File

@@ -0,0 +1,113 @@
using System;
using System.Linq;
using System.Web.Hosting;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Packaging.Models;
using Orchard.Packaging.Services;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
using Orchard.Themes.Services;
namespace Orchard.Recipes.RecipeHandlers {
public class ThemeRecipeHandler : IRecipeHandler {
private readonly IPackagingSourceManager _packagingSourceManager;
private readonly IPackageManager _packageManager;
private readonly IExtensionManager _extensionManager;
private readonly IThemeService _themeService;
private readonly ISiteThemeService _siteThemeService;
public ThemeRecipeHandler(
IPackagingSourceManager packagingSourceManager,
IPackageManager packageManager,
IExtensionManager extensionManager,
IThemeService themeService,
ISiteThemeService siteThemeService) {
_packagingSourceManager = packagingSourceManager;
_packageManager = packageManager;
_extensionManager = extensionManager;
_themeService = themeService;
_siteThemeService = siteThemeService;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
ILogger Logger { get; set; }
// <Theme name="theme1" repository="somethemerepo" version="1.1" enable="true" current="true" />
// install themes from feed.
public void ExecuteRecipeStep(RecipeContext recipeContext) {
if (!String.Equals(recipeContext.RecipeStep.Name, "Theme", StringComparison.OrdinalIgnoreCase)) {
return;
}
bool enable = false, current = false;
string name = null, version = null, repository = null;
foreach (var attribute in recipeContext.RecipeStep.Step.Attributes()) {
if (String.Equals(attribute.Name.LocalName, "enable", StringComparison.OrdinalIgnoreCase)) {
enable = Boolean.Parse(attribute.Value);
}
else if (String.Equals(attribute.Name.LocalName, "current", StringComparison.OrdinalIgnoreCase)) {
current = Boolean.Parse(attribute.Value);
}
else if (String.Equals(attribute.Name.LocalName, "name", StringComparison.OrdinalIgnoreCase)) {
name = attribute.Value;
}
else if (String.Equals(attribute.Name.LocalName, "version", StringComparison.OrdinalIgnoreCase)) {
version = attribute.Value;
}
else if (String.Equals(attribute.Name.LocalName, "repository", StringComparison.OrdinalIgnoreCase)) {
repository = attribute.Value;
}
else {
Logger.Error("Unrecognized attribute {0} encountered in step Theme. Skipping.", attribute.Name.LocalName);
}
}
if (name == null) {
throw new InvalidOperationException("Name is required in a Theme declaration in a recipe file.");
}
// download and install theme from the orchard feed or a custom feed if repository is specified.
bool enforceVersion = version != null;
bool installed = false;
PackagingSource packagingSource = _packagingSourceManager.GetSources().FirstOrDefault();
if (repository != null) {
enforceVersion = false;
packagingSource = new PackagingSource { FeedTitle = repository, FeedUrl = repository };
}
foreach (var packagingEntry in _packagingSourceManager.GetExtensionList(packagingSource)) {
if (String.Equals(packagingEntry.Title, name, StringComparison.OrdinalIgnoreCase)) {
if (enforceVersion && !String.Equals(packagingEntry.Version, version, StringComparison.OrdinalIgnoreCase)) {
continue;
}
if (_extensionManager.AvailableExtensions().Where(extension =>
DefaultExtensionTypes.IsTheme(extension.ExtensionType) &&
String.Equals(packagingEntry.Title, extension.Name, StringComparison.OrdinalIgnoreCase)).Any()) {
throw new InvalidOperationException(string.Format("Theme {0} already exists.", name));
}
_packageManager.Install(packagingEntry.PackageId, packagingEntry.Version, packagingSource.FeedUrl, HostingEnvironment.MapPath("~/"));
if (current) {
_themeService.EnableThemeFeatures(packagingEntry.Title);
_siteThemeService.SetSiteTheme(packagingEntry.Title);
}
else if (enable) {
_themeService.EnableThemeFeatures(packagingEntry.Title);
}
installed = true;
break;
}
}
if (!installed) {
throw new InvalidOperationException(string.Format("Theme {0} was not found in the specified location.", name));
}
recipeContext.Executed = true;
}
}
}

View File

@@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Routing;
using Orchard.Mvc.Routes;
namespace Orchard.Recipes {
public class Routes : IRouteProvider {
public void GetRoutes(ICollection<RouteDescriptor> routes) {
foreach (var routeDescriptor in GetRoutes())
routes.Add(routeDescriptor);
}
public IEnumerable<RouteDescriptor> GetRoutes() {
return new[] {
new RouteDescriptor { Priority = 5,
Route = new Route(
"Recipes/Status/{executionId}",
new RouteValueDictionary {
{"area", "Orchard.Recipes"},
{"controller", "Recipes"},
{"action", "RecipeExecutionStatus"}
},
new RouteValueDictionary(),
new RouteValueDictionary {
{"area", "Orchard.Recipes"}
},
new MvcRouteHandler())
}
};
}
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Orchard.Environment.Extensions;
using Orchard.FileSystems.WebSite;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Recipes.Models;
namespace Orchard.Recipes.Services {
public class RecipeHarvester : IRecipeHarvester {
private readonly IExtensionManager _extensionManager;
private readonly IWebSiteFolder _webSiteFolder;
private readonly IRecipeParser _recipeParser;
public RecipeHarvester(
IExtensionManager extensionManager,
IWebSiteFolder webSiteFolder,
IRecipeParser recipeParser) {
_extensionManager = extensionManager;
_webSiteFolder = webSiteFolder;
_recipeParser = recipeParser;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
ILogger Logger { get; set; }
public IEnumerable<Recipe> HarvestRecipes(string extensionName) {
var recipes = new List<Recipe>();
var extension = _extensionManager.GetExtension(extensionName);
if (extension != null) {
var recipeLocation = Path.Combine(extension.Location, extensionName, "Recipes");
var recipeFiles = _webSiteFolder.ListFiles(recipeLocation, true);
recipes.AddRange(
from recipeFile in recipeFiles
where recipeFile.EndsWith(".recipe.xml", StringComparison.OrdinalIgnoreCase)
select _recipeParser.ParseRecipe(_webSiteFolder.ReadFile(recipeFile)));
}
else {
Logger.Error("Could not discover recipes because module '{0}' was not found.", extensionName);
}
return recipes;
}
}
}

View File

@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;
using Orchard.FileSystems.Media;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Recipes.Models;
namespace Orchard.Recipes.Services {
public class RecipeJournalManager : IRecipeJournal {
private readonly IStorageProvider _storageProvider;
private readonly string _recipeJournalFolder = "RecipeJournal" + Path.DirectorySeparatorChar;
public RecipeJournalManager(IStorageProvider storageProvider) {
_storageProvider = storageProvider;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
ILogger Logger { get; set; }
public void ExecutionStart(string executionId) {
var executionJournal = GetJournalFile(executionId);
var xElement = XElement.Parse(ReadJournal(executionJournal));
xElement.Element("Status").Value = "Started";
WriteJournal(executionJournal, xElement);
}
public void ExecutionComplete(string executionId) {
var executionJournal = GetJournalFile(executionId);
var xElement = XElement.Parse(ReadJournal(executionJournal));
xElement.Element("Status").Value = "Complete";
WriteJournal(executionJournal, xElement);
}
public void ExecutionFailed(string executionId) {
var executionJournal = GetJournalFile(executionId);
var xElement = XElement.Parse(ReadJournal(executionJournal));
xElement.Element("Status").Value = "Failed";
WriteJournal(executionJournal, xElement);
}
public void WriteJournalEntry(string executionId, string message) {
var executionJournal = GetJournalFile(executionId);
var xElement = XElement.Parse(ReadJournal(executionJournal));
var journalEntry = new XElement("Message", message);
xElement.Add(journalEntry);
WriteJournal(executionJournal, xElement);
}
public RecipeJournal GetRecipeJournal(string executionId) {
var executionJournal = GetJournalFile(executionId);
var xElement = XElement.Parse(ReadJournal(executionJournal));
var journal = new RecipeJournal { ExecutionId = executionId };
var messages = new List<JournalMessage>();
journal.Status = ReadStatusFromJournal(xElement);
foreach (var message in xElement.Elements("Message")) {
messages.Add(new JournalMessage {Message = message.Value});
}
journal.Messages = messages;
return journal;
}
public RecipeStatus GetRecipeStatus(string executionId) {
var executionJournal = GetJournalFile(executionId);
var xElement = XElement.Parse(ReadJournal(executionJournal));
return ReadStatusFromJournal(xElement);
}
private IStorageFile GetJournalFile(string executionId) {
IStorageFile journalFile;
var journalPath = Path.Combine(_recipeJournalFolder, executionId);
try {
_storageProvider.TryCreateFolder(_recipeJournalFolder);
journalFile = _storageProvider.GetFile(journalPath);
}
catch (ArgumentException) {
journalFile = _storageProvider.CreateFile(journalPath);
var recipeStepElement = new XElement("RecipeJournal");
recipeStepElement.Add(new XElement("Status", "Unknown"));
WriteJournal(journalFile, recipeStepElement);
}
return journalFile;
}
private static string ReadJournal(IStorageFile executionJournal) {
using (var stream = executionJournal.OpenRead()) {
using (var streamReader = new StreamReader(stream)) {
return streamReader.ReadToEnd();
}
}
}
private static void WriteJournal(IStorageFile journalFile, XElement journal) {
string content = journal.ToString();
using (var stream = journalFile.OpenWrite()) {
using (var tw = new StreamWriter(stream)) {
tw.Write(content);
stream.SetLength(stream.Position);
}
}
}
private static RecipeStatus ReadStatusFromJournal(XElement xElement) {
switch (xElement.Element("Status").Value) {
case "Started":
return RecipeStatus.Started;
case "Complete":
return RecipeStatus.Complete;
case "Failed":
return RecipeStatus.Failed;
default:
return RecipeStatus.Unknown;
}
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Recipes.Models;
namespace Orchard.Recipes.Services {
public class RecipeManager : IRecipeManager {
private readonly IRecipeStepQueue _recipeStepQueue;
private readonly IRecipeScheduler _recipeScheduler;
private readonly IRecipeJournal _recipeJournal;
public RecipeManager(IRecipeStepQueue recipeStepQueue, IRecipeScheduler recipeScheduler, IRecipeJournal recipeJournal) {
_recipeStepQueue = recipeStepQueue;
_recipeScheduler = recipeScheduler;
_recipeJournal = recipeJournal;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
ILogger Logger { get; set; }
public string Execute(Recipe recipe) {
if (recipe == null)
return null;
var executionId = Guid.NewGuid().ToString("n");
_recipeJournal.ExecutionStart(executionId);
foreach (var recipeStep in recipe.RecipeSteps) {
_recipeStepQueue.Enqueue(executionId, recipeStep);
}
_recipeScheduler.ScheduleWork(executionId);
return executionId;
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Recipes.Models;
namespace Orchard.Recipes.Services {
public class RecipeParser : IRecipeParser {
public RecipeParser() {
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
ILogger Logger { get; set; }
public Recipe ParseRecipe(string recipeText) {
var recipe = new Recipe();
try {
var recipeSteps = new List<RecipeStep>();
TextReader textReader = new StringReader(recipeText);
var recipeTree = XElement.Load(textReader, LoadOptions.PreserveWhitespace);
textReader.Close();
foreach (var element in recipeTree.Elements()) {
// Recipe mETaDaTA
if (element.Name.LocalName == "Recipe") {
foreach (var metadataElement in element.Elements()) {
switch (metadataElement.Name.LocalName) {
case "Name":
recipe.Name = metadataElement.Value;
break;
case "Description":
recipe.Description = metadataElement.Value;
break;
case "Author":
recipe.Author = metadataElement.Value;
break;
case "WebSite":
recipe.WebSite = metadataElement.Value;
break;
case "Version":
recipe.Version = metadataElement.Value;
break;
case "Tags":
recipe.Tags = metadataElement.Value;
break;
default:
Logger.Error("Unrecognized recipe metadata element {0} encountered. Skipping.", metadataElement.Name.LocalName);
break;
}
}
}
// Recipe step
else {
var recipeStep = new RecipeStep { Name = element.Name.LocalName, Step = element };
recipeSteps.Add(recipeStep);
}
}
recipe.RecipeSteps = recipeSteps;
}
catch (Exception exception) {
Logger.Error(exception, "Parsing recipe failed. Recipe text was: {0}.", recipeText);
throw;
}
return recipe;
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using Orchard.Environment.Configuration;
using Orchard.Environment.Descriptor;
using Orchard.Environment.State;
using Orchard.Recipes.Events;
namespace Orchard.Recipes.Services {
public class RecipeScheduler : IRecipeScheduler, IRecipeSchedulerEventHandler {
private readonly IProcessingEngine _processingEngine;
private readonly ShellSettings _shellSettings;
private readonly IShellDescriptorManager _shellDescriptorManager;
private readonly Lazy<IRecipeStepExecutor> _recipeStepExecutor;
public RecipeScheduler(
IProcessingEngine processingEngine,
ShellSettings shellSettings,
IShellDescriptorManager shellDescriptorManager,
Lazy<IRecipeStepExecutor> recipeStepExecutor) {
_processingEngine = processingEngine;
_shellSettings = shellSettings;
_shellDescriptorManager = shellDescriptorManager;
_recipeStepExecutor = recipeStepExecutor;
}
public void ScheduleWork(string executionId) {
var shellDescriptor = _shellDescriptorManager.GetShellDescriptor();
// TODO: this task entry may need to become appdata folder backed if it isn't already
_processingEngine.AddTask(
_shellSettings,
shellDescriptor,
"IRecipeSchedulerEventHandler.ExecuteWork",
new Dictionary<string, object> { { "executionId", executionId } });
}
public void ExecuteWork(string executionId) {
// todo: this callback should be guarded against concurrency by the IProcessingEngine
var scheduleMore = _recipeStepExecutor.Value.ExecuteNextStep(executionId);
if (scheduleMore)
ScheduleWork(executionId);
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Recipes.Models;
namespace Orchard.Recipes.Services {
public class RecipeStepExecutor : IRecipeStepExecutor {
private readonly IRecipeStepQueue _recipeStepQueue;
private readonly IRecipeJournal _recipeJournal;
private readonly IEnumerable<IRecipeHandler> _recipeHandlers;
public RecipeStepExecutor(IRecipeStepQueue recipeStepQueue, IRecipeJournal recipeJournal, IEnumerable<IRecipeHandler> recipeHandlers) {
_recipeStepQueue = recipeStepQueue;
_recipeJournal = recipeJournal;
_recipeHandlers = recipeHandlers;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
ILogger Logger { get; set; }
public bool ExecuteNextStep(string executionId) {
var nextRecipeStep= _recipeStepQueue.Dequeue(executionId);
if (nextRecipeStep == null) {
_recipeJournal.ExecutionComplete(executionId);
return false;
}
_recipeJournal.WriteJournalEntry(executionId, string.Format("Executing step {0}.", nextRecipeStep.Name));
var recipeContext = new RecipeContext { RecipeStep = nextRecipeStep, Executed = false };
try {
foreach (var recipeHandler in _recipeHandlers) {
recipeHandler.ExecuteRecipeStep(recipeContext);
}
}
catch(Exception exception) {
Logger.Error(exception, "Recipe execution {0} was cancelled because a step failed to execute", executionId);
while (_recipeStepQueue.Dequeue(executionId) != null) ;
_recipeJournal.ExecutionFailed(executionId);
return false;
}
if (!recipeContext.Executed) {
Logger.Error("Could not execute recipe step '{0}' because the recipe handler was not found.", recipeContext.RecipeStep.Name);
while (_recipeStepQueue.Dequeue(executionId) != null) ;
_recipeJournal.ExecutionFailed(executionId);
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Orchard.FileSystems.AppData;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Recipes.Models;
namespace Orchard.Recipes.Services {
public class RecipeStepQueue : IRecipeStepQueue {
private readonly IAppDataFolder _appDataFolder;
private readonly string _recipeQueueFolder = "RecipeQueue" + Path.DirectorySeparatorChar;
public RecipeStepQueue(IAppDataFolder appDataFolder) {
_appDataFolder = appDataFolder;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
ILogger Logger { get; set; }
public void Enqueue(string executionId, RecipeStep step) {
var recipeStepElement = new XElement("RecipeStep");
recipeStepElement.Add(new XElement("Name", step.Name));
recipeStepElement.Add(step.Step);
if (_appDataFolder.DirectoryExists(Path.Combine(_recipeQueueFolder, executionId))) {
int stepIndex = GetLastStepIndex(executionId) + 1;
_appDataFolder.CreateFile(Path.Combine(_recipeQueueFolder, executionId + Path.DirectorySeparatorChar + stepIndex),
recipeStepElement.ToString());
}
else {
_appDataFolder.CreateFile(
Path.Combine(_recipeQueueFolder, executionId + Path.DirectorySeparatorChar + "0"),
recipeStepElement.ToString());
}
}
public RecipeStep Dequeue(string executionId) {
if (!_appDataFolder.DirectoryExists(Path.Combine(_recipeQueueFolder, executionId))) {
return null;
}
RecipeStep recipeStep = null;
int stepIndex = GetFirstStepIndex(executionId);
if (stepIndex >= 0) {
var stepPath = Path.Combine(_recipeQueueFolder, executionId + Path.DirectorySeparatorChar + stepIndex);
// string to xelement
var stepElement = XElement.Parse(_appDataFolder.ReadFile(stepPath));
var stepName = stepElement.Element("Name").Value;
recipeStep = new RecipeStep {
Name = stepName,
Step = stepElement.Element(stepName)
};
_appDataFolder.DeleteFile(stepPath);
}
if (stepIndex < 1) {
_appDataFolder.DeleteFile(Path.Combine(_recipeQueueFolder, executionId));
}
return recipeStep;
}
private int GetFirstStepIndex(string executionId) {
var stepFiles = new List<string>(_appDataFolder.ListFiles(Path.Combine(_recipeQueueFolder, executionId)));
if (stepFiles.Count == 0)
return -1;
var currentSteps = stepFiles.Select(stepFile => Int32.Parse(stepFile.Substring(stepFile.LastIndexOf('/') + 1))).ToList();
currentSteps.Sort();
return currentSteps[0];
}
private int GetLastStepIndex(string executionId) {
int lastIndex = -1;
var stepFiles = _appDataFolder.ListFiles(Path.Combine(_recipeQueueFolder, executionId));
// we always have only a handful of steps.
foreach (var stepFile in stepFiles) {
int stepOrder = Int32.Parse(stepFile.Substring(stepFile.LastIndexOf('/') + 1));
if (stepOrder > lastIndex)
lastIndex = stepOrder;
}
return lastIndex;
}
}
}

View File

@@ -0,0 +1,39 @@
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<remove name="host" />
<remove name="pages" />
<section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
</sectionGroup>
</configSections>
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<pages pageBaseType="Orchard.Mvc.ViewEngines.Razor.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="System.Linq"/>
<add namespace="System.Collections.Generic"/>
<add namespace="Orchard.Mvc.Html"/>
</namespaces>
</pages>
</system.web.webPages.razor>
<system.web>
<compilation targetFramework="4.0">
<assemblies>
<add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add assembly="System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</assemblies>
</compilation>
</system.web>
</configuration>

View File

@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Orchard.Environment;
using Orchard.Environment.Configuration;
using Orchard.Logging;
using Orchard.Recipes.Models;
using Orchard.Setup.Services;
using Orchard.Setup.ViewModels;
using Orchard.Localization;
@@ -16,8 +19,12 @@ namespace Orchard.Setup.Controllers {
private readonly IViewsBackgroundCompilation _viewsBackgroundCompilation;
private readonly INotifier _notifier;
private readonly ISetupService _setupService;
private const string DefaultRecipe = "Default";
public SetupController(INotifier notifier, ISetupService setupService, IViewsBackgroundCompilation viewsBackgroundCompilation) {
public SetupController(
INotifier notifier,
ISetupService setupService,
IViewsBackgroundCompilation viewsBackgroundCompilation) {
_viewsBackgroundCompilation = viewsBackgroundCompilation;
_notifier = notifier;
_setupService = setupService;
@@ -35,18 +42,24 @@ namespace Orchard.Setup.Controllers {
public ActionResult Index() {
var initialSettings = _setupService.Prime();
var recipes = _setupService.Recipes().Where(r => r.Name != DefaultRecipe);
// On the first time installation of Orchard, the user gets to the setup screen, which
// will take a while to finish (user inputting data and the setup process itself).
// We use this opportunity to start a background task to "pre-compile" all the known
// views in the app folder, so that the application is more reponsive when the user
// hits the homepage and admin screens for the first time.
// hits the homepage and admin screens for the first time.))
if (StringComparer.OrdinalIgnoreCase.Equals(initialSettings.Name, ShellSettings.DefaultName)) {
_viewsBackgroundCompilation.Start();
}
//
return IndexViewResult(new SetupViewModel { AdminUsername = "admin", DatabaseIsPreconfigured = !string.IsNullOrEmpty(initialSettings.DataProvider)});
return IndexViewResult(new SetupViewModel {
AdminUsername = "admin",
DatabaseIsPreconfigured = !string.IsNullOrEmpty(initialSettings.DataProvider),
Recipes = recipes
});
}
[HttpPost, ActionName("Index")]
@@ -65,14 +78,21 @@ namespace Orchard.Setup.Controllers {
ModelState.AddModelError("DatabaseTablePrefix", T("The table prefix must begin with a letter").Text);
}
}
if (model.Recipe == null) {
model.Recipe = DefaultRecipe;
}
if (!ModelState.IsValid) {
var recipes = _setupService.Recipes().Where(r => r.Name != DefaultRecipe);
model.Recipes = recipes;
foreach (var recipe in recipes.Where(recipe => recipe.Name == model.Recipe)) {
model.RecipeDescription = recipe.Description;
}
model.DatabaseIsPreconfigured = !string.IsNullOrEmpty(_setupService.Prime().DataProvider);
return IndexViewResult(model);
}
try {
var setupContext = new SetupContext {
SiteName = model.SiteName,
AdminUsername = model.AdminUsername,
@@ -80,10 +100,11 @@ namespace Orchard.Setup.Controllers {
DatabaseProvider = model.DatabaseOptions ? "SqlCe" : "SqlServer",
DatabaseConnectionString = model.DatabaseConnectionString,
DatabaseTablePrefix = model.DatabaseTablePrefix,
EnabledFeatures = null // default list
EnabledFeatures = null, // default list
Recipe = model.Recipe
};
_setupService.Setup(setupContext);
string executionId = _setupService.Setup(setupContext);
// First time installation if finally done. Tell the background views compilation
// process to stop, so that it doesn't interfere with the user (asp.net compilation

View File

@@ -9,3 +9,4 @@ Features:
Name: Setup
Description: Standard site setup. This feature is disabled automatically once setup is over.
Category: Core

View File

@@ -76,6 +76,10 @@
</ItemGroup>
<ItemGroup>
<Content Include="Content\synchronizing.gif" />
<Content Include="Recipes\blog.recipe.xml" />
<Content Include="Recipes\default.recipe.xml" />
<Content Include="Recipes\customcontentsite.recipe.xml" />
<Content Include="Recipes\minimal.recipe.xml" />
<Content Include="Scripts\setup.js" />
<Content Include="Views\Setup\Index.cshtml" />
</ItemGroup>
@@ -88,14 +92,14 @@
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
<Name>Orchard.Core</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Recipes\Orchard.Recipes.csproj">
<Project>{FC1D74E8-7A4D-48F4-83DE-95C6173780C4}</Project>
<Name>Orchard.Recipes</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Themes\Orchard.Themes.csproj">
<Project>{CDE24A24-01D3-403C-84B9-37722E18DFB7}</Project>
<Name>Orchard.Themes</Name>
</ProjectReference>
<ProjectReference Include="..\Orchard.Widgets\Orchard.Widgets.csproj">
<Project>{194D3CCC-1153-474D-8176-FDE8D7D0D0BD}</Project>
<Name>Orchard.Widgets</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="Module.txt" />

View File

@@ -0,0 +1,56 @@
<?xml version="1.0"?>
<Orchard>
<Recipe>
<Name>Blog</Name>
<Description>A recipe providing an installation profile with the features you need for a personal blog.</Description>
<Author>The Orchard Team</Author>
<WebSite>http://orchardproject.net</WebSite>
<Tags>blog</Tags>
<Version>1.0</Version>
</Recipe>
<Feature enable="Orchard.Blogs,Orchard.Comments,Orchard.Tags,
XmlRpc,Orchard.Blogs.RemotePublishing,
TinyMce,Orchard.Media,Orchard.PublishLater,
Orchard.jQuery,Orchard.Widgets,
Orchard.Scripting,Orchard.Scripting.Lightweight,
PackagingServices,Orchard.Packaging,Gallery,
TheThemeMachine" />
<Metadata>
<Types>
<Page ContentTypeSettings.Draftable="True" TypeIndexing.Included="true">
<TagsPart />
<LocalizationPart />
</Page>
<BlogPost ContentTypeSettings.Draftable="True" TypeIndexing.Included="true">
<CommentsPart />
<TagsPart />
<LocalizationPart />
</BlogPost>
</Types>
<Parts>
<BodyPart BodyPartSettings.FlavorDefault="html" />
</Parts>
</Metadata>
<Settings>
<SiteSettingsPart PageSize="20" PageTitleSeparator = " - " />
<CommentSettingsPart ModerateComments="true" />
</Settings>
<Command>
layer create /Name:"Default" /LayerRule:"true"
layer create /Name:"Authenticated" /LayerRule:"authenticated"
layer create /Name:"Anonymous" /LayerRule:"not authenticated"
layer create /Name:"Disabled" /LayerRule:"false"
layer create /Name:"TheHomepage" /LayerRule:"url '~/'"
blog create /Slug:"blog" /Title:"Blog" /Homepage:true /Description:"This is your Orchard Blog."
widget create /Type:"HtmlWidget" /Title:"First Leader Aside" /Zone:"TripelFirst" /Position:"5" /Layer:"TheHomepage" /UseLoremIpsumText:true
widget create /Type:"HtmlWidget" /Title:"Second Leader Aside" /Zone:"TripelSecond" /Position:"5" /Layer:"TheHomepage" /UseLoremIpsumText:true
widget create /Type:"HtmlWidget" /Title:"Third Leader Aside" /Zone:"TripelThird" /Position:"5" /Layer:"TheHomepage" /UseLoremIpsumText:true
menuitem create /MenuPosition:"1" /MenuText:"Home" /Url:"" /OnMainMenu:true
</Command>
<Migration features="*" />
</Orchard>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0"?>
<Orchard>
<Recipe>
<Name>Custom Content Site</Name>
<Description>A recipe providing an installation profile with the functionality needed to create content-based custom sites.</Description>
<Author>The Orchard Team</Author>
<WebSite>http://orchardproject.net</WebSite>
<Tags></Tags>
<Version>1.0</Version>
</Recipe>
<Feature enable="Orchard.ContentTypes,Orchard.Lists,Orchard.PublishLater,
Orchard.jQuery,TinyMce,Orchard.Media,
PackagingServices,Orchard.Packaging,Gallery,TheThemeMachine" />
<Metadata>
<Types>
<Page ContentTypeSettings.Draftable="True" TypeIndexing.Included="true" />
</Types>
<Parts>
<BodyPart BodyPartSettings.FlavorDefault="html" />
<!-- Dynamic parts -->
</Parts>
</Metadata>
<Settings>
</Settings>
<Command>
page create /Slug:"welcome-to-orchard" /Title:"Welcome to Orchard!" /Path:"welcome-to-orchard" /Homepage:true /Publish:true /UseWelcomeText:true
menuitem create /MenuPosition:"1" /MenuText:"Home" /Url:"" /OnMainMenu:true
</Command>
<Migration features="*" />
</Orchard>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0"?>
<Orchard>
<Recipe>
<Name>Default</Name>
<Description>A recipe providing a default Orchard installation profile.</Description>
<Author>The Orchard Team</Author>
<WebSite>http://orchardproject.net</WebSite>
<Tags></Tags>
<Version>1.0</Version>
</Recipe>
<Feature enable="Orchard.Blogs,Orchard.Comments,Orchard.Tags,
Orchard.Lists,TinyMce,Orchard.Media,Orchard.PublishLater,
Orchard.jQuery,Orchard.Widgets,Orchard.ContentTypes,
Orchard.Scripting,Orchard.Scripting.Lightweight,
PackagingServices,Orchard.Packaging,Gallery,
TheThemeMachine" />
<Metadata>
<Types>
<Page ContentTypeSettings.Draftable="True" TypeIndexing.Included="true">
<TagsPart />
<LocalizationPart />
</Page>
<BlogPost ContentTypeSettings.Draftable="True" TypeIndexing.Included="true">
<CommentsPart />
<TagsPart />
<LocalizationPart />
</BlogPost>
</Types>
<Parts>
<BodyPart BodyPartSettings.FlavorDefault="html" />
</Parts>
</Metadata>
<Settings />
<Command>
layer create /Name:"Default" /LayerRule:"true"
layer create /Name:"Authenticated" /LayerRule:"authenticated"
layer create /Name:"Anonymous" /LayerRule:"not authenticated"
layer create /Name:"Disabled" /LayerRule:"false"
layer create /Name:"TheHomepage" /LayerRule:"url '~/'"
page create /Slug:"welcome-to-orchard" /Title:"Welcome to Orchard!" /Path:"welcome-to-orchard" /Homepage:true /Publish:true /UseWelcomeText:true
widget create /Type:"HtmlWidget" /Title:"First Leader Aside" /Zone:"TripelFirst" /Position:"5" /Layer:"TheHomepage" /UseLoremIpsumText:true
widget create /Type:"HtmlWidget" /Title:"Second Leader Aside" /Zone:"TripelSecond" /Position:"5" /Layer:"TheHomepage" /UseLoremIpsumText:true
widget create /Type:"HtmlWidget" /Title:"Third Leader Aside" /Zone:"TripelThird" /Position:"5" /Layer:"TheHomepage" /UseLoremIpsumText:true
menuitem create /MenuPosition:"1" /MenuText:"Home" /Url:"" /OnMainMenu:true
</Command>
<Migration features="*" />
</Orchard>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0"?>
<Orchard>
<Recipe>
<Name>Minimal</Name>
<Description>A recipe providing the Orchard framework with limited end-user functionality that can be used during development.</Description>
<Author>The Orchard Team</Author>
<WebSite>http://orchardproject.net</WebSite>
<Tags>developer</Tags>
<Version>1.0</Version>
</Recipe>
<Feature disable="Feeds, Containers"
enable="Orchard.jQuery" />
<Metadata>
<Types>
<Page ContentTypeSettings.Draftable="True" />
</Types>
<Parts>
<BodyPart BodyPartSettings.FlavorDefault="html" />
</Parts>
</Metadata>
<Settings />
<Command>
page create /Slug:"welcome-to-orchard" /Title:"Welcome to Orchard!" /Path:"welcome-to-orchard" /Homepage:true /Publish:true /Text:"Welcome To Orchard!"
menuitem create /MenuPosition:"1" /MenuText:"Home" /Url:"" /OnMainMenu:true
</Command>
<Migration features="*" />
</Orchard>

View File

@@ -12,3 +12,12 @@
document.forms[0].attachEvent("onsubmit", show);
}
})();
(function ($) {
$("select.recipe").change(function () { // class="recipe" on the select element
var description = $(this).find(":selected").attr("recipedescription"); // reads the html attribute of the selected option
$("#recipedescription").text(description); // make the contents of <div id="recipe-description"></div> be the escaped description string
});
})(jQuery);

View File

@@ -1,8 +1,11 @@
using Orchard.Environment.Configuration;
using System.Collections.Generic;
using Orchard.Environment.Configuration;
using Orchard.Recipes.Models;
namespace Orchard.Setup.Services {
public interface ISetupService : IDependency {
ShellSettings Prime();
void Setup(SetupContext context);
IEnumerable<Recipe> Recipes();
string Setup(SetupContext context);
}
}

View File

@@ -9,5 +9,6 @@ namespace Orchard.Setup.Services {
public string DatabaseConnectionString { get; set; }
public string DatabaseTablePrefix { get; set; }
public IEnumerable<string> EnabledFeatures { get; set; }
public string Recipe { get; set; }
}
}

View File

@@ -1,14 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Web;
using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData;
using Orchard.Core.Common.Models;
using Orchard.Core.Common.Settings;
using Orchard.Core.Contents.Extensions;
using Orchard.Core.Navigation.Models;
using Orchard.Core.Routable.Models;
using Orchard.Core.Settings.Descriptor.Records;
using Orchard.Core.Settings.Models;
using Orchard.Data;
@@ -16,13 +11,13 @@ using Orchard.Data.Migration.Interpreters;
using Orchard.Data.Migration.Schema;
using Orchard.Environment;
using Orchard.Environment.Configuration;
using Orchard.Environment.Extensions;
using Orchard.Environment.ShellBuilders;
using Orchard.Environment.Descriptor;
using Orchard.Environment.Descriptor.Models;
using Orchard.Indexing;
using Orchard.Localization;
using Orchard.Localization.Services;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
using Orchard.Reports.Services;
using Orchard.Security;
using Orchard.Settings;
@@ -30,8 +25,6 @@ using Orchard.Environment.State;
using Orchard.Data.Migration;
using Orchard.Themes.Services;
using Orchard.Utility.Extensions;
using Orchard.Widgets.Models;
using Orchard.Widgets;
namespace Orchard.Setup.Services {
public class SetupService : ISetupService {
@@ -41,6 +34,8 @@ namespace Orchard.Setup.Services {
private readonly IShellContainerFactory _shellContainerFactory;
private readonly ICompositionStrategy _compositionStrategy;
private readonly IProcessingEngine _processingEngine;
private readonly IRecipeHarvester _recipeHarvester;
private readonly IEnumerable<Recipe> _recipes;
public SetupService(
ShellSettings shellSettings,
@@ -48,13 +43,16 @@ namespace Orchard.Setup.Services {
IShellSettingsManager shellSettingsManager,
IShellContainerFactory shellContainerFactory,
ICompositionStrategy compositionStrategy,
IProcessingEngine processingEngine) {
IProcessingEngine processingEngine,
IRecipeHarvester recipeHarvester) {
_shellSettings = shellSettings;
_orchardHost = orchardHost;
_shellSettingsManager = shellSettingsManager;
_shellContainerFactory = shellContainerFactory;
_compositionStrategy = compositionStrategy;
_processingEngine = processingEngine;
_recipeHarvester = recipeHarvester;
_recipes = _recipeHarvester.HarvestRecipes("Orchard.Setup");
T = NullLocalizer.Instance;
}
@@ -64,54 +62,23 @@ namespace Orchard.Setup.Services {
return _shellSettings;
}
public void Setup(SetupContext context) {
public IEnumerable<Recipe> Recipes() {
return _recipes;
}
public string Setup(SetupContext context) {
string executionId = null;
// The vanilla Orchard distibution has the following features enabled.
if (context.EnabledFeatures == null || context.EnabledFeatures.Count() == 0) {
string[] hardcoded = {
// Framework
"Orchard.Framework",
// Core
"Common",
"Containers",
"Contents",
"Dashboard",
"Feeds",
"HomePage",
"Navigation",
"Reports",
"Routable",
"Scheduling",
"Settings",
"Shapes",
// Other
"Orchard.PublishLater", // todo: (sebros) remove
"Orchard.Blogs",
"Orchard.Comments",
"Orchard.ContentTypes",
"Orchard.jQuery",
"Orchard.Lists",
"Orchard.Media",
"Common", "Containers", "Contents", "Dashboard", "Feeds", "HomePage", "Navigation", "Reports", "Routable", "Scheduling", "Settings", "Shapes",
// Modules
"Orchard.Pages", "Orchard.Themes", "Orchard.Users", "Orchard.Roles", "Orchard.Modules",
"PackagingServices", "Orchard.Packaging", "Gallery", "Orchard.Recipes"
"Orchard.MediaPicker",
"Orchard.Modules",
"Orchard.Pages",
"Orchard.Roles",
"Orchard.Tags",
"Orchard.Themes",
"Orchard.Users",
"Orchard.Scripting",
"Orchard.Scripting.Lightweight",
"Orchard.Widgets",
"TinyMce",
// Gallery/Packaging
"PackagingServices",
"Orchard.Packaging",
"Gallery",
// Themes
"TheThemeMachine",
};
context.EnabledFeatures = hardcoded;
@@ -168,7 +135,7 @@ namespace Orchard.Setup.Services {
var dataMigrationManager = environment.Resolve<IDataMigrationManager>();
dataMigrationManager.Update("Settings");
foreach ( var feature in context.EnabledFeatures ) {
foreach (var feature in context.EnabledFeatures) {
dataMigrationManager.Update(feature);
}
@@ -183,7 +150,6 @@ namespace Orchard.Setup.Services {
while ( _processingEngine.AreTasksPending() )
_processingEngine.ExecuteNextTask();
// creating a standalone environment.
// in theory this environment can be used to resolve any normal components by interface, and those
// components will exist entirely in isolation - no crossover between the safemode container currently in effect
@@ -192,7 +158,7 @@ namespace Orchard.Setup.Services {
shellSettings.State = new TenantState("Running");
using (var environment = _orchardHost.CreateStandaloneEnvironment(shellSettings)) {
try {
CreateTenantData(context, environment);
executionId = CreateTenantData(context, environment);
}
catch {
environment.Resolve<ITransactionManager>().Cancel();
@@ -201,9 +167,12 @@ namespace Orchard.Setup.Services {
}
_shellSettingsManager.SaveSettings(shellSettings);
return executionId;
}
private void CreateTenantData(SetupContext context, IWorkContextScope environment) {
private string CreateTenantData(SetupContext context, IWorkContextScope environment) {
string executionId = null;
// create superuser
var membershipService = environment.Resolve<IMembershipService>();
var user =
@@ -221,7 +190,6 @@ namespace Orchard.Setup.Services {
siteSettings.Record.SiteSalt = Guid.NewGuid().ToString("N");
siteSettings.Record.SiteName = context.SiteName;
siteSettings.Record.SuperUser = context.AdminUsername;
siteSettings.Record.PageTitleSeparator = " - ";
siteSettings.Record.SiteCulture = "en-US";
// set site theme
@@ -232,110 +200,15 @@ namespace Orchard.Setup.Services {
var cultureManager = environment.Resolve<ICultureManager>();
cultureManager.AddCulture("en-US");
var contentManager = environment.Resolve<IContentManager>();
// this needs to exit the standalone environment? rework this process entirely?
// simulate installation-time module activation events
//var hackInstallationGenerator = environment.Resolve<IHackInstallationGenerator>();
//hackInstallationGenerator.GenerateInstallEvents();
var contentDefinitionManager = environment.Resolve<IContentDefinitionManager>();
//todo: (heskew) pull these definitions (and initial content creation) out into a distribution configuration when we have that capability
contentDefinitionManager.AlterTypeDefinition("BlogPost", cfg => cfg
.WithPart("CommentsPart")
.WithPart("TagsPart")
.WithPart("LocalizationPart")
.Draftable()
.Indexed()
);
contentDefinitionManager.AlterTypeDefinition("Page", cfg => cfg
.WithPart("TagsPart")
.WithPart("LocalizationPart")
.Draftable()
.Indexed()
);
contentDefinitionManager.AlterPartDefinition("BodyPart", cfg => cfg
.WithSetting("BodyPartSettings.FlavorDefault", BodyPartSettings.FlavorDefaultDefault));
// If "Orchard.Widgets" is enabled, setup default layers and widgets
var extensionManager = environment.Resolve<IExtensionManager>();
var shellDescriptor = environment.Resolve<ShellDescriptor>();
if (extensionManager.EnabledFeatures(shellDescriptor).Where(d => d.Id == "Orchard.Widgets").Any()) {
// Create default layers
var layerInitializer = environment.Resolve<IDefaultLayersInitializer>();
layerInitializer.CreateDefaultLayers();
// add a layer for the homepage
var homepageLayer = contentManager.Create("Layer", VersionOptions.Draft);
homepageLayer.As<LayerPart>().Name = "TheHomepage";
homepageLayer.As<LayerPart>().LayerRule = "url \"~/\"";
contentManager.Publish(homepageLayer);
// and three more for the tripel...really need this elsewhere...
var tripelFirst = contentManager.Create("HtmlWidget", VersionOptions.Draft);
tripelFirst.As<WidgetPart>().LayerPart = homepageLayer.As<LayerPart>();
tripelFirst.As<WidgetPart>().Title = T("First Leader Aside").Text;
tripelFirst.As<WidgetPart>().Zone = "TripelFirst";
tripelFirst.As<WidgetPart>().Position = "5";
tripelFirst.As<BodyPart>().Text = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a nibh ut tortor dapibus vestibulum. Aliquam vel sem nibh. Suspendisse vel condimentum tellus.</p>";
contentManager.Publish(tripelFirst);
var tripelSecond = contentManager.Create("HtmlWidget", VersionOptions.Draft);
tripelSecond.As<WidgetPart>().LayerPart = homepageLayer.As<LayerPart>();
tripelSecond.As<WidgetPart>().Title = T("Second Leader Aside").Text;
tripelSecond.As<WidgetPart>().Zone = "TripelSecond";
tripelSecond.As<WidgetPart>().Position = "5";
tripelSecond.As<BodyPart>().Text = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a nibh ut tortor dapibus vestibulum. Aliquam vel sem nibh. Suspendisse vel condimentum tellus.</p>";
contentManager.Publish(tripelSecond);
var tripelThird = contentManager.Create("HtmlWidget", VersionOptions.Draft);
tripelThird.As<WidgetPart>().LayerPart = homepageLayer.As<LayerPart>();
tripelThird.As<WidgetPart>().Title = T("Third Leader Aside").Text;
tripelThird.As<WidgetPart>().Zone = "TripelThird";
tripelThird.As<WidgetPart>().Position = "5";
tripelThird.As<BodyPart>().Text = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a nibh ut tortor dapibus vestibulum. Aliquam vel sem nibh. Suspendisse vel condimentum tellus.</p>";
contentManager.Publish(tripelThird);
}
// create a welcome page that's promoted to the home page
var page = contentManager.Create("Page", VersionOptions.Draft);
page.As<RoutePart>().Title = T("Welcome to Orchard!").Text;
page.As<RoutePart>().Path = "welcome-to-orchard";
page.As<RoutePart>().Slug = "welcome-to-orchard";
page.As<RoutePart>().PromoteToHomePage = true;
page.As<BodyPart>().Text = T(
@"<p>You've successfully setup your Orchard Site and this is the homepage of your new site.
Here are a few things you can look at to get familiar with the application.
Once you feel confident you don't need this anymore, you can
<a href=""Admin/Contents/Edit/{0}"">remove it by going into editing mode</a>
and replacing it with whatever you want.</p>
<p>First things first - You'll probably want to <a href=""Admin/Settings"">manage your settings</a>
and configure Orchard to your liking. After that, you can head over to
<a href=""Admin/Themes"">manage themes to change or install new themes</a>
and really make it your own. Once you're happy with a look and feel, it's time for some content.
You can start creating new custom content types or start from the built-in ones by
<a href=""Admin/Contents/Create/Page"">adding a page</a>, <a href=""Admin/Blogs/Create"">creating a blog</a>
or <a href=""Admin/Navigation"">managing your menus.</a></p>
<p>Finally, Orchard has been designed to be extended. It comes with a few built-in
modules such as pages and blogs or themes. If you're looking to add additional functionality,
you can do so by creating your own module or by installing one that somebody else built.
Modules are created by other users of Orchard just like you so if you feel up to it,
<a href=""http://orchardproject.net/contribution"">please consider participating</a>.</p>
<p>Thanks for using Orchard The Orchard Team </p>", page.Id).Text;
contentManager.Publish(page);
// add a menu item for the shiny new home page
var menuItem = contentManager.Create("MenuItem");
menuItem.As<MenuPart>().MenuPosition = "1";
menuItem.As<MenuPart>().MenuText = T("Home").ToString();
menuItem.As<MenuPart>().OnMainMenu = true;
menuItem.As<MenuItemPart>().Url = "";
var recipeManager = environment.Resolve<IRecipeManager>();
executionId = recipeManager.Execute(Recipes().Where(r => r.Name == context.Recipe).FirstOrDefault());
//null check: temporary fix for running setup in command line
if (HttpContext.Current != null) {
authenticationService.SignIn(user, true);
}
return executionId;
}
}
}

View File

@@ -27,6 +27,7 @@ using Orchard.Mvc.ViewEngines;
using Orchard.Mvc.ViewEngines.Razor;
using Orchard.Mvc.ViewEngines.ThemeAwareness;
using Orchard.Mvc.ViewEngines.WebForms;
using Orchard.Recipes.Services;
using Orchard.Settings;
using Orchard.Themes;
using Orchard.UI.Notify;
@@ -75,6 +76,9 @@ namespace Orchard.Setup {
builder.RegisterType<DefaultDataMigrationInterpreter>().As<IDataMigrationInterpreter>().InstancePerLifetimeScope();
builder.RegisterType<DataMigrationManager>().As<IDataMigrationManager>().InstancePerLifetimeScope();
builder.RegisterType<RecipeHarvester>().As<IRecipeHarvester>().InstancePerLifetimeScope();
builder.RegisterType<RecipeParser>().As<IRecipeParser>().InstancePerLifetimeScope();
// in progress - adding services for display/shape support in setup
builder.RegisterType<DisplayHelperFactory>().As<IDisplayHelperFactory>();
builder.RegisterType<DefaultDisplayManager>().As<IDisplayManager>();

View File

@@ -1,3 +1,5 @@
using System.Collections.Generic;
using Orchard.Recipes.Models;
using Orchard.Setup.Annotations;
namespace Orchard.Setup.ViewModels {
@@ -19,5 +21,9 @@ namespace Orchard.Setup.ViewModels {
public string DatabaseConnectionString { get; set; }
public string DatabaseTablePrefix { get; set; }
public bool DatabaseIsPreconfigured { get; set; }
public IEnumerable<Recipe> Recipes { get; set; }
public string Recipe { get; set; }
public string RecipeDescription { get; set; }
}
}

View File

@@ -1,5 +1,6 @@
@model Orchard.Setup.ViewModels.SetupViewModel
@{
Script.Require("jquery").AtFoot();
Script.Include("setup.js").AtFoot();
}
<h1>@Html.TitleForPage(T("Get Started").ToString())</h1>
@@ -50,11 +51,25 @@ if (!Model.DatabaseIsPreconfigured) {
</div>
</fieldset>
}
<fieldset>
<legend>@T("Choose an Orchard Recipe (Optional)")</legend>
<div>@T("Orchard Recipes allow you to setup your site with additional pre-configured options, features and settings out of the box")</div>
<div>
<select id="@Html.FieldIdFor(m => m.Recipe)" name="@Html.FieldNameFor(m => m.Recipe)" class="recipe">
@Html.SelectOption("", false, T("Choose").ToString(), new { recipedescription = "" })
@Html.SelectOption("", false, T("-------------------").ToString(), new { recipedescription = "" })
@foreach(var recipe in Model.Recipes) {
@Html.SelectOption(Model.Recipe, recipe.Name, recipe.Name, new { recipedescription = recipe.Description })
}
</select>
</div>
<div id="recipedescription">@Model.RecipeDescription</div>
</fieldset>
<div id="throbber">
<div class="curtain"></div>
<div class="curtain-content">
<div>
<h1>@T("Configuring Orchard...")</h1>
<h1>@T("Cooking Orchard Recipe ...")</h1>
<p>
<img src="@Href("../../content/synchronizing.gif")" alt="" />
</p>

View File

@@ -0,0 +1,53 @@
using System;
using Orchard.Commands;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Security;
using Orchard.Settings;
using Orchard.Widgets.Models;
namespace Orchard.Widgets.Commands {
public class LayerCommands : DefaultOrchardCommandHandler {
private readonly IContentManager _contentManager;
private readonly ISiteService _siteService;
private readonly IMembershipService _membershipService;
public LayerCommands(IContentManager contentManager, ISiteService siteService, IMembershipService membershipService) {
_contentManager = contentManager;
_siteService = siteService;
_membershipService = membershipService;
}
[OrchardSwitch]
public string Name { get; set; }
[OrchardSwitch]
public string LayerRule { get; set; }
[OrchardSwitch]
public string Description { get; set; }
[OrchardSwitch]
public string Owner { get; set; }
[CommandName("layer create")]
[CommandHelp("layer create /Name:<name> /LayerRule:<rule> [/Description:<description>] [/Owner:<owner>]\r\n\t" + "Creates a new layer")]
[OrchardSwitches("Name,LayerRule,Description,Owner")]
public void Create() {
IContent layer = _contentManager.Create<LayerPart>("Layer", t => {
t.Record.Name = Name;
t.Record.LayerRule = LayerRule;
t.Record.Description = Description ?? String.Empty;
});
_contentManager.Publish(layer.ContentItem);
if (String.IsNullOrEmpty(Owner)) {
Owner = _siteService.GetSiteSettings().SuperUser;
}
var owner = _membershipService.GetUser(Owner);
layer.As<ICommonPart>().Owner = owner;
Context.Output.WriteLine(T("Layer created successfully.").Text);
}
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.Linq;
using Orchard.Commands;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Common.Models;
using Orchard.Security;
using Orchard.Settings;
using Orchard.Widgets.Models;
using Orchard.Widgets.Services;
namespace Orchard.Widgets.Commands {
public class WidgetCommands : DefaultOrchardCommandHandler {
private readonly IWidgetsService _widgetsService;
private readonly ISiteService _siteService;
private readonly IMembershipService _membershipService;
private const string LoremIpsum = "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a nibh ut tortor dapibus vestibulum. Aliquam vel sem nibh. Suspendisse vel condimentum tellus.</p>";
public WidgetCommands(IWidgetsService widgetsService, ISiteService siteService, IMembershipService membershipService) {
_widgetsService = widgetsService;
_siteService = siteService;
_membershipService = membershipService;
}
[OrchardSwitch]
public string Type { get; set; }
[OrchardSwitch]
public string Title { get; set; }
[OrchardSwitch]
public string Zone { get; set; }
[OrchardSwitch]
public string Position { get; set; }
[OrchardSwitch]
public string Layer { get; set; }
[OrchardSwitch]
public string Owner { get; set; }
[OrchardSwitch]
public string Text { get; set; }
[OrchardSwitch]
public bool UseLoremIpsumText { get; set; }
[OrchardSwitch]
public bool Publish { get; set; }
[CommandName("widget create")]
[CommandHelp("widget create /Type:<type> /Title:<title> /Zone:<zone> /Position:<position> /Layer:<layer> [/Owner:<owner>] [/Text:<text>] [/UseLoremIpsumText:true|false]\r\n\t" + "Creates a new html widget")]
[OrchardSwitches("Type,Title,Zone,Position,Layer,Owner,Text,UseLoremIpsumText")]
public void Create() {
var layer = GetLayer(Layer);
if (layer == null) {
throw new OrchardException(T("Creating widget failed : layer {0} was not found.", Layer));
}
var widget = _widgetsService.CreateWidget(layer.ContentItem.Id, Type, T(Title).Text, Position, Zone);
var text = String.Empty;
if (UseLoremIpsumText) {
text = T(LoremIpsum).Text;
}
else {
if (!String.IsNullOrEmpty(Text)) {
text = Text;
}
}
widget.As<BodyPart>().Text = text;
if (String.IsNullOrEmpty(Owner)) {
Owner = _siteService.GetSiteSettings().SuperUser;
}
var owner = _membershipService.GetUser(Owner);
widget.As<ICommonPart>().Owner = owner;
Context.Output.WriteLine(T("Widget created successfully.").Text);
}
private LayerPart GetLayer(string layer) {
var layers = _widgetsService.GetLayers();
return layers.FirstOrDefault(layerPart => String.Equals(layerPart.Name, layer, StringComparison.OrdinalIgnoreCase));
}
}
}

View File

@@ -45,6 +45,8 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AdminMenu.cs" />
<Compile Include="Commands\LayerCommands.cs" />
<Compile Include="Commands\WidgetCommands.cs" />
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Drivers\LayerPartDriver.cs" />
<Compile Include="Drivers\WidgetPartDriver.cs" />

View File

@@ -128,6 +128,12 @@ input[type="password"] {
width:98%;
}
select {
padding:3px;
border:1px solid #bdbcbc;
width:100%;
}
button.primaryAction, .button.primaryAction, .button.primaryAction:link, .button.primaryAction:visited {
background:#4687ad;
border:1px solid #405f71;