diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..9d4bf1c
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,28 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{cs,svc,asax,aspx,cshtml,csproj,sln}]
+charset = utf-8-bom
+
+[*.{config,csproj,nuspec,xml}]
+indent_size = 2
+insert_final_newline = false
+
+[*.sln]
+indent_style = tab
+
+[*.md]
+trim_trailing_whitespace = false
+
+# Ignore these files
+[{LICENSE,build/database/*,src/alloy/App_Data/**,src/alloy/modulesbin/**,src/alloy/obj/**,src/alloy/Static/**}]
+indent_style = unset
+indent_size = unset
+trim_trailing_whitespace = unset
+insert_final_newline = unset
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..0d84e23
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,42 @@
+## Set default behavior to automatically normalize line endings.
+* text=auto
+
+## Visual Studio and .NET text files
+*.ascx text
+*.aspx text
+*.cmd text
+*.config text
+*.cs text diff=csharp
+*.cshtml text
+*.csproj text eol=crlf
+*.master text
+*.msbuild text
+*.ps1 text
+*.psm1 text
+*.sln text eol=crlf
+*.sql text
+*.targets text
+*.transform text
+*.xml text
+
+## Common web text files
+*.css text
+*.htm text
+*.html text
+*.js text
+*.less text
+*.md text
+
+## Bash and Linux text files
+*.sh text eol=lf
+
+## Visual Studio and .NET binary files
+*.dll binary
+*.snk binary
+
+## Common web binary files
+*.gif binary
+*.ico binary
+*.jpeg binary
+*.jpg binary
+*.png binary
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f7e2dbb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,30 @@
+## Repository specific files
+
+App_Code/
+App_Data/
+
+License.config
+
+## Ignore Visual Studio temporary files and build results.
+
+# User-specific files
+*.user
+
+# Build results
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+
+# NuGet Packages
+[Pp]ackages/
+
+.idea/
+.vscode
+
+node_modules/
+
+*.nupkg
+episerver-labs-block-enhancements.zip
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..28f12c7
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+# EPiServer Labs - Block Enhancements
diff --git a/build.cmd b/build.cmd
new file mode 100644
index 0000000..0ae1af6
--- /dev/null
+++ b/build.cmd
@@ -0,0 +1 @@
+CALL msbuild
diff --git a/build/Templates.targets b/build/Templates.targets
new file mode 100644
index 0000000..99352b9
--- /dev/null
+++ b/build/Templates.targets
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+ CopyTemplatesSourceFiles;ReplaceNamespacesSourceFiles;$(BuildDependsOn)
+ $(CoreCleanDependsOn);CleanTemplatesSourceFiles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/database/Alloy.mdf b/build/database/Alloy.mdf
new file mode 100644
index 0000000..7bc638f
Binary files /dev/null and b/build/database/Alloy.mdf differ
diff --git a/build/database/DefaultSiteContent.episerverdata b/build/database/DefaultSiteContent.episerverdata
new file mode 100644
index 0000000..b360d87
Binary files /dev/null and b/build/database/DefaultSiteContent.episerverdata differ
diff --git a/build/tools/EPiServer.Build.Tasks.dll b/build/tools/EPiServer.Build.Tasks.dll
new file mode 100644
index 0000000..9d0f6ef
Binary files /dev/null and b/build/tools/EPiServer.Build.Tasks.dll differ
diff --git a/build/transformers/AlloyMvc.Views.web.config.transform b/build/transformers/AlloyMvc.Views.web.config.transform
new file mode 100644
index 0000000..ca9bf4f
--- /dev/null
+++ b/build/transformers/AlloyMvc.Views.web.config.transform
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/transformers/AlloyMvc.web.config.transform b/build/transformers/AlloyMvc.web.config.transform
new file mode 100644
index 0000000..ebe787d
--- /dev/null
+++ b/build/transformers/AlloyMvc.web.config.transform
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/transformers/web.config.install.xdt b/build/transformers/web.config.install.xdt
new file mode 100644
index 0000000..c11102a
--- /dev/null
+++ b/build/transformers/web.config.install.xdt
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/episerver-labs-block-enhancements.sln b/episerver-labs-block-enhancements.sln
new file mode 100644
index 0000000..3de2e34
--- /dev/null
+++ b/episerver-labs-block-enhancements.sln
@@ -0,0 +1,54 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.28307.271
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "alloy", "src\alloy\Alloy.Mvc.Template.csproj", "{3D79ADE5-DADB-47D2-933D-E98F9773AFAD}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F856CAEC-B3F9-4320-B39D-E8F2EB8F6111}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{A1272902-1CD0-4507-9714-CB3AE96E78FD}"
+ ProjectSection(SolutionItems) = preProject
+ build\Templates.targets = build\Templates.targets
+ .editorconfig = .editorconfig
+ .gitattributes = .gitattributes
+ .gitignore = .gitignore
+ build.cmd = build.cmd
+ iisexpress.cmd = iisexpress.cmd
+ LICENSE = LICENSE
+ pack.cmd = pack.cmd
+ pack.ps1 = pack.ps1
+ README.md = README.md
+ setup.cmd = setup.cmd
+ site.cmd = site.cmd
+ version.cs = version.cs
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EPiServer.Labs.BlockEnhancements", "src\episerver-labs-block-enhancements\EPiServer.Labs.BlockEnhancements.csproj", "{7D3CB0B4-BF55-4577-ABF2-D83F8EA5D49F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3D79ADE5-DADB-47D2-933D-E98F9773AFAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3D79ADE5-DADB-47D2-933D-E98F9773AFAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3D79ADE5-DADB-47D2-933D-E98F9773AFAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3D79ADE5-DADB-47D2-933D-E98F9773AFAD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7D3CB0B4-BF55-4577-ABF2-D83F8EA5D49F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7D3CB0B4-BF55-4577-ABF2-D83F8EA5D49F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7D3CB0B4-BF55-4577-ABF2-D83F8EA5D49F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7D3CB0B4-BF55-4577-ABF2-D83F8EA5D49F}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {3D79ADE5-DADB-47D2-933D-E98F9773AFAD} = {F856CAEC-B3F9-4320-B39D-E8F2EB8F6111}
+ {7D3CB0B4-BF55-4577-ABF2-D83F8EA5D49F} = {F856CAEC-B3F9-4320-B39D-E8F2EB8F6111}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {31F10C96-F924-4A0A-9B12-36A09C66A0B3}
+ EndGlobalSection
+EndGlobal
diff --git a/iisexpress.cmd b/iisexpress.cmd
new file mode 100644
index 0000000..c4d4328
--- /dev/null
+++ b/iisexpress.cmd
@@ -0,0 +1 @@
+CALL "C:\Program Files\IIS Express\iisexpress.exe" /path:%~dp0src\alloy
diff --git a/pack.cmd b/pack.cmd
new file mode 100644
index 0000000..6a00f3d
--- /dev/null
+++ b/pack.cmd
@@ -0,0 +1 @@
+powershell ./pack.ps1
\ No newline at end of file
diff --git a/pack.ps1 b/pack.ps1
new file mode 100644
index 0000000..db9b2d1
--- /dev/null
+++ b/pack.ps1
@@ -0,0 +1,25 @@
+$defaultVersion="1.0.0"
+function ZipCurrentModule
+{
+ Param ([String]$moduleName)
+ Robocopy.exe $defaultVersion\ $version\ /S
+ ((Get-Content -Path module.config -Raw) -Replace $defaultVersion, $version ) | Set-Content -Path module.config
+ 7z a "$moduleName.zip" $version Views module.config
+ git checkout module.config
+ Remove-Item $version -Force -Recurse
+}
+
+msbuild /p:Configuration=Release
+npm run build --prefix ./src/ui
+
+$fullVersion=[System.Reflection.Assembly]::LoadFrom("src\alloy\bin\EPiServer.Labs.BlockEnhancements.dll").GetName().Version
+$version="$($fullVersion.major).$($fullVersion.minor).$($fullVersion.build)"
+Write-Host "Creating nuget with $version version"
+
+Set-Location src\alloy\modules\_protected\episerver-labs-block-enhancements
+ZipCurrentModule -moduleName episerver-labs-block-enhancements
+
+Set-Location ..\..\..\
+nuget pack EPiServer.Labs.BlockEnhancements.nuspec -Version $version
+Set-Location ..\..\
+Move-Item src\alloy\EPiServer.Labs.BlockEnhancements.$version.nupkg EPiServer.Labs.BlockEnhancements.$version.nupkg -Force
diff --git a/setup.cmd b/setup.cmd
new file mode 100644
index 0000000..77267f5
--- /dev/null
+++ b/setup.cmd
@@ -0,0 +1,16 @@
+@ECHO OFF
+
+SET AlloyMVC=src\alloy
+
+IF EXIST %AlloyMVC%\App_Data (
+ ECHO Remove all files from the app data folder
+ DEL %AlloyMVC%\App_Data\*.* /F /Q || Exit /B 1
+) ELSE (
+ MKDIR %AlloyMVC%\App_Data || Exit /B 1
+)
+
+REM Copy the database files to the site.
+XCOPY /y/i build\Database\DefaultSiteContent.episerverdata %AlloyMVC%\App_Data\ || Exit /B 1
+XCOPY /y/i/k build\database\Alloy.mdf %AlloyMVC%\App_Data\ || Exit /B 1
+
+EXIT /B %ERRORLEVEL%
diff --git a/site.cmd b/site.cmd
new file mode 100644
index 0000000..7bd4836
--- /dev/null
+++ b/site.cmd
@@ -0,0 +1,2 @@
+CALL build.cmd
+CALL iisexpress.cmd
diff --git a/src/alloy/Alloy.Mvc.Template.csproj b/src/alloy/Alloy.Mvc.Template.csproj
new file mode 100644
index 0000000..ff722e1
--- /dev/null
+++ b/src/alloy/Alloy.Mvc.Template.csproj
@@ -0,0 +1,564 @@
+
+
+
+
+ Debug
+ AnyCPU
+
+
+ 2.0
+ {3D79ADE5-DADB-47D2-933D-E98F9773AFAD}
+ {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}
+ Library
+ Properties
+ AlloyTemplates
+ AlloyMvcTemplates
+ v4.6.1
+ true
+
+
+ 4.0
+
+
+
+
+
+ ..\..\
+ true
+ SAK
+ SAK
+ SAK
+ SAK
+
+ true
+
+
+ true
+ full
+ false
+ bin\
+ DEBUG;TRACE
+ prompt
+ 4
+ true
+ false
+
+
+ pdbonly
+ true
+ bin\
+ TRACE
+ prompt
+ 4
+ true
+ false
+
+
+
+ ..\..\packages\Antlr.3.5.0.2\lib\Antlr3.Runtime.dll
+ True
+
+
+ ..\..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll
+ True
+
+
+ ..\..\packages\Castle.Windsor.4.1.0\lib\net45\Castle.Windsor.dll
+ True
+
+
+ ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.dll
+ True
+
+
+ ..\..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.SqlServer.dll
+ True
+
+
+ ..\..\packages\EPiServer.CMS.Core.11.9.2\lib\net461\EPiServer.dll
+
+
+ ..\..\packages\EPiServer.Framework.11.9.2\lib\net461\EPiServer.ApplicationModules.dll
+
+
+ ..\..\packages\EPiServer.CMS.AspNet.11.9.2\lib\net461\EPiServer.Cms.AspNet.dll
+
+
+ ..\..\packages\EPiServer.CMS.UI.Core.11.5.3\lib\net461\EPiServer.Cms.Shell.UI.dll
+
+
+ ..\..\packages\EPiServer.CMS.TinyMce.2.6.5\lib\net461\EPiServer.Cms.TinyMce.dll
+
+
+ ..\..\packages\EPiServer.CMS.UI.AspNetIdentity.11.5.3\lib\net461\EPiServer.Cms.UI.AspNetIdentity.dll
+
+
+ ..\..\packages\EPiServer.CMS.AspNet.11.9.2\lib\net461\EPiServer.Configuration.dll
+
+
+ ..\..\packages\EPiServer.Framework.11.9.2\lib\net461\EPiServer.Data.dll
+
+
+ ..\..\packages\EPiServer.Framework.11.9.2\lib\net461\EPiServer.Data.Cache.dll
+
+
+ ..\..\packages\EPiServer.CMS.Core.11.9.2\lib\net461\EPiServer.Enterprise.dll
+
+
+ ..\..\packages\EPiServer.Framework.11.9.2\lib\net461\EPiServer.Events.dll
+
+
+ ..\..\packages\EPiServer.Framework.11.9.2\lib\net461\EPiServer.Framework.dll
+
+
+ ..\..\packages\EPiServer.Framework.AspNet.11.9.2\lib\net461\EPiServer.Framework.AspNet.dll
+
+
+ ..\..\packages\EPiServer.CMS.AspNet.11.9.2\lib\net461\EPiServer.ImageLibrary.dll
+
+
+ ..\..\packages\EPiServer.Framework.11.9.2\lib\net461\EPiServer.Licensing.dll
+
+
+ ..\..\packages\EPiServer.CMS.Core.11.9.2\lib\net461\EPiServer.LinkAnalyzer.dll
+
+
+ ..\..\packages\EPiServer.Search.Cms.9.0.1\lib\net461\EPiServer.Search.Cms.dll
+ True
+
+
+ ..\..\packages\EPiServer.Search.9.0.1\lib\net461\EPiServer.Search.IndexingService.dll
+ True
+
+
+ ..\..\packages\EPiServer.ServiceLocation.StructureMap.2.0.1\lib\net461\EPiServer.ServiceLocation.StructureMap.dll
+
+
+ ..\..\packages\EPiServer.CMS.UI.Core.11.5.3\lib\net461\EPiServer.Shell.dll
+
+
+ ..\..\packages\EPiServer.CMS.UI.Core.11.5.3\lib\net461\EPiServer.Shell.UI.dll
+
+
+ ..\..\packages\EPiServer.CMS.UI.Core.11.5.3\lib\net461\EPiServer.UI.dll
+
+
+ ..\..\packages\EPiServer.CMS.AspNet.11.9.2\lib\net461\EPiServer.Web.WebControls.dll
+
+
+ ..\..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll
+ True
+
+
+ ..\..\packages\Lucene.Net.3.0.3\lib\NET40\Lucene.Net.dll
+ True
+
+
+ ..\..\packages\Microsoft.AspNet.Identity.Core.2.2.1\lib\net45\Microsoft.AspNet.Identity.Core.dll
+ True
+
+
+ ..\..\packages\Microsoft.AspNet.Identity.EntityFramework.2.2.1\lib\net45\Microsoft.AspNet.Identity.EntityFramework.dll
+ True
+
+
+ ..\..\packages\Microsoft.AspNet.Identity.Owin.2.2.1\lib\net45\Microsoft.AspNet.Identity.Owin.dll
+ True
+
+
+
+ ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll
+ True
+
+
+ ..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll
+ True
+
+
+ ..\..\packages\Microsoft.Owin.Security.3.0.0\lib\net45\Microsoft.Owin.Security.dll
+ True
+
+
+ ..\..\packages\Microsoft.Owin.Security.Cookies.3.0.0\lib\net45\Microsoft.Owin.Security.Cookies.dll
+ True
+
+
+ ..\..\packages\Microsoft.Owin.Security.OAuth.3.0.0\lib\net45\Microsoft.Owin.Security.OAuth.dll
+ True
+
+
+ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll
+ True
+
+
+ ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+ ..\..\packages\Owin.1.0\lib\net40\Owin.dll
+ True
+
+
+ ..\..\packages\StructureMap.4.5.2\lib\net45\StructureMap.dll
+
+
+ ..\..\packages\structuremap-signed.3.1.9.463\lib\net40\StructureMap.Net4.dll
+
+
+ ..\..\packages\structuremap.web.4.0.0.315\lib\net40\StructureMap.Web.dll
+
+
+ ..\..\packages\System.ComponentModel.Annotations.4.4.0\lib\net461\System.ComponentModel.Annotations.dll
+
+
+
+
+ ..\..\packages\System.Data.SqlClient.4.4.0\lib\net461\System.Data.SqlClient.dll
+
+
+
+
+
+ ..\..\packages\System.Security.AccessControl.4.4.0\lib\net461\System.Security.AccessControl.dll
+
+
+ ..\..\packages\System.Security.Cryptography.Xml.4.4.2\lib\net461\System.Security.Cryptography.Xml.dll
+
+
+ ..\..\packages\System.Security.Permissions.4.4.0\lib\net461\System.Security.Permissions.dll
+
+
+ ..\..\packages\System.Security.Principal.Windows.4.4.0\lib\net461\System.Security.Principal.Windows.dll
+
+
+ ..\..\packages\System.Threading.AccessControl.4.4.0\lib\net461\System.Threading.AccessControl.dll
+
+
+ ..\..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll
+ True
+
+
+
+ ..\..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll
+
+
+
+
+
+
+
+
+
+
+ ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll
+ True
+
+
+ ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll
+ True
+
+
+ ..\..\packages\Microsoft.AspNet.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.dll
+ True
+
+
+ ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll
+ True
+
+
+
+
+
+ ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll
+ True
+
+
+ ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll
+ True
+
+
+ ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll
+ True
+
+
+
+
+
+
+
+ ..\..\packages\WebGrease.1.6.0\lib\WebGrease.dll
+ True
+
+
+
+ 10.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Global.asax
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Code
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+ Designer
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+ Designer
+
+
+ Designer
+
+
+ Designer
+
+
+ web.config
+
+
+ web.config
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+ {7D3CB0B4-BF55-4577-ABF2-D83F8EA5D49F}
+ episerver-labs-block-enhancements
+
+
+
\ No newline at end of file
diff --git a/src/alloy/Business/AdministratorRegistrationPage.cs b/src/alloy/Business/AdministratorRegistrationPage.cs
new file mode 100644
index 0000000..e9bab95
--- /dev/null
+++ b/src/alloy/Business/AdministratorRegistrationPage.cs
@@ -0,0 +1,82 @@
+using EPiServer.ServiceLocation;
+using EPiServer.Shell.Security;
+using Owin;
+using System;
+using System.Web;
+using System.Web.Mvc;
+using System.Web.Routing;
+
+namespace AlloyTemplates
+{
+ public static class AdministratorRegistrationPage
+ {
+ private static Func _isLocalRequest = () => false;
+
+ private static Lazy _isAnyUserRegistered = new Lazy(() => false);
+
+ private static bool? _isEnabled = null;
+
+ public static bool IsEnabled
+ {
+ get
+ {
+ if (_isEnabled.HasValue)
+ {
+ return _isEnabled.Value;
+ }
+
+ var showUserRegistration = _isLocalRequest() && !_isAnyUserRegistered.Value;
+ if (!showUserRegistration)
+ {
+ _isEnabled = false;
+ }
+
+ return showUserRegistration;
+ }
+ set
+ {
+ _isEnabled = value;
+ }
+ }
+
+ public static void UseAdministratorRegistrationPage(this IAppBuilder app, Func isLocalRequest)
+ {
+ _isLocalRequest = isLocalRequest;
+ _isAnyUserRegistered = new Lazy(IsAnyUserRegistered);
+ GlobalFilters.Filters.Add(new RegistrationActionFilterAttribute());
+ if (isLocalRequest())
+ {
+ AddRoute();
+ }
+ }
+
+ private static bool IsAnyUserRegistered()
+ {
+ var provider = ServiceLocator.Current.GetInstance();
+ int totalUsers = 0;
+ var res = provider.GetAllUsers(0, 1, out totalUsers);
+ return totalUsers > 0;
+ }
+
+ public class RegistrationActionFilterAttribute : ActionFilterAttribute
+ {
+ public override void OnActionExecuting(ActionExecutingContext context)
+ {
+ var registerUrl = VirtualPathUtility.ToAbsolute("~/Register");
+ if (IsEnabled && !context.RequestContext.HttpContext.Request.Path.StartsWith(registerUrl))
+ {
+ context.Result = new RedirectResult(registerUrl);
+ }
+ }
+ }
+
+ static void AddRoute()
+ {
+ var routeData = new RouteValueDictionary();
+ routeData.Add("Controller", "Register");
+ routeData.Add("action", "Index");
+ routeData.Add("id", " UrlParameter.Optional");
+ RouteTable.Routes.Add("Register", new Route("{controller}/{action}/{id}", routeData, new MvcRouteHandler()) { RouteExistingFiles = false });
+ }
+ }
+}
diff --git a/src/alloy/Business/Channels/DisplayResolutionBase.cs b/src/alloy/Business/Channels/DisplayResolutionBase.cs
new file mode 100644
index 0000000..996be89
--- /dev/null
+++ b/src/alloy/Business/Channels/DisplayResolutionBase.cs
@@ -0,0 +1,54 @@
+using EPiServer.Framework.Localization;
+using EPiServer.ServiceLocation;
+using EPiServer.Web;
+
+namespace AlloyTemplates.Business.Channels
+{
+ ///
+ /// Base class for all resolution definitions
+ ///
+ public abstract class DisplayResolutionBase : IDisplayResolution
+ {
+ private Injected LocalizationService { get; set; }
+
+ protected DisplayResolutionBase(string name, int width, int height)
+ {
+ Id = GetType().FullName;
+ Name = Translate(name);
+ Width = width;
+ Height = height;
+ }
+
+ ///
+ /// Gets the unique ID for this resolution
+ ///
+ public string Id { get; protected set; }
+
+ ///
+ /// Gets the name of resolution
+ ///
+ public string Name { get; protected set; }
+
+ ///
+ /// Gets the resolution width in pixels
+ ///
+ public int Width { get; protected set; }
+
+ ///
+ /// Gets the resolution height in pixels
+ ///
+ public int Height { get; protected set; }
+
+ private string Translate(string resurceKey)
+ {
+ string value;
+
+ if(!LocalizationService.Service.TryGetString(resurceKey, out value))
+ {
+ value = resurceKey;
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/alloy/Business/Channels/DisplayResolutions.cs b/src/alloy/Business/Channels/DisplayResolutions.cs
new file mode 100644
index 0000000..29c2f12
--- /dev/null
+++ b/src/alloy/Business/Channels/DisplayResolutions.cs
@@ -0,0 +1,42 @@
+namespace AlloyTemplates.Business.Channels
+{
+ ///
+ /// Defines resolution for desktop displays
+ ///
+ public class StandardResolution : DisplayResolutionBase
+ {
+ public StandardResolution() : base("/resolutions/standard", 1366, 768)
+ {
+ }
+ }
+
+ ///
+ /// Defines resolution for a horizontal iPad
+ ///
+ public class IpadHorizontalResolution : DisplayResolutionBase
+ {
+ public IpadHorizontalResolution() : base("/resolutions/ipadhorizontal", 1024, 768)
+ {
+ }
+ }
+
+ ///
+ /// Defines resolution for a vertical iPhone 5s
+ ///
+ public class IphoneVerticalResolution : DisplayResolutionBase
+ {
+ public IphoneVerticalResolution() : base("/resolutions/iphonevertical", 320, 568)
+ {
+ }
+ }
+
+ ///
+ /// Defines resolution for a vertical Android handheld device
+ ///
+ public class AndroidVerticalResolution : DisplayResolutionBase
+ {
+ public AndroidVerticalResolution() : base("/resolutions/androidvertical", 480, 800)
+ {
+ }
+ }
+}
diff --git a/src/alloy/Business/Channels/MobileChannel.cs b/src/alloy/Business/Channels/MobileChannel.cs
new file mode 100644
index 0000000..79b1879
--- /dev/null
+++ b/src/alloy/Business/Channels/MobileChannel.cs
@@ -0,0 +1,35 @@
+using System.Web;
+using System.Web.WebPages;
+using EPiServer.Web;
+
+namespace AlloyTemplates.Business.Channels
+{
+ //
+ //Defines the 'Mobile' content channel
+ //
+ public class MobileChannel : DisplayChannel
+ {
+ public const string Name = "mobile";
+
+ public override string ChannelName
+ {
+ get
+ {
+ return Name;
+ }
+ }
+
+ public override string ResolutionId
+ {
+ get
+ {
+ return typeof(IphoneVerticalResolution).FullName;
+ }
+ }
+
+ public override bool IsActive(HttpContextBase context)
+ {
+ return context.GetOverriddenBrowser().IsMobileDevice;
+ }
+ }
+}
diff --git a/src/alloy/Business/Channels/WebChannel.cs b/src/alloy/Business/Channels/WebChannel.cs
new file mode 100644
index 0000000..2237f35
--- /dev/null
+++ b/src/alloy/Business/Channels/WebChannel.cs
@@ -0,0 +1,24 @@
+using System.Web;
+using EPiServer.Web;
+
+namespace AlloyTemplates.Business.Channels
+{
+ ///
+ /// Defines the 'Web' content channel
+ ///
+ public class WebChannel : DisplayChannel
+ {
+ public override string ChannelName
+ {
+ get
+ {
+ return "web";
+ }
+ }
+
+ public override bool IsActive(HttpContextBase context)
+ {
+ return !context.Request.Browser.IsMobileDevice;
+ }
+ }
+}
diff --git a/src/alloy/Business/ContentExtensions.cs b/src/alloy/Business/ContentExtensions.cs
new file mode 100644
index 0000000..4784bc4
--- /dev/null
+++ b/src/alloy/Business/ContentExtensions.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using System.Linq;
+using EPiServer.Core;
+using EPiServer.Filters;
+using EPiServer.Framework.Web;
+using EPiServer.ServiceLocation;
+using EPiServer;
+
+namespace AlloyTemplates.Business
+{
+ ///
+ /// Extension methods for content
+ ///
+ public static class ContentExtensions
+ {
+ ///
+ /// Filters content which should not be visible to the user.
+ ///
+ public static IEnumerable FilterForDisplay(this IEnumerable contents, bool requirePageTemplate = false, bool requireVisibleInMenu = false)
+ where T : IContent
+ {
+ var accessFilter = new FilterAccess();
+ var publishedFilter = new FilterPublished();
+ contents = contents.Where(x => !publishedFilter.ShouldFilter(x) && !accessFilter.ShouldFilter(x));
+ if (requirePageTemplate)
+ {
+ var templateFilter = ServiceLocator.Current.GetInstance();
+ templateFilter.TemplateTypeCategories = TemplateTypeCategories.Page;
+ contents = contents.Where(x => !templateFilter.ShouldFilter(x));
+ }
+ if (requireVisibleInMenu)
+ {
+ contents = contents.Where(x => VisibleInMenu(x));
+ }
+ return contents;
+ }
+
+ private static bool VisibleInMenu(IContent content)
+ {
+ var page = content as PageData;
+ if (page == null)
+ {
+ return true;
+ }
+ return page.VisibleInMenu;
+ }
+ }
+}
diff --git a/src/alloy/Business/ContentLocator.cs b/src/alloy/Business/ContentLocator.cs
new file mode 100644
index 0000000..2eed3a9
--- /dev/null
+++ b/src/alloy/Business/ContentLocator.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using AlloyTemplates.Models.Pages;
+using EPiServer;
+using EPiServer.Core;
+using EPiServer.Filters;
+using EPiServer.Shell.Configuration;
+using EPiServer.Web;
+
+namespace AlloyTemplates.Business
+{
+ public class ContentLocator
+ {
+ private readonly IContentLoader _contentLoader;
+ private readonly IContentProviderManager _providerManager;
+ private readonly IPageCriteriaQueryService _pageCriteriaQueryService;
+
+ public ContentLocator(IContentLoader contentLoader, IContentProviderManager providerManager, IPageCriteriaQueryService pageCriteriaQueryService)
+ {
+ _contentLoader = contentLoader;
+ _providerManager = providerManager;
+ _pageCriteriaQueryService = pageCriteriaQueryService;
+ }
+
+ public virtual IEnumerable GetAll(ContentReference rootLink)
+ where T : PageData
+ {
+ var children = _contentLoader.GetChildren(rootLink);
+ foreach (var child in children)
+ {
+ var childOfRequestedTyped = child as T;
+ if (childOfRequestedTyped != null)
+ {
+ yield return childOfRequestedTyped;
+ }
+ foreach (var descendant in GetAll(child.ContentLink))
+ {
+ yield return descendant;
+ }
+ }
+ }
+
+ ///
+ /// Returns pages of a specific page type
+ ///
+ ///
+ ///
+ /// ID of the page type to filter by
+ ///
+ public IEnumerable FindPagesByPageType(PageReference pageLink, bool recursive, int pageTypeId)
+ {
+ if (ContentReference.IsNullOrEmpty(pageLink))
+ {
+ throw new ArgumentNullException("pageLink", "No page link specified, unable to find pages");
+ }
+
+ var pages = recursive
+ ? FindPagesByPageTypeRecursively(pageLink, pageTypeId)
+ : _contentLoader.GetChildren(pageLink);
+
+ return pages;
+ }
+
+ // Type specified through page type ID
+ private IEnumerable FindPagesByPageTypeRecursively(PageReference pageLink, int pageTypeId)
+ {
+ var criteria = new PropertyCriteriaCollection
+ {
+ new PropertyCriteria
+ {
+ Name = "PageTypeID",
+ Type = PropertyDataType.PageType,
+ Condition = CompareCondition.Equal,
+ Value = pageTypeId.ToString(CultureInfo.InvariantCulture)
+ }
+ };
+
+ // Include content providers serving content beneath the page link specified for the search
+ if (_providerManager.ProviderMap.CustomProvidersExist)
+ {
+ var contentProvider = _providerManager.ProviderMap.GetProvider(pageLink);
+
+ if (contentProvider.HasCapability(ContentProviderCapabilities.Search))
+ {
+ criteria.Add(new PropertyCriteria
+ {
+ Name = "EPI:MultipleSearch",
+ Value = contentProvider.ProviderKey
+ });
+ }
+ }
+
+ return _pageCriteriaQueryService.FindPagesWithCriteria(pageLink, criteria);
+ }
+
+ ///
+ /// Returns all contact pages beneath the main contacts container
+ ///
+ ///
+ public IEnumerable GetContactPages()
+ {
+ var contactsRootPageLink = _contentLoader.Get(SiteDefinition.Current.StartPage).ContactsPageLink;
+
+ if (ContentReference.IsNullOrEmpty(contactsRootPageLink))
+ {
+ throw new MissingConfigurationException("No contact page root specified in site settings, unable to retrieve contact pages");
+ }
+
+ return _contentLoader.GetChildren(contactsRootPageLink).OrderBy(p => p.PageName);
+ }
+ }
+}
diff --git a/src/alloy/Business/EditorDescriptors/ContactPageSelectionFactory.cs b/src/alloy/Business/EditorDescriptors/ContactPageSelectionFactory.cs
new file mode 100644
index 0000000..0313777
--- /dev/null
+++ b/src/alloy/Business/EditorDescriptors/ContactPageSelectionFactory.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using System.Linq;
+using EPiServer.ServiceLocation;
+using EPiServer.Shell.ObjectEditing;
+
+namespace AlloyTemplates.Business.EditorDescriptors
+{
+ ///
+ /// Provides a list of options corresponding to ContactPage pages on the site
+ ///
+ ///
+ public class ContactPageSelectionFactory : ISelectionFactory
+ {
+ private Injected ContentLocator { get; set; }
+
+ public IEnumerable GetSelections(ExtendedMetadata metadata)
+ {
+ var contactPages = ContentLocator.Service.GetContactPages();
+
+ return new List(contactPages.Select(c => new SelectItem {Value = c.PageLink, Text = c.Name}));
+ }
+ }
+}
diff --git a/src/alloy/Business/EditorDescriptors/ContactPageSelector.cs b/src/alloy/Business/EditorDescriptors/ContactPageSelector.cs
new file mode 100644
index 0000000..f257576
--- /dev/null
+++ b/src/alloy/Business/EditorDescriptors/ContactPageSelector.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using EPiServer.Core;
+using EPiServer.Shell.ObjectEditing;
+using EPiServer.Shell.ObjectEditing.EditorDescriptors;
+
+namespace AlloyTemplates.Business.EditorDescriptors
+{
+ ///
+ /// Registers an editor to select a ContactPage for a PageReference property using a dropdown
+ ///
+ [EditorDescriptorRegistration(TargetType = typeof(PageReference), UIHint = Global.SiteUIHints.Contact)]
+ public class ContactPageSelector : EditorDescriptor
+ {
+ public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable attributes)
+ {
+ SelectionFactoryType = typeof(ContactPageSelectionFactory);
+
+ ClientEditingClass = "epi-cms/contentediting/editors/SelectionEditor";
+
+ base.ModifyMetadata(metadata, attributes);
+ }
+ }
+}
diff --git a/src/alloy/Business/EditorDescriptors/StringListEditorDescriptor.cs b/src/alloy/Business/EditorDescriptors/StringListEditorDescriptor.cs
new file mode 100644
index 0000000..6002a6a
--- /dev/null
+++ b/src/alloy/Business/EditorDescriptors/StringListEditorDescriptor.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using EPiServer.Shell.ObjectEditing.EditorDescriptors;
+using EPiServer.Shell.ObjectEditing;
+
+namespace AlloyTemplates.Business.EditorDescriptors
+{
+ ///
+ /// Register an editor for StringList properties
+ ///
+ [EditorDescriptorRegistration(TargetType = typeof(String[]), UIHint = Global.SiteUIHints.Strings)]
+ public class StringListEditorDescriptor : EditorDescriptor
+ {
+ public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable attributes)
+ {
+ ClientEditingClass = "alloy/editors/StringList";
+
+ base.ModifyMetadata(metadata, attributes);
+ }
+ }
+}
diff --git a/src/alloy/Business/IModifyLayout.cs b/src/alloy/Business/IModifyLayout.cs
new file mode 100644
index 0000000..9db6d36
--- /dev/null
+++ b/src/alloy/Business/IModifyLayout.cs
@@ -0,0 +1,13 @@
+using AlloyTemplates.Models.ViewModels;
+
+namespace AlloyTemplates.Business
+{
+ ///
+ /// Defines a method which may be invoked by PageContextActionFilter allowing controllers
+ /// to modify common layout properties of the view model.
+ ///
+ interface IModifyLayout
+ {
+ void ModifyLayout(LayoutModel layoutModel);
+ }
+}
diff --git a/src/alloy/Business/Initialization/BundleConfig.cs b/src/alloy/Business/Initialization/BundleConfig.cs
new file mode 100644
index 0000000..57dd961
--- /dev/null
+++ b/src/alloy/Business/Initialization/BundleConfig.cs
@@ -0,0 +1,41 @@
+using System.Web.Optimization;
+using EPiServer.Framework;
+using EPiServer.Framework.Initialization;
+
+namespace AlloyTemplates.Business.Initialization
+{
+ [InitializableModule]
+ public class BundleConfig : IInitializableModule
+ {
+ public void Initialize(InitializationEngine context)
+ {
+ if (context.HostType == HostType.WebApplication)
+ {
+ RegisterBundles(BundleTable.Bundles);
+ }
+ }
+
+ // For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725
+ public static void RegisterBundles(BundleCollection bundles)
+ {
+ bundles.Add(new ScriptBundle("~/bundles/js").Include(
+ "~/Static/js/jquery.js", //jquery.js can be removed and linked from CDN instead, we use a local one for demo purposes without internet connectionzz
+ "~/Static/js/bootstrap.js"));
+
+ bundles.Add(new StyleBundle("~/bundles/css")
+ .Include("~/Static/css/bootstrap.css", new CssRewriteUrlTransform())
+ .Include("~/Static/css/bootstrap-responsive.css")
+ .Include("~/Static/css/media.css")
+ .Include("~/Static/css/style.css", new CssRewriteUrlTransform())
+ .Include("~/Static/css/editmode.css"));
+ }
+
+ public void Uninitialize(InitializationEngine context)
+ {
+ }
+
+ public void Preload(string[] parameters)
+ {
+ }
+ }
+}
diff --git a/src/alloy/Business/Initialization/CustomizedRenderingInitialization.cs b/src/alloy/Business/Initialization/CustomizedRenderingInitialization.cs
new file mode 100644
index 0000000..515625e
--- /dev/null
+++ b/src/alloy/Business/Initialization/CustomizedRenderingInitialization.cs
@@ -0,0 +1,36 @@
+using System.Web.Mvc;
+using EPiServer.Framework;
+using EPiServer.Framework.Initialization;
+using EPiServer.ServiceLocation;
+using AlloyTemplates.Business.Rendering;
+using EPiServer.Web;
+
+namespace AlloyTemplates.Business.Initialization
+{
+ ///
+ /// Module for customizing templates and rendering.
+ ///
+ [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
+ public class CustomizedRenderingInitialization : IInitializableModule
+ {
+ public void Initialize(InitializationEngine context)
+ {
+ //Add custom view engine allowing partials to be placed in additional locations
+ //Note that we add it first in the list to optimize view resolving when using DisplayFor/PropertyFor
+ ViewEngines.Engines.Insert(0, new SiteViewEngine());
+
+ context.Locate.TemplateResolver()
+ .TemplateResolved += TemplateCoordinator.OnTemplateResolved;
+ }
+
+ public void Uninitialize(InitializationEngine context)
+ {
+ ServiceLocator.Current.GetInstance()
+ .TemplateResolved -= TemplateCoordinator.OnTemplateResolved;
+ }
+
+ public void Preload(string[] parameters)
+ {
+ }
+ }
+}
diff --git a/src/alloy/Business/Initialization/DependencyResolverInitialization.cs b/src/alloy/Business/Initialization/DependencyResolverInitialization.cs
new file mode 100644
index 0000000..604702e
--- /dev/null
+++ b/src/alloy/Business/Initialization/DependencyResolverInitialization.cs
@@ -0,0 +1,39 @@
+using System.Web.Mvc;
+using EPiServer.Framework;
+using EPiServer.Framework.Initialization;
+using EPiServer.ServiceLocation;
+using AlloyTemplates.Business.Rendering;
+using EPiServer.Web.Mvc;
+using EPiServer.Web.Mvc.Html;
+
+namespace AlloyTemplates.Business.Initialization
+{
+ [InitializableModule]
+ public class DependencyResolverInitialization : IConfigurableModule
+ {
+ public void ConfigureContainer(ServiceConfigurationContext context)
+ {
+ //Implementations for custom interfaces can be registered here.
+
+ context.ConfigurationComplete += (o, e) =>
+ {
+ //Register custom implementations that should be used in favour of the default implementations
+ context.Services.AddTransient()
+ .AddTransient();
+ };
+ }
+
+ public void Initialize(InitializationEngine context)
+ {
+ DependencyResolver.SetResolver(new ServiceLocatorDependencyResolver(context.Locate.Advanced));
+ }
+
+ public void Uninitialize(InitializationEngine context)
+ {
+ }
+
+ public void Preload(string[] parameters)
+ {
+ }
+ }
+}
diff --git a/src/alloy/Business/Initialization/DisplayModesInitialization.cs b/src/alloy/Business/Initialization/DisplayModesInitialization.cs
new file mode 100644
index 0000000..c8a01d9
--- /dev/null
+++ b/src/alloy/Business/Initialization/DisplayModesInitialization.cs
@@ -0,0 +1,49 @@
+using System.Linq;
+using System.Web;
+using System.Web.WebPages;
+using EPiServer.Framework;
+using EPiServer.Framework.Initialization;
+using EPiServer.ServiceLocation;
+using AlloyTemplates.Business.Channels;
+using EPiServer.Web;
+
+namespace AlloyTemplates.Business.Initialization
+{
+ ///
+ /// Adds a new display mode for mobile which is active if the mobile channel is active in addition to if the request is from a mobile device (like the default one)
+ ///
+ ///
+ /// It's also possible to map a display mode as a channel through the DisplayChannelService.RegisterDisplayMode() method.
+ /// Adding channels that way does not however enable specifying ResolutionId which we want to do for the mobile channel.
+ ///
+ [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
+ public class DisplayModesInitialization : IInitializableModule
+ {
+ public void Initialize(InitializationEngine context)
+ {
+ var mobileChannelDisplayMode = new DefaultDisplayMode("mobile")
+ {
+ ContextCondition = IsMobileDisplayModeActive
+ };
+ DisplayModeProvider.Instance.Modes.Insert(0, mobileChannelDisplayMode);
+ }
+
+ private static bool IsMobileDisplayModeActive(HttpContextBase httpContext)
+ {
+ if (httpContext.GetOverriddenBrowser().IsMobileDevice)
+ {
+ return true;
+ }
+ var displayChannelService = ServiceLocator.Current.GetInstance();
+ return displayChannelService.GetActiveChannels(httpContext).Any(x => x.ChannelName == MobileChannel.Name);
+ }
+
+ public void Uninitialize(InitializationEngine context)
+ {
+ }
+
+ public void Preload(string[] parameters)
+ {
+ }
+ }
+}
diff --git a/src/alloy/Business/Initialization/DisplayRegistryInitialization.cs b/src/alloy/Business/Initialization/DisplayRegistryInitialization.cs
new file mode 100644
index 0000000..4aec591
--- /dev/null
+++ b/src/alloy/Business/Initialization/DisplayRegistryInitialization.cs
@@ -0,0 +1,34 @@
+using EPiServer.Framework;
+using EPiServer.Framework.Initialization;
+using EPiServer.ServiceLocation;
+using EPiServer.Web;
+using System.Collections.Generic;
+using System.Web.Mvc;
+
+namespace AlloyTemplates.Business.Initialization
+{
+ [InitializableModule]
+ [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
+ public class DisplayRegistryInitialization : IInitializableModule
+ {
+ public void Initialize(InitializationEngine context)
+ {
+ if (context.HostType == HostType.WebApplication)
+ {
+ // Register Display Options
+ var options = ServiceLocator.Current.GetInstance();
+ options
+ .Add("full", "/displayoptions/full", Global.ContentAreaTags.FullWidth, "", "epi-icon__layout--full")
+ .Add("wide", "/displayoptions/wide", Global.ContentAreaTags.TwoThirdsWidth, "", "epi-icon__layout--two-thirds")
+ .Add("narrow", "/displayoptions/narrow", Global.ContentAreaTags.OneThirdWidth, "", "epi-icon__layout--one-third");
+
+ AreaRegistration.RegisterAllAreas();
+
+ }
+ }
+
+ public void Preload(string[] parameters){}
+
+ public void Uninitialize(InitializationEngine context){}
+ }
+}
diff --git a/src/alloy/Business/Initialization/ExtendedTinyMceInitialization.cs b/src/alloy/Business/Initialization/ExtendedTinyMceInitialization.cs
new file mode 100644
index 0000000..771d87c
--- /dev/null
+++ b/src/alloy/Business/Initialization/ExtendedTinyMceInitialization.cs
@@ -0,0 +1,43 @@
+using AlloyTemplates.Models.Blocks;
+using AlloyTemplates.Models.Pages;
+using EPiServer.Cms.TinyMce.Core;
+using EPiServer.Framework;
+using EPiServer.Framework.Initialization;
+using EPiServer.ServiceLocation;
+
+namespace AlloyTemplates.Business.Initialization
+{
+ [ModuleDependency(typeof(TinyMceInitialization))]
+ public class ExtendedTinyMceInitialization : IConfigurableModule
+ {
+ public void Initialize(InitializationEngine context)
+ {
+ }
+
+ public void Uninitialize(InitializationEngine context)
+ {
+ }
+
+ public void ConfigureContainer(ServiceConfigurationContext context)
+ {
+ context.Services.Configure(config =>
+ {
+ // Add content CSS to the default settings.
+ config.Default()
+ .ContentCss("/static/css/editor.css");
+
+ // This will clone the default settings object and extend it by
+ // limiting the block formats for the MainBody property of an ArticlePage.
+ config.For(t => t.MainBody)
+ .BlockFormats("Paragraph=p;Header 1=h1;Header 2=h2;Header 3=h3");
+
+ // Passing a second argument to For<> will clone the given settings object
+ // instead of the default one and extend it with some basic toolbar commands.
+ config.For(t => t.MainBody, config.Empty())
+ .AddEpiserverSupport()
+ .DisableMenubar()
+ .Toolbar("bold italic underline strikethrough");
+ });
+ }
+ }
+}
diff --git a/src/alloy/Business/Initialization/FilterConfig.cs b/src/alloy/Business/Initialization/FilterConfig.cs
new file mode 100644
index 0000000..880fd92
--- /dev/null
+++ b/src/alloy/Business/Initialization/FilterConfig.cs
@@ -0,0 +1,27 @@
+using System.Web.Mvc;
+using EPiServer.Framework;
+using EPiServer.Framework.Initialization;
+using EPiServer.ServiceLocation;
+
+namespace AlloyTemplates.Business.Initialization
+{
+ ///
+ /// Module for registering filters which will be applied to controller actions.
+ ///
+ [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
+ public class FilterConfig : IInitializableModule
+ {
+ public void Initialize(InitializationEngine context)
+ {
+ GlobalFilters.Filters.Add(ServiceLocator.Current.GetInstance());
+ }
+
+ public void Uninitialize(InitializationEngine context)
+ {
+ }
+
+ public void Preload(string[] parameters)
+ {
+ }
+ }
+}
diff --git a/src/alloy/Business/PageContextActionFilter.cs b/src/alloy/Business/PageContextActionFilter.cs
new file mode 100644
index 0000000..44dbba4
--- /dev/null
+++ b/src/alloy/Business/PageContextActionFilter.cs
@@ -0,0 +1,55 @@
+using System.Web.Mvc;
+using AlloyTemplates.Models.Pages;
+using AlloyTemplates.Models.ViewModels;
+using EPiServer.Web.Routing;
+
+namespace AlloyTemplates.Business
+{
+ ///
+ /// Intercepts actions with view models of type IPageViewModel and populates the view models
+ /// Layout and Section properties.
+ ///
+ ///
+ /// This filter frees controllers for pages from having to care about common context needed by layouts
+ /// and other page framework components allowing the controllers to focus on the specifics for the page types
+ /// and actions that they handle.
+ ///
+ public class PageContextActionFilter : IResultFilter
+ {
+ private readonly PageViewContextFactory _contextFactory;
+ public PageContextActionFilter(PageViewContextFactory contextFactory)
+ {
+ _contextFactory = contextFactory;
+ }
+
+ public void OnResultExecuting(ResultExecutingContext filterContext)
+ {
+ var viewModel = filterContext.Controller.ViewData.Model;
+
+ var model = viewModel as IPageViewModel;
+ if (model != null)
+ {
+ var currentContentLink = filterContext.RequestContext.GetContentLink();
+
+ var layoutModel = model.Layout ?? _contextFactory.CreateLayoutModel(currentContentLink, filterContext.RequestContext);
+
+ var layoutController = filterContext.Controller as IModifyLayout;
+ if(layoutController != null)
+ {
+ layoutController.ModifyLayout(layoutModel);
+ }
+
+ model.Layout = layoutModel;
+
+ if (model.Section == null)
+ {
+ model.Section = _contextFactory.GetSection(currentContentLink);
+ }
+ }
+ }
+
+ public void OnResultExecuted(ResultExecutedContext filterContext)
+ {
+ }
+ }
+}
diff --git a/src/alloy/Business/PageTypeExtensions.cs b/src/alloy/Business/PageTypeExtensions.cs
new file mode 100644
index 0000000..a528683
--- /dev/null
+++ b/src/alloy/Business/PageTypeExtensions.cs
@@ -0,0 +1,24 @@
+using System;
+using EPiServer.DataAbstraction;
+using EPiServer.ServiceLocation;
+
+namespace AlloyTemplates.Business
+{
+ ///
+ /// Provides extension methods for types intended to be used when working with page types
+ ///
+ public static class PageTypeExtensions
+ {
+ ///
+ /// Returns the definition for a specific page type
+ ///
+ ///
+ ///
+ public static PageType GetPageType(this Type pageType)
+ {
+ var pageTypeRepository = ServiceLocator.Current.GetInstance>();
+
+ return pageTypeRepository.Load(pageType);
+ }
+ }
+}
diff --git a/src/alloy/Business/PageViewContextFactory.cs b/src/alloy/Business/PageViewContextFactory.cs
new file mode 100644
index 0000000..93f4725
--- /dev/null
+++ b/src/alloy/Business/PageViewContextFactory.cs
@@ -0,0 +1,79 @@
+using System.Linq;
+using System.Web;
+using System.Web.Mvc;
+using System.Web.Routing;
+using System.Web.Security;
+using AlloyTemplates.Models.Pages;
+using AlloyTemplates.Models.ViewModels;
+using EPiServer;
+using EPiServer.Core;
+using EPiServer.Data;
+using EPiServer.Web;
+using EPiServer.Web.Routing;
+
+namespace AlloyTemplates.Business
+{
+ public class PageViewContextFactory
+ {
+ private readonly IContentLoader _contentLoader;
+ private readonly UrlResolver _urlResolver;
+ private readonly IDatabaseMode _databaseMode;
+
+ public PageViewContextFactory(IContentLoader contentLoader, UrlResolver urlResolver, IDatabaseMode databaseMode)
+ {
+ _contentLoader = contentLoader;
+ _urlResolver = urlResolver;
+ _databaseMode = databaseMode;
+ }
+
+ public virtual LayoutModel CreateLayoutModel(ContentReference currentContentLink, RequestContext requestContext)
+ {
+ var startPageContentLink = SiteDefinition.Current.StartPage;
+
+ // Use the content link with version information when editing the startpage,
+ // otherwise the published version will be used when rendering the props below.
+ if (currentContentLink.CompareToIgnoreWorkID(startPageContentLink))
+ {
+ startPageContentLink = currentContentLink;
+ }
+
+ var startPage = _contentLoader.Get(startPageContentLink);
+
+ return new LayoutModel
+ {
+ Logotype = startPage.SiteLogotype,
+ LogotypeLinkUrl = new MvcHtmlString(_urlResolver.GetUrl(SiteDefinition.Current.StartPage)),
+ ProductPages = startPage.ProductPageLinks,
+ CompanyInformationPages = startPage.CompanyInformationPageLinks,
+ NewsPages = startPage.NewsPageLinks,
+ CustomerZonePages = startPage.CustomerZonePageLinks,
+ LoggedIn = requestContext.HttpContext.User.Identity.IsAuthenticated,
+ LoginUrl = new MvcHtmlString(GetLoginUrl(currentContentLink)),
+ SearchActionUrl = new MvcHtmlString(EPiServer.Web.Routing.UrlResolver.Current.GetUrl(startPage.SearchPageLink)),
+ IsInReadonlyMode = _databaseMode.DatabaseMode == DatabaseMode.ReadOnly
+ };
+ }
+
+ private string GetLoginUrl(ContentReference returnToContentLink)
+ {
+ return string.Format(
+ "{0}?ReturnUrl={1}",
+ (FormsAuthentication.IsEnabled ? FormsAuthentication.LoginUrl : VirtualPathUtility.ToAbsolute(Global.AppRelativeLoginPath)),
+ _urlResolver.GetUrl(returnToContentLink));
+ }
+
+ public virtual IContent GetSection(ContentReference contentLink)
+ {
+ var currentContent = _contentLoader.Get(contentLink);
+ if (currentContent.ParentLink != null && currentContent.ParentLink.CompareToIgnoreWorkID(SiteDefinition.Current.StartPage))
+ {
+ return currentContent;
+ }
+
+ return _contentLoader.GetAncestors(contentLink)
+ .OfType()
+ .SkipWhile(x => x.ParentLink == null || !x.ParentLink.CompareToIgnoreWorkID(SiteDefinition.Current.StartPage))
+ .FirstOrDefault();
+ }
+ }
+}
diff --git a/src/alloy/Business/Rendering/AlloyContentAreaRenderer.cs b/src/alloy/Business/Rendering/AlloyContentAreaRenderer.cs
new file mode 100644
index 0000000..404e14e
--- /dev/null
+++ b/src/alloy/Business/Rendering/AlloyContentAreaRenderer.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Web.Mvc;
+using EPiServer.Core;
+using EPiServer.Core.Html.StringParsing;
+using EPiServer.Web;
+using EPiServer.Web.Mvc;
+using EPiServer.Web.Mvc.Html;
+using EPiServer;
+
+namespace AlloyTemplates.Business.Rendering
+{
+ ///
+ /// Extends the default to apply custom CSS classes to each .
+ ///
+ public class AlloyContentAreaRenderer : ContentAreaRenderer
+ {
+ protected override string GetContentAreaItemCssClass(HtmlHelper htmlHelper, ContentAreaItem contentAreaItem)
+ {
+ var baseItemClass = base.GetContentAreaItemCssClass(htmlHelper, contentAreaItem);
+
+ var tag = GetContentAreaItemTemplateTag(htmlHelper, contentAreaItem);
+ return $"block {baseItemClass} {GetTypeSpecificCssClasses(contentAreaItem, ContentRepository)} {GetCssClassForTag(tag)} {tag}";
+ }
+
+ ///
+ /// Gets a CSS class used for styling based on a tag name (ie a Bootstrap class name)
+ ///
+ /// Any tag name available, see
+ private static string GetCssClassForTag(string tagName)
+ {
+ if (string.IsNullOrEmpty(tagName))
+ {
+ return "";
+ }
+ switch (tagName.ToLower())
+ {
+ case "span12":
+ return "full";
+ case "span8":
+ return "wide";
+ case "span6":
+ return "half";
+ default:
+ return string.Empty;
+ }
+ }
+
+ private static string GetTypeSpecificCssClasses(ContentAreaItem contentAreaItem, IContentRepository contentRepository)
+ {
+ var content = contentAreaItem.GetContent();
+ var cssClass = content == null ? String.Empty : content.GetOriginalType().Name.ToLowerInvariant();
+
+ var customClassContent = content as ICustomCssInContentArea;
+ if (customClassContent != null && !string.IsNullOrWhiteSpace(customClassContent.ContentAreaCssClass))
+ {
+ cssClass += string.Format(" {0}", customClassContent.ContentAreaCssClass);
+ }
+
+ return cssClass;
+ }
+ }
+}
diff --git a/src/alloy/Business/Rendering/ErrorHandlingContentRenderer.cs b/src/alloy/Business/Rendering/ErrorHandlingContentRenderer.cs
new file mode 100644
index 0000000..06409b9
--- /dev/null
+++ b/src/alloy/Business/Rendering/ErrorHandlingContentRenderer.cs
@@ -0,0 +1,106 @@
+using System;
+using System.IO;
+using System.Web;
+using System.Web.Mvc;
+using System.Web.Mvc.Html;
+using EPiServer.Core;
+using EPiServer.DataAbstraction;
+using EPiServer.Security;
+using AlloyTemplates.Models.ViewModels;
+using EPiServer.Web.Mvc;
+
+namespace AlloyTemplates.Business.Rendering
+{
+ ///
+ /// Wraps an MvcContentRenderer and adds error handling to ensure that blocks and other content
+ /// rendered as parts of pages won't crash the entire page if a non-critical exception occurs while rendering it.
+ ///
+ ///
+ /// Prints an error message for editors so that they can easily report errors to developers.
+ ///
+ public class ErrorHandlingContentRenderer : IContentRenderer
+ {
+ private readonly MvcContentRenderer _mvcRenderer;
+ public ErrorHandlingContentRenderer(MvcContentRenderer mvcRenderer)
+ {
+ _mvcRenderer = mvcRenderer;
+ }
+
+ ///
+ /// Renders the contentData using the wrapped renderer and catches common, non-critical exceptions.
+ ///
+ public void Render(HtmlHelper helper, PartialRequest partialRequestHandler, IContentData contentData, TemplateModel templateModel)
+ {
+ try
+ {
+ _mvcRenderer.Render(helper, partialRequestHandler, contentData, templateModel);
+ }
+ catch (NullReferenceException ex)
+ {
+ if (HttpContext.Current.IsDebuggingEnabled)
+ {
+ //If debug="true" we assume a developer is making the request
+ throw;
+ }
+ HandlerError(helper, contentData, ex);
+ }
+ catch (ArgumentException ex)
+ {
+ if (HttpContext.Current.IsDebuggingEnabled)
+ {
+ throw;
+ }
+ HandlerError(helper, contentData, ex);
+ }
+ catch (ApplicationException ex)
+ {
+ if (HttpContext.Current.IsDebuggingEnabled)
+ {
+ throw;
+ }
+ HandlerError(helper, contentData, ex);
+ }
+ catch (InvalidOperationException ex)
+ {
+ if (HttpContext.Current.IsDebuggingEnabled)
+ {
+ throw;
+ }
+ HandlerError(helper, contentData, ex);
+ }
+ catch (NotImplementedException ex)
+ {
+ if (HttpContext.Current.IsDebuggingEnabled)
+ {
+ throw;
+ }
+ HandlerError(helper, contentData, ex);
+ }
+ catch (IOException ex)
+ {
+ if (HttpContext.Current.IsDebuggingEnabled)
+ {
+ throw;
+ }
+ HandlerError(helper, contentData, ex);
+ }
+ catch (EPiServerException ex)
+ {
+ if (HttpContext.Current.IsDebuggingEnabled)
+ {
+ throw;
+ }
+ HandlerError(helper, contentData, ex);
+ }
+ }
+
+ private void HandlerError(HtmlHelper helper, IContentData contentData, Exception renderingException)
+ {
+ if (PrincipalInfo.HasEditAccess)
+ {
+ var errorModel = new ContentRenderingErrorModel(contentData, renderingException);
+ helper.RenderPartial("TemplateError", errorModel);
+ }
+ }
+ }
+}
diff --git a/src/alloy/Business/Rendering/IContainerPage.cs b/src/alloy/Business/Rendering/IContainerPage.cs
new file mode 100644
index 0000000..7aee406
--- /dev/null
+++ b/src/alloy/Business/Rendering/IContainerPage.cs
@@ -0,0 +1,9 @@
+namespace AlloyTemplates.Business.Rendering
+{
+ ///
+ /// Marker interface for content types which should not be handled by DefaultPageController.
+ ///
+ interface IContainerPage
+ {
+ }
+}
diff --git a/src/alloy/Business/Rendering/ICustomCssInContentArea.cs b/src/alloy/Business/Rendering/ICustomCssInContentArea.cs
new file mode 100644
index 0000000..5878368
--- /dev/null
+++ b/src/alloy/Business/Rendering/ICustomCssInContentArea.cs
@@ -0,0 +1,11 @@
+namespace AlloyTemplates.Business.Rendering
+{
+ ///
+ /// Defines a property for CSS class(es) which will be added to the class
+ /// attribute of containing elements when rendered in a content area with a size tag.
+ ///
+ interface ICustomCssInContentArea
+ {
+ string ContentAreaCssClass { get; }
+ }
+}
diff --git a/src/alloy/Business/Rendering/SiteViewEngine.cs b/src/alloy/Business/Rendering/SiteViewEngine.cs
new file mode 100644
index 0000000..e7fa1a3
--- /dev/null
+++ b/src/alloy/Business/Rendering/SiteViewEngine.cs
@@ -0,0 +1,23 @@
+using System.Linq;
+using System.Web.Mvc;
+
+namespace AlloyTemplates.Business.Rendering
+{
+ ///
+ /// Extends the Razor view engine to include the folders ~/Views/Shared/Blocks/ and ~/Views/Shared/PagePartials/
+ /// when looking for partial views.
+ ///
+ public class SiteViewEngine : RazorViewEngine
+ {
+ private static readonly string[] AdditionalPartialViewFormats = new[]
+ {
+ TemplateCoordinator.BlockFolder + "{0}.cshtml",
+ TemplateCoordinator.PagePartialsFolder + "{0}.cshtml"
+ };
+
+ public SiteViewEngine()
+ {
+ PartialViewLocationFormats = PartialViewLocationFormats.Union(AdditionalPartialViewFormats).ToArray();
+ }
+ }
+}
diff --git a/src/alloy/Business/Rendering/TemplateCoordinator.cs b/src/alloy/Business/Rendering/TemplateCoordinator.cs
new file mode 100644
index 0000000..6c69177
--- /dev/null
+++ b/src/alloy/Business/Rendering/TemplateCoordinator.cs
@@ -0,0 +1,100 @@
+using EPiServer.Core;
+using EPiServer.DataAbstraction;
+using EPiServer.ServiceLocation;
+using AlloyTemplates.Controllers;
+using AlloyTemplates.Models.Blocks;
+using AlloyTemplates.Models.Pages;
+using EPiServer.Web;
+using EPiServer.Web.Mvc;
+
+namespace AlloyTemplates.Business.Rendering
+{
+ [ServiceConfiguration(typeof(IViewTemplateModelRegistrator))]
+ public class TemplateCoordinator : IViewTemplateModelRegistrator
+ {
+ public const string BlockFolder = "~/Views/Shared/Blocks/";
+ public const string PagePartialsFolder = "~/Views/Shared/PagePartials/";
+
+ public static void OnTemplateResolved(object sender, TemplateResolverEventArgs args)
+ {
+ //Disable DefaultPageController for page types that shouldn't have any renderer as pages
+ if (args.ItemToRender is IContainerPage && args.SelectedTemplate != null && args.SelectedTemplate.TemplateType == typeof(DefaultPageController))
+ {
+ args.SelectedTemplate = null;
+ }
+ }
+
+ ///
+ /// Registers renderers/templates which are not automatically discovered,
+ /// i.e. partial views whose names does not match a content type's name.
+ ///
+ ///
+ /// Using only partial views instead of controllers for blocks and page partials
+ /// has performance benefits as they will only require calls to RenderPartial instead of
+ /// RenderAction for controllers.
+ /// Registering partial views as templates this way also enables specifying tags and
+ /// that a template supports all types inheriting from the content type/model type.
+ ///
+ public void Register(TemplateModelCollection viewTemplateModelRegistrator)
+ {
+ viewTemplateModelRegistrator.Add(typeof(JumbotronBlock), new TemplateModel
+ {
+ Tags = new[] { Global.ContentAreaTags.FullWidth },
+ AvailableWithoutTag = false,
+ Path = BlockPath("JumbotronBlockWide.cshtml")
+ });
+
+ viewTemplateModelRegistrator.Add(typeof(TeaserBlock), new TemplateModel
+ {
+ Name = "TeaserBlockWide",
+ Tags = new[] { Global.ContentAreaTags.TwoThirdsWidth, Global.ContentAreaTags.FullWidth },
+ AvailableWithoutTag = false,
+ Path = BlockPath("TeaserBlockWide.cshtml")
+ });
+
+ viewTemplateModelRegistrator.Add(typeof(SitePageData), new TemplateModel
+ {
+ Name = "PagePartial",
+ Inherit = true,
+ AvailableWithoutTag = true,
+ Path = PagePartialPath("Page.cshtml")
+ });
+
+ viewTemplateModelRegistrator.Add(typeof(SitePageData), new TemplateModel
+ {
+ Name = "PagePartialWide",
+ Inherit = true,
+ Tags = new[] { Global.ContentAreaTags.TwoThirdsWidth, Global.ContentAreaTags.FullWidth },
+ AvailableWithoutTag = false,
+ Path = PagePartialPath("PageWide.cshtml")
+ });
+
+ viewTemplateModelRegistrator.Add(typeof(ContactPage), new TemplateModel
+ {
+ Name = "ContactPagePartialWide",
+ Tags = new[] { Global.ContentAreaTags.TwoThirdsWidth, Global.ContentAreaTags.FullWidth },
+ AvailableWithoutTag = false,
+ Path = PagePartialPath("ContactPageWide.cshtml")
+ });
+
+ viewTemplateModelRegistrator.Add(typeof(IContentData), new TemplateModel
+ {
+ Name = "NoRendererMessage",
+ Inherit = true,
+ Tags = new[] { Global.ContentAreaTags.NoRenderer },
+ AvailableWithoutTag = false,
+ Path = BlockPath("NoRenderer.cshtml")
+ });
+ }
+
+ private static string BlockPath(string fileName)
+ {
+ return string.Format("{0}{1}", BlockFolder, fileName);
+ }
+
+ private static string PagePartialPath(string fileName)
+ {
+ return string.Format("{0}{1}", PagePartialsFolder, fileName);
+ }
+ }
+}
diff --git a/src/alloy/Business/SearchService.cs b/src/alloy/Business/SearchService.cs
new file mode 100644
index 0000000..44744a3
--- /dev/null
+++ b/src/alloy/Business/SearchService.cs
@@ -0,0 +1,74 @@
+using System.Collections.Generic;
+using System.Web;
+using EPiServer.Core;
+using EPiServer.Search;
+using EPiServer.Search.Queries;
+using EPiServer.Search.Queries.Lucene;
+using EPiServer.Security;
+using EPiServer;
+using EPiServer.ServiceLocation;
+
+namespace AlloyTemplates.Business
+{
+ public class SearchService
+ {
+ private readonly SearchHandler _searchHandler;
+ private readonly IContentLoader _contentLoader;
+
+ public SearchService(SearchHandler searchHandler, IContentLoader contentLoader)
+ {
+ _searchHandler = searchHandler;
+ _contentLoader = contentLoader;
+ }
+
+ public virtual bool IsActive
+ {
+ get { return ServiceLocator.Current.GetInstance().Active; }
+ }
+
+ public virtual SearchResults Search(string searchText, IEnumerable searchRoots, HttpContextBase context, string languageBranch, int maxResults)
+ {
+ var query = CreateQuery(searchText, searchRoots, context, languageBranch);
+ return _searchHandler.GetSearchResults(query, 1, maxResults);
+ }
+
+ private IQueryExpression CreateQuery(string searchText, IEnumerable searchRoots, HttpContextBase context, string languageBranch)
+ {
+ //Main query which groups other queries. Each query added
+ //must match in order for a page or file to be returned.
+ var query = new GroupQuery(LuceneOperator.AND);
+
+ //Add free text query to the main query
+ query.QueryExpressions.Add(new FieldQuery(searchText));
+
+ //Search for pages using the provided language
+ var pageTypeQuery = new GroupQuery(LuceneOperator.AND);
+ pageTypeQuery.QueryExpressions.Add(new ContentQuery());
+ pageTypeQuery.QueryExpressions.Add(new FieldQuery(languageBranch, Field.Culture));
+
+ //Search for media without languages
+ var contentTypeQuery = new GroupQuery(LuceneOperator.OR);
+ contentTypeQuery.QueryExpressions.Add(new ContentQuery());
+ contentTypeQuery.QueryExpressions.Add(pageTypeQuery);
+
+ query.QueryExpressions.Add(contentTypeQuery);
+
+ //Create and add query which groups type conditions using OR
+ var typeQueries = new GroupQuery(LuceneOperator.OR);
+ query.QueryExpressions.Add(typeQueries);
+
+ foreach (var root in searchRoots)
+ {
+ var contentRootQuery = new VirtualPathQuery();
+ contentRootQuery.AddContentNodes(root);
+ typeQueries.QueryExpressions.Add(contentRootQuery);
+ }
+
+ var accessRightsQuery = new AccessControlListQuery();
+ accessRightsQuery.AddAclForUser(PrincipalInfo.Current, context);
+ query.QueryExpressions.Add(accessRightsQuery);
+
+ return query;
+ }
+ }
+}
diff --git a/src/alloy/Business/ServiceLocatorDependencyResolver.cs b/src/alloy/Business/ServiceLocatorDependencyResolver.cs
new file mode 100644
index 0000000..c8e76c1
--- /dev/null
+++ b/src/alloy/Business/ServiceLocatorDependencyResolver.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Mvc;
+using EPiServer.ServiceLocation;
+
+namespace AlloyTemplates.Business
+{
+ public class ServiceLocatorDependencyResolver : IDependencyResolver
+ {
+ readonly IServiceLocator _serviceLocator;
+
+ public ServiceLocatorDependencyResolver(IServiceLocator serviceLocator)
+ {
+ _serviceLocator = serviceLocator;
+ }
+
+ public object GetService(Type serviceType)
+ {
+ if (serviceType.IsInterface || serviceType.IsAbstract)
+ {
+ return GetInterfaceService(serviceType);
+ }
+ return GetConcreteService(serviceType);
+ }
+
+ private object GetConcreteService(Type serviceType)
+ {
+ try
+ {
+ // Can't use TryGetInstance here because it won’t create concrete types
+ return _serviceLocator.GetInstance(serviceType);
+ }
+ catch (ActivationException)
+ {
+ return null;
+ }
+ }
+
+ private object GetInterfaceService(Type serviceType)
+ {
+ object instance;
+ return _serviceLocator.TryGetExistingInstance(serviceType, out instance) ? instance : null;
+ }
+
+ public IEnumerable